Bug 1479394 - Clean up exception handling in bailout code and remove a MOZ_ASSERT_IF that triggered a UAF. r=nbp
authorJan de Mooij <jdemooij@mozilla.com>
Tue, 27 Nov 2018 10:34:24 +0000
changeset 504679 b81f4153e1a8c77521d089bf1ede2ab611145be9
parent 504678 6b2aceb0979a49dbcce80db1b59ef8238bd0d2b8
child 504680 cc18f53a55f09bbe03416841f4718d8c983f9a35
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnbp
bugs1479394
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1479394 - Clean up exception handling in bailout code and remove a MOZ_ASSERT_IF that triggered a UAF. r=nbp Differential Revision: https://phabricator.services.mozilla.com/D12110
js/src/jit-test/tests/baseline/bug1209585.js
js/src/jit-test/tests/ion/bug1479394.js
js/src/jit/Bailouts.cpp
js/src/jit/Bailouts.h
js/src/jit/BaselineBailouts.cpp
js/src/jit/BaselineJIT.h
js/src/jit/JitFrames.cpp
js/src/jit/MacroAssembler.cpp
js/src/jit/arm/MacroAssembler-arm.cpp
js/src/jit/arm64/MacroAssembler-arm64.cpp
js/src/jit/mips32/MacroAssembler-mips32.cpp
js/src/jit/mips64/MacroAssembler-mips64.cpp
js/src/jit/x64/MacroAssembler-x64.cpp
js/src/jit/x86/MacroAssembler-x86.cpp
--- a/js/src/jit-test/tests/baseline/bug1209585.js
+++ b/js/src/jit-test/tests/baseline/bug1209585.js
@@ -24,9 +24,14 @@ function oomTest(f) {
             more = resetOOMFailure();
         }
         i++;
     } while(more);
 }
 var g = newGlobal();
 oomTest(function() { new revocable(); });
 `);
-lfGlobal.runOffThreadScript();
+try {
+    lfGlobal.runOffThreadScript();
+} catch(e) {
+    // This can happen if we OOM while bailing out in Ion.
+    assertEq(e, "out of memory");
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1479394.js
@@ -0,0 +1,10 @@
+// |jit-test| skip-if: !('stackTest' in this)
+var dbgGlobal = newGlobal();
+var dbg = new dbgGlobal.Debugger(this);
+function f1() {
+    dbg.getNewestFrame().older;
+    throw new Error();
+}
+function f2() { f1(); }
+function f3() { f2(); }
+stackTest(f3);
--- a/js/src/jit/Bailouts.cpp
+++ b/js/src/jit/Bailouts.cpp
@@ -20,17 +20,17 @@
 #include "vm/Probes-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::IsInRange;
 
-uint32_t
+bool
 jit::Bailout(BailoutStack* sp, BaselineBailoutInfo** bailoutInfo)
 {
     JSContext* cx = TlsContext.get();
     MOZ_ASSERT(bailoutInfo);
 
     // We don't have an exit frame.
     MOZ_ASSERT(IsInRange(FAKE_EXITFP_FOR_BAILOUT, 0, 0x1000) &&
                IsInRange(FAKE_EXITFP_FOR_BAILOUT + sizeof(CommonFrameLayout), 0, 0x1000),
@@ -47,24 +47,22 @@ jit::Bailout(BailoutStack* sp, BaselineB
     TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
     TraceLogTimestamp(logger, TraceLogger_Bailout);
 
     JitSpew(JitSpew_IonBailouts, "Took bailout! Snapshot offset: %d", frame.snapshotOffset());
 
     MOZ_ASSERT(IsBaselineEnabled(cx));
 
     *bailoutInfo = nullptr;
-    uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), frame, false, bailoutInfo,
-                                           /* excInfo = */ nullptr);
-    MOZ_ASSERT(retval == BAILOUT_RETURN_OK ||
-               retval == BAILOUT_RETURN_FATAL_ERROR ||
-               retval == BAILOUT_RETURN_OVERRECURSED);
-    MOZ_ASSERT_IF(retval == BAILOUT_RETURN_OK, *bailoutInfo != nullptr);
+    bool success = BailoutIonToBaseline(cx, bailoutData.activation(), frame, false, bailoutInfo,
+                                        /* excInfo = */ nullptr);
+    MOZ_ASSERT_IF(success, *bailoutInfo != nullptr);
 
-    if (retval != BAILOUT_RETURN_OK) {
+    if (!success) {
+        MOZ_ASSERT(cx->isExceptionPending());
         JSScript* script = frame.script();
         probes::ExitScript(cx, script, script->functionNonDelazifying(),
                            /* popProfilerFrame = */ false);
     }
 
     // This condition was wrong when we entered this bailout function, but it
     // might be true now. A GC might have reclaimed all the Jit code and
     // invalidated all frames which are currently on the stack. As we are
@@ -93,20 +91,20 @@ jit::Bailout(BailoutStack* sp, BaselineB
     // (see |AutoResetLastProfilerFrameOnReturnFromException|).
     //
     // In both cases, we want to temporarily set the |lastProfilingFrame|
     // to the current frame being bailed out, and then fix it up later.
     if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) {
         cx->jitActivation->setLastProfilingFrame(currentFramePtr);
     }
 
-    return retval;
+    return success;
 }
 
-uint32_t
+bool
 jit::InvalidationBailout(InvalidationBailoutStack* sp, size_t* frameSizeOut,
                          BaselineBailoutInfo** bailoutInfo)
 {
     sp->checkInvariants();
 
     JSContext* cx = TlsContext.get();
 
     // We don't have an exit frame.
@@ -124,24 +122,23 @@ jit::InvalidationBailout(InvalidationBai
             frame.snapshotOffset());
 
     // Note: the frame size must be computed before we return from this function.
     *frameSizeOut = frame.frameSize();
 
     MOZ_ASSERT(IsBaselineEnabled(cx));
 
     *bailoutInfo = nullptr;
-    uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), frame, true, bailoutInfo,
-                                           /* excInfo = */ nullptr);
-    MOZ_ASSERT(retval == BAILOUT_RETURN_OK ||
-               retval == BAILOUT_RETURN_FATAL_ERROR ||
-               retval == BAILOUT_RETURN_OVERRECURSED);
-    MOZ_ASSERT_IF(retval == BAILOUT_RETURN_OK, *bailoutInfo != nullptr);
+    bool success = BailoutIonToBaseline(cx, bailoutData.activation(), frame, true, bailoutInfo,
+                                        /* excInfo = */ nullptr);
+    MOZ_ASSERT_IF(success, *bailoutInfo != nullptr);
 
-    if (retval != BAILOUT_RETURN_OK) {
+    if (!success) {
+        MOZ_ASSERT(cx->isExceptionPending());
+
         // If the bailout failed, then bailout trampoline will pop the
         // current frame and jump straight to exception handling code when
         // this function returns.  Any Gecko Profiler entry pushed for this
         // frame will be silently forgotten.
         //
         // We call ExitScript here to ensure that if the ionScript had Gecko
         // Profiler instrumentation, then the entry for it is popped.
         //
@@ -149,52 +146,50 @@ jit::InvalidationBailout(InvalidationBai
         // pseudostack frame would not have been pushed in the first
         // place, so don't pop anything in that case.
         JSScript* script = frame.script();
         probes::ExitScript(cx, script, script->functionNonDelazifying(),
                            /* popProfilerFrame = */ false);
 
 #ifdef JS_JITSPEW
         JitFrameLayout* layout = frame.jsFrame();
-        JitSpew(JitSpew_IonInvalidate, "Bailout failed (%s)",
-                (retval == BAILOUT_RETURN_FATAL_ERROR) ? "Fatal Error" : "Over Recursion");
+        JitSpew(JitSpew_IonInvalidate, "Bailout failed (Fatal Error)");
         JitSpew(JitSpew_IonInvalidate, "   calleeToken %p", (void*) layout->calleeToken());
         JitSpew(JitSpew_IonInvalidate, "   frameSize %u", unsigned(layout->prevFrameLocalSize()));
         JitSpew(JitSpew_IonInvalidate, "   ra %p", (void*) layout->returnAddress());
 #endif
     }
 
     frame.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp());
 
     // Make the frame being bailed out the top profiled frame.
     if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) {
         cx->jitActivation->setLastProfilingFrame(currentFramePtr);
     }
 
-    return retval;
+    return success;
 }
 
 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations,
                                    const JSJitFrameIter& frame)
   : machine_(frame.machineState())
 {
     framePointer_ = (uint8_t*) frame.fp();
     topFrameSize_ = frame.frameSize();
     topIonScript_ = frame.ionScript();
     attachOnJitActivation(activations);
 
     const OsiIndex* osiIndex = frame.osiIndex();
     snapshotOffset_ = osiIndex->snapshotOffset();
 }
 
-uint32_t
+bool
 jit::ExceptionHandlerBailout(JSContext* cx, const InlineFrameIterator& frame,
                              ResumeFromException* rfe,
-                             const ExceptionBailoutInfo& excInfo,
-                             bool* overrecursed)
+                             const ExceptionBailoutInfo& excInfo)
 {
     // We can be propagating debug mode exceptions without there being an
     // actual exception pending. For instance, when we return false from an
     // operation callback like a timeout handler.
     MOZ_ASSERT_IF(!excInfo.propagatingIonExceptionForDebugMode(), cx->isExceptionPending());
 
     JitActivation* act = cx->activation()->asJit();
     uint8_t* prevExitFP = act->jsExitFP();
@@ -204,68 +199,41 @@ jit::ExceptionHandlerBailout(JSContext* 
     gc::AutoSuppressGC suppress(cx);
 
     JitActivationIterator jitActivations(cx);
     BailoutFrameInfo bailoutData(jitActivations, frame.frame());
     JSJitFrameIter frameView(jitActivations->asJit());
     CommonFrameLayout* currentFramePtr = frameView.current();
 
     BaselineBailoutInfo* bailoutInfo = nullptr;
-    uint32_t retval;
-
-    {
-        // Currently we do not tolerate OOM here so as not to complicate the
-        // exception handling code further.
-        AutoEnterOOMUnsafeRegion oomUnsafe;
-
-        retval = BailoutIonToBaseline(cx, bailoutData.activation(), frameView, true,
-                                      &bailoutInfo, &excInfo);
-        if (retval == BAILOUT_RETURN_FATAL_ERROR && cx->isThrowingOutOfMemory()) {
-            oomUnsafe.crash("ExceptionHandlerBailout");
-        }
-    }
-
-    if (retval == BAILOUT_RETURN_OK) {
+    bool success = BailoutIonToBaseline(cx, bailoutData.activation(), frameView, true,
+                                        &bailoutInfo, &excInfo);
+    if (success) {
         MOZ_ASSERT(bailoutInfo);
 
         // Overwrite the kind so HandleException after the bailout returns
         // false, jumping directly to the exception tail.
         if (excInfo.propagatingIonExceptionForDebugMode()) {
             bailoutInfo->bailoutKind = Bailout_IonExceptionDebugMode;
         }
 
         rfe->kind = ResumeFromException::RESUME_BAILOUT;
         rfe->target = cx->runtime()->jitRuntime()->getBailoutTail().value;
         rfe->bailoutInfo = bailoutInfo;
     } else {
-        // Bailout failed. If the overrecursion check failed, clear the
-        // exception to turn this into an uncatchable error, continue popping
-        // all inline frames and have the caller report the error.
         MOZ_ASSERT(!bailoutInfo);
-
-        if (retval == BAILOUT_RETURN_OVERRECURSED) {
-            *overrecursed = true;
-            if (!excInfo.propagatingIonExceptionForDebugMode()) {
-                cx->clearPendingException();
-            }
-        } else {
-            MOZ_ASSERT(retval == BAILOUT_RETURN_FATAL_ERROR);
-
-            // Crash for now so as not to complicate the exception handling code
-            // further.
-            MOZ_CRASH();
-        }
+        MOZ_ASSERT(cx->isExceptionPending());
     }
 
     // Make the frame being bailed out the top profiled frame.
     if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) {
         cx->jitActivation->setLastProfilingFrame(currentFramePtr);
     }
 
-    return retval;
+    return success;
 }
 
 // Initialize the decl env Object, call object, and any arguments obj of the
 // current frame.
 bool
 jit::EnsureHasEnvironmentObjects(JSContext* cx, AbstractFramePtr fp)
 {
     // Ion does not compile eval scripts.
--- a/js/src/jit/Bailouts.h
+++ b/js/src/jit/Bailouts.h
@@ -91,22 +91,16 @@ namespace jit {
 // 10 byte cost is more optimal than a bailout table. See JitFrames.h for more
 // detail.
 
 static const BailoutId INVALID_BAILOUT_ID = BailoutId(-1);
 
 // Keep this arbitrarily small for now, for testing.
 static const uint32_t BAILOUT_TABLE_SIZE = 16;
 
-// Bailout return codes.
-// N.B. the relative order of these values is hard-coded into ::GenerateBailoutThunk.
-static const uint32_t BAILOUT_RETURN_OK = 0;
-static const uint32_t BAILOUT_RETURN_FATAL_ERROR = 1;
-static const uint32_t BAILOUT_RETURN_OVERRECURSED = 2;
-
 // This address is a magic number made to cause crashes while indicating that we
 // are making an attempt to mark the stack during a bailout.
 static const uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR = 0xba2;
 static uint8_t* const FAKE_EXITFP_FOR_BAILOUT =
     reinterpret_cast<uint8_t*>(FAKE_EXITFP_FOR_BAILOUT_ADDR);
 
 static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & wasm::ExitOrJitEntryFPTag),
               "FAKE_EXITFP_FOR_BAILOUT could be mistaken as a low-bit tagged wasm exit fp");
@@ -157,22 +151,24 @@ class BailoutFrameInfo
         return activation_;
     }
 };
 
 MOZ_MUST_USE bool EnsureHasEnvironmentObjects(JSContext* cx, AbstractFramePtr fp);
 
 struct BaselineBailoutInfo;
 
-// Called from a bailout thunk. Returns a BAILOUT_* error code.
-uint32_t Bailout(BailoutStack* sp, BaselineBailoutInfo** info);
+// Called from a bailout thunk.
+MOZ_MUST_USE bool
+Bailout(BailoutStack* sp, BaselineBailoutInfo** info);
 
-// Called from the invalidation thunk. Returns a BAILOUT_* error code.
-uint32_t InvalidationBailout(InvalidationBailoutStack* sp, size_t* frameSizeOut,
-                             BaselineBailoutInfo** info);
+// Called from the invalidation thunk.
+MOZ_MUST_USE bool
+InvalidationBailout(InvalidationBailoutStack* sp, size_t* frameSizeOut,
+                    BaselineBailoutInfo** info);
 
 class ExceptionBailoutInfo
 {
     size_t frameNo_;
     jsbytecode* resumePC_;
     size_t numExprSlots_;
 
   public:
@@ -205,22 +201,22 @@ class ExceptionBailoutInfo
     }
     size_t numExprSlots() const {
         MOZ_ASSERT(catchingException());
         return numExprSlots_;
     }
 };
 
 // Called from the exception handler to enter a catch or finally block.
-// Returns a BAILOUT_* error code.
-uint32_t ExceptionHandlerBailout(JSContext* cx, const InlineFrameIterator& frame,
-                                 ResumeFromException* rfe,
-                                 const ExceptionBailoutInfo& excInfo,
-                                 bool* overrecursed);
+MOZ_MUST_USE bool
+ExceptionHandlerBailout(JSContext* cx, const InlineFrameIterator& frame,
+                        ResumeFromException* rfe,
+                        const ExceptionBailoutInfo& excInfo);
 
-uint32_t FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfo);
+MOZ_MUST_USE bool
+FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfo);
 
 void CheckFrequentBailouts(JSContext* cx, JSScript* script, BailoutKind bailoutKind);
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_Bailouts_h */
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -80,32 +80,34 @@ class BufferPointer
  * needs to be enlarged to accommodate new data.  Similarly to the C stack, the
  * data that's written to the reconstructed stack grows from high to low in memory.
  *
  * The lowest region of the allocated memory contains a BaselineBailoutInfo structure that
  * points to the start and end of the written data.
  */
 struct BaselineStackBuilder
 {
+    JSContext* cx_;
     const JSJitFrameIter& iter_;
     JitFrameLayout* frame_;
 
     static size_t HeaderSize() {
         return AlignBytes(sizeof(BaselineBailoutInfo), sizeof(void*));
     }
     size_t bufferTotal_;
     size_t bufferAvail_;
     size_t bufferUsed_;
     uint8_t* buffer_;
     BaselineBailoutInfo* header_;
 
     size_t framePushed_;
 
-    BaselineStackBuilder(const JSJitFrameIter& iter, size_t initialSize)
-      : iter_(iter),
+    BaselineStackBuilder(JSContext* cx, const JSJitFrameIter& iter, size_t initialSize)
+      : cx_(cx),
+        iter_(iter),
         frame_(static_cast<JitFrameLayout*>(iter.current())),
         bufferTotal_(initialSize),
         bufferAvail_(0),
         bufferUsed_(0),
         buffer_(nullptr),
         header_(nullptr),
         framePushed_(0)
     {
@@ -115,17 +117,17 @@ struct BaselineStackBuilder
 
     ~BaselineStackBuilder() {
         js_free(buffer_);
     }
 
     MOZ_MUST_USE bool init() {
         MOZ_ASSERT(!buffer_);
         MOZ_ASSERT(bufferUsed_ == 0);
-        buffer_ = js_pod_calloc<uint8_t>(bufferTotal_);
+        buffer_ = cx_->pod_calloc<uint8_t>(bufferTotal_);
         if (!buffer_) {
             return false;
         }
         bufferAvail_ = bufferTotal_ - HeaderSize();
         bufferUsed_ = 0;
 
         header_ = reinterpret_cast<BaselineBailoutInfo*>(buffer_);
         header_->incomingStack = reinterpret_cast<uint8_t*>(frame_);
@@ -144,20 +146,21 @@ struct BaselineStackBuilder
         header_->numFrames = 0;
         header_->checkGlobalDeclarationConflicts = false;
         return true;
     }
 
     MOZ_MUST_USE bool enlarge() {
         MOZ_ASSERT(buffer_ != nullptr);
         if (bufferTotal_ & mozilla::tl::MulOverflowMask<2>::value) {
+            ReportOutOfMemory(cx_);
             return false;
         }
         size_t newSize = bufferTotal_ * 2;
-        uint8_t* newBuffer = js_pod_calloc<uint8_t>(newSize);
+        uint8_t* newBuffer = cx_->pod_calloc<uint8_t>(newSize);
         if (!newBuffer) {
             return false;
         }
         memcpy((newBuffer + newSize) - bufferUsed_, header_->copyStackBottom, bufferUsed_);
         memcpy(newBuffer, header_, sizeof(BaselineBailoutInfo));
         js_free(buffer_);
         buffer_ = newBuffer;
         bufferTotal_ = newSize;
@@ -1579,17 +1582,17 @@ InitFromBailout(JSContext* cx, size_t fr
     if (!builder.writePtr(rectReturnAddr, "ReturnAddr")) {
         return false;
     }
     MOZ_ASSERT(builder.framePushed() % JitStackAlignment == 0);
 
     return true;
 }
 
-uint32_t
+bool
 jit::BailoutIonToBaseline(JSContext* cx, JitActivation* activation,
                           const JSJitFrameIter& iter, bool invalidate,
                           BaselineBailoutInfo** bailoutInfo,
                           const ExceptionBailoutInfo* excInfo)
 {
     MOZ_ASSERT(bailoutInfo != nullptr);
     MOZ_ASSERT(*bailoutInfo == nullptr);
     MOZ_ASSERT(iter.isBailoutJS());
@@ -1675,35 +1678,34 @@ jit::BailoutIonToBaseline(JSContext* cx,
             iter.snapshotOffset(), iter.ionScript()->snapshotsListSize());
 
     if (!excInfo) {
         iter.ionScript()->incNumBailouts();
     }
     iter.script()->updateJitCodeRaw(cx->runtime());
 
     // Allocate buffer to hold stack replacement data.
-    BaselineStackBuilder builder(iter, 1024);
+    BaselineStackBuilder builder(cx, iter, 1024);
     if (!builder.init()) {
-        ReportOutOfMemory(cx);
-        return BAILOUT_RETURN_FATAL_ERROR;
+        return false;
     }
     JitSpew(JitSpew_BaselineBailouts, "  Incoming frame ptr = %p", builder.startFrame());
 
     // Under a bailout, there is no need to invalidate the frame after
     // evaluating the recover instruction, as the invalidation is only needed in
     // cases where the frame is introspected ahead of the bailout.
     MaybeReadFallback recoverBailout(cx, activation, &iter, MaybeReadFallback::Fallback_DoNothing);
 
     // Ensure that all value locations are readable from the SnapshotIterator.
     // Get the RInstructionResults from the JitActivation if the frame got
     // recovered ahead of the bailout.
     SnapshotIterator snapIter(iter, activation->bailoutData()->machineState());
     if (!snapIter.initInstructionResults(recoverBailout)) {
         ReportOutOfMemory(cx);
-        return BAILOUT_RETURN_FATAL_ERROR;
+        return false;
     }
 
 #ifdef TRACK_SNAPSHOTS
     snapIter.spewBailingFrom();
 #endif
 
     RootedFunction callee(cx, iter.maybeCallee());
     RootedScript scr(cx, iter.script());
@@ -1752,17 +1754,18 @@ jit::BailoutIonToBaseline(JSContext* cx,
         // debug mode.
         bool passExcInfo = handleException || propagatingExceptionForDebugMode;
 
         RootedFunction nextCallee(cx, nullptr);
         if (!InitFromBailout(cx, frameNo, fun, scr,
                              snapIter, invalidate, builder, &startFrameFormals,
                              &nextCallee, passExcInfo ? excInfo : nullptr))
         {
-            return BAILOUT_RETURN_FATAL_ERROR;
+            MOZ_ASSERT(cx->isExceptionPending());
+            return false;
         }
 
         if (!snapIter.moreFrames()) {
             MOZ_ASSERT(!nextCallee);
             break;
         }
 
         if (handleException) {
@@ -1797,26 +1800,27 @@ jit::BailoutIonToBaseline(JSContext* cx,
     }
 #else
     if (!CheckRecursionLimitWithStackPointerDontReport(cx, newsp)) {
         overRecursed = true;
     }
 #endif
     if (overRecursed) {
         JitSpew(JitSpew_BaselineBailouts, "  Overrecursion check failed!");
-        return BAILOUT_RETURN_OVERRECURSED;
+        ReportOverRecursed(cx);
+        return false;
     }
 
     // Take the reconstructed baseline stack so it doesn't get freed when builder destructs.
     info = builder.takeBuffer();
     info->numFrames = frameNo + 1;
     info->bailoutKind = bailoutKind;
     *bailoutInfo = info;
     guardRemoveRematerializedFramesFromDebugger.release();
-    return BAILOUT_RETURN_OK;
+    return true;
 }
 
 static void
 InvalidateAfterBailout(JSContext* cx, HandleScript outerScript, const char* reason)
 {
     // In some cases, the computation of recover instruction can invalidate the
     // Ion script before we reach the end of the bailout. Thus, if the outer
     // script no longer have any Ion script attached, then we just skip the
@@ -1940,17 +1944,17 @@ CopyFromRematerializedFrame(JSContext* c
     if (rematFrame->isDebuggee()) {
         frame->setIsDebuggee();
         return Debugger::handleIonBailout(cx, rematFrame, frame);
     }
 
     return true;
 }
 
-uint32_t
+bool
 jit::FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfo)
 {
     // The caller pushes R0 and R1 on the stack without rooting them.
     // Since GC here is very unlikely just suppress it.
     JSContext* cx = TlsContext.get();
     js::gc::AutoSuppressGC suppressGC(cx);
 
     JitSpew(JitSpew_BaselineBailouts, "  Done restoring frames");
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -748,17 +748,17 @@ struct BaselineBailoutInfo
     // resumed at the first pc instead of the prologue, so an extra flag is
     // needed to perform the check.
     bool checkGlobalDeclarationConflicts;
 
     // The bailout kind.
     BailoutKind bailoutKind;
 };
 
-uint32_t
+MOZ_MUST_USE bool
 BailoutIonToBaseline(JSContext* cx, JitActivation* activation, const JSJitFrameIter& iter,
                      bool invalidate, BaselineBailoutInfo** bailoutInfo,
                      const ExceptionBailoutInfo* exceptionInfo);
 
 // Mark baseline scripts on the stack as active, so that they are not discarded
 // during GC.
 void
 MarkActiveBaselineScripts(Zone* zone);
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -173,52 +173,52 @@ class TryNoteIterIon : public TryNoteIte
   public:
     TryNoteIterIon(JSContext* cx, const InlineFrameIterator& frame)
       : TryNoteIter(cx, frame.script(), frame.pc(), IonFrameStackDepthOp(frame))
     { }
 };
 
 static void
 HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromException* rfe,
-                   bool* overrecursed)
+                   bool* hitBailoutException)
 {
     if (cx->realm()->isDebuggee()) {
         // We need to bail when there is a catchable exception, and we are the
         // debuggee of a Debugger with a live onExceptionUnwind hook, or if a
         // Debugger has observed this frame (e.g., for onPop).
         bool shouldBail = Debugger::hasLiveHook(cx->global(), Debugger::OnExceptionUnwind);
         RematerializedFrame* rematFrame = nullptr;
         if (!shouldBail) {
             JitActivation* act = cx->activation()->asJit();
             rematFrame = act->lookupRematerializedFrame(frame.frame().fp(), frame.frameNo());
             shouldBail = rematFrame && rematFrame->isDebuggee();
         }
 
-        if (shouldBail) {
+        if (shouldBail && !*hitBailoutException) {
             // If we have an exception from within Ion and the debugger is active,
             // we do the following:
             //
             //   1. Bailout to baseline to reconstruct a baseline frame.
             //   2. Resume immediately into the exception tail afterwards, and
             //      handle the exception again with the top frame now a baseline
             //      frame.
             //
             // An empty exception info denotes that we're propagating an Ion
             // exception due to debug mode, which BailoutIonToBaseline needs to
             // know. This is because we might not be able to fully reconstruct up
             // to the stack depth at the snapshot, as we could've thrown in the
             // middle of a call.
             ExceptionBailoutInfo propagateInfo;
-            uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, propagateInfo, overrecursed);
-            if (retval == BAILOUT_RETURN_OK) {
+            if (ExceptionHandlerBailout(cx, frame, rfe, propagateInfo)) {
                 return;
             }
+            // Note: the bailout code deleted rematFrame. Don't use after this
+            // point!
+            *hitBailoutException = true;
         }
-
-        MOZ_ASSERT_IF(rematFrame, !Debugger::inFrameMaps(rematFrame));
     }
 
     RootedScript script(cx, frame.script());
     if (!script->hasTrynotes()) {
         return;
     }
 
     bool inForOfIterClose = false;
@@ -257,31 +257,34 @@ HandleExceptionIon(JSContext* cx, const 
                     break;
                 }
 
                 // Ion can compile try-catch, but bailing out to catch
                 // exceptions is slow. Reset the warm-up counter so that if we
                 // catch many exceptions we won't Ion-compile the script.
                 script->resetWarmUpCounter();
 
+                if (*hitBailoutException) {
+                    break;
+                }
+
                 // Bailout at the start of the catch block.
                 jsbytecode* catchPC = script->offsetToPC(tn->start + tn->length);
                 ExceptionBailoutInfo excInfo(frame.frameNo(), catchPC, tn->stackDepth);
-                uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, excInfo, overrecursed);
-                if (retval == BAILOUT_RETURN_OK) {
+                if (ExceptionHandlerBailout(cx, frame, rfe, excInfo)) {
                     // Record exception locations to allow scope unwinding in
                     // |FinishBailoutToBaseline|
                     MOZ_ASSERT(cx->isExceptionPending());
                     rfe->bailoutInfo->tryPC = UnwindEnvironmentToTryPc(frame.script(), tn);
                     rfe->bailoutInfo->faultPC = frame.pc();
                     return;
                 }
 
-                // Error on bailout clears pending exception.
-                MOZ_ASSERT(!cx->isExceptionPending());
+                *hitBailoutException = true;                
+                MOZ_ASSERT(cx->isExceptionPending());
             }
             break;
 
           default:
             MOZ_CRASH("Unexpected try note");
         }
     }
 }
@@ -687,17 +690,16 @@ HandleException(ResumeFromException* rfe
         }
 
         // JIT code can enter same-compartment realms, so reset cx->realm to
         // this frame's realm.
         cx->setRealmForJitExceptionHandler(iter.realm());
 
         const JSJitFrameIter& frame = iter.asJSJit();
 
-        bool overrecursed = false;
         if (frame.isIonJS()) {
             // Search each inlined frame for live iterator objects, and close
             // them.
             InlineFrameIterator frames(cx, &frame);
 
             // Invalidation state will be the same for all inlined scripts in the frame.
             IonScript* ionScript = nullptr;
             bool invalidated = frame.checkInvalidation(&ionScript);
@@ -706,18 +708,22 @@ HandleException(ResumeFromException* rfe
             if (logger && cx->realm()->isDebuggee() && logger->enabled()) {
                 logger->disable(/* force = */ true,
                                 "Forcefully disabled tracelogger, due to "
                                 "throwing an exception with an active Debugger "
                                 "in IonMonkey.");
             }
 #endif
 
+            // If we hit OOM or overrecursion while bailing out, we don't
+            // attempt to bail out a second time for this Ion frame. Just unwind
+            // and continue at the next frame.
+            bool hitBailoutException = false;
             for (;;) {
-                HandleExceptionIon(cx, frames, rfe, &overrecursed);
+                HandleExceptionIon(cx, frames, rfe, &hitBailoutException);
 
                 if (rfe->kind == ResumeFromException::RESUME_BAILOUT) {
                     if (invalidated) {
                         ionScript->decrementInvalidationCount(cx->runtime()->defaultFreeOp());
                     }
                     return;
                 }
 
@@ -790,21 +796,16 @@ HandleException(ResumeFromException* rfe
                                /* popProfilerFrame = */ false);
 
             if (rfe->kind == ResumeFromException::RESUME_FORCED_RETURN) {
                 return;
             }
         }
 
         ++iter;
-
-        if (overrecursed) {
-            // We hit an overrecursion error during bailout. Report it now.
-            ReportOverRecursed(cx);
-        }
     }
 
     // Wasm sets its own value of SP in HandleExceptionWasm.
     if (iter.isJSJit()) {
         rfe->stackPointer = iter.asJSJit().fp();
     }
 }
 
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1861,52 +1861,25 @@ MacroAssembler::guardGroupHasUnanalyzedN
     loadPtr(Address(group, ObjectGroup::offsetOfAddendum()), scratch);
     branchPtr(Assembler::Equal,
               Address(scratch, TypeNewScript::offsetOfPreliminaryObjects()),
               ImmWord(0), fail);
 
     bind(&noNewScript);
 }
 
-static void
-BailoutReportOverRecursed(JSContext* cx)
-{
-    ReportOverRecursed(cx);
-}
-
 void
 MacroAssembler::generateBailoutTail(Register scratch, Register bailoutInfo)
 {
     loadJSContext(scratch);
     enterFakeExitFrame(scratch, scratch, ExitFrameType::Bare);
 
-    Label baseline;
-
-    // The return value from Bailout is tagged as:
-    // - 0x0: done (enter baseline)
-    // - 0x1: error (handle exception)
-    // - 0x2: overrecursed
-    JS_STATIC_ASSERT(BAILOUT_RETURN_OK == 0);
-    JS_STATIC_ASSERT(BAILOUT_RETURN_FATAL_ERROR == 1);
-    JS_STATIC_ASSERT(BAILOUT_RETURN_OVERRECURSED == 2);
-
-    branch32(Equal, ReturnReg, Imm32(BAILOUT_RETURN_OK), &baseline);
-    branch32(Equal, ReturnReg, Imm32(BAILOUT_RETURN_FATAL_ERROR), exceptionLabel());
-
-    // Fall-through: overrecursed.
-    {
-        loadJSContext(ReturnReg);
-        setupUnalignedABICall(scratch);
-        passABIArg(ReturnReg);
-        callWithABI(JS_FUNC_TO_DATA_PTR(void*, BailoutReportOverRecursed), MoveOp::GENERAL,
-                    CheckUnsafeCallWithABI::DontCheckHasExitFrame);
-        jump(exceptionLabel());
-    }
-
-    bind(&baseline);
+    branchIfFalseBool(ReturnReg, exceptionLabel());
+
+    // Finish bailing out to Baseline.
     {
         // Prepare a register set for use in this case.
         AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
         MOZ_ASSERT_IF(!IsHiddenSP(getStackPointer()), !regs.has(AsRegister(getStackPointer())));
         regs.take(bailoutInfo);
 
         // Reset SP to the point where clobbering starts.
         loadStackPtr(Address(bailoutInfo, offsetof(BaselineBailoutInfo, incomingStack)));
@@ -1959,17 +1932,17 @@ MacroAssembler::generateBailoutTail(Regi
             push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeAddr)));
             push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, monitorStub)));
 
             // Call a stub to free allocated memory and create arguments objects.
             setupUnalignedABICall(temp);
             passABIArg(bailoutInfo);
             callWithABI(JS_FUNC_TO_DATA_PTR(void*, FinishBailoutToBaseline),
                         MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
-            branchTest32(Zero, ReturnReg, ReturnReg, exceptionLabel());
+            branchIfFalseBool(ReturnReg, exceptionLabel());
 
             // Restore values where they need to be and resume execution.
             AllocatableGeneralRegisterSet enterMonRegs(GeneralRegisterSet::All());
             enterMonRegs.take(R0);
             enterMonRegs.take(ICStubReg);
             enterMonRegs.take(BaselineFrameReg);
             enterMonRegs.takeUnchecked(ICTailCallReg);
 
@@ -1998,17 +1971,17 @@ MacroAssembler::generateBailoutTail(Regi
             push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeFramePtr)));
             push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeAddr)));
 
             // Call a stub to free allocated memory and create arguments objects.
             setupUnalignedABICall(temp);
             passABIArg(bailoutInfo);
             callWithABI(JS_FUNC_TO_DATA_PTR(void*, FinishBailoutToBaseline),
                         MoveOp::GENERAL, CheckUnsafeCallWithABI::DontCheckHasExitFrame);
-            branchTest32(Zero, ReturnReg, ReturnReg, exceptionLabel());
+            branchIfFalseBool(ReturnReg, exceptionLabel());
 
             // Restore values where they need to be and resume execution.
             AllocatableGeneralRegisterSet enterRegs(GeneralRegisterSet::All());
             enterRegs.take(R0);
             enterRegs.take(R1);
             enterRegs.take(BaselineFrameReg);
             Register jitcodeReg = enterRegs.takeAny();
 
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -3725,22 +3725,22 @@ MacroAssemblerARMCompat::handleFailureWi
                           &skipProfilingInstrumentation);
         jump(profilerExitTail);
         bind(&skipProfilingInstrumentation);
     }
 
     ret();
 
     // If we are bailing out to baseline to handle an exception, jump to the
-    // bailout tail stub.
+    // bailout tail stub. Load 1 (true) in ReturnReg to indicate success.
     bind(&bailout);
     {
         ScratchRegisterScope scratch(asMasm());
         ma_ldr(Address(sp, offsetof(ResumeFromException, bailoutInfo)), r2, scratch);
-        ma_mov(Imm32(BAILOUT_RETURN_OK), r0);
+        ma_mov(Imm32(1), ReturnReg);
         ma_ldr(Address(sp, offsetof(ResumeFromException, target)), r1, scratch);
     }
     jump(r1);
 
     // If we are throwing and the innermost frame was a wasm frame, reset SP and
     // FP; SP is pointing to the unwound return address to the wasm entry, so
     // we can just ret().
     bind(&wasm);
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp
+++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp
@@ -194,22 +194,22 @@ MacroAssemblerCompat::handleFailureWithH
     loadPtr(Address(r28, offsetof(ResumeFromException, stackPointer)), r28);
     loadValue(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfReturnValue()),
               JSReturnOperand);
     movePtr(BaselineFrameReg, r28);
     vixl::MacroAssembler::Pop(ARMRegister(BaselineFrameReg, 64), vixl::lr);
     syncStackPtr();
     vixl::MacroAssembler::Ret(vixl::lr);
 
-    // If we are bailing out to baseline to handle an exception,
-    // jump to the bailout tail stub.
+    // If we are bailing out to baseline to handle an exception, jump to the
+    // bailout tail stub. Load 1 (true) in x0 (ReturnReg) to indicate success.
     bind(&bailout);
     Ldr(x2, MemOperand(GetStackPointer64(), offsetof(ResumeFromException, bailoutInfo)));
     Ldr(x1, MemOperand(GetStackPointer64(), offsetof(ResumeFromException, target)));
-    Mov(x0, BAILOUT_RETURN_OK);
+    Mov(x0, 1);
     Br(x1);
 
     // If we are throwing and the innermost frame was a wasm frame, reset SP and
     // FP; SP is pointing to the unwound return address to the wasm entry, so
     // we can just ret().
     bind(&wasm);
     Ldr(x29, MemOperand(GetStackPointer64(), offsetof(ResumeFromException, framePointer)));
     Ldr(x28, MemOperand(GetStackPointer64(), offsetof(ResumeFromException, stackPointer)));
--- a/js/src/jit/mips32/MacroAssembler-mips32.cpp
+++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp
@@ -2039,20 +2039,20 @@ MacroAssemblerMIPSCompat::handleFailureW
                           &skipProfilingInstrumentation);
         jump(profilerExitTail);
         bind(&skipProfilingInstrumentation);
     }
 
     ret();
 
     // If we are bailing out to baseline to handle an exception, jump to
-    // the bailout tail stub.
+    // the bailout tail stub. Load 1 (true) in ReturnReg to indicate success.
     bind(&bailout);
     loadPtr(Address(sp, offsetof(ResumeFromException, bailoutInfo)), a2);
-    ma_li(ReturnReg, Imm32(BAILOUT_RETURN_OK));
+    ma_li(ReturnReg, Imm32(1));
     loadPtr(Address(sp, offsetof(ResumeFromException, target)), a1);
     jump(a1);
 
     // If we are throwing and the innermost frame was a wasm frame, reset SP and
     // FP; SP is pointing to the unwound return address to the wasm entry, so
     // we can just ret().
     bind(&wasm);
     loadPtr(Address(StackPointer, offsetof(ResumeFromException, framePointer)), FramePointer);
--- a/js/src/jit/mips64/MacroAssembler-mips64.cpp
+++ b/js/src/jit/mips64/MacroAssembler-mips64.cpp
@@ -1878,20 +1878,20 @@ MacroAssemblerMIPS64Compat::handleFailur
                           &skipProfilingInstrumentation);
         jump(profilerExitTail);
         bind(&skipProfilingInstrumentation);
     }
 
     ret();
 
     // If we are bailing out to baseline to handle an exception, jump to
-    // the bailout tail stub.
+    // the bailout tail stub. Load 1 (true) in ReturnReg to indicate success.
     bind(&bailout);
     loadPtr(Address(sp, offsetof(ResumeFromException, bailoutInfo)), a2);
-    ma_li(ReturnReg, Imm32(BAILOUT_RETURN_OK));
+    ma_li(ReturnReg, Imm32(1));
     loadPtr(Address(sp, offsetof(ResumeFromException, target)), a1);
     jump(a1);
 
     // If we are throwing and the innermost frame was a wasm frame, reset SP and
     // FP; SP is pointing to the unwound return address to the wasm entry, so
     // we can just ret().
     bind(&wasm);
     loadPtr(Address(StackPointer, offsetof(ResumeFromException, framePointer)), FramePointer);
--- a/js/src/jit/x64/MacroAssembler-x64.cpp
+++ b/js/src/jit/x64/MacroAssembler-x64.cpp
@@ -216,21 +216,21 @@ MacroAssemblerX64::handleFailureWithHand
         AbsoluteAddress addressOfEnabled(GetJitContext()->runtime->geckoProfiler().addressOfEnabled());
         asMasm().branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation);
         jump(profilerExitTail);
         bind(&skipProfilingInstrumentation);
     }
 
     ret();
 
-    // If we are bailing out to baseline to handle an exception, jump to
-    // the bailout tail stub.
+    // If we are bailing out to baseline to handle an exception, jump to the
+    // bailout tail stub. Load 1 (true) in ReturnReg to indicate success.
     bind(&bailout);
     loadPtr(Address(esp, offsetof(ResumeFromException, bailoutInfo)), r9);
-    mov(ImmWord(BAILOUT_RETURN_OK), rax);
+    move32(Imm32(1), ReturnReg);
     jmp(Operand(rsp, offsetof(ResumeFromException, target)));
 
     // If we are throwing and the innermost frame was a wasm frame, reset SP and
     // FP; SP is pointing to the unwound return address to the wasm entry, so
     // we can just ret().
     bind(&wasm);
     loadPtr(Address(rsp, offsetof(ResumeFromException, framePointer)), rbp);
     loadPtr(Address(rsp, offsetof(ResumeFromException, stackPointer)), rsp);
--- a/js/src/jit/x86/MacroAssembler-x86.cpp
+++ b/js/src/jit/x86/MacroAssembler-x86.cpp
@@ -208,21 +208,21 @@ MacroAssemblerX86::handleFailureWithHand
         asMasm().branch32(Assembler::Equal, addressOfEnabled, Imm32(0),
                           &skipProfilingInstrumentation);
         jump(profilerExitTail);
         bind(&skipProfilingInstrumentation);
     }
 
     ret();
 
-    // If we are bailing out to baseline to handle an exception, jump to
-    // the bailout tail stub.
+    // If we are bailing out to baseline to handle an exception, jump to the
+    // bailout tail stub. Load 1 (true) in ReturnReg to indicate success.
     bind(&bailout);
     loadPtr(Address(esp, offsetof(ResumeFromException, bailoutInfo)), ecx);
-    movl(Imm32(BAILOUT_RETURN_OK), eax);
+    move32(Imm32(1), ReturnReg);
     jmp(Operand(esp, offsetof(ResumeFromException, target)));
 
     // If we are throwing and the innermost frame was a wasm frame, reset SP and
     // FP; SP is pointing to the unwound return address to the wasm entry, so
     // we can just ret().
     bind(&wasm);
     loadPtr(Address(esp, offsetof(ResumeFromException, framePointer)), ebp);
     loadPtr(Address(esp, offsetof(ResumeFromException, stackPointer)), esp);