Bug 1757166 [wpt PR 32982] - Merge xslt/ into dom/ again, a=testonly
[gecko.git] / xpcom / build / PoisonIOInterposerBase.cpp
blob8add6740da879ecb4d25c4f357161f7e61e13735
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/Maybe.h"
8 #include "mozilla/Mutex.h"
9 #include "mozilla/Scoped.h"
10 #include "mozilla/UniquePtr.h"
12 #include <algorithm>
14 #include "PoisonIOInterposer.h"
16 #include "prlock.h"
18 #ifdef MOZ_REPLACE_MALLOC
19 # include "replace_malloc_bridge.h"
20 #endif
22 // Auxiliary method to convert file descriptors to ids
23 #if defined(XP_WIN)
24 # include <io.h>
25 inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) {
26 intptr_t handle = _get_osfhandle(aFd);
27 if ((handle == -1) || (handle == -2)) {
28 // -1: Invalid handle. -2: stdin/out/err not associated with a stream.
29 return mozilla::Nothing();
31 return mozilla::Some(handle);
33 #else
34 inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) {
35 return mozilla::Some<intptr_t>(aFd);
37 #endif /* if not XP_WIN */
39 namespace {
41 struct DebugFilesAutoLockTraits {
42 typedef PRLock* type;
43 typedef const PRLock* const_type;
44 static const_type empty() { return nullptr; }
45 static void release(type aL) { PR_Unlock(aL); }
48 class DebugFilesAutoLock : public mozilla::Scoped<DebugFilesAutoLockTraits> {
49 static PRLock* Lock;
51 public:
52 static PRLock* getDebugFileIDsLock() {
53 // On windows this static is not thread safe, but we know that the first
54 // call is from
55 // * An early registration of a debug FD or
56 // * The call to InitWritePoisoning.
57 // Since the early debug FDs are logs created early in the main thread
58 // and no writes are trapped before InitWritePoisoning, we are safe.
59 if (!Lock) {
60 Lock = PR_NewLock();
63 // We have to use something lower level than a mutex. If we don't, we
64 // can get recursive in here when called from logging a call to free.
65 return Lock;
68 DebugFilesAutoLock()
69 : mozilla::Scoped<DebugFilesAutoLockTraits>(getDebugFileIDsLock()) {
70 PR_Lock(get());
74 PRLock* DebugFilesAutoLock::Lock;
76 // The ChunkedList<T> class implements, at the high level, a non-iterable
77 // list of instances of T. Its goal is to be somehow minimalist for the
78 // use case of storing the debug files handles here, with the property of
79 // not requiring a lock to look up whether it contains a specific value.
80 // It is also chunked in blocks of chunk_size bytes so that its
81 // initialization doesn't require a memory allocation, while keeping the
82 // possibility to increase its size as necessary. Note that chunks are
83 // never deallocated (except in the destructor).
84 // All operations are essentially O(N) but N is not expected to be large
85 // enough to matter.
86 template <typename T, size_t chunk_size = 64>
87 class ChunkedList {
88 struct ListChunk {
89 static const size_t kLength =
90 (chunk_size - sizeof(ListChunk*)) / sizeof(mozilla::Atomic<T>);
92 mozilla::Atomic<T> mElements[kLength];
93 mozilla::UniquePtr<ListChunk> mNext;
95 ListChunk() : mNext(nullptr) {}
98 ListChunk mList;
99 mozilla::Atomic<size_t> mLength;
101 public:
102 ChunkedList() : mLength(0) {}
104 ~ChunkedList() {
105 // There can be writes happening after this destructor runs, so keep
106 // the list contents and don't reset mLength. But if there are more
107 // elements left than the first chunk can hold, then all hell breaks
108 // loose for any write that would happen after that because any extra
109 // chunk would be deallocated, so just crash in that case.
110 MOZ_RELEASE_ASSERT(mLength <= ListChunk::kLength);
113 // Add an element at the end of the last chunk of the list. Create a new
114 // chunk if there is not enough room.
115 // This is not thread-safe with another thread calling Add or Remove.
116 void Add(T aValue) {
117 ListChunk* list = &mList;
118 size_t position = mLength;
119 for (; position >= ListChunk::kLength; position -= ListChunk::kLength) {
120 if (!list->mNext) {
121 list->mNext.reset(new ListChunk());
123 list = list->mNext.get();
125 // Use an order of operations that ensures any racing Contains call
126 // can't be hurt.
127 list->mElements[position] = aValue;
128 mLength++;
131 // Remove an element from the list by replacing it with the last element
132 // of the list, and then shrinking the list.
133 // This is not thread-safe with another thread calling Add or Remove.
134 void Remove(T aValue) {
135 if (!mLength) {
136 return;
138 ListChunk* list = &mList;
139 size_t last = mLength - 1;
140 do {
141 size_t position = 0;
142 // Look for an element matching the given value.
143 for (; position < ListChunk::kLength; position++) {
144 if (aValue == list->mElements[position]) {
145 ListChunk* last_list = list;
146 // Look for the last element in the list, starting from where we are
147 // instead of starting over.
148 for (; last >= ListChunk::kLength; last -= ListChunk::kLength) {
149 last_list = last_list->mNext.get();
151 // Use an order of operations that ensures any racing Contains call
152 // can't be hurt.
153 T value = last_list->mElements[last];
154 list->mElements[position] = value;
155 mLength--;
156 return;
159 last -= ListChunk::kLength;
160 list = list->mNext.get();
161 } while (list);
164 // Returns whether the list contains the given value. It is meant to be safe
165 // to use without locking, with the tradeoff of being not entirely accurate
166 // if another thread adds or removes an element while this function runs.
167 bool Contains(T aValue) {
168 ListChunk* list = &mList;
169 // Fix the range of the lookup to whatever the list length is when the
170 // function is called.
171 size_t length = mLength;
172 do {
173 size_t list_length = ListChunk::kLength;
174 list_length = std::min(list_length, length);
175 for (size_t position = 0; position < list_length; position++) {
176 if (aValue == list->mElements[position]) {
177 return true;
180 length -= ListChunk::kLength;
181 list = list->mNext.get();
182 } while (list);
184 return false;
188 typedef ChunkedList<intptr_t> FdList;
190 // Return a list used to hold the IDs of the current debug files. On unix
191 // an ID is a file descriptor. On Windows it is a file HANDLE.
192 FdList& getDebugFileIDs() {
193 static FdList DebugFileIDs;
194 return DebugFileIDs;
197 } // namespace
199 namespace mozilla {
201 // Auxiliary Method to test if a file descriptor is registered to be ignored
202 // by the poisoning IO interposer
203 bool IsDebugFile(intptr_t aFileID) {
204 return getDebugFileIDs().Contains(aFileID);
207 } // namespace mozilla
209 extern "C" {
211 void MozillaRegisterDebugHandle(intptr_t aHandle) {
212 DebugFilesAutoLock lockedScope;
213 FdList& DebugFileIDs = getDebugFileIDs();
214 MOZ_ASSERT(!DebugFileIDs.Contains(aHandle));
215 DebugFileIDs.Add(aHandle);
218 void MozillaRegisterDebugFD(int aFd) {
219 mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd);
220 if (!handle.isSome()) {
221 return;
223 MozillaRegisterDebugHandle(handle.value());
226 void MozillaRegisterDebugFILE(FILE* aFile) {
227 int fd = fileno(aFile);
228 if (fd == 1 || fd == 2) {
229 return;
231 MozillaRegisterDebugFD(fd);
234 void MozillaUnRegisterDebugHandle(intptr_t aHandle) {
235 DebugFilesAutoLock lockedScope;
236 FdList& DebugFileIDs = getDebugFileIDs();
237 MOZ_ASSERT(DebugFileIDs.Contains(aHandle));
238 DebugFileIDs.Remove(aHandle);
241 void MozillaUnRegisterDebugFD(int aFd) {
242 mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd);
243 if (!handle.isSome()) {
244 return;
246 MozillaUnRegisterDebugHandle(handle.value());
249 void MozillaUnRegisterDebugFILE(FILE* aFile) {
250 int fd = fileno(aFile);
251 if (fd == 1 || fd == 2) {
252 return;
254 fflush(aFile);
255 MozillaUnRegisterDebugFD(fd);
258 } // extern "C"
260 #ifdef MOZ_REPLACE_MALLOC
261 void mozilla::DebugFdRegistry::RegisterHandle(intptr_t aHandle) {
262 MozillaRegisterDebugHandle(aHandle);
265 void mozilla::DebugFdRegistry::UnRegisterHandle(intptr_t aHandle) {
266 MozillaUnRegisterDebugHandle(aHandle);
268 #endif