summaryrefslogtreecommitdiffstats
path: root/xpcom/build/PoisonIOInterposerBase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/build/PoisonIOInterposerBase.cpp')
-rw-r--r--xpcom/build/PoisonIOInterposerBase.cpp291
1 files changed, 291 insertions, 0 deletions
diff --git a/xpcom/build/PoisonIOInterposerBase.cpp b/xpcom/build/PoisonIOInterposerBase.cpp
new file mode 100644
index 000000000..0e5754392
--- /dev/null
+++ b/xpcom/build/PoisonIOInterposerBase.cpp
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Mutex.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/UniquePtr.h"
+
+#include <algorithm>
+
+#include "PoisonIOInterposer.h"
+
+#ifdef MOZ_REPLACE_MALLOC
+#include "replace_malloc_bridge.h"
+#endif
+
+// Auxiliary method to convert file descriptors to ids
+#if defined(XP_WIN32)
+#include <io.h>
+inline intptr_t
+FileDescriptorToHandle(int aFd)
+{
+ return _get_osfhandle(aFd);
+}
+#else
+inline intptr_t
+FileDescriptorToHandle(int aFd)
+{
+ return aFd;
+}
+#endif /* if not XP_WIN32 */
+
+using namespace mozilla;
+
+namespace {
+struct DebugFilesAutoLockTraits
+{
+ typedef PRLock* type;
+ typedef const PRLock* const_type;
+ static const_type empty() { return nullptr; }
+ static void release(type aL) { PR_Unlock(aL); }
+};
+
+class DebugFilesAutoLock : public Scoped<DebugFilesAutoLockTraits>
+{
+ static PRLock* Lock;
+public:
+ static void Clear();
+ static PRLock* getDebugFileIDsLock()
+ {
+ // On windows this static is not thread safe, but we know that the first
+ // call is from
+ // * An early registration of a debug FD or
+ // * The call to InitWritePoisoning.
+ // Since the early debug FDs are logs created early in the main thread
+ // and no writes are trapped before InitWritePoisoning, we are safe.
+ if (!Lock) {
+ Lock = PR_NewLock();
+ }
+
+ // We have to use something lower level than a mutex. If we don't, we
+ // can get recursive in here when called from logging a call to free.
+ return Lock;
+ }
+
+ DebugFilesAutoLock()
+ : Scoped<DebugFilesAutoLockTraits>(getDebugFileIDsLock())
+ {
+ PR_Lock(get());
+ }
+};
+
+PRLock* DebugFilesAutoLock::Lock;
+void
+DebugFilesAutoLock::Clear()
+{
+ MOZ_ASSERT(Lock != nullptr);
+ Lock = nullptr;
+}
+
+// The ChunkedList<T> class implements, at the high level, a non-iterable
+// list of instances of T. Its goal is to be somehow minimalist for the
+// use case of storing the debug files handles here, with the property of
+// not requiring a lock to look up whether it contains a specific value.
+// It is also chunked in blocks of chunk_size bytes so that its
+// initialization doesn't require a memory allocation, while keeping the
+// possibility to increase its size as necessary. Note that chunks are
+// never deallocated (except in the destructor).
+// All operations are essentially O(N) but N is not expected to be large
+// enough to matter.
+template <typename T, size_t chunk_size=64>
+class ChunkedList {
+ struct ListChunk {
+ static const size_t kLength = \
+ (chunk_size - sizeof(ListChunk*)) / sizeof(mozilla::Atomic<T>);
+
+ mozilla::Atomic<T> mElements[kLength];
+ mozilla::UniquePtr<ListChunk> mNext;
+
+ ListChunk() : mNext(nullptr) {}
+ };
+
+ ListChunk mList;
+ mozilla::Atomic<size_t> mLength;
+
+public:
+ ChunkedList() : mLength(0) {}
+
+ ~ChunkedList() {
+ // There can be writes happening after this destructor runs, so keep
+ // the list contents and don't reset mLength. But if there are more
+ // elements left than the first chunk can hold, then all hell breaks
+ // loose for any write that would happen after that because any extra
+ // chunk would be deallocated, so just crash in that case.
+ MOZ_RELEASE_ASSERT(mLength <= ListChunk::kLength);
+ }
+
+ // Add an element at the end of the last chunk of the list. Create a new
+ // chunk if there is not enough room.
+ // This is not thread-safe with another thread calling Add or Remove.
+ void Add(T aValue)
+ {
+ ListChunk *list = &mList;
+ size_t position = mLength;
+ for (; position >= ListChunk::kLength; position -= ListChunk::kLength) {
+ if (!list->mNext) {
+ list->mNext.reset(new ListChunk());
+ }
+ list = list->mNext.get();
+ }
+ // Use an order of operations that ensures any racing Contains call
+ // can't be hurt.
+ list->mElements[position] = aValue;
+ mLength++;
+ }
+
+ // Remove an element from the list by replacing it with the last element
+ // of the list, and then shrinking the list.
+ // This is not thread-safe with another thread calling Add or Remove.
+ void Remove(T aValue)
+ {
+ if (!mLength) {
+ return;
+ }
+ ListChunk *list = &mList;
+ size_t last = mLength - 1;
+ do {
+ size_t position = 0;
+ // Look for an element matching the given value.
+ for (; position < ListChunk::kLength; position++) {
+ if (aValue == list->mElements[position]) {
+ ListChunk *last_list = list;
+ // Look for the last element in the list, starting from where we are
+ // instead of starting over.
+ for (; last >= ListChunk::kLength; last -= ListChunk::kLength) {
+ last_list = last_list->mNext.get();
+ }
+ // Use an order of operations that ensures any racing Contains call
+ // can't be hurt.
+ T value = last_list->mElements[last];
+ list->mElements[position] = value;
+ mLength--;
+ return;
+ }
+ }
+ last -= ListChunk::kLength;
+ list = list->mNext.get();
+ } while (list);
+ }
+
+ // Returns whether the list contains the given value. It is meant to be safe
+ // to use without locking, with the tradeoff of being not entirely accurate
+ // if another thread adds or removes an element while this function runs.
+ bool Contains(T aValue)
+ {
+ ListChunk *list = &mList;
+ // Fix the range of the lookup to whatever the list length is when the
+ // function is called.
+ size_t length = mLength;
+ do {
+ size_t list_length = ListChunk::kLength;
+ list_length = std::min(list_length, length);
+ for (size_t position = 0; position < list_length; position++) {
+ if (aValue == list->mElements[position]) {
+ return true;
+ }
+ }
+ length -= ListChunk::kLength;
+ list = list->mNext.get();
+ } while (list);
+
+ return false;
+ }
+};
+
+typedef ChunkedList<intptr_t> FdList;
+
+// Return a list used to hold the IDs of the current debug files. On unix
+// an ID is a file descriptor. On Windows it is a file HANDLE.
+FdList&
+getDebugFileIDs()
+{
+ static FdList DebugFileIDs;
+ return DebugFileIDs;
+}
+
+
+} // namespace
+
+namespace mozilla {
+
+// Auxiliary Method to test if a file descriptor is registered to be ignored
+// by the poisoning IO interposer
+bool
+IsDebugFile(intptr_t aFileID)
+{
+ return getDebugFileIDs().Contains(aFileID);
+}
+
+} // namespace mozilla
+
+extern "C" {
+
+void
+MozillaRegisterDebugHandle(intptr_t aHandle)
+{
+ DebugFilesAutoLock lockedScope;
+ FdList& DebugFileIDs = getDebugFileIDs();
+ MOZ_ASSERT(!DebugFileIDs.Contains(aHandle));
+ DebugFileIDs.Add(aHandle);
+}
+
+void
+MozillaRegisterDebugFD(int aFd)
+{
+ MozillaRegisterDebugHandle(FileDescriptorToHandle(aFd));
+}
+
+void
+MozillaRegisterDebugFILE(FILE* aFile)
+{
+ int fd = fileno(aFile);
+ if (fd == 1 || fd == 2) {
+ return;
+ }
+ MozillaRegisterDebugFD(fd);
+}
+
+void
+MozillaUnRegisterDebugHandle(intptr_t aHandle)
+{
+ DebugFilesAutoLock lockedScope;
+ FdList& DebugFileIDs = getDebugFileIDs();
+ MOZ_ASSERT(DebugFileIDs.Contains(aHandle));
+ DebugFileIDs.Remove(aHandle);
+}
+
+void
+MozillaUnRegisterDebugFD(int aFd)
+{
+ MozillaUnRegisterDebugHandle(FileDescriptorToHandle(aFd));
+}
+
+void
+MozillaUnRegisterDebugFILE(FILE* aFile)
+{
+ int fd = fileno(aFile);
+ if (fd == 1 || fd == 2) {
+ return;
+ }
+ fflush(aFile);
+ MozillaUnRegisterDebugFD(fd);
+}
+
+} // extern "C"
+
+#ifdef MOZ_REPLACE_MALLOC
+void
+DebugFdRegistry::RegisterHandle(intptr_t aHandle)
+{
+ MozillaRegisterDebugHandle(aHandle);
+}
+
+void
+DebugFdRegistry::UnRegisterHandle(intptr_t aHandle)
+{
+ MozillaUnRegisterDebugHandle(aHandle);
+}
+#endif