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"
14 #include "PoisonIOInterposer.h"
18 #ifdef MOZ_REPLACE_MALLOC
19 # include "replace_malloc_bridge.h"
22 // Auxiliary method to convert file descriptors to ids
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
);
34 inline mozilla::Maybe
<intptr_t> FileDescriptorToHandle(int aFd
) {
35 return mozilla::Some
<intptr_t>(aFd
);
37 #endif /* if not XP_WIN */
41 struct DebugFilesAutoLockTraits
{
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
> {
52 static PRLock
* getDebugFileIDsLock() {
53 // On windows this static is not thread safe, but we know that the first
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.
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.
69 : mozilla::Scoped
<DebugFilesAutoLockTraits
>(getDebugFileIDsLock()) {
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
86 template <typename T
, size_t chunk_size
= 64>
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) {}
99 mozilla::Atomic
<size_t> mLength
;
102 ChunkedList() : mLength(0) {}
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.
117 ListChunk
* list
= &mList
;
118 size_t position
= mLength
;
119 for (; position
>= ListChunk::kLength
; position
-= ListChunk::kLength
) {
121 list
->mNext
.reset(new ListChunk());
123 list
= list
->mNext
.get();
125 // Use an order of operations that ensures any racing Contains call
127 list
->mElements
[position
] = aValue
;
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
) {
138 ListChunk
* list
= &mList
;
139 size_t last
= mLength
- 1;
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
153 T value
= last_list
->mElements
[last
];
154 list
->mElements
[position
] = value
;
159 last
-= ListChunk::kLength
;
160 list
= list
->mNext
.get();
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
;
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
]) {
180 length
-= ListChunk::kLength
;
181 list
= list
->mNext
.get();
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
;
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
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()) {
223 MozillaRegisterDebugHandle(handle
.value());
226 void MozillaRegisterDebugFILE(FILE* aFile
) {
227 int fd
= fileno(aFile
);
228 if (fd
== 1 || fd
== 2) {
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()) {
246 MozillaUnRegisterDebugHandle(handle
.value());
249 void MozillaUnRegisterDebugFILE(FILE* aFile
) {
250 int fd
= fileno(aFile
);
251 if (fd
== 1 || fd
== 2) {
255 MozillaUnRegisterDebugFD(fd
);
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
);