summaryrefslogtreecommitdiffstats
path: root/xpcom/build/IOInterposer.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /xpcom/build/IOInterposer.cpp
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/build/IOInterposer.cpp')
-rw-r--r--xpcom/build/IOInterposer.cpp532
1 files changed, 532 insertions, 0 deletions
diff --git a/xpcom/build/IOInterposer.cpp b/xpcom/build/IOInterposer.cpp
new file mode 100644
index 0000000000..0420965b91
--- /dev/null
+++ b/xpcom/build/IOInterposer.cpp
@@ -0,0 +1,532 @@
+/* -*- 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 <algorithm>
+#include <vector>
+
+#include "IOInterposer.h"
+
+#include "IOInterposerPrivate.h"
+#include "MainThreadIOLogger.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ThreadLocal.h"
+#include "nscore.h" // for NS_FREE_PERMANENT_DATA
+#if !defined(XP_WIN)
+# include "NSPRInterposer.h"
+#endif // !defined(XP_WIN)
+#include "nsXULAppAPI.h"
+#include "PoisonIOInterposer.h"
+#include "prenv.h"
+
+namespace {
+
+/** Find if a vector contains a specific element */
+template <class T>
+bool VectorContains(const std::vector<T>& aVector, const T& aElement) {
+ return std::find(aVector.begin(), aVector.end(), aElement) != aVector.end();
+}
+
+/** Remove element from a vector */
+template <class T>
+void VectorRemove(std::vector<T>& aVector, const T& aElement) {
+ typename std::vector<T>::iterator newEnd =
+ std::remove(aVector.begin(), aVector.end(), aElement);
+ aVector.erase(newEnd, aVector.end());
+}
+
+/** Lists of Observers */
+struct ObserverLists {
+ private:
+ ~ObserverLists() = default;
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ObserverLists)
+
+ ObserverLists() = default;
+
+ ObserverLists(ObserverLists const& aOther)
+ : mCreateObservers(aOther.mCreateObservers),
+ mReadObservers(aOther.mReadObservers),
+ mWriteObservers(aOther.mWriteObservers),
+ mFSyncObservers(aOther.mFSyncObservers),
+ mStatObservers(aOther.mStatObservers),
+ mCloseObservers(aOther.mCloseObservers),
+ mStageObservers(aOther.mStageObservers) {}
+ // Lists of observers for I/O events.
+ // These are implemented as vectors since they are allowed to survive gecko,
+ // without reporting leaks. This is necessary for the IOInterposer to be used
+ // for late-write checks.
+ std::vector<mozilla::IOInterposeObserver*> mCreateObservers;
+ std::vector<mozilla::IOInterposeObserver*> mReadObservers;
+ std::vector<mozilla::IOInterposeObserver*> mWriteObservers;
+ std::vector<mozilla::IOInterposeObserver*> mFSyncObservers;
+ std::vector<mozilla::IOInterposeObserver*> mStatObservers;
+ std::vector<mozilla::IOInterposeObserver*> mCloseObservers;
+ std::vector<mozilla::IOInterposeObserver*> mStageObservers;
+};
+
+class PerThreadData {
+ public:
+ explicit PerThreadData(bool aIsMainThread = false)
+ : mIsMainThread(aIsMainThread),
+ mIsHandlingObservation(false),
+ mCurrentGeneration(0) {
+ MOZ_COUNT_CTOR(PerThreadData);
+ }
+
+ MOZ_COUNTED_DTOR(PerThreadData)
+
+ void CallObservers(mozilla::IOInterposeObserver::Observation& aObservation) {
+ // Prevent recursive reporting.
+ if (mIsHandlingObservation) {
+ return;
+ }
+
+ mIsHandlingObservation = true;
+ // Decide which list of observers to inform
+ const std::vector<mozilla::IOInterposeObserver*>* observers = nullptr;
+ switch (aObservation.ObservedOperation()) {
+ case mozilla::IOInterposeObserver::OpCreateOrOpen:
+ observers = &mObserverLists->mCreateObservers;
+ break;
+ case mozilla::IOInterposeObserver::OpRead:
+ observers = &mObserverLists->mReadObservers;
+ break;
+ case mozilla::IOInterposeObserver::OpWrite:
+ observers = &mObserverLists->mWriteObservers;
+ break;
+ case mozilla::IOInterposeObserver::OpFSync:
+ observers = &mObserverLists->mFSyncObservers;
+ break;
+ case mozilla::IOInterposeObserver::OpStat:
+ observers = &mObserverLists->mStatObservers;
+ break;
+ case mozilla::IOInterposeObserver::OpClose:
+ observers = &mObserverLists->mCloseObservers;
+ break;
+ case mozilla::IOInterposeObserver::OpNextStage:
+ observers = &mObserverLists->mStageObservers;
+ break;
+ default: {
+ // Invalid IO operation, see documentation comment for
+ // IOInterposer::Report()
+ MOZ_ASSERT(false);
+ // Just ignore it in non-debug builds.
+ return;
+ }
+ }
+ MOZ_ASSERT(observers);
+
+ // Inform observers
+ for (auto i = observers->begin(), e = observers->end(); i != e; ++i) {
+ (*i)->Observe(aObservation);
+ }
+ mIsHandlingObservation = false;
+ }
+
+ inline uint32_t GetCurrentGeneration() const { return mCurrentGeneration; }
+
+ inline bool IsMainThread() const { return mIsMainThread; }
+
+ inline void SetObserverLists(uint32_t aNewGeneration,
+ RefPtr<const ObserverLists>& aNewLists) {
+ mCurrentGeneration = aNewGeneration;
+ mObserverLists = aNewLists;
+ }
+
+ inline void ClearObserverLists() {
+ if (mObserverLists) {
+ mCurrentGeneration = 0;
+ mObserverLists = nullptr;
+ }
+ }
+
+ private:
+ bool mIsMainThread;
+ bool mIsHandlingObservation;
+ uint32_t mCurrentGeneration;
+ RefPtr<const ObserverLists> mObserverLists;
+};
+
+// Thread-safe list of observers, from which `PerThreadData` sources its own
+// local list when needed.
+class SourceList {
+ public:
+ SourceList()
+ : mObservedOperations(mozilla::IOInterposeObserver::OpNone),
+ mIsEnabled(true) {
+ MOZ_COUNT_CTOR(SourceList);
+ }
+
+ MOZ_COUNTED_DTOR(SourceList)
+
+ inline void Disable() { mIsEnabled = false; }
+ inline void Enable() { mIsEnabled = true; }
+
+ void Register(mozilla::IOInterposeObserver::Operation aOp,
+ mozilla::IOInterposeObserver* aStaticObserver) {
+ mozilla::IOInterposer::AutoLock lock(mLock);
+
+ ObserverLists* newLists = nullptr;
+ if (mObserverLists) {
+ newLists = new ObserverLists(*mObserverLists);
+ } else {
+ newLists = new ObserverLists();
+ }
+ // You can register to observe multiple types of observations
+ // but you'll never be registered twice for the same observations.
+ if (aOp & mozilla::IOInterposeObserver::OpCreateOrOpen &&
+ !VectorContains(newLists->mCreateObservers, aStaticObserver)) {
+ newLists->mCreateObservers.push_back(aStaticObserver);
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpRead &&
+ !VectorContains(newLists->mReadObservers, aStaticObserver)) {
+ newLists->mReadObservers.push_back(aStaticObserver);
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpWrite &&
+ !VectorContains(newLists->mWriteObservers, aStaticObserver)) {
+ newLists->mWriteObservers.push_back(aStaticObserver);
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpFSync &&
+ !VectorContains(newLists->mFSyncObservers, aStaticObserver)) {
+ newLists->mFSyncObservers.push_back(aStaticObserver);
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpStat &&
+ !VectorContains(newLists->mStatObservers, aStaticObserver)) {
+ newLists->mStatObservers.push_back(aStaticObserver);
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpClose &&
+ !VectorContains(newLists->mCloseObservers, aStaticObserver)) {
+ newLists->mCloseObservers.push_back(aStaticObserver);
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpNextStage &&
+ !VectorContains(newLists->mStageObservers, aStaticObserver)) {
+ newLists->mStageObservers.push_back(aStaticObserver);
+ }
+ mObserverLists = newLists;
+ mObservedOperations =
+ (mozilla::IOInterposeObserver::Operation)(mObservedOperations | aOp);
+
+ mCurrentGeneration++;
+ }
+
+ void Unregister(mozilla::IOInterposeObserver::Operation aOp,
+ mozilla::IOInterposeObserver* aStaticObserver) {
+ mozilla::IOInterposer::AutoLock lock(mLock);
+
+ ObserverLists* newLists = nullptr;
+ if (mObserverLists) {
+ newLists = new ObserverLists(*mObserverLists);
+ } else {
+ newLists = new ObserverLists();
+ }
+
+ if (aOp & mozilla::IOInterposeObserver::OpCreateOrOpen) {
+ VectorRemove(newLists->mCreateObservers, aStaticObserver);
+ if (newLists->mCreateObservers.empty()) {
+ mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
+ mObservedOperations &
+ ~mozilla::IOInterposeObserver::OpCreateOrOpen);
+ }
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpRead) {
+ VectorRemove(newLists->mReadObservers, aStaticObserver);
+ if (newLists->mReadObservers.empty()) {
+ mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
+ mObservedOperations & ~mozilla::IOInterposeObserver::OpRead);
+ }
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpWrite) {
+ VectorRemove(newLists->mWriteObservers, aStaticObserver);
+ if (newLists->mWriteObservers.empty()) {
+ mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
+ mObservedOperations & ~mozilla::IOInterposeObserver::OpWrite);
+ }
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpFSync) {
+ VectorRemove(newLists->mFSyncObservers, aStaticObserver);
+ if (newLists->mFSyncObservers.empty()) {
+ mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
+ mObservedOperations & ~mozilla::IOInterposeObserver::OpFSync);
+ }
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpStat) {
+ VectorRemove(newLists->mStatObservers, aStaticObserver);
+ if (newLists->mStatObservers.empty()) {
+ mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
+ mObservedOperations & ~mozilla::IOInterposeObserver::OpStat);
+ }
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpClose) {
+ VectorRemove(newLists->mCloseObservers, aStaticObserver);
+ if (newLists->mCloseObservers.empty()) {
+ mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
+ mObservedOperations & ~mozilla::IOInterposeObserver::OpClose);
+ }
+ }
+ if (aOp & mozilla::IOInterposeObserver::OpNextStage) {
+ VectorRemove(newLists->mStageObservers, aStaticObserver);
+ if (newLists->mStageObservers.empty()) {
+ mObservedOperations = (mozilla::IOInterposeObserver::Operation)(
+ mObservedOperations & ~mozilla::IOInterposeObserver::OpNextStage);
+ }
+ }
+ mObserverLists = newLists;
+ mCurrentGeneration++;
+ }
+
+ void Update(PerThreadData& aPtd) {
+ if (mCurrentGeneration == aPtd.GetCurrentGeneration()) {
+ return;
+ }
+ // If the generation counts don't match then we need to update the current
+ // thread's observer list with the new source list.
+ mozilla::IOInterposer::AutoLock lock(mLock);
+ aPtd.SetObserverLists(mCurrentGeneration, mObserverLists);
+ }
+
+ inline bool IsObservedOperation(mozilla::IOInterposeObserver::Operation aOp) {
+ // This does not occur inside of a lock, so this makes no guarantees that
+ // the observers are in sync with this. That is acceptable; it is not a
+ // problem if we occasionally report more or less IO than is actually
+ // occurring.
+ return mIsEnabled && !!(mObservedOperations & aOp);
+ }
+
+ private:
+ RefPtr<const ObserverLists> mObserverLists MOZ_GUARDED_BY(mLock);
+ // Note, we cannot use mozilla::Mutex here as the ObserverLists may be leaked
+ // (We want to monitor IO during shutdown). Furthermore, as we may have to
+ // unregister observers during shutdown an OffTheBooksMutex is not an option
+ // either, as its base calls into sDeadlockDetector which may be nullptr
+ // during shutdown.
+ mozilla::IOInterposer::Mutex mLock;
+ // Flags tracking which operations are being observed
+ mozilla::Atomic<mozilla::IOInterposeObserver::Operation,
+ mozilla::MemoryOrdering::Relaxed>
+ mObservedOperations;
+ // Used for quickly disabling everything by IOInterposer::Disable()
+ mozilla::Atomic<bool> mIsEnabled;
+ // Used to inform threads that the source observer list has changed
+ mozilla::Atomic<uint32_t> mCurrentGeneration;
+};
+
+// Special observation used by IOInterposer::EnteringNextStage()
+class NextStageObservation : public mozilla::IOInterposeObserver::Observation {
+ public:
+ NextStageObservation()
+ : mozilla::IOInterposeObserver::Observation(
+ mozilla::IOInterposeObserver::OpNextStage, "IOInterposer", false) {
+ mStart = mozilla::TimeStamp::Now();
+ mEnd = mStart;
+ }
+};
+
+// List of observers registered
+static mozilla::StaticAutoPtr<SourceList> sSourceList;
+static MOZ_THREAD_LOCAL(PerThreadData*) sThreadLocalData;
+static bool sThreadLocalDataInitialized;
+
+} // anonymous namespace
+
+namespace mozilla {
+
+IOInterposeObserver::Observation::Observation(Operation aOperation,
+ const char* aReference,
+ bool aShouldReport)
+ : mOperation(aOperation),
+ mReference(aReference),
+ mShouldReport(IOInterposer::IsObservedOperation(aOperation) &&
+ aShouldReport) {
+ if (mShouldReport) {
+ mStart = TimeStamp::Now();
+ }
+}
+
+IOInterposeObserver::Observation::Observation(Operation aOperation,
+ const TimeStamp& aStart,
+ const TimeStamp& aEnd,
+ const char* aReference)
+ : mOperation(aOperation),
+ mStart(aStart),
+ mEnd(aEnd),
+ mReference(aReference),
+ mShouldReport(false) {}
+
+const char* IOInterposeObserver::Observation::ObservedOperationString() const {
+ switch (mOperation) {
+ case OpCreateOrOpen:
+ return "create/open";
+ case OpRead:
+ return "read";
+ case OpWrite:
+ return "write";
+ case OpFSync:
+ return "fsync";
+ case OpStat:
+ return "stat";
+ case OpClose:
+ return "close";
+ case OpNextStage:
+ return "NextStage";
+ default:
+ return "unknown";
+ }
+}
+
+void IOInterposeObserver::Observation::Report() {
+ if (mShouldReport) {
+ mEnd = TimeStamp::Now();
+ IOInterposer::Report(*this);
+ }
+}
+
+bool IOInterposer::Init() {
+ // Don't initialize twice...
+ if (sSourceList) {
+ return true;
+ }
+ if (!sThreadLocalData.init()) {
+ return false;
+ }
+ sThreadLocalDataInitialized = true;
+ bool isMainThread = true;
+ RegisterCurrentThread(isMainThread);
+ sSourceList = new SourceList();
+
+ MainThreadIOLogger::Init();
+
+ // Now we initialize the various interposers depending on platform
+
+ // Under certain conditions it may be unsafe to initialize PoisonIOInterposer,
+ // such as when a background thread is already running. We set this variable
+ // elsewhere when such a condition applies.
+ if (!PR_GetEnv("MOZ_DISABLE_POISON_IO_INTERPOSER")) {
+ InitPoisonIOInterposer();
+ }
+
+ // We don't hook NSPR on Windows because PoisonIOInterposer captures a
+ // superset of the former's events.
+#if !defined(XP_WIN)
+ InitNSPRIOInterposing();
+#endif
+ return true;
+}
+
+bool IOInterposeObserver::IsMainThread() {
+ if (!sThreadLocalDataInitialized) {
+ return false;
+ }
+ PerThreadData* ptd = sThreadLocalData.get();
+ if (!ptd) {
+ return false;
+ }
+ return ptd->IsMainThread();
+}
+
+void IOInterposer::Clear() {
+ /* Clear() is a no-op on release builds so that we may continue to trap I/O
+ until process termination. In leak-checking builds, we need to shut down
+ IOInterposer so that all references are properly released. */
+#ifdef NS_FREE_PERMANENT_DATA
+ UnregisterCurrentThread();
+ sSourceList = nullptr;
+#endif
+}
+
+void IOInterposer::Disable() {
+ if (!sSourceList) {
+ return;
+ }
+ sSourceList->Disable();
+}
+
+void IOInterposer::Enable() {
+ if (!sSourceList) {
+ return;
+ }
+ sSourceList->Enable();
+}
+
+void IOInterposer::Report(IOInterposeObserver::Observation& aObservation) {
+ PerThreadData* ptd = sThreadLocalData.get();
+ if (!ptd) {
+ // In this case the current thread is not registered with IOInterposer.
+ // Alternatively we could take the slow path and just lock everything if
+ // we're not registered. That could potentially perform poorly, though.
+ return;
+ }
+
+ if (!sSourceList) {
+ // If there is no longer a source list then we should clear the local one.
+ ptd->ClearObserverLists();
+ return;
+ }
+
+ sSourceList->Update(*ptd);
+
+ // Don't try to report if there's nobody listening.
+ if (!IOInterposer::IsObservedOperation(aObservation.ObservedOperation())) {
+ return;
+ }
+
+ ptd->CallObservers(aObservation);
+}
+
+bool IOInterposer::IsObservedOperation(IOInterposeObserver::Operation aOp) {
+ return sSourceList && sSourceList->IsObservedOperation(aOp);
+}
+
+void IOInterposer::Register(IOInterposeObserver::Operation aOp,
+ IOInterposeObserver* aStaticObserver) {
+ MOZ_ASSERT(aStaticObserver);
+ if (!sSourceList || !aStaticObserver) {
+ return;
+ }
+
+ sSourceList->Register(aOp, aStaticObserver);
+}
+
+void IOInterposer::Unregister(IOInterposeObserver::Operation aOp,
+ IOInterposeObserver* aStaticObserver) {
+ if (!sSourceList) {
+ return;
+ }
+
+ sSourceList->Unregister(aOp, aStaticObserver);
+}
+
+void IOInterposer::RegisterCurrentThread(bool aIsMainThread) {
+ if (!sThreadLocalDataInitialized) {
+ return;
+ }
+ MOZ_ASSERT(!sThreadLocalData.get());
+ PerThreadData* curThreadData = new PerThreadData(aIsMainThread);
+ sThreadLocalData.set(curThreadData);
+}
+
+void IOInterposer::UnregisterCurrentThread() {
+ if (!sThreadLocalDataInitialized) {
+ return;
+ }
+ if (PerThreadData* curThreadData = sThreadLocalData.get()) {
+ sThreadLocalData.set(nullptr);
+ delete curThreadData;
+ }
+}
+
+void IOInterposer::EnteringNextStage() {
+ if (!sSourceList) {
+ return;
+ }
+ NextStageObservation observation;
+ Report(observation);
+}
+
+} // namespace mozilla