Bug 1114757 - Rollup of Debugger-go-faster fuzz bugs (1113710, 1111477, 1109964, 1111300, 1107937, 1111199, 1109915, 1109375, 1109328, 1108145, 1107525, 1108159, 1107913, 1106719, 1106164) for Fx36 backport. (r=efaust,djvj,jandem,jimb,luke,terrence) a=sledru
authorShu-yu Guo <shu@rfrn.org>
Mon, 22 Dec 2014 13:52:00 -0500
changeset 242589 2f714060ef558fe6d9296024c7e730cfdc8a3884
parent 242588 b304acd94272071c734e6b81a3d4d02c132d3552
child 242590 8a4a3e9e1fd757814231e353d7c1ac7c1e7e170a
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust, djvj, jandem, jimb, luke, terrence, sledru
bugs1114757, 1113710, 1111477, 1109964, 1111300, 1107937, 1111199, 1109915, 1109375, 1109328, 1108145, 1107525, 1108159, 1107913, 1106719, 1106164
milestone36.0a2
Bug 1114757 - Rollup of Debugger-go-faster fuzz bugs (1113710, 1111477, 1109964, 1111300, 1107937, 1111199, 1109915, 1109375, 1109328, 1108145, 1107525, 1108159, 1107913, 1106719, 1106164) for Fx36 backport. (r=efaust,djvj,jandem,jimb,luke,terrence) a=sledru
js/src/doc/Debugger/Debugger.Frame.md
js/src/doc/Debugger/Debugger.md
js/src/jit-test/tests/debug/Frame-eval-25.js
js/src/jit-test/tests/debug/bug1106164.js
js/src/jit-test/tests/debug/bug1106719.js
js/src/jit-test/tests/debug/bug1107525.js
js/src/jit-test/tests/debug/bug1107913.js
js/src/jit-test/tests/debug/bug1108159.js
js/src/jit-test/tests/debug/bug1108556.js
js/src/jit-test/tests/debug/bug1109328.js
js/src/jit-test/tests/debug/bug1109915.js
js/src/jit-test/tests/debug/bug1111199.js
js/src/jit/Bailouts.cpp
js/src/jit/Bailouts.h
js/src/jit/BaselineBailouts.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineDebugModeOSR.cpp
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/BaselineJIT.cpp
js/src/jit/JitFrameIterator.h
js/src/jit/JitFrames.cpp
js/src/jit/ParallelFunctions.cpp
js/src/jit/RematerializedFrame.cpp
js/src/jit/RematerializedFrame.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/vm/Debugger.cpp
js/src/vm/ForkJoin.cpp
js/src/vm/ForkJoin.h
js/src/vm/HelperThreads.cpp
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/js/src/doc/Debugger/Debugger.Frame.md
+++ b/js/src/doc/Debugger/Debugger.Frame.md
@@ -252,17 +252,19 @@ the compartment to which the handler met
     `Debugger.Frame` instance remains live.
 
     If multiple [`Debugger`][debugger-object] instances each have
     `Debugger.Frame` instances for a given stack frame with `onPop`
     handlers set, their handlers are run in an unspecified order. The
     resumption value each handler returns establishes the completion value
     reported to the next handler.
 
-    This property is ignored on `"debugger"` frames.
+    This handler is not called on `"debugger"` frames. It is also not called
+    when unwinding a frame due to an over-recursion or out-of-memory
+    exception.
 
 `onResume`
 :   This property must be either `undefined` or a function. If it is a
     function, SpiderMonkey calls it if the current frame is a generator
     frame whose execution has just been resumed. The function should return
     a [resumption value][rv] indicating how execution should proceed. On
     newly created frames, this property's value is `undefined`.
 
--- a/js/src/doc/Debugger/Debugger.md
+++ b/js/src/doc/Debugger/Debugger.md
@@ -146,16 +146,19 @@ compartment.
     When an exception's propagation causes control to enter a `finally`
     block, the exception is temporarily set aside. If the `finally` block
     finishes normally, the exception resumes propagation, and the debugger's
     `onExceptionUnwind` handler is called again, in the same frame. (The
     other possibility is for the `finally` block to exit due to a `return`,
     `continue`, or `break` statement, or a new exception. In those cases the
     old exception does not continue to propagate; it is discarded.)
 
+    This handler is not called when unwinding a frame due to an over-recursion
+    or out-of-memory exception.
+
 <code>sourceHandler(<i>ASuffusionOfYellow</i>)</code>
 :   This method is never called. If it is ever called, a contradiction has
     been proven, and the debugger is free to assume that everything is true.
 
 <code>onError(<i>frame</i>, <i>report</i>)</code>
 :   SpiderMonkey is about to report an error in <i>frame</i>. <i>Report</i>
     is an object describing the error, with the following properties:
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-25.js
@@ -0,0 +1,17 @@
+// |jit-test| error: TypeError
+//
+// Make sure we can recover missing arguments even when it gets assigned to
+// another slot.
+
+load(libdir + "evalInFrame.js");
+
+function h() {
+  evalInFrame(1, "a.push(0)");
+}
+
+function f() {
+  var a = arguments;
+  h();
+}
+
+f();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1106164.js
@@ -0,0 +1,17 @@
+// |jit-test| error: ReferenceError
+var g = newGlobal();
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () { };");
+evaluate("\
+        var tokenCodes  = {};\
+        tokenCodes.continue = 0;\
+        var arr = [\
+            (0.E87  ), \
+        ];\
+        for(var reportCompare   in tokenCodes) {\
+            for(var p1 in arr) {\
+                if(arr[j  . arr      ++    ] === p) {\
+            }\
+        }\
+    }\
+", { noScriptRval : true, compileAndGo : true });
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1106719.js
@@ -0,0 +1,11 @@
+// |jit-test| allow-oom
+
+g = newGlobal()
+g.parent = this
+g.eval("Debugger(parent).onExceptionUnwind=(function(){})")
+gcparam("maxBytes", gcparam("gcBytes"))
+function f() {
+    f()
+    y(arguments)
+}
+f()
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1107525.js
@@ -0,0 +1,9 @@
+// |jit-test| error: InternalError
+enableSPSProfiling();
+var g = newGlobal();
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () { hits++; };");
+function f() {
+    var x = f();
+}
+f();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1107913.js
@@ -0,0 +1,7 @@
+// |jit-test| error: TypeError
+
+var g = newGlobal();
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () {};");
+Object.preventExtensions(this);
+evaluate("function testcase() { }", { noScriptRval : true, compileAndGo : true });
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1108159.js
@@ -0,0 +1,15 @@
+// |jit-test| slow
+
+if (helperThreadCount() == 0)
+  quit(0);
+
+var s = '';
+for (var i = 0; i < 70000; i++)
+ {
+    s += 'function x' + i + '() { x' + i + '(); }\n';
+}
+evaluate(s);
+var g = newGlobal();
+(new Debugger).addDebuggee(g);
+g.offThreadCompileScript('debugger;',{});
+g.runOffThreadScript();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1108556.js
@@ -0,0 +1,10 @@
+// |jit-test| error: ReferenceError
+
+var g = newGlobal();
+g.parent = this;
+g.eval("new Debugger(parent).onExceptionUnwind = function () { hits++; };");
+evaluate('\
+var fe="v";\
+for (i=0; String.fromCharCode(0x004E); i++)\
+  fe += fe;\
+', { compileAndGo : true });
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1109328.js
@@ -0,0 +1,7 @@
+try {
+    gcslice(0)(""());
+} catch (e) {}
+g = newGlobal()
+g.parent = this
+g.eval("Debugger(parent).onExceptionUnwind=(function(){})");
+gcparam("maxBytes", gcparam("gcBytes"));
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1109915.js
@@ -0,0 +1,17 @@
+var evalInFrame = (function (global) {
+  var dbgGlobal = newGlobal();
+  var dbg = new dbgGlobal.Debugger();
+  return function evalInFrame(upCount, code) {
+    dbg.addDebuggee(global);
+    var frame = dbg.getNewestFrame().older;
+    var completion = frame.eval(code);
+  };
+})(this);
+function g1(x, args) {}
+function f1(x, y, o) {
+    for (var i=0; i<50; i++) {
+        o.apply(evalInFrame(0, "x"), x);
+    }
+}
+var o1 = {apply: g1};
+assertEq(f1(3, 5, o1), undefined);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1111199.js
@@ -0,0 +1,17 @@
+g = newGlobal()
+g.parent = this
+g.eval("Debugger(parent).onExceptionUnwind=(function(){})")
+try {
+function f(code) {
+    n = parseInt('', 0);
+    return g("try{}catch(e){}", n)
+}
+function g(s, n) {
+    s2 = s + s
+    d = (n - (function  ()  {
+            return "" + this.id + eval.id;
+        } )().abstract) / 2
+    m = g(s2, d)
+}
+f("switch(''){default:break;}")
+} catch(exc1) {}
--- a/js/src/jit/Bailouts.cpp
+++ b/js/src/jit/Bailouts.cpp
@@ -176,34 +176,33 @@ BailoutFrameInfo::BailoutFrameInfo(const
     const OsiIndex *osiIndex = frame.osiIndex();
     snapshotOffset_ = osiIndex->snapshotOffset();
 }
 
 uint32_t
 jit::ExceptionHandlerBailout(JSContext *cx, const InlineFrameIterator &frame,
                              ResumeFromException *rfe,
                              const ExceptionBailoutInfo &excInfo,
-                             bool *overrecursed)
+                             bool *overrecursed, bool *poppedLastSPSFrameOut)
 {
     // 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());
 
     cx->mainThread().jitTop = FAKE_JIT_TOP_FOR_BAILOUT;
     gc::AutoSuppressGC suppress(cx);
 
     JitActivationIterator jitActivations(cx->runtime());
     BailoutFrameInfo bailoutData(jitActivations, frame.frame());
     JitFrameIterator iter(jitActivations);
 
     BaselineBailoutInfo *bailoutInfo = nullptr;
-    bool poppedLastSPSFrame = false;
     uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, true,
-                                           &bailoutInfo, &excInfo, &poppedLastSPSFrame);
+                                           &bailoutInfo, &excInfo, poppedLastSPSFrameOut);
 
     if (retval == BAILOUT_RETURN_OK) {
         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;
--- a/js/src/jit/Bailouts.h
+++ b/js/src/jit/Bailouts.h
@@ -204,17 +204,17 @@ class ExceptionBailoutInfo
     }
 };
 
 // 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);
+                                 bool *overrecursed, bool *poppedLastSPSFrameOut);
 
 uint32_t FinishBailoutToBaseline(BaselineBailoutInfo *bailoutInfo);
 
 bool CheckFrequentBailouts(JSContext *cx, JSScript *script);
 
 } // namespace jit
 } // namespace js
 
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -668,18 +668,25 @@ InitFromBailout(JSContext *cx, HandleScr
         } else {
             MOZ_ASSERT(v.isUndefined() || v.isMagic(JS_OPTIMIZED_OUT));
 
             // Get scope chain from function or script.
             if (fun) {
                 // If pcOffset == 0, we may have to push a new call object, so
                 // we leave scopeChain nullptr and enter baseline code before
                 // the prologue.
-                if (iter.pcOffset() != 0 || iter.resumeAfter())
+                //
+                // If we are propagating an exception for debug mode, we will
+                // not resume into baseline code, but instead into
+                // HandleExceptionBaseline, so *do* set the scope chain here.
+                if (iter.pcOffset() != 0 || iter.resumeAfter() ||
+                    (excInfo && excInfo->propagatingIonExceptionForDebugMode()))
+                {
                     scopeChain = fun->environment();
+                }
             } else {
                 // For global, compile-and-go scripts the scope chain is the
                 // script's global (Ion does not compile non-compile-and-go
                 // scripts). Also note that it's invalid to resume into the
                 // prologue in this case because the prologue expects the scope
                 // chain in R1 for eval and global scripts.
                 MOZ_ASSERT(!script->isForEval());
                 MOZ_ASSERT(script->compileAndGo());
@@ -868,20 +875,22 @@ InitFromBailout(JSContext *cx, HandleScr
             // the stack if we are at the newest frame.
             //
             // For instance, if calling |f()| pushed an Ion frame which threw,
             // the snapshot expects the return value to be pushed, but it's
             // possible nothing was pushed before we threw. We can't drop
             // iterators, however, so read them out. They will be closed by
             // HandleExceptionBaseline.
             MOZ_ASSERT(cx->compartment()->isDebuggee());
-            if (iter.moreFrames() || HasLiveIteratorAtStackDepth(script, pc, i + 1))
+            if (iter.moreFrames() || HasLiveIteratorAtStackDepth(script, pc, i + 1)) {
                 v = iter.read();
-            else
+            } else {
+                iter.skip();
                 v = MagicValue(JS_OPTIMIZED_OUT);
+            }
         } else {
             v = iter.read();
         }
         if (!builder.writeValue(v, "StackValue"))
             return false;
     }
 
     size_t endOfBaselineJSFrameStack = builder.framePushed();
@@ -1019,38 +1028,39 @@ InitFromBailout(JSContext *cx, HandleScr
         } else {
             // If needed, initialize BaselineBailoutInfo's valueR0 and/or valueR1 with the
             // top stack values.
             //
             // Note that we use the 'maybe' variant of nativeCodeForPC because
             // of exception propagation for debug mode. See note below.
             PCMappingSlotInfo slotInfo;
             uint8_t *nativeCodeForPC = baselineScript->maybeNativeCodeForPC(script, pc, &slotInfo);
-            unsigned numUnsynced;
+            unsigned numUnsynced = slotInfo.numUnsynced();
 
-            if (excInfo && excInfo->propagatingIonExceptionForDebugMode()) {
+            if (excInfo && excInfo->propagatingIonExceptionForDebugMode() && resumeAfter) {
                 // When propagating an exception for debug mode, set the
-                // return address as the return-from-IC for the throw, so that
-                // Debugger hooks report the correct pc offset of the throwing
-                // op instead of its successor.
+                // return address as the return-from-IC for the throwing op,
+                // so that Debugger hooks report the correct pc offset of the
+                // throwing op instead of its successor.
+                //
+                // This should not be done if we are at a resume-at point, as
+                // might be the case when propagating an exception thrown from
+                // an interrupt handler. That interrupt could have happened to
+                // interrupt at a loop head, which would have no ICEntry at
+                // that point.
                 //
                 // Note that we never resume into this address, it is set for
                 // the sake of frame iterators giving the correct answer.
                 ICEntry &icEntry = baselineScript->anyKindICEntryFromPCOffset(iter.pcOffset());
                 nativeCodeForPC = baselineScript->returnAddressForIC(icEntry);
-
-                // The pc after the throwing PC could be unreachable, in which
-                // case we have no native code for it and no slot info. But in
-                // that case, there are definitely no unsynced slots.
-                numUnsynced = nativeCodeForPC ? slotInfo.numUnsynced() : 0;
             } else {
                 MOZ_ASSERT(nativeCodeForPC);
-                numUnsynced = slotInfo.numUnsynced();
             }
 
+            MOZ_ASSERT(nativeCodeForPC);
             MOZ_ASSERT(numUnsynced <= 2);
             PCMappingSlotInfo::SlotLocation loc1, loc2;
             if (numUnsynced > 0) {
                 loc1 = slotInfo.topSlotLocation();
                 JitSpew(JitSpew_BaselineBailouts, "      Popping top stack value into %d.",
                         (int) loc1);
                 builder.popValueInto(loc1);
             }
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -753,16 +753,18 @@ BaselineCompiler::emitDebugTrap()
 {
     MOZ_ASSERT(compileDebugInstrumentation_);
     MOZ_ASSERT(frame.numUnsyncedSlots() == 0);
 
     bool enabled = script->stepModeEnabled() || script->hasBreakpointsAt(pc);
 
     // Emit patchable call to debug trap handler.
     JitCode *handler = cx->runtime()->jitRuntime()->debugTrapHandler(cx);
+    if (!handler)
+        return false;
     mozilla::DebugOnly<CodeOffsetLabel> offset = masm.toggledCall(handler, enabled);
 
 #ifdef DEBUG
     // Patchable call offset has to match the pc mapping offset.
     PCMappingEntry &entry = pcMappingEntries_.back();
     MOZ_ASSERT((&offset)->offset() == entry.nativeOffset);
 #endif
 
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -212,17 +212,17 @@ CollectJitStackScripts(JSContext *cx, co
                         return false;
                 } else {
                     // Otherwise, we are in the middle of handling an
                     // exception. This happens since we could have bailed out
                     // in place from Ion after a throw, settling on the pc
                     // *after* the bytecode that threw the exception, which
                     // may have no ICEntry.
                     MOZ_ASSERT(iter.baselineFrame()->isDebuggerHandlingException());
-                    jsbytecode *pc = script->baselineScript()->pcForReturnAddress(script, retAddr);
+                    jsbytecode *pc = script->baselineScript()->pcForNativeAddress(script, retAddr);
                     if (!entries.append(DebugModeOSREntry(script, script->pcToOffset(pc))))
                         return false;
                 }
             }
 
             if (entries.back().needsRecompileInfo()) {
                 if (!entries.back().allocateRecompileInfo(cx))
                     return false;
@@ -395,27 +395,41 @@ PatchBaselineFramesForDebugMode(JSContex
             jsbytecode *pc = script->offsetToPC(pcOffset);
 
             MOZ_ASSERT(script == iter.script());
             MOZ_ASSERT(pcOffset < script->length());
 
             BaselineScript *bl = script->baselineScript();
             ICEntry::Kind kind = entry.frameKind;
 
-            if (kind == ICEntry::Kind_Op) {
-                // Case A above.
-                //
-                // Patching this case needs to patch both the stub frame and
+            if (kind == ICEntry::Kind_Op || kind == ICEntry::Kind_NonOp) {
+                uint8_t *retAddr;
+                if (kind == ICEntry::Kind_Op) {
+                    // Case A above.
+                    retAddr = bl->returnAddressForIC(bl->icEntryFromPCOffset(pcOffset));
+                } else {
+                    // Case H above.
+                    //
+                    // It could happen that the in-place Ion bailout chose the
+                    // return-from-IC address of a NonOp IC for the frame
+                    // iterators to report the correct bytecode pc.
+                    //
+                    // See note under propagatingIonExceptionForDebugMode in
+                    // InitFromBailout.
+                    MOZ_ASSERT(iter.baselineFrame()->isDebuggerHandlingException());
+                    retAddr = bl->returnAddressForIC(bl->anyKindICEntryFromPCOffset(pcOffset));
+                }
+
+                // Patching these cases needs to patch both the stub frame and
                 // the baseline frame. The stub frame is patched below. For
                 // the baseline frame here, we resume right after the IC
                 // returns.
                 //
                 // Since we're using the same IC stub code, we can resume
                 // directly to the IC resume address.
-                uint8_t *retAddr = bl->returnAddressForIC(bl->icEntryFromPCOffset(pcOffset));
                 SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind, pc);
                 DebugModeOSRVolatileJitFrameIterator::forwardLiveIterators(
                     cx, prev->returnAddress(), retAddr);
                 prev->setReturnAddress(retAddr);
                 entryIndex++;
                 break;
             }
 
@@ -666,16 +680,17 @@ RecompileBaselineScriptForDebugMode(JSCo
     _(GetElem_NativePrototypeCallNative)        \
     _(GetElem_NativePrototypeCallScripted)      \
     _(GetProp_CallScripted)                     \
     _(GetProp_CallNative)                       \
     _(GetProp_CallNativePrototype)              \
     _(GetProp_CallDOMProxyNative)               \
     _(GetProp_CallDOMProxyWithGenerationNative) \
     _(GetProp_DOMProxyShadowed)                 \
+    _(GetProp_Generic)                          \
     _(SetProp_CallScripted)                     \
     _(SetProp_CallNative)
 
 #if JS_HAS_NO_SUCH_METHOD
 #define PATCHABLE_NSM_ICSTUB_KIND_LIST(_)       \
     _(GetElem_Dense)                            \
     _(GetElem_Arguments)                        \
     _(GetProp_NativePrototype)                  \
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -7855,16 +7855,23 @@ ICGetProp_ArgumentsCallee::Compiler::gen
 
     EmitEnterTypeMonitorIC(masm);
 
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
+/* static */ ICGetProp_Generic *
+ICGetProp_Generic::Clone(JSContext *cx, ICStubSpace *space, ICStub *firstMonitorStub,
+                         ICGetProp_Generic &other)
+{
+    return New(space, other.jitCode(), firstMonitorStub);
+}
+
 static bool
 DoGetPropGeneric(JSContext *cx, BaselineFrame *frame, ICGetProp_Generic *stub, MutableHandleValue val, MutableHandleValue res)
 {
     jsbytecode *pc = stub->getChainFallback()->icEntry()->pc(frame->script());
     JSOp op = JSOp(*pc);
     RootedPropertyName name(cx, frame->script()->getName(pc));
     return ComputeGetPropResult(cx, frame, op, name, val, res);
 }
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -4193,16 +4193,19 @@ class ICGetProp_Generic : public ICMonit
   public:
     static inline ICGetProp_Generic *New(ICStubSpace *space, JitCode *code, ICStub *firstMonitorStub)
     {
         if(!code)
             return nullptr;
         return space->allocate<ICGetProp_Generic>(code, firstMonitorStub);
     }
 
+    static ICGetProp_Generic *Clone(JSContext *cx, ICStubSpace *space, ICStub *firstMonitorStub,
+                                    ICGetProp_Generic &other);
+
     class Compiler : public ICStubCompiler {
       protected:
         bool generateStubCode(MacroAssembler &masm);
         ICStub *firstMonitorStub_;
       public:
         explicit Compiler(JSContext *cx, ICStub *firstMonitorStub)
           : ICStubCompiler(cx, ICStub::GetProp_Generic),
             firstMonitorStub_(firstMonitorStub)
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -575,17 +575,17 @@ size_t ComputeBinarySearchMid(BaselineSc
 
 ICEntry &
 BaselineScript::anyKindICEntryFromPCOffset(uint32_t pcOffset)
 {
     size_t mid = ComputeBinarySearchMid(this, pcOffset);
 
     // Return any IC entry with a matching PC offset.
     for (size_t i = mid; i < numICEntries() && icEntry(i).pcOffset() == pcOffset; i--)
-            return icEntry(i);
+        return icEntry(i);
     for (size_t i = mid+1; i < numICEntries() && icEntry(i).pcOffset() == pcOffset; i++)
         return icEntry(i);
     MOZ_CRASH("Invalid PC offset for IC entry.");
 }
 
 ICEntry &
 BaselineScript::icEntryFromPCOffset(uint32_t pcOffset)
 {
--- a/js/src/jit/JitFrameIterator.h
+++ b/js/src/jit/JitFrameIterator.h
@@ -582,17 +582,17 @@ class InlineFrameIterator
     uint32_t numActualArgs_;
 
     struct Nop {
         void operator()(const Value &v) { }
     };
 
   private:
     void findNextFrame();
-    JSObject *computeScopeChain(Value scopeChainValue) const;
+    JSObject *computeScopeChain(Value scopeChainValue, bool *hasCallObj = nullptr) const;
 
   public:
     InlineFrameIterator(ThreadSafeContext *cx, const JitFrameIterator *iter);
     InlineFrameIterator(JSRuntime *rt, const JitFrameIterator *iter);
     InlineFrameIterator(ThreadSafeContext *cx, const InlineFrameIterator *iter);
 
     bool more() const {
         return frame_ && framesRead_ < frameCount_;
@@ -614,29 +614,29 @@ class InlineFrameIterator
         if (more())
             return numActualArgs_;
 
         return frame_->numActualArgs();
     }
 
     template <class ArgOp, class LocalOp>
     void readFrameArgsAndLocals(ThreadSafeContext *cx, ArgOp &argOp, LocalOp &localOp,
-                                JSObject **scopeChain, Value *rval,
+                                JSObject **scopeChain, bool *hasCallObj, Value *rval,
                                 ArgumentsObject **argsObj, Value *thisv,
                                 ReadFrameArgsBehavior behavior,
                                 MaybeReadFallback &fallback) const
     {
         SnapshotIterator s(si_);
 
         // Read frame slots common to both function and global frames.
         Value scopeChainValue;
         s.readCommonFrameSlots(&scopeChainValue, rval);
 
         if (scopeChain)
-            *scopeChain = computeScopeChain(scopeChainValue);
+            *scopeChain = computeScopeChain(scopeChainValue, hasCallObj);
 
         // Read arguments, which only function frames have.
         if (isFunctionFrame()) {
             unsigned nactual = numActualArgs();
             unsigned nformal = callee()->nargs();
 
             // Get the non overflown arguments, which are taken from the inlined
             // frame, because it will have the updated value when JSOP_SETARG is
@@ -690,22 +690,23 @@ class InlineFrameIterator
             // recovering slots.
             //
             // FIXME bug 1029963.
             localOp(s.maybeRead(fallback));
         }
     }
 
     template <class Op>
-    void unaliasedForEachActual(ThreadSafeContext *cx, Op op,
+    void unaliasedForEachActual(JSContext *cx, Op op,
                                 ReadFrameArgsBehavior behavior,
                                 MaybeReadFallback &fallback) const
     {
         Nop nop;
-        readFrameArgsAndLocals(cx, op, nop, nullptr, nullptr, nullptr, nullptr, behavior, fallback);
+        readFrameArgsAndLocals(cx, op, nop, nullptr, nullptr, nullptr,
+                               nullptr, nullptr, behavior, fallback);
     }
 
     JSScript *script() const {
         return script_;
     }
     jsbytecode *pc() const {
         return pc_;
     }
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -407,25 +407,25 @@ CloseLiveIterator(JSContext *cx, const I
     if (cx->isExceptionPending())
         UnwindIteratorForException(cx, obj);
     else
         UnwindIteratorForUncatchableException(cx, obj);
 }
 
 static void
 HandleExceptionIon(JSContext *cx, const InlineFrameIterator &frame, ResumeFromException *rfe,
-                   bool *overrecursed)
+                   bool *overrecursed, bool *poppedLastSPSFrameOut)
 {
     RootedScript script(cx, frame.script());
     jsbytecode *pc = frame.pc();
 
-    if (cx->compartment()->isDebuggee()) {
-        // We need to bail when we are the debuggee of a Debugger with a live
-        // onExceptionUnwind hook, or if a Debugger has observed this frame
-        // (e.g., for onPop).
+    if (cx->compartment()->isDebuggee() && cx->isExceptionPending()) {
+        // 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);
         if (!shouldBail) {
             JitActivation *act = cx->mainThread().activation()->asJit();
             RematerializedFrame *rematFrame =
                 act->lookupRematerializedFrame(frame.frame().fp(), frame.frameNo());
             shouldBail = rematFrame && rematFrame->isDebuggee();
         }
 
@@ -439,17 +439,18 @@ HandleExceptionIon(JSContext *cx, const 
             //      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);
+            uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, propagateInfo, overrecursed,
+                                                      poppedLastSPSFrameOut);
             if (retval == BAILOUT_RETURN_OK)
                 return;
         }
     }
 
     if (!script->hasTrynotes())
         return;
 
@@ -481,17 +482,18 @@ HandleExceptionIon(JSContext *cx, const 
                 // 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();
 
                 // Bailout at the start of the catch block.
                 jsbytecode *catchPC = script->main() + tn->start + tn->length;
                 ExceptionBailoutInfo excInfo(frame.frameNo(), catchPC, tn->stackDepth);
-                uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, excInfo, overrecursed);
+                uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, excInfo, overrecursed,
+                                                          poppedLastSPSFrameOut);
                 if (retval == BAILOUT_RETURN_OK)
                     return;
 
                 // Error on bailout clears pending exception.
                 MOZ_ASSERT(!cx->isExceptionPending());
             }
             break;
 
@@ -737,17 +739,18 @@ HandleException(ResumeFromException *rfe
             // them.
             InlineFrameIterator frames(cx, &iter);
 
             // Invalidation state will be the same for all inlined scripts in the frame.
             IonScript *ionScript = nullptr;
             bool invalidated = iter.checkInvalidation(&ionScript);
 
             for (;;) {
-                HandleExceptionIon(cx, frames, rfe, &overrecursed);
+                bool poppedLastSPSFrame = false;
+                HandleExceptionIon(cx, frames, rfe, &overrecursed, &poppedLastSPSFrame);
 
                 if (rfe->kind == ResumeFromException::RESUME_BAILOUT) {
                     if (invalidated)
                         ionScript->decrementInvalidationCount(cx->runtime()->defaultFreeOp());
                     return;
                 }
 
                 MOZ_ASSERT(rfe->kind == ResumeFromException::RESUME_ENTRY_FRAME);
@@ -759,16 +762,21 @@ HandleException(ResumeFromException *rfe
                 bool popSPSFrame = cx->runtime()->spsProfiler.enabled();
                 if (invalidated)
                     popSPSFrame = ionScript->hasSPSInstrumentation();
 
                 // Don't pop an SPS frame for inlined frames, since they are not instrumented.
                 if (frames.more())
                     popSPSFrame = false;
 
+                // Don't pop the last SPS frame if it's already been popped by
+                // bailing out.
+                if (poppedLastSPSFrame)
+                    popSPSFrame = false;
+
                 // When profiling, each frame popped needs a notification that
                 // the function has exited, so invoke the probe that a function
                 // is exiting.
 
                 JSScript *script = frames.script();
                 probes::ExitScript(cx, script, script->functionNonDelazifying(), popSPSFrame);
                 if (!frames.more()) {
                     TraceLogStopEvent(logger, TraceLogger::IonMonkey);
@@ -869,17 +877,18 @@ HandleParallelFailure(ResumeFromExceptio
     JitFrameIterator frameIter(cx);
 
     // Advance to the first Ion frame so we can pull out the BailoutKind.
     while (!frameIter.isIonJS())
         ++frameIter;
     SnapshotIterator snapIter(frameIter);
 
     cx->bailoutRecord->setIonBailoutKind(snapIter.bailoutKind());
-    cx->bailoutRecord->rematerializeFrames(cx, frameIter);
+    while (!frameIter.done())
+        ++frameIter;
 
     rfe->kind = ResumeFromException::RESUME_ENTRY_FRAME;
 
     MOZ_ASSERT(frameIter.done());
     rfe->stackPointer = frameIter.fp();
 }
 
 void
@@ -2336,20 +2345,23 @@ InlineFrameIterator::findNextFrame()
         MOZ_ASSERT(!si_.moreFrames());
         frameCount_ = i;
     }
 
     framesRead_++;
 }
 
 JSObject *
-InlineFrameIterator::computeScopeChain(Value scopeChainValue) const
+InlineFrameIterator::computeScopeChain(Value scopeChainValue, bool *hasCallObj) const
 {
-    if (scopeChainValue.isObject())
+    if (scopeChainValue.isObject()) {
+        if (hasCallObj)
+            *hasCallObj = isFunctionFrame() && callee()->isHeavyweight();
         return &scopeChainValue.toObject();
+    }
 
     // Note we can hit this case even for heavyweight functions, in case we
     // are walking the frame during the function prologue, before the scope
     // chain has been initialized.
     if (isFunctionFrame())
         return callee()->environment();
 
     // Ion does not handle scripts that are not compile-and-go.
--- a/js/src/jit/ParallelFunctions.cpp
+++ b/js/src/jit/ParallelFunctions.cpp
@@ -544,17 +544,18 @@ jit::BailoutPar(BailoutStack *sp, uint8_
     cx->perThreadData->jitTop = FAKE_JIT_TOP_FOR_BAILOUT;
 
     JitActivationIterator jitActivations(cx->perThreadData);
     BailoutFrameInfo bailoutData(jitActivations, sp);
     JitFrameIterator frameIter(jitActivations);
     SnapshotIterator snapIter(frameIter);
 
     cx->bailoutRecord->setIonBailoutKind(snapIter.bailoutKind());
-    cx->bailoutRecord->rematerializeFrames(cx, frameIter);
+    while (!frameIter.done())
+        ++frameIter;
 
     MOZ_ASSERT(frameIter.done());
     *entryFramePointer = frameIter.fp();
 }
 
 bool
 jit::CallToUncompiledScriptPar(ForkJoinContext *cx, JSObject *obj)
 {
--- a/js/src/jit/RematerializedFrame.cpp
+++ b/js/src/jit/RematerializedFrame.cpp
@@ -23,62 +23,72 @@ struct CopyValueToRematerializedFrame
       : slots(slots)
     { }
 
     void operator()(const Value &v) {
         *slots++ = v;
     }
 };
 
-RematerializedFrame::RematerializedFrame(ThreadSafeContext *cx, uint8_t *top,
-                                         unsigned numActualArgs, InlineFrameIterator &iter)
+RematerializedFrame::RematerializedFrame(JSContext *cx, uint8_t *top, unsigned numActualArgs,
+                                         InlineFrameIterator &iter)
   : prevUpToDate_(false),
     isDebuggee_(iter.script()->isDebuggee()),
     top_(top),
     pc_(iter.pc()),
     frameNo_(iter.frameNo()),
     numActualArgs_(numActualArgs),
     script_(iter.script())
 {
     CopyValueToRematerializedFrame op(slots_);
     MaybeReadFallback fallback(MagicValue(JS_OPTIMIZED_OUT));
-    iter.readFrameArgsAndLocals(cx, op, op, &scopeChain_, &returnValue_,
+    iter.readFrameArgsAndLocals(cx, op, op, &scopeChain_, &hasCallObj_, &returnValue_,
                                 &argsObj_, &thisValue_, ReadFrame_Actuals,
                                 fallback);
 }
 
 /* static */ RematerializedFrame *
-RematerializedFrame::New(ThreadSafeContext *cx, uint8_t *top, InlineFrameIterator &iter)
+RematerializedFrame::New(JSContext *cx, uint8_t *top, InlineFrameIterator &iter)
 {
     unsigned numFormals = iter.isFunctionFrame() ? iter.callee()->nargs() : 0;
-    unsigned numActualArgs = Max(numFormals, iter.numActualArgs());
+    unsigned argSlots = Max(numFormals, iter.numActualArgs());
     size_t numBytes = sizeof(RematerializedFrame) +
-        (numActualArgs + iter.script()->nfixed()) * sizeof(Value) -
+        (argSlots + iter.script()->nfixed()) * sizeof(Value) -
         sizeof(Value); // 1 Value included in sizeof(RematerializedFrame)
 
     void *buf = cx->pod_calloc<uint8_t>(numBytes);
     if (!buf)
         return nullptr;
 
-    return new (buf) RematerializedFrame(cx, top, numActualArgs, iter);
+    return new (buf) RematerializedFrame(cx, top, iter.numActualArgs(), iter);
 }
 
 /* static */ bool
-RematerializedFrame::RematerializeInlineFrames(ThreadSafeContext *cx, uint8_t *top,
+RematerializedFrame::RematerializeInlineFrames(JSContext *cx, uint8_t *top,
                                                InlineFrameIterator &iter,
                                                Vector<RematerializedFrame *> &frames)
 {
     if (!frames.resize(iter.frameCount()))
         return false;
 
     while (true) {
         size_t frameNo = iter.frameNo();
-        frames[frameNo] = RematerializedFrame::New(cx, top, iter);
-        if (!frames[frameNo])
+        RematerializedFrame *frame = RematerializedFrame::New(cx, top, iter);
+        if (!frame)
             return false;
+        if (frame->scopeChain()) {
+            // Frames are often rematerialized with the cx inside a Debugger's
+            // compartment. To create CallObjects, we need to be in that
+            // frame's compartment.
+            AutoCompartment ac(cx, frame->scopeChain());
+            if (!EnsureHasScopeObjects(cx, frame))
+                return false;
+        }
+
+        frames[frameNo] = frame;
 
         if (!iter.more())
             break;
         ++iter;
     }
 
     return true;
 }
@@ -108,16 +118,37 @@ RematerializedFrame::callObj() const
 
     JSObject *scope = scopeChain();
     while (!scope->is<CallObject>())
         scope = scope->enclosingScope();
     return scope->as<CallObject>();
 }
 
 void
+RematerializedFrame::pushOnScopeChain(ScopeObject &scope)
+{
+    MOZ_ASSERT(*scopeChain() == scope.enclosingScope() ||
+               *scopeChain() == scope.as<CallObject>().enclosingScope().as<DeclEnvObject>().enclosingScope());
+    scopeChain_ = &scope;
+}
+
+bool
+RematerializedFrame::initFunctionScopeObjects(JSContext *cx)
+{
+    MOZ_ASSERT(isNonEvalFunctionFrame());
+    MOZ_ASSERT(fun()->isHeavyweight());
+    CallObject *callobj = CallObject::createForFunction(cx, this);
+    if (!callobj)
+        return false;
+    pushOnScopeChain(*callobj);
+    hasCallObj_ = true;
+    return true;
+}
+
+void
 RematerializedFrame::mark(JSTracer *trc)
 {
     gc::MarkScriptRoot(trc, &script_, "remat ion frame script");
     gc::MarkObjectRoot(trc, &scopeChain_, "remat ion frame scope chain");
     gc::MarkValueRoot(trc, &returnValue_, "remat ion frame return value");
     gc::MarkValueRoot(trc, &thisValue_, "remat ion frame this");
     gc::MarkValueRootRange(trc, slots_, slots_ + numActualArgs_ + script_->nfixed(),
                            "remat ion frame stack");
--- a/js/src/jit/RematerializedFrame.h
+++ b/js/src/jit/RematerializedFrame.h
@@ -24,16 +24,19 @@ namespace jit {
 class RematerializedFrame
 {
     // See DebugScopes::updateLiveScopes.
     bool prevUpToDate_;
 
     // Propagated to the Baseline frame once this is popped.
     bool isDebuggee_;
 
+    // Has a call object been pushed?
+    bool hasCallObj_;
+
     // The fp of the top frame associated with this possibly inlined frame.
     uint8_t *top_;
 
     // The bytecode at the time of rematerialization.
     jsbytecode *pc_;
 
     size_t frameNo_;
     unsigned numActualArgs_;
@@ -41,26 +44,25 @@ class RematerializedFrame
     JSScript *script_;
     JSObject *scopeChain_;
     ArgumentsObject *argsObj_;
 
     Value returnValue_;
     Value thisValue_;
     Value slots_[1];
 
-    RematerializedFrame(ThreadSafeContext *cx, uint8_t *top, unsigned numActualArgs,
+    RematerializedFrame(JSContext *cx, uint8_t *top, unsigned numActualArgs,
                         InlineFrameIterator &iter);
 
   public:
-    static RematerializedFrame *New(ThreadSafeContext *cx, uint8_t *top,
-                                    InlineFrameIterator &iter);
+    static RematerializedFrame *New(JSContext *cx, uint8_t *top, InlineFrameIterator &iter);
 
     // Rematerialize all remaining frames pointed to by |iter| into |frames|
     // in older-to-younger order, e.g., frames[0] is the oldest frame.
-    static bool RematerializeInlineFrames(ThreadSafeContext *cx, uint8_t *top,
+    static bool RematerializeInlineFrames(JSContext *cx, uint8_t *top,
                                           InlineFrameIterator &iter,
                                           Vector<RematerializedFrame *> &frames);
 
     // Free a vector of RematerializedFrames; takes care to call the
     // destructor. Also clears the vector.
     static void FreeInVector(Vector<RematerializedFrame *> &frames);
 
     // Mark a vector of RematerializedFrames.
@@ -99,18 +101,22 @@ class RematerializedFrame
     }
     bool inlined() const {
         return frameNo_ > 0;
     }
 
     JSObject *scopeChain() const {
         return scopeChain_;
     }
+    void pushOnScopeChain(ScopeObject &scope);
+    bool initFunctionScopeObjects(JSContext *cx);
+
     bool hasCallObj() const {
-        return maybeFun() && fun()->isHeavyweight();
+        MOZ_ASSERT(fun()->isHeavyweight());
+        return hasCallObj_;
     }
     CallObject &callObj() const;
 
     bool hasArgsObj() const {
         return !!argsObj_;
     }
     ArgumentsObject &argsObj() const {
         MOZ_ASSERT(hasArgsObj());
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -559,22 +559,17 @@ JSCompartment::sweepSavedStacks()
 {
     savedStacks_.sweep(runtimeFromAnyThread());
 }
 
 void
 JSCompartment::sweepGlobalObject(FreeOp *fop)
 {
     if (global_.unbarrieredGet() && IsObjectAboutToBeFinalizedFromAnyThread(global_.unsafeGet())) {
-        // For main thread compartments, the invariant is that debug mode
-        // implies having at least one Debugger still attached. However, for
-        // off-thread compartments, which are used in off-thread parsing, they
-        // may be isDebuggee() without there being any Debuggers to prohibit
-        // asm.js.
-        if (isDebuggee() && !global_->compartment()->options().invisibleToDebugger())
+        if (isDebuggee())
             Debugger::detachAllDebuggersFromGlobal(fop, global_);
         global_.set(nullptr);
     }
 }
 
 void
 JSCompartment::sweepSelfHostingScriptSource()
 {
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -425,18 +425,18 @@ struct JSCompartment
 
   public:
     //
     // The Debugger observes execution on a frame-by-frame basis. The
     // invariants of JSCompartment's debug mode bits, JSScript::isDebuggee,
     // InterpreterFrame::isDebuggee, and Baseline::isDebuggee are enumerated
     // below.
     //
-    // 1. When a compartment's isDebuggee() == true, relazification and lazy
-    //    parsing are disabled.
+    // 1. When a compartment's isDebuggee() == true, relazification, lazy
+    //    parsing, and asm.js are disabled.
     //
     // 2. When a compartment's debugObservesAllExecution() == true, all of the
     //    compartment's scripts are considered debuggee scripts.
     //
     // 3. A script is considered a debuggee script either when, per above, its
     //    compartment is observing all execution, or if it has breakpoints set.
     //
     // 4. A debuggee script always pushes a debuggee frame.
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -433,41 +433,41 @@ Debugger::getScriptFrameWithIter(JSConte
                                  const ScriptFrameIter *maybeIter, MutableHandleValue vp)
 {
     MOZ_ASSERT_IF(maybeIter, maybeIter->abstractFramePtr() == frame);
 
     FrameMap::AddPtr p = frames.lookupForAdd(frame);
     if (!p) {
         /* Create and populate the Debugger.Frame object. */
         JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject();
-        NativeObject *frameobj =
-            NewNativeObjectWithGivenProto(cx, &DebuggerFrame_class, proto, nullptr);
+        RootedNativeObject frameobj(cx, NewNativeObjectWithGivenProto(cx, &DebuggerFrame_class,
+                                                                      proto, nullptr));
         if (!frameobj)
             return false;
 
         // Eagerly copy ScriptFrameIter data if we've already walked the
         // stack.
         if (maybeIter) {
             AbstractFramePtr data = maybeIter->copyDataAsAbstractFramePtr();
             if (!data)
                 return false;
             frameobj->setPrivate(data.raw());
         } else {
             frameobj->setPrivate(frame.raw());
         }
 
         frameobj->setReservedSlot(JSSLOT_DEBUGFRAME_OWNER, ObjectValue(*object));
 
+        if (!ensureExecutionObservabilityOfFrame(cx, frame))
+            return false;
+
         if (!frames.add(p, frame, frameobj)) {
             js_ReportOutOfMemory(cx);
             return false;
         }
-
-        if (!ensureExecutionObservabilityOfFrame(cx, frame))
-            return false;
     }
     vp.setObject(*p->value());
     return true;
 }
 
 /* static */ bool
 Debugger::hasLiveHook(GlobalObject *global, Hook which)
 {
@@ -590,21 +590,21 @@ Debugger::slowPathOnLeaveFrame(JSContext
 {
     Handle<GlobalObject*> global = cx->global();
 
     /* Save the frame's completion value. */
     JSTrapStatus status;
     RootedValue value(cx);
     Debugger::resultToCompletion(cx, frameOk, frame.returnValue(), &status, &value);
 
-    // This path can be hit via unwinding the stack due to
-    // over-recursion. Only fire the frames' onPop handlers if we haven't
-    // over-recursed, because invoking more JS will only result in more
-    // over-recursion errors. See slowPathOnExceptionUnwind.
-    if (!cx->isThrowingOverRecursed()) {
+    // This path can be hit via unwinding the stack due to over-recursion or
+    // OOM. In those cases, don't fire the frames' onPop handlers, because
+    // invoking JS will only trigger the same condition. See
+    // slowPathOnExceptionUnwind.
+    if (!cx->isThrowingOverRecursed() && !cx->isThrowingOutOfMemory()) {
         /* Build a list of the recipients. */
         AutoObjectVector frames(cx);
         for (FrameRange r(frame, global); !r.empty(); r.popFront()) {
             if (!frames.append(r.frontFrame())) {
                 cx->clearPendingException();
                 return false;
             }
         }
@@ -719,19 +719,19 @@ Debugger::slowPathOnDebuggerStatement(JS
     }
 
     return status;
 }
 
 /* static */ JSTrapStatus
 Debugger::slowPathOnExceptionUnwind(JSContext *cx, AbstractFramePtr frame)
 {
-    // Invoking more JS on an over-recursed stack is only going to result in
-    // more over-recursion errors.
-    if (cx->isThrowingOverRecursed())
+    // Invoking more JS on an over-recursed stack or after OOM is only going
+    // to result in more of the same error.
+    if (cx->isThrowingOverRecursed() || cx->isThrowingOutOfMemory())
         return JSTRAP_CONTINUE;
 
     RootedValue rval(cx);
     JSTrapStatus status = dispatchHook(cx, &rval, OnExceptionUnwind, NullPtr());
 
     switch (status) {
       case JSTRAP_CONTINUE:
         break;
@@ -1854,30 +1854,25 @@ Debugger::updateExecutionObservabilityOf
 static inline void
 MarkBaselineScriptActiveIfObservable(JSScript *script, const Debugger::ExecutionObservableSet &obs)
 {
     if (obs.shouldRecompileOrInvalidate(script))
         script->baselineScript()->setActive();
 }
 
 static bool
-AppendAndInvalidateScriptIfObservable(JSContext *cx, Zone *zone, JSScript *script,
-                                      const Debugger::ExecutionObservableSet &obs,
-                                      Vector<JSScript *> &scripts)
-{
-    if (obs.shouldRecompileOrInvalidate(script)) {
-        // Enter the script's compartment as addPendingRecompile attempts to
-        // cancel off-thread compilations, whose books are kept on the
-        // script's compartment.
-        MOZ_ASSERT(script->compartment()->zone() == zone);
-        AutoCompartment ac(cx, script->compartment());
-        zone->types.addPendingRecompile(cx, script);
-        return scripts.append(script);
-    }
-    return true;
+AppendAndInvalidateScript(JSContext *cx, Zone *zone, JSScript *script, Vector<JSScript *> &scripts)
+{
+    // Enter the script's compartment as addPendingRecompile attempts to
+    // cancel off-thread compilations, whose books are kept on the
+    // script's compartment.
+    MOZ_ASSERT(script->compartment()->zone() == zone);
+    AutoCompartment ac(cx, script->compartment());
+    zone->types.addPendingRecompile(cx, script);
+    return scripts.append(script);
 }
 
 static bool
 UpdateExecutionObservabilityOfScriptsInZone(JSContext *cx, Zone *zone,
                                             const Debugger::ExecutionObservableSet &obs,
                                             Debugger::IsObserving observing)
 {
     using namespace js::jit;
@@ -1915,23 +1910,29 @@ UpdateExecutionObservabilityOfScriptsInZ
 
     Vector<JSScript *> scripts(cx);
 
     // Iterate through observable scripts, invalidating their Ion scripts and
     // appending them to a vector for discarding their baseline scripts later.
     {
         types::AutoEnterAnalysis enter(fop, zone);
         if (JSScript *script = obs.singleScriptForZoneInvalidation()) {
-            if (!AppendAndInvalidateScriptIfObservable(cx, zone, script, obs, scripts))
-                return false;
+            if (obs.shouldRecompileOrInvalidate(script)) {
+                if (!AppendAndInvalidateScript(cx, zone, script, scripts))
+                    return false;
+            }
         } else {
             for (gc::ZoneCellIter iter(zone, gc::FINALIZE_SCRIPT); !iter.done(); iter.next()) {
                 JSScript *script = iter.get<JSScript>();
-                if (!AppendAndInvalidateScriptIfObservable(cx, zone, script, obs, scripts))
-                    return false;
+                if (obs.shouldRecompileOrInvalidate(script) &&
+                    !gc::IsScriptAboutToBeFinalized(&script))
+                {
+                    if (!AppendAndInvalidateScript(cx, zone, script, scripts))
+                        return false;
+                }
             }
         }
     }
 
     // Iterate through the scripts again and finish discarding
     // BaselineScripts. This must be done as a separate phase as we can only
     // discard the BaselineScript on scripts that have no IonScript.
     for (size_t i = 0; i < scripts.length(); i++) {
@@ -4528,18 +4529,20 @@ Debugger::replaceFrameGuts(JSContext *cx
 
         // Add the frame object with |to| as key.
         if (!dbg->frames.putNew(to, frameobj)) {
             js_ReportOutOfMemory(cx);
             return false;
         }
     }
 
-    // Rekey missingScopes to maintain Debugger.Environment identity.
-    DebugScopes::rekeyMissingScopes(cx, from, to);
+    // Rekey missingScopes to maintain Debugger.Environment identity and
+    // forward liveScopes to point to the new frame, as the old frame will be
+    // gone.
+    DebugScopes::forwardLiveFrame(cx, from, to);
 
     return true;
 }
 
 /* static */ bool
 Debugger::handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to)
 {
     ScriptFrameIter iter(cx);
--- a/js/src/vm/ForkJoin.cpp
+++ b/js/src/vm/ForkJoin.cpp
@@ -1870,51 +1870,16 @@ ParallelBailoutRecord::init(JSContext *c
 
 void
 ParallelBailoutRecord::reset()
 {
     RematerializedFrame::FreeInVector(frames());
     cause = ParallelBailoutNone;
 }
 
-void
-ParallelBailoutRecord::rematerializeFrames(ForkJoinContext *cx, JitFrameIterator &frameIter)
-{
-    // This function is infallible. These are only called when we are already
-    // erroring out. If we OOM here, free what we've allocated and return. Error
-    // reporting is then unable to give the user detailed stack information.
-
-    MOZ_ASSERT(frames().empty());
-
-    for (; !frameIter.done(); ++frameIter) {
-        if (!frameIter.isIonJS())
-            continue;
-
-        InlineFrameIterator inlineIter(cx, &frameIter);
-        Vector<RematerializedFrame *> inlineFrames(cx);
-
-        if (!RematerializedFrame::RematerializeInlineFrames(cx, frameIter.fp(),
-                                                            inlineIter, inlineFrames))
-        {
-            RematerializedFrame::FreeInVector(inlineFrames);
-            RematerializedFrame::FreeInVector(frames());
-            return;
-        }
-
-        // Reverse the inline frames into the main vector.
-        while (!inlineFrames.empty()) {
-            if (!frames().append(inlineFrames.popCopy())) {
-                RematerializedFrame::FreeInVector(inlineFrames);
-                RematerializedFrame::FreeInVector(frames());
-                return;
-            }
-        }
-    }
-}
-
 //////////////////////////////////////////////////////////////////////////////
 
 //
 // Debug spew
 //
 
 #ifdef FORKJOIN_SPEW
 
--- a/js/src/vm/ForkJoin.h
+++ b/js/src/vm/ForkJoin.h
@@ -365,18 +365,16 @@ struct ParallelBailoutRecord
             this->cause = cause;
         }
     }
 
     void setIonBailoutKind(jit::BailoutKind kind) {
         joinCause(ParallelBailoutExecution);
         ionBailoutKind = kind;
     }
-
-    void rematerializeFrames(ForkJoinContext *cx, jit::JitFrameIterator &frameIter);
 };
 
 class ForkJoinShared;
 
 class ForkJoinContext : public ThreadSafeContext
 {
   public:
     // Bailout record used to record the reason this thread stopped executing
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -193,17 +193,27 @@ ParseTask::ParseTask(ExclusiveContext *c
     callback(callback), callbackData(callbackData),
     script(nullptr), errors(cx), overRecursed(false)
 {
 }
 
 bool
 ParseTask::init(JSContext *cx, const ReadOnlyCompileOptions &options)
 {
-    return this->options.copy(cx, options);
+    if (!this->options.copy(cx, options))
+        return false;
+
+    // If the main-thread global is a debuggee, disable asm.js
+    // compilation. This is preferred to marking the task compartment as a
+    // debuggee, as the task compartment is (1) invisible to Debugger and (2)
+    // cannot have any Debuggers.
+    if (cx->compartment()->isDebuggee())
+        this->options.asmJSOption = false;
+
+    return true;
 }
 
 void
 ParseTask::activate(JSRuntime *rt)
 {
     rt->setUsedByExclusiveThread(exclusiveContextGlobal->zone());
     cx->enterCompartment(exclusiveContextGlobal->compartment());
 }
@@ -360,22 +370,16 @@ js::StartOffThreadParseScript(JSContext 
 
     if (OffThreadParsingMustWaitForGC(cx->runtime())) {
         AutoLockHelperThreadState lock;
         if (!HelperThreadState().parseWaitingOnGC().append(task.get()))
             return false;
     } else {
         task->activate(cx->runtime());
 
-        if (cx->compartment()->isDebuggee()) {
-            task->cx->compartment()->setIsDebuggee();
-            if (cx->compartment()->debugObservesAllExecution())
-                task->cx->compartment()->setDebugObservesAllExecution();
-        }
-
         AutoLockHelperThreadState lock;
 
         if (!HelperThreadState().parseWorklist().append(task.get()))
             return false;
 
         HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
     }
 
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -1505,23 +1505,43 @@ class DebugScopeProxy : public BaseProxy
      */
     static bool isMissingArguments(JSContext *cx, jsid id, ScopeObject &scope)
     {
         return isArguments(cx, id) && isFunctionScope(scope) &&
                !scope.as<CallObject>().callee().nonLazyScript()->needsArgsObj();
     }
 
     /*
+     * Check if the value is the magic value JS_OPTIMIZED_ARGUMENTS. The
+     * arguments analysis may have optimized out the 'arguments', and this
+     * magic value could have propagated to other local slots. e.g.,
+     *
+     *   function f() { var a = arguments; h(); }
+     *   function h() { evalInFrame(1, "a.push(0)"); }
+     *
+     * where evalInFrame(N, str) means to evaluate str N frames up.
+     *
+     * In this case we don't know we need to recover a missing arguments
+     * object until after we've performed the property get.
+     */
+    static bool isMagicMissingArgumentsValue(JSContext *cx, ScopeObject &scope, HandleValue v)
+    {
+        bool isMagic = v.isMagic() && v.whyMagic() == JS_OPTIMIZED_ARGUMENTS;
+        MOZ_ASSERT_IF(isMagic, isFunctionScope(scope) &&
+                               !scope.as<CallObject>().callee().nonLazyScript()->needsArgsObj());
+        return isMagic;
+    }
+
+    /*
      * Create a missing arguments object. If the function returns true but
      * argsObj is null, it means the scope is dead.
      */
-    static bool createMissingArguments(JSContext *cx, jsid id, ScopeObject &scope,
+    static bool createMissingArguments(JSContext *cx, ScopeObject &scope,
                                        MutableHandleArgumentsObject argsObj)
     {
-        MOZ_ASSERT(isMissingArguments(cx, id, scope));
         argsObj.set(nullptr);
 
         ScopeIterVal *maybeScope = DebugScopes::hasLiveScope(scope);
         if (!maybeScope)
             return true;
 
         argsObj.set(ArgumentsObject::createUnexpected(cx, maybeScope->frame()));
         return !!argsObj;
@@ -1549,125 +1569,147 @@ class DebugScopeProxy : public BaseProxy
     }
 
     bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                MutableHandle<PropertyDescriptor> desc) const MOZ_OVERRIDE
     {
         return getOwnPropertyDescriptor(cx, proxy, id, desc);
     }
 
+    bool getMissingArgumentsPropertyDescriptor(JSContext *cx,
+                                               Handle<DebugScopeObject *> debugScope,
+                                               ScopeObject &scope,
+                                               MutableHandle<PropertyDescriptor> desc) const
+    {
+        RootedArgumentsObject argsObj(cx);
+        if (!createMissingArguments(cx, scope, &argsObj))
+            return false;
+
+        if (!argsObj) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
+                                 "Debugger scope");
+            return false;
+        }
+
+        desc.object().set(debugScope);
+        desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT);
+        desc.value().setObject(*argsObj);
+        desc.setGetter(nullptr);
+        desc.setSetter(nullptr);
+        return true;
+    }
+
     bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
-                                  MutableHandle<PropertyDescriptor> desc) const MOZ_OVERRIDE
+                                  MutableHandle<PropertyDescriptor> desc) const
     {
         Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>());
         Rooted<ScopeObject*> scope(cx, &debugScope->scope());
 
-        if (isMissingArguments(cx, id, *scope)) {
-            RootedArgumentsObject argsObj(cx);
-            if (!createMissingArguments(cx, id, *scope, &argsObj))
-                return false;
-
-            if (!argsObj) {
-                JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
-                                     "Debugger scope");
-                return false;
-            }
-
-            desc.object().set(debugScope);
-            desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT);
-            desc.value().setObject(*argsObj);
-            desc.setGetter(nullptr);
-            desc.setSetter(nullptr);
-            return true;
-        }
+        if (isMissingArguments(cx, id, *scope))
+            return getMissingArgumentsPropertyDescriptor(cx, debugScope, *scope, desc);
 
         RootedValue v(cx);
         AccessResult access;
         if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, &v, &access))
             return false;
 
         switch (access) {
           case ACCESS_UNALIASED:
+            if (isMagicMissingArgumentsValue(cx, *scope, v))
+                return getMissingArgumentsPropertyDescriptor(cx, debugScope, *scope, desc);
             desc.object().set(debugScope);
             desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT);
             desc.value().set(v);
             desc.setGetter(nullptr);
             desc.setSetter(nullptr);
             return true;
           case ACCESS_GENERIC:
             return JS_GetOwnPropertyDescriptorById(cx, scope, id, desc);
           case ACCESS_LOST:
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT);
             return false;
           default:
             MOZ_CRASH("bad AccessResult");
         }
     }
 
+    bool getMissingArguments(JSContext *cx, ScopeObject &scope, MutableHandleValue vp) const
+    {
+        RootedArgumentsObject argsObj(cx);
+        if (!createMissingArguments(cx, scope, &argsObj))
+            return false;
+
+        if (!argsObj) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
+                                 "Debugger scope");
+            return false;
+        }
+
+        vp.setObject(*argsObj);
+        return true;
+    }
+
     bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
              MutableHandleValue vp) const MOZ_OVERRIDE
     {
         Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>());
         Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope());
 
-        if (isMissingArguments(cx, id, *scope)) {
-            RootedArgumentsObject argsObj(cx);
-            if (!createMissingArguments(cx, id, *scope, &argsObj))
-                return false;
-
-            if (!argsObj) {
-                JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
-                                     "Debugger scope");
-                return false;
-            }
-
-            vp.setObject(*argsObj);
-            return true;
-        }
+        if (isMissingArguments(cx, id, *scope))
+            return getMissingArguments(cx, *scope, vp);
 
         AccessResult access;
         if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access))
             return false;
 
         switch (access) {
           case ACCESS_UNALIASED:
+            if (isMagicMissingArgumentsValue(cx, *scope, vp))
+                return getMissingArguments(cx, *scope, vp);
             return true;
           case ACCESS_GENERIC:
             return JSObject::getGeneric(cx, scope, scope, id, vp);
           case ACCESS_LOST:
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT);
             return false;
           default:
             MOZ_CRASH("bad AccessResult");
         }
     }
 
+    bool getMissingArgumentsMaybeSentinelValue(JSContext *cx, ScopeObject &scope,
+                                               MutableHandleValue vp) const
+    {
+        RootedArgumentsObject argsObj(cx);
+        if (!createMissingArguments(cx, scope, &argsObj))
+            return false;
+        vp.set(argsObj ? ObjectValue(*argsObj) : MagicValue(JS_OPTIMIZED_ARGUMENTS));
+        return true;
+    }
+
     /*
      * Like 'get', but returns sentinel values instead of throwing on
      * exceptional cases.
      */
     bool getMaybeSentinelValue(JSContext *cx, Handle<DebugScopeObject *> debugScope, HandleId id,
                                MutableHandleValue vp) const
     {
         Rooted<ScopeObject*> scope(cx, &debugScope->scope());
 
-        if (isMissingArguments(cx, id, *scope)) {
-            RootedArgumentsObject argsObj(cx);
-            if (!createMissingArguments(cx, id, *scope, &argsObj))
-                return false;
-            vp.set(argsObj ? ObjectValue(*argsObj) : MagicValue(JS_OPTIMIZED_ARGUMENTS));
-            return true;
-        }
+        if (isMissingArguments(cx, id, *scope))
+            return getMissingArgumentsMaybeSentinelValue(cx, *scope, vp);
 
         AccessResult access;
         if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access))
             return false;
 
         switch (access) {
           case ACCESS_UNALIASED:
+            if (isMagicMissingArgumentsValue(cx, *scope, vp))
+                return getMissingArgumentsMaybeSentinelValue(cx, *scope, vp);
             return true;
           case ACCESS_GENERIC:
             return JSObject::getGeneric(cx, scope, scope, id, vp);
           case ACCESS_LOST:
             vp.setMagic(JS_OPTIMIZED_OUT);
             return true;
           default:
             MOZ_CRASH("bad AccessResult");
@@ -2376,29 +2418,35 @@ DebugScopes::hasLiveScope(ScopeObject &s
 
     if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope))
         return &p->value();
 
     return nullptr;
 }
 
 /* static */ void
-DebugScopes::rekeyMissingScopes(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to)
+DebugScopes::forwardLiveFrame(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to)
 {
     DebugScopes *scopes = cx->compartment()->debugScopes;
     if (!scopes)
         return;
 
     for (MissingScopeMap::Enum e(scopes->missingScopes); !e.empty(); e.popFront()) {
         ScopeIterKey key = e.front().key();
         if (key.frame() == from) {
             key.updateFrame(to);
             e.rekeyFront(key);
         }
     }
+
+    for (LiveScopeMap::Enum e(scopes->liveScopes); !e.empty(); e.popFront()) {
+        ScopeIterVal &val = e.front().value();
+        if (val.frame() == from)
+            val.updateFrame(to);
+    }
 }
 
 /*****************************************************************************/
 
 static JSObject *
 GetDebugScope(JSContext *cx, const ScopeIter &si);
 
 static DebugScopeObject *
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -807,16 +807,17 @@ class ScopeIterVal
     static void staticAsserts();
 
   public:
     explicit ScopeIterVal(const ScopeIter &si)
       : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
         hasScopeObject_(si.hasScopeObject_) {}
 
     AbstractFramePtr frame() const { return frame_; }
+    void updateFrame(AbstractFramePtr frame) { frame_ = frame; }
 };
 
 /*****************************************************************************/
 
 /*
  * Debug scope objects
  *
  * The debugger effectively turns every opcode into a potential direct eval.
@@ -937,17 +938,20 @@ class DebugScopes
     static bool addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope);
 
     static DebugScopeObject *hasDebugScope(JSContext *cx, const ScopeIter &si);
     static bool addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope);
 
     static bool updateLiveScopes(JSContext *cx);
     static ScopeIterVal *hasLiveScope(ScopeObject &scope);
 
-    static void rekeyMissingScopes(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to);
+    // When a frame bails out from Ion to Baseline, there might be missing
+    // scopes keyed on, and live scopes containing, the old
+    // RematerializedFrame. Forward those values to the new BaselineFrame.
+    static void forwardLiveFrame(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to);
 
     // In debug-mode, these must be called whenever exiting a scope that might
     // have stack-allocated locals.
     static void onPopCall(AbstractFramePtr frame, JSContext *cx);
     static void onPopBlock(JSContext *cx, const ScopeIter &si);
     static void onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
     static void onPopWith(AbstractFramePtr frame);
     static void onPopStrictEvalScope(AbstractFramePtr frame);
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -458,17 +458,19 @@ AbstractFramePtr::callObj() const
     return asRematerializedFrame()->callObj();
 }
 
 inline bool
 AbstractFramePtr::initFunctionScopeObjects(JSContext *cx)
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->initFunctionScopeObjects(cx);
-    return asBaselineFrame()->initFunctionScopeObjects(cx);
+    if (isBaselineFrame())
+        return asBaselineFrame()->initFunctionScopeObjects(cx);
+    return asRematerializedFrame()->initFunctionScopeObjects(cx);
 }
 
 inline JSCompartment *
 AbstractFramePtr::compartment() const
 {
     return scopeChain()->compartment();
 }
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -958,17 +958,17 @@ FrameIter::isConstructing() const
       case INTERP:
         return interpFrame()->isConstructing();
     }
 
     MOZ_CRASH("Unexpected state");
 }
 
 bool
-FrameIter::ensureHasRematerializedFrame(ThreadSafeContext *cx)
+FrameIter::ensureHasRematerializedFrame(JSContext *cx)
 {
     MOZ_ASSERT(isIon());
     return !!activation()->asJit()->getRematerializedFrame(cx, data_.jitFrames_);
 }
 
 bool
 FrameIter::hasUsableAbstractFramePtr() const
 {
@@ -1440,17 +1440,17 @@ jit::JitActivation::clearRematerializedF
 
     for (RematerializedFrameTable::Enum e(*rematerializedFrames_); !e.empty(); e.popFront()) {
         RematerializedFrame::FreeInVector(e.front().value());
         e.removeFront();
     }
 }
 
 jit::RematerializedFrame *
-jit::JitActivation::getRematerializedFrame(ThreadSafeContext *cx, const JitFrameIterator &iter, size_t inlineDepth)
+jit::JitActivation::getRematerializedFrame(JSContext *cx, const JitFrameIterator &iter, size_t inlineDepth)
 {
     // Only allow rematerializing from the same thread.
     MOZ_ASSERT(cx->perThreadData == cx_->perThreadData);
     MOZ_ASSERT(iter.activation() == this);
     MOZ_ASSERT(iter.isIonScripted());
 
     if (!rematerializedFrames_) {
         rematerializedFrames_ = cx->new_<RematerializedFrameTable>(cx);
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1325,17 +1325,17 @@ class JitActivation : public Activation
 #endif
 
     // Look up a rematerialized frame keyed by the fp, rematerializing the
     // frame if one doesn't already exist. A frame can only be rematerialized
     // if an IonFrameIterator pointing to the nearest uninlined frame can be
     // provided, as values need to be read out of snapshots.
     //
     // The inlineDepth must be within bounds of the frame pointed to by iter.
-    RematerializedFrame *getRematerializedFrame(ThreadSafeContext *cx, const JitFrameIterator &iter,
+    RematerializedFrame *getRematerializedFrame(JSContext *cx, const JitFrameIterator &iter,
                                                 size_t inlineDepth = 0);
 
     // Look up a rematerialized frame by the fp. If inlineDepth is out of
     // bounds of what has been rematerialized, nullptr is returned.
     RematerializedFrame *lookupRematerializedFrame(uint8_t *top, size_t inlineDepth = 0);
 
     bool hasRematerializedFrame(uint8_t *top, size_t inlineDepth = 0) {
         return !!lookupRematerializedFrame(top, inlineDepth);
@@ -1631,17 +1631,17 @@ class FrameIter
     }
 
     // These are only valid for the top frame.
     size_t      numFrameSlots() const;
     Value       frameSlotValue(size_t index) const;
 
     // Ensures that we have rematerialized the top frame and its associated
     // inline frames. Can only be called when isIon().
-    bool ensureHasRematerializedFrame(ThreadSafeContext *cx);
+    bool ensureHasRematerializedFrame(JSContext *cx);
 
     // True when isInterp() or isBaseline(). True when isIon() if it
     // has a rematerialized frame. False otherwise false otherwise.
     bool hasUsableAbstractFramePtr() const;
 
     // -----------------------------------------------------------
     // The following functions can only be called when isInterp(),
     // isBaseline(), or isIon(). Further, abstractFramePtr() can