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.cpp268
1 files changed, 268 insertions, 0 deletions
diff --git a/xpcom/build/PoisonIOInterposerBase.cpp b/xpcom/build/PoisonIOInterposerBase.cpp
new file mode 100644
index 0000000000..8add6740da
--- /dev/null
+++ b/xpcom/build/PoisonIOInterposerBase.cpp
@@ -0,0 +1,268 @@
+/* -*- 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/Maybe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/UniquePtr.h"
+
+#include <algorithm>
+
+#include "PoisonIOInterposer.h"
+
+#include "prlock.h"
+
+#ifdef MOZ_REPLACE_MALLOC
+# include "replace_malloc_bridge.h"
+#endif
+
+// Auxiliary method to convert file descriptors to ids
+#if defined(XP_WIN)
+# include <io.h>
+inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) {
+ intptr_t handle = _get_osfhandle(aFd);
+ if ((handle == -1) || (handle == -2)) {
+ // -1: Invalid handle. -2: stdin/out/err not associated with a stream.
+ return mozilla::Nothing();
+ }
+ return mozilla::Some(handle);
+}
+#else
+inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) {
+ return mozilla::Some<intptr_t>(aFd);
+}
+#endif /* if not XP_WIN */
+
+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 mozilla::Scoped<DebugFilesAutoLockTraits> {
+ static PRLock* Lock;
+
+ public:
+ 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()
+ : mozilla::Scoped<DebugFilesAutoLockTraits>(getDebugFileIDsLock()) {
+ PR_Lock(get());
+ }
+};
+
+PRLock* DebugFilesAutoLock::Lock;
+
+// 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) {
+ mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd);
+ if (!handle.isSome()) {
+ return;
+ }
+ MozillaRegisterDebugHandle(handle.value());
+}
+
+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) {
+ mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd);
+ if (!handle.isSome()) {
+ return;
+ }
+ MozillaUnRegisterDebugHandle(handle.value());
+}
+
+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 mozilla::DebugFdRegistry::RegisterHandle(intptr_t aHandle) {
+ MozillaRegisterDebugHandle(aHandle);
+}
+
+void mozilla::DebugFdRegistry::UnRegisterHandle(intptr_t aHandle) {
+ MozillaUnRegisterDebugHandle(aHandle);
+}
+#endif