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/. */
6 
7 #include "mozilla/Maybe.h"
8 #include "mozilla/Mutex.h"
9 #include "mozilla/Scoped.h"
10 #include "mozilla/UniquePtr.h"
11 
12 #include <algorithm>
13 
14 #include "PoisonIOInterposer.h"
15 
16 #include "prlock.h"
17 
18 #ifdef MOZ_REPLACE_MALLOC
19 #  include "replace_malloc_bridge.h"
20 #endif
21 
22 // Auxiliary method to convert file descriptors to ids
23 #if defined(XP_WIN)
24 #  include <io.h>
FileDescriptorToHandle(int aFd)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();
30   }
31   return mozilla::Some(handle);
32 }
33 #else
FileDescriptorToHandle(int aFd)34 inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) {
35   return mozilla::Some<intptr_t>(aFd);
36 }
37 #endif /* if not XP_WIN */
38 
39 namespace {
40 
41 struct DebugFilesAutoLockTraits {
42   typedef PRLock* type;
43   typedef const PRLock* const_type;
empty__anon99c135d00111::DebugFilesAutoLockTraits44   static const_type empty() { return nullptr; }
release__anon99c135d00111::DebugFilesAutoLockTraits45   static void release(type aL) { PR_Unlock(aL); }
46 };
47 
48 class DebugFilesAutoLock : public mozilla::Scoped<DebugFilesAutoLockTraits> {
49   static PRLock* Lock;
50 
51  public:
getDebugFileIDsLock()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();
61     }
62 
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;
66   }
67 
DebugFilesAutoLock()68   DebugFilesAutoLock()
69       : mozilla::Scoped<DebugFilesAutoLockTraits>(getDebugFileIDsLock()) {
70     PR_Lock(get());
71   }
72 };
73 
74 PRLock* DebugFilesAutoLock::Lock;
75 
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>);
91 
92     mozilla::Atomic<T> mElements[kLength];
93     mozilla::UniquePtr<ListChunk> mNext;
94 
ListChunk__anon99c135d00111::ChunkedList::ListChunk95     ListChunk() : mNext(nullptr) {}
96   };
97 
98   ListChunk mList;
99   mozilla::Atomic<size_t> mLength;
100 
101  public:
ChunkedList()102   ChunkedList() : mLength(0) {}
103 
~ChunkedList()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);
111   }
112 
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.
Add(T aValue)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());
122       }
123       list = list->mNext.get();
124     }
125     // Use an order of operations that ensures any racing Contains call
126     // can't be hurt.
127     list->mElements[position] = aValue;
128     mLength++;
129   }
130 
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.
Remove(T aValue)134   void Remove(T aValue) {
135     if (!mLength) {
136       return;
137     }
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();
150           }
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;
157         }
158       }
159       last -= ListChunk::kLength;
160       list = list->mNext.get();
161     } while (list);
162   }
163 
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.
Contains(T aValue)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;
178         }
179       }
180       length -= ListChunk::kLength;
181       list = list->mNext.get();
182     } while (list);
183 
184     return false;
185   }
186 };
187 
188 typedef ChunkedList<intptr_t> FdList;
189 
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.
getDebugFileIDs()192 FdList& getDebugFileIDs() {
193   static FdList DebugFileIDs;
194   return DebugFileIDs;
195 }
196 
197 }  // namespace
198 
199 namespace mozilla {
200 
201 // Auxiliary Method to test if a file descriptor is registered to be ignored
202 // by the poisoning IO interposer
IsDebugFile(intptr_t aFileID)203 bool IsDebugFile(intptr_t aFileID) {
204   return getDebugFileIDs().Contains(aFileID);
205 }
206 
207 }  // namespace mozilla
208 
209 extern "C" {
210 
MozillaRegisterDebugHandle(intptr_t aHandle)211 void MozillaRegisterDebugHandle(intptr_t aHandle) {
212   DebugFilesAutoLock lockedScope;
213   FdList& DebugFileIDs = getDebugFileIDs();
214   MOZ_ASSERT(!DebugFileIDs.Contains(aHandle));
215   DebugFileIDs.Add(aHandle);
216 }
217 
MozillaRegisterDebugFD(int aFd)218 void MozillaRegisterDebugFD(int aFd) {
219   mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd);
220   if (!handle.isSome()) {
221     return;
222   }
223   MozillaRegisterDebugHandle(handle.value());
224 }
225 
MozillaRegisterDebugFILE(FILE * aFile)226 void MozillaRegisterDebugFILE(FILE* aFile) {
227   int fd = fileno(aFile);
228   if (fd == 1 || fd == 2) {
229     return;
230   }
231   MozillaRegisterDebugFD(fd);
232 }
233 
MozillaUnRegisterDebugHandle(intptr_t aHandle)234 void MozillaUnRegisterDebugHandle(intptr_t aHandle) {
235   DebugFilesAutoLock lockedScope;
236   FdList& DebugFileIDs = getDebugFileIDs();
237   MOZ_ASSERT(DebugFileIDs.Contains(aHandle));
238   DebugFileIDs.Remove(aHandle);
239 }
240 
MozillaUnRegisterDebugFD(int aFd)241 void MozillaUnRegisterDebugFD(int aFd) {
242   mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd);
243   if (!handle.isSome()) {
244     return;
245   }
246   MozillaUnRegisterDebugHandle(handle.value());
247 }
248 
MozillaUnRegisterDebugFILE(FILE * aFile)249 void MozillaUnRegisterDebugFILE(FILE* aFile) {
250   int fd = fileno(aFile);
251   if (fd == 1 || fd == 2) {
252     return;
253   }
254   fflush(aFile);
255   MozillaUnRegisterDebugFD(fd);
256 }
257 
258 }  // extern "C"
259 
260 #ifdef MOZ_REPLACE_MALLOC
RegisterHandle(intptr_t aHandle)261 void mozilla::DebugFdRegistry::RegisterHandle(intptr_t aHandle) {
262   MozillaRegisterDebugHandle(aHandle);
263 }
264 
UnRegisterHandle(intptr_t aHandle)265 void mozilla::DebugFdRegistry::UnRegisterHandle(intptr_t aHandle) {
266   MozillaUnRegisterDebugHandle(aHandle);
267 }
268 #endif
269