Bug 1493627 part 2 - Unify OOM testing state variables for different kinds to make AutoEnterOOMUnsafeRegion work for stack checks. r=jonco
authorJan de Mooij <jdemooij@mozilla.com>
Tue, 23 Oct 2018 01:18:22 +0000
changeset 490880 9697472e6ab7298445ae8f169fe7b1ca5b247f11
parent 490879 271d76b2997b8010c69e15be8f972ce278d99727
child 490881 72ce1b22eee8336b6c1b60221696a1e01cc2dc39
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersjonco
bugs1493627
milestone65.0a1
Bug 1493627 part 2 - Unify OOM testing state variables for different kinds to make AutoEnterOOMUnsafeRegion work for stack checks. r=jonco Depends on D9253 Differential Revision: https://phabricator.services.mozilla.com/D9254
js/public/Utility.h
js/src/builtin/TestingFunctions.cpp
js/src/jsapi-tests/testHashTable.cpp
js/src/jsapi-tests/testOOM.cpp
js/src/jsutil.cpp
js/src/shell/js.cpp
js/src/vm/HelperThreads.cpp
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -94,19 +94,17 @@ namespace oom {
 const ThreadType FirstThreadTypeToTest = THREAD_TYPE_MAIN;
 const ThreadType LastThreadTypeToTest = THREAD_TYPE_WASM_TIER2;
 const ThreadType WorkerFirstThreadTypeToTest = THREAD_TYPE_CURRENT;
 const ThreadType WorkerLastThreadTypeToTest = THREAD_TYPE_CURRENT;
 
 extern bool InitThreadType(void);
 extern void SetThreadType(ThreadType);
 extern JS_FRIEND_API(uint32_t) GetThreadType(void);
-extern JS_FRIEND_API(uint32_t) GetAllocationThreadType(void);
-extern JS_FRIEND_API(uint32_t) GetStackCheckThreadType(void);
-extern JS_FRIEND_API(uint32_t) GetInterruptCheckThreadType(void);
+extern JS_FRIEND_API(uint32_t) GetSimulatingThreadType(void);
 
 # else
 
 inline bool InitThreadType(void) { return true; }
 inline void SetThreadType(ThreadType t) {};
 inline uint32_t GetThreadType(void) { return 0; }
 inline uint32_t GetAllocationThreadType(void) { return 0; }
 inline uint32_t GetStackCheckThreadType(void) { return 0; }
@@ -132,160 +130,133 @@ static MOZ_NEVER_INLINE void js_failedAl
 
 namespace js {
 namespace oom {
 
 /*
  * Out of memory testing support.  We provide various testing functions to
  * simulate OOM conditions and so we can test that they are handled correctly.
  */
+class FailureSimulator
+{
+  public:
+    enum class Kind : uint8_t { Nothing, OOM, StackOOM, Interrupt };
 
-extern JS_PUBLIC_DATA(uint32_t) targetThread;
-extern JS_PUBLIC_DATA(uint64_t) maxAllocations;
-extern JS_PUBLIC_DATA(uint64_t) counter;
-extern JS_PUBLIC_DATA(bool) failAlways;
-
-extern void
-SimulateOOMAfter(uint64_t allocations, uint32_t thread, bool always);
+  private:
+    Kind kind_ = Kind::Nothing;
+    uint32_t targetThread_ = 0;
+    uint64_t maxChecks_ = UINT64_MAX;
+    uint64_t counter_ = 0;
+    bool failAlways_ = true;
 
-extern void
-ResetSimulatedOOM();
+  public:
+    uint64_t maxChecks() const {
+        return maxChecks_;
+    }
+    void setMaxChecks(uint64_t value) {
+        maxChecks_ = value;
+    }
+    uint64_t counter() const {
+        return counter_;
+    }
+    uint32_t targetThread() const {
+        return targetThread_;
+    }
+    bool isThreadSimulatingAny() const {
+        return targetThread_ && targetThread_ == js::oom::GetSimulatingThreadType();
+    }
+    bool isThreadSimulating(Kind kind) const {
+        return kind_ == kind && isThreadSimulatingAny();
+    }
+    bool isSimulatedFailure(Kind kind) const {
+        if (!isThreadSimulating(kind)) {
+            return false;
+        }
+        return counter_ == maxChecks_ || (counter_ > maxChecks_ && failAlways_);
+    }
+    bool hadFailure(Kind kind) const {
+        return kind_ == kind && counter_ >= maxChecks_;
+    }
+    bool shouldFail(Kind kind) {
+        if (!isThreadSimulating(kind)) {
+            return false;
+        }
+        counter_++;
+        if (isSimulatedFailure(kind)) {
+            JS_OOM_CALL_BP_FUNC();
+            return true;
+        }
+        return false;
+    }
 
-inline bool
-IsThreadSimulatingOOM()
-{
-    return js::oom::targetThread &&
-        js::oom::targetThread == js::oom::GetAllocationThreadType();
-}
+    void simulateFailureAfter(Kind kind, uint64_t checks, uint32_t thread, bool always);
+    void reset();
+};
+extern JS_PUBLIC_DATA(FailureSimulator) simulator;
 
 inline bool
 IsSimulatedOOMAllocation()
 {
-    return IsThreadSimulatingOOM() &&
-           (counter == maxAllocations || (counter > maxAllocations && failAlways));
+    return simulator.isSimulatedFailure(FailureSimulator::Kind::OOM);
 }
 
 inline bool
 ShouldFailWithOOM()
 {
-    if (!IsThreadSimulatingOOM()) {
-        return false;
-    }
-
-    counter++;
-    if (IsSimulatedOOMAllocation()) {
-        JS_OOM_CALL_BP_FUNC();
-        return true;
-    }
-    return false;
+    return simulator.shouldFail(FailureSimulator::Kind::OOM);
 }
 
 inline bool
-HadSimulatedOOM() {
-    return counter >= maxAllocations;
+HadSimulatedOOM()
+{
+    return simulator.hadFailure(FailureSimulator::Kind::OOM);
 }
 
 /*
  * Out of stack space testing support, similar to OOM testing functions.
  */
 
-extern JS_PUBLIC_DATA(uint32_t) stackTargetThread;
-extern JS_PUBLIC_DATA(uint64_t) maxStackChecks;
-extern JS_PUBLIC_DATA(uint64_t) stackCheckCounter;
-extern JS_PUBLIC_DATA(bool) stackCheckFailAlways;
-
-extern void
-SimulateStackOOMAfter(uint64_t checks, uint32_t thread, bool always);
-
-extern void
-ResetSimulatedStackOOM();
-
-inline bool
-IsThreadSimulatingStackOOM()
-{
-    return js::oom::stackTargetThread &&
-        js::oom::stackTargetThread == js::oom::GetStackCheckThreadType();
-}
-
 inline bool
 IsSimulatedStackOOMCheck()
 {
-    return IsThreadSimulatingStackOOM() &&
-           (stackCheckCounter == maxStackChecks || (stackCheckCounter > maxStackChecks && stackCheckFailAlways));
+    return simulator.isSimulatedFailure(FailureSimulator::Kind::StackOOM);
 }
 
 inline bool
 ShouldFailWithStackOOM()
 {
-    if (!IsThreadSimulatingStackOOM()) {
-        return false;
-    }
-
-    stackCheckCounter++;
-    if (IsSimulatedStackOOMCheck()) {
-        JS_OOM_CALL_BP_FUNC();
-        return true;
-    }
-    return false;
+    return simulator.shouldFail(FailureSimulator::Kind::StackOOM);
 }
 
 inline bool
 HadSimulatedStackOOM()
 {
-    return stackCheckCounter >= maxStackChecks;
+    return simulator.hadFailure(FailureSimulator::Kind::StackOOM);
 }
 
 /*
  * Interrupt testing support, similar to OOM testing functions.
  */
 
-extern JS_PUBLIC_DATA(uint32_t) interruptTargetThread;
-extern JS_PUBLIC_DATA(uint64_t) maxInterruptChecks;
-extern JS_PUBLIC_DATA(uint64_t) interruptCheckCounter;
-extern JS_PUBLIC_DATA(bool) interruptCheckFailAlways;
-
-extern void
-SimulateInterruptAfter(uint64_t checks, uint32_t thread, bool always);
-
-extern void
-ResetSimulatedInterrupt();
-
-inline bool
-IsThreadSimulatingInterrupt()
-{
-    return js::oom::interruptTargetThread &&
-        js::oom::interruptTargetThread == js::oom::GetInterruptCheckThreadType();
-}
-
 inline bool
 IsSimulatedInterruptCheck()
 {
-    return IsThreadSimulatingInterrupt() &&
-           (interruptCheckCounter == maxInterruptChecks || (interruptCheckCounter > maxInterruptChecks && interruptCheckFailAlways));
+    return simulator.isSimulatedFailure(FailureSimulator::Kind::Interrupt);
 }
 
 inline bool
 ShouldFailWithInterrupt()
 {
-    if (!IsThreadSimulatingInterrupt()) {
-        return false;
-    }
-
-    interruptCheckCounter++;
-    if (IsSimulatedInterruptCheck()) {
-        JS_OOM_CALL_BP_FUNC();
-        return true;
-    }
-    return false;
+    return simulator.shouldFail(FailureSimulator::Kind::Interrupt);
 }
 
 inline bool
 HadSimulatedInterrupt()
 {
-    return interruptCheckCounter >= maxInterruptChecks;
+    return simulator.hadFailure(FailureSimulator::Kind::Interrupt);
 }
 
 } /* namespace oom */
 } /* namespace js */
 
 #  define JS_OOM_POSSIBLY_FAIL()                                              \
     do {                                                                      \
         if (js::oom::ShouldFailWithOOM())                                     \
@@ -347,33 +318,35 @@ struct MOZ_RAII JS_PUBLIC_DATA(AutoEnter
     using AnnotateOOMAllocationSizeCallback = void(*)(size_t);
     static AnnotateOOMAllocationSizeCallback annotateOOMSizeCallback;
     static void setAnnotateOOMAllocationSizeCallback(AnnotateOOMAllocationSizeCallback callback) {
         annotateOOMSizeCallback = callback;
     }
 
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
     AutoEnterOOMUnsafeRegion()
-      : oomEnabled_(oom::IsThreadSimulatingOOM() && oom::maxAllocations != UINT64_MAX),
+      : oomEnabled_(oom::simulator.isThreadSimulatingAny() &&
+                    oom::simulator.maxChecks() != UINT64_MAX),
         oomAfter_(0)
     {
         if (oomEnabled_) {
             MOZ_ALWAYS_TRUE(owner_.compareExchange(nullptr, this));
-            oomAfter_ = int64_t(oom::maxAllocations) - int64_t(oom::counter);
-            oom::maxAllocations = UINT64_MAX;
+            oomAfter_ = (int64_t(oom::simulator.maxChecks()) -
+                         int64_t(oom::simulator.counter()));
+            oom::simulator.setMaxChecks(UINT64_MAX);
         }
     }
 
     ~AutoEnterOOMUnsafeRegion() {
         if (oomEnabled_) {
-            MOZ_ASSERT(oom::maxAllocations == UINT64_MAX);
-            int64_t maxAllocations = int64_t(oom::counter) + oomAfter_;
-            MOZ_ASSERT(maxAllocations >= 0,
+            MOZ_ASSERT(oom::simulator.maxChecks() == UINT64_MAX);
+            int64_t maxChecks = int64_t(oom::simulator.counter()) + oomAfter_;
+            MOZ_ASSERT(maxChecks >= 0,
                        "alloc count + oom limit exceeds range, your oom limit is probably too large");
-            oom::maxAllocations = uint64_t(maxAllocations);
+            oom::simulator.setMaxChecks(maxChecks);
             MOZ_ALWAYS_TRUE(owner_.compareExchange(this, nullptr));
         }
     }
 
   private:
     // Used to catch concurrent use from other threads.
     static mozilla::Atomic<AutoEnterOOMUnsafeRegion*> owner_;
 
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1800,17 +1800,18 @@ SetupOOMFailure(JSContext* cx, bool fail
         JS_ReportErrorASCII(cx, "Invalid thread type specified");
         return false;
     }
 
     if (!CheckCanSimulateOOM(cx)) {
         return false;
     }
 
-    js::oom::SimulateOOMAfter(count, targetThread, failAlways);
+    js::oom::simulator.simulateFailureAfter(js::oom::FailureSimulator::Kind::OOM,
+                                            count, targetThread, failAlways);
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 OOMAfterAllocations(JSContext* cx, unsigned argc, Value* vp)
 {
     return SetupOOMFailure(cx, true, argc, vp);
@@ -1827,17 +1828,17 @@ ResetOOMFailure(JSContext* cx, unsigned 
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!CheckCanSimulateOOM(cx)) {
         return false;
     }
 
     args.rval().setBoolean(js::oom::HadSimulatedOOM());
-    js::oom::ResetSimulatedOOM();
+    js::oom::simulator.reset();
     return true;
 }
 
 static size_t
 CountCompartments(JSContext* cx)
 {
     size_t count = 0;
     for (auto zone : cx->runtime()->gc.zones()) {
@@ -2077,22 +2078,23 @@ ParseIterativeFailureTestParams(JSContex
 struct OOMSimulator : public IterativeFailureSimulator
 {
     void setup(JSContext* cx) override {
         cx->runtime()->hadOutOfMemory = false;
     }
 
     void startSimulating(JSContext* cx, unsigned i, unsigned thread, bool keepFailing) override {
         MOZ_ASSERT(!cx->runtime()->hadOutOfMemory);
-        js::oom::SimulateOOMAfter(i, thread, keepFailing);
+        js::oom::simulator.simulateFailureAfter(js::oom::FailureSimulator::Kind::OOM,
+                                                i, thread, keepFailing);
     }
 
     bool stopSimulating() override {
         bool handledOOM = js::oom::HadSimulatedOOM();
-        js::oom::ResetSimulatedOOM();
+        js::oom::simulator.reset();
         return handledOOM;
     }
 
     void cleanup(JSContext* cx) override {
         cx->runtime()->hadOutOfMemory = false;
     }
 };
 
@@ -2113,22 +2115,23 @@ OOMTest(JSContext* cx, unsigned argc, Va
 
     args.rval().setUndefined();
     return true;
 }
 
 struct StackOOMSimulator : public IterativeFailureSimulator
 {
     void startSimulating(JSContext* cx, unsigned i, unsigned thread, bool keepFailing) override {
-        js::oom::SimulateStackOOMAfter(i, thread, keepFailing);
+        js::oom::simulator.simulateFailureAfter(js::oom::FailureSimulator::Kind::StackOOM,
+                                                i, thread, keepFailing);
     }
 
     bool stopSimulating() override {
         bool handledOOM = js::oom::HadSimulatedStackOOM();
-        js::oom::ResetSimulatedStackOOM();
+        js::oom::simulator.reset();
         return handledOOM;
     }
 };
 
 static bool
 StackTest(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -2161,22 +2164,23 @@ struct FailingIterruptSimulator : public
         JS_AddInterruptCallback(cx, failingInterruptCallback);
     }
 
     void teardown(JSContext* cx) override {
         cx->interruptCallbacks().erase(prevEnd, cx->interruptCallbacks().end());
     }
 
     void startSimulating(JSContext* cx, unsigned i, unsigned thread, bool keepFailing) override {
-        js::oom::SimulateInterruptAfter(i, thread, keepFailing);
+        js::oom::simulator.simulateFailureAfter(js::oom::FailureSimulator::Kind::Interrupt,
+                                                i, thread, keepFailing);
     }
 
     bool stopSimulating() override {
         bool handledInterrupt = js::oom::HadSimulatedInterrupt();
-        js::oom::ResetSimulatedInterrupt();
+        js::oom::simulator.reset();
         return handledInterrupt;
     }
 };
 
 static bool
 InterruptTest(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
--- a/js/src/jsapi-tests/testHashTable.cpp
+++ b/js/src/jsapi-tests/testHashTable.cpp
@@ -383,21 +383,22 @@ GrowUntilResize()
 
     return true;
 }
 
 BEGIN_TEST(testHashMapGrowOOM)
 {
     uint32_t timeToFail;
     for (timeToFail = 1; timeToFail < 1000; timeToFail++) {
-        js::oom::SimulateOOMAfter(timeToFail, js::THREAD_TYPE_MAIN, false);
+        js::oom::simulator.simulateFailureAfter(js::oom::FailureSimulator::Kind::OOM,
+						timeToFail, js::THREAD_TYPE_MAIN, false);
         GrowUntilResize();
     }
 
-    js::oom::ResetSimulatedOOM();
+    js::oom::simulator.reset();
     return true;
 }
 
 END_TEST(testHashMapGrowOOM)
 #endif // defined(DEBUG)
 
 BEGIN_TEST(testHashTableMovableModIterator)
 {
--- a/js/src/jsapi-tests/testOOM.cpp
+++ b/js/src/jsapi-tests/testOOM.cpp
@@ -33,29 +33,30 @@ END_TEST(testOOM)
 #ifdef DEBUG  // js::oom functions are only available in debug builds.
 
 const uint32_t maxAllocsPerTest = 100;
 
 #define START_OOM_TEST(name)                                                  \
     testName = name;                                                          \
     printf("Test %s: started\n", testName);                                   \
     for (oomAfter = 1; oomAfter < maxAllocsPerTest; ++oomAfter) {             \
-    js::oom::SimulateOOMAfter(oomAfter, js::THREAD_TYPE_MAIN, true)
+    js::oom::simulator.simulateFailureAfter(js::oom::FailureSimulator::Kind::OOM, \
+                                            oomAfter, js::THREAD_TYPE_MAIN, true)
 
 #define OOM_TEST_FINISHED                                                     \
     {                                                                         \
         printf("Test %s: finished with %" PRIu64 " allocations\n",            \
                testName, oomAfter - 1);                                       \
         break;                                                                \
     }
 
 #define END_OOM_TEST                                                          \
     }                                                                         \
-    js::oom::ResetSimulatedOOM();                                             \
-    CHECK(oomAfter != maxAllocsPerTest)
+    js::oom::simulator.reset();                                    \
+CHECK(oomAfter != maxAllocsPerTest)
 
 BEGIN_TEST(testNewContext)
 {
     uninit(); // Get rid of test harness' original JSContext.
 
     JSContext* cx;
     START_OOM_TEST("new context");
     cx = JS_NewContext(8L * 1024 * 1024);
--- a/js/src/jsutil.cpp
+++ b/js/src/jsutil.cpp
@@ -28,185 +28,91 @@ using mozilla::Maybe;
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 /* For OOM testing functionality in Utility.h. */
 namespace js {
 
 mozilla::Atomic<AutoEnterOOMUnsafeRegion*> AutoEnterOOMUnsafeRegion::owner_;
 
 namespace oom {
 
-JS_PUBLIC_DATA(uint32_t) targetThread = 0;
-MOZ_THREAD_LOCAL(uint32_t) threadType;
-JS_PUBLIC_DATA(uint64_t) maxAllocations = UINT64_MAX;
-JS_PUBLIC_DATA(uint64_t) counter = 0;
-JS_PUBLIC_DATA(bool) failAlways = true;
-MOZ_THREAD_LOCAL(bool) isAllocationThread;
-
-JS_PUBLIC_DATA(uint32_t) stackTargetThread = 0;
-JS_PUBLIC_DATA(uint64_t) maxStackChecks = UINT64_MAX;
-JS_PUBLIC_DATA(uint64_t) stackCheckCounter = 0;
-JS_PUBLIC_DATA(bool) stackCheckFailAlways = true;
-MOZ_THREAD_LOCAL(bool) isStackCheckThread;
-
-JS_PUBLIC_DATA(uint32_t) interruptTargetThread = 0;
-JS_PUBLIC_DATA(uint64_t) maxInterruptChecks = UINT64_MAX;
-JS_PUBLIC_DATA(uint64_t) interruptCheckCounter = 0;
-JS_PUBLIC_DATA(bool) interruptCheckFailAlways = true;
-MOZ_THREAD_LOCAL(bool) isInterruptCheckThread;
+JS_PUBLIC_DATA(FailureSimulator) simulator;
+static MOZ_THREAD_LOCAL(uint32_t) threadType;
+static MOZ_THREAD_LOCAL(bool) isSimulatingThread;
 
 bool
-InitThreadType(void) {
-    return threadType.init() && isAllocationThread.init() &&
-        isStackCheckThread.init() && isInterruptCheckThread.init();
+InitThreadType()
+{
+    return threadType.init() && isSimulatingThread.init();
 }
 
 void
-SetThreadType(ThreadType type) {
+SetThreadType(ThreadType type)
+{
     threadType.set(type);
-    isAllocationThread.set(false);
-    isStackCheckThread.set(false);
-    isInterruptCheckThread.set(false);
+    isSimulatingThread.set(false);
 }
 
 uint32_t
-GetThreadType(void) {
+GetThreadType(void)
+{
     return threadType.get();
 }
 
 uint32_t
-GetAllocationThreadType(void) {
-    if (isAllocationThread.get()) {
+GetSimulatingThreadType()
+{
+    if (isSimulatingThread.get()) {
         return js::THREAD_TYPE_CURRENT;
     }
     return threadType.get();
 }
 
-uint32_t
-GetStackCheckThreadType(void) {
-    if (isStackCheckThread.get()) {
-        return js::THREAD_TYPE_CURRENT;
-    }
-    return threadType.get();
-}
-
-uint32_t
-GetInterruptCheckThreadType(void) {
-    if (isInterruptCheckThread.get()) {
-        return js::THREAD_TYPE_CURRENT;
-    }
-    return threadType.get();
-}
 
 static inline bool
 IsHelperThreadType(uint32_t thread)
 {
     return thread != THREAD_TYPE_NONE && thread != THREAD_TYPE_MAIN &&
         thread != THREAD_TYPE_CURRENT;
 }
 
 void
-SimulateOOMAfter(uint64_t allocations, uint32_t thread, bool always)
+FailureSimulator::simulateFailureAfter(Kind kind, uint64_t checks, uint32_t thread,
+                                       bool always)
 {
     Maybe<AutoLockHelperThreadState> lock;
-    if (IsHelperThreadType(targetThread) || IsHelperThreadType(thread)) {
-        lock.emplace();
-        HelperThreadState().waitForAllThreadsLocked(lock.ref());
-    }
-
-    MOZ_ASSERT(counter + allocations > counter);
-    MOZ_ASSERT(thread > js::THREAD_TYPE_NONE && thread < js::THREAD_TYPE_MAX);
-    targetThread = thread;
-    if (thread == js::THREAD_TYPE_CURRENT) {
-        isAllocationThread.set(true);
-    }
-    maxAllocations = counter + allocations;
-    failAlways = always;
-}
-
-void
-ResetSimulatedOOM()
-{
-    Maybe<AutoLockHelperThreadState> lock;
-    if (IsHelperThreadType(targetThread)) {
-        lock.emplace();
-        HelperThreadState().waitForAllThreadsLocked(lock.ref());
-    }
-
-    targetThread = THREAD_TYPE_NONE;
-    isAllocationThread.set(false);
-    maxAllocations = UINT64_MAX;
-    failAlways = false;
-}
-
-void
-SimulateStackOOMAfter(uint64_t checks, uint32_t thread, bool always)
-{
-    Maybe<AutoLockHelperThreadState> lock;
-    if (IsHelperThreadType(stackTargetThread) || IsHelperThreadType(thread)) {
+    if (IsHelperThreadType(targetThread_) || IsHelperThreadType(thread)) {
         lock.emplace();
         HelperThreadState().waitForAllThreadsLocked(lock.ref());
     }
 
-    MOZ_ASSERT(stackCheckCounter + checks > stackCheckCounter);
+    MOZ_ASSERT(counter_ + checks > counter_);
     MOZ_ASSERT(thread > js::THREAD_TYPE_NONE && thread < js::THREAD_TYPE_MAX);
-    stackTargetThread = thread;
+    targetThread_ = thread;
     if (thread == js::THREAD_TYPE_CURRENT) {
-        isStackCheckThread.set(true);
+        isSimulatingThread.set(true);
     }
-    maxStackChecks = stackCheckCounter + checks;
-    stackCheckFailAlways = always;
+    maxChecks_ = counter_ + checks;
+    failAlways_ = always;
+    kind_ = kind;
 }
 
 void
-ResetSimulatedStackOOM()
+FailureSimulator::reset()
 {
     Maybe<AutoLockHelperThreadState> lock;
-    if (IsHelperThreadType(stackTargetThread)) {
+    if (IsHelperThreadType(targetThread_)) {
         lock.emplace();
         HelperThreadState().waitForAllThreadsLocked(lock.ref());
     }
 
-    stackTargetThread = THREAD_TYPE_NONE;
-    isStackCheckThread.set(false);
-    maxStackChecks = UINT64_MAX;
-    stackCheckFailAlways = false;
-}
-
-void
-SimulateInterruptAfter(uint64_t checks, uint32_t thread, bool always)
-{
-    Maybe<AutoLockHelperThreadState> lock;
-    if (IsHelperThreadType(interruptTargetThread) || IsHelperThreadType(thread)) {
-        lock.emplace();
-        HelperThreadState().waitForAllThreadsLocked(lock.ref());
-    }
-
-    MOZ_ASSERT(interruptCheckCounter + checks > interruptCheckCounter);
-    MOZ_ASSERT(thread > js::THREAD_TYPE_NONE && thread < js::THREAD_TYPE_MAX);
-    interruptTargetThread = thread;
-    if (thread == js::THREAD_TYPE_CURRENT) {
-        isInterruptCheckThread.set(true);
-    }
-    maxInterruptChecks = interruptCheckCounter + checks;
-    interruptCheckFailAlways = always;
-}
-
-void
-ResetSimulatedInterrupt()
-{
-    Maybe<AutoLockHelperThreadState> lock;
-    if (IsHelperThreadType(interruptTargetThread)) {
-        lock.emplace();
-        HelperThreadState().waitForAllThreadsLocked(lock.ref());
-    }
-
-    interruptTargetThread = THREAD_TYPE_NONE;
-    isInterruptCheckThread.set(false);
-    maxInterruptChecks = UINT64_MAX;
-    interruptCheckFailAlways = false;
+    targetThread_ = THREAD_TYPE_NONE;
+    isSimulatingThread.set(false);
+    maxChecks_ = UINT64_MAX;
+    failAlways_ = false;
+    kind_ = Kind::Nothing;
 }
 
 } // namespace oom
 } // namespace js
 #endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
 
 bool js::gDisablePoisoning = false;
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -10943,17 +10943,17 @@ main(int argc, char** argv, char** envp)
     JS::SetModuleResolveHook(cx->runtime(), ShellModuleResolveHook);
     JS::SetModuleDynamicImportHook(cx->runtime(), ShellModuleDynamicImportHook);
     JS::SetModuleMetadataHook(cx->runtime(), CallModuleMetadataHook);
 
     result = Shell(cx, &op, envp);
 
 #ifdef DEBUG
     if (OOM_printAllocationCount) {
-        printf("OOM max count: %" PRIu64 "\n", js::oom::counter);
+        printf("OOM max count: %" PRIu64 "\n", js::oom::simulator.counter());
     }
 #endif
 
     JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
 
     // Must clear out some of sc's pointer containers before JS_DestroyContext.
     sc->markObservers.reset();
 
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -1320,17 +1320,17 @@ struct MOZ_RAII AutoSetContextRuntime
         TlsContext.get()->setRuntime(nullptr);
     }
 };
 
 static inline bool
 IsHelperThreadSimulatingOOM(js::ThreadType threadType)
 {
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
-    return js::oom::targetThread == threadType;
+    return js::oom::simulator.targetThread() == threadType;
 #else
     return false;
 #endif
 }
 
 void
 GlobalHelperThreadState::addSizeOfIncludingThis(JS::GlobalStats* stats,
                                                 AutoLockHelperThreadState& lock) const