Bug 1232685 - Prevent forcing illegal return values in derived class constructors. (r=shu, a=kwierso)
authorEric Faust <efaustbmo@gmail.com>
Thu, 18 Feb 2016 14:10:35 -0800
changeset 321402 7723ac2ee7ce143112c8506826a858c7b0df455a
parent 321401 38d59bc8f3397c30cbf9802214b2f9adc2e0f2f3
child 321403 22b111e834ddd1c604d4d9da4cf89967a09432bf
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu, kwierso
bugs1232685
milestone47.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 1232685 - Prevent forcing illegal return values in derived class constructors. (r=shu, a=kwierso)
js/src/doc/Debugger/Conventions.md
js/src/jit-test/tests/debug/class-01.js
js/src/jit-test/tests/debug/class-02.js
js/src/jit-test/tests/debug/class-03.js
js/src/jit-test/tests/debug/class-04.js
js/src/jit-test/tests/debug/class-05.js
js/src/jit-test/tests/debug/class-06.js
js/src/jit-test/tests/debug/class-07.js
js/src/jit/JitFrames.cpp
js/src/jit/VMFunctions.cpp
js/src/vm/Debugger-inl.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/Interpreter.cpp
js/src/vm/Stack-inl.h
js/src/vm/Stack.h
--- a/js/src/doc/Debugger/Conventions.md
+++ b/js/src/doc/Debugger/Conventions.md
@@ -106,17 +106,19 @@ resumption value has one of the followin
 
 <code>{ return: <i>value</i> }</code>
 :   Return <i>value</i> immediately as the current value of the function.
     <i>Value</i> must be a debuggee value. (Most handler functions support
     this, except those whose descriptions say otherwise.) If the function
     was called as a constructor (that is, via a `new` expression), then
     <i>value</i> serves as the value returned by the function's body, not
     that produced by the `new` expression: if the value is not an object,
-    the `new` expression returns the frame's `this` value.
+    the `new` expression returns the frame's `this` value. Similarly, if
+    the function is the constructor for a subclass, then a non-object
+    value may result in a TypeError.
 
 <code>{ yield: <i>value</i> }</code>
 :   <i>(Not yet implemented.)</i> Yield <i>value</i> immediately as the
     next value of the current frame, which must be a generator frame.
     <i>Value</i> is a debuggee value. The current frame must be a generator
     frame that has not yet completed in some other way. You may use `yield`
     resumption values to substitute a new value or one already yielded by a
     generator, or to make a generator yield additional values.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-01.js
@@ -0,0 +1,20 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+    (class extends class {} {
+        // Calling this will throw for using |this| uninitialized.
+        constructor() { }
+    })
+`);
+
+dbg.onExceptionUnwind = function() {
+    return {
+        // Force the return of an illegal value.
+        return: 1
+    }
+}
+
+new forceException;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-02.js
@@ -0,0 +1,20 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+    (class extends class {} {
+        // Calling this will return a primitive immediately.
+        constructor() { return {}; }
+    })
+`);
+
+dbg.onEnterFrame = function() {
+    return {
+        // Force the return of an illegal value.
+        return: 1
+    }
+}
+
+new forceException;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-03.js
@@ -0,0 +1,23 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+    (class extends class {} {
+        // Calling this will return a primitive immediately.
+        constructor() {
+            debugger;
+            return {};
+        }
+    })
+`);
+
+dbg.onDebuggerStatement = function() {
+    return {
+        // Force the return of an illegal value.
+        return: 1
+    }
+}
+
+new forceException;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-04.js
@@ -0,0 +1,22 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+    (class extends class {} {
+        // Calling this will return a primitive on return.
+        constructor() { return {}; }
+    })
+`);
+
+dbg.onEnterFrame = function(f) {
+    f.onPop = function() {
+        return {
+            // Force the return of an illegal value.
+            return: 1
+        }
+    }
+}
+
+new forceException;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-05.js
@@ -0,0 +1,31 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+    (class extends class {} {
+        // Calling this will return a primitive immediately.
+        constructor() {
+            debugger;
+            return {};
+        }
+    })
+`);
+
+let handler = {
+    hit() {
+        return {
+            // Force the return of an illegal value.
+            return: 1
+        }
+    }
+};
+
+dbg.onDebuggerStatement = function(frame) {
+    var line0 = frame.script.getOffsetLocation(frame.offset).lineNumber;
+    var offs = frame.script.getLineOffsets(line0 + 1);
+    frame.script.setBreakpoint(offs[0], handler);
+}
+
+new forceException;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-06.js
@@ -0,0 +1,22 @@
+// |jit-test| error: TypeError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+    (class extends class {} {
+        // Calling this will return a primitive immediately.
+        constructor() { return {}; }
+    })
+`);
+
+dbg.onEnterFrame = function(f) {
+    f.onStep = function() {
+        return {
+            // Force the return of an illegal value.
+            return: 1
+        }
+    }
+}
+
+new forceException;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/class-07.js
@@ -0,0 +1,21 @@
+// |jit-test| error: ReferenceError
+
+let g = newGlobal();
+let dbg = Debugger(g);
+
+let forceException = g.eval(`
+    (class extends class {} {
+        // Calling this will return a primitive immediately.
+        constructor() { return {}; }
+    })
+`);
+
+dbg.onEnterFrame = function() {
+    return {
+        // Force the return undefined, which will throw for returning
+        // while |this| is still undefined.
+        return: undefined
+    }
+}
+print("break here");
+new forceException;
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -683,17 +683,17 @@ HandleExceptionBaseline(JSContext* cx, c
                 // No need to increment the PCCounts number of execution here,
                 // as the interpreter increments any PCCounts if present.
                 MOZ_ASSERT_IF(script->hasScriptCounts(), script->maybeGetPCCounts(pc));
                 return;
             }
         }
 
         frameOk = HandleClosingGeneratorReturn(cx, frame.baselineFrame(), frameOk);
-        frameOk = Debugger::onLeaveFrame(cx, frame.baselineFrame(), frameOk);
+        frameOk = Debugger::onLeaveFrame(cx, frame.baselineFrame(), pc, frameOk);
     } else if (script->hasTrynotes()) {
         CloseLiveIteratorsBaselineForUncatchableException(cx, frame, pc);
     }
 
     OnLeaveBaselineFrame(cx, frame, pc, rfe, frameOk);
 }
 
 struct AutoDeleteDebugModeOSRInfo
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -705,17 +705,17 @@ DebugEpilogueOnBaselineReturn(JSContext*
 }
 
 bool
 DebugEpilogue(JSContext* cx, BaselineFrame* frame, jsbytecode* pc, bool ok)
 {
     // If Debugger::onLeaveFrame returns |true| we have to return the frame's
     // return value. If it returns |false|, the debugger threw an exception.
     // In both cases we have to pop debug scopes.
-    ok = Debugger::onLeaveFrame(cx, frame, ok);
+    ok = Debugger::onLeaveFrame(cx, frame, pc, ok);
 
     // Unwind to the outermost scope and set pc to the end of the script,
     // regardless of error.
     ScopeIter si(cx, frame, pc);
     UnwindAllScopesInFrame(cx, si);
     JSScript* script = frame->script();
     frame->setOverridePc(script->lastPC());
 
--- a/js/src/vm/Debugger-inl.h
+++ b/js/src/vm/Debugger-inl.h
@@ -7,26 +7,26 @@
 #ifndef vm_Debugger_inl_h
 #define vm_Debugger_inl_h
 
 #include "vm/Debugger.h"
 
 #include "vm/Stack-inl.h"
 
 /* static */ inline bool
-js::Debugger::onLeaveFrame(JSContext* cx, AbstractFramePtr frame, bool ok)
+js::Debugger::onLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool ok)
 {
     MOZ_ASSERT_IF(frame.isInterpreterFrame(), frame.asInterpreterFrame() == cx->interpreterFrame());
     MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
     /* Traps must be cleared from eval frames, see slowPathOnLeaveFrame. */
     mozilla::DebugOnly<bool> evalTraps = frame.isEvalFrame() &&
                                          frame.script()->hasAnyBreakpointsOrStepMode();
     MOZ_ASSERT_IF(evalTraps, frame.isDebuggee());
     if (frame.isDebuggee())
-        ok = slowPathOnLeaveFrame(cx, frame, ok);
+        ok = slowPathOnLeaveFrame(cx, frame, pc, ok);
     MOZ_ASSERT(!inFrameMaps(frame));
     return ok;
 }
 
 /* static */ inline js::Debugger*
 js::Debugger::fromJSObject(const JSObject* obj)
 {
     MOZ_ASSERT(js::GetObjectClass(obj) == &jsclass);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -739,17 +739,17 @@ static void
 DebuggerFrame_freeScriptFrameIterData(FreeOp* fop, JSObject* obj);
 
 /*
  * Handle leaving a frame with debuggers watching. |frameOk| indicates whether
  * the frame is exiting normally or abruptly. Set |cx|'s exception and/or
  * |cx->fp()|'s return value, and return a new success value.
  */
 /* static */ bool
-Debugger::slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, bool frameOk)
+Debugger::slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool frameOk)
 {
     Handle<GlobalObject*> global = cx->global();
 
     // The onPop handler and associated clean up logic should not run multiple
     // times on the same frame. If slowPathOnLeaveFrame has already been
     // called, the frame will not be present in the Debugger frame maps.
     FrameRange frameRange(frame, global);
     if (frameRange.empty())
@@ -783,34 +783,36 @@ Debugger::slowPathOnLeaveFrame(JSContext
 
         /* For each Debugger.Frame, fire its onPop handler, if any. */
         for (JSObject** p = frames.begin(); p != frames.end(); p++) {
             RootedNativeObject frameobj(cx, &(*p)->as<NativeObject>());
             Debugger* dbg = Debugger::fromChildJSObject(frameobj);
             EnterDebuggeeNoExecute nx(cx, *dbg);
 
             if (dbg->enabled &&
-                !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined()) {
+                !frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER).isUndefined())
+            {
                 RootedValue handler(cx, frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONPOP_HANDLER));
 
                 Maybe<AutoCompartment> ac;
                 ac.emplace(cx, dbg->object);
 
                 RootedValue completion(cx);
                 if (!dbg->newCompletionValue(cx, status, value, &completion)) {
                     status = dbg->handleUncaughtException(ac, false);
                     break;
                 }
 
                 /* Call the onPop handler. */
                 RootedValue rval(cx);
                 bool hookOk = Invoke(cx, ObjectValue(*frameobj), handler, 1, completion.address(),
                                      &rval);
                 RootedValue nextValue(cx);
-                JSTrapStatus nextStatus = dbg->parseResumptionValue(ac, hookOk, rval, &nextValue);
+                JSTrapStatus nextStatus = dbg->parseResumptionValue(ac, hookOk, rval,
+                                                                    frame, pc, &nextValue);
 
                 /*
                  * At this point, we are back in the debuggee compartment, and any error has
                  * been wrapped up as a completion value.
                  */
                 MOZ_ASSERT(cx->compartment() == global->compartment());
                 MOZ_ASSERT(!cx->isExceptionPending());
 
@@ -1145,34 +1147,38 @@ public:
 
 private:
     RootedValue& exn_;
 };
 } // anonymous namespace
 
 JSTrapStatus
 Debugger::handleUncaughtExceptionHelper(Maybe<AutoCompartment>& ac,
-                                        MutableHandleValue* vp, bool callHook)
+                                        MutableHandleValue* vp, bool callHook,
+                                        const Maybe<HandleValue>& thisVForCheck,
+                                        AbstractFramePtr frame)
 {
     JSContext* cx = ac->context()->asJSContext();
 
     // Uncaught exceptions arise from Debugger code, and so we must already be
     // in an NX section.
     MOZ_ASSERT(EnterDebuggeeNoExecute::isUniqueLockedInStack(cx, *this));
 
     if (cx->isExceptionPending()) {
         if (callHook && uncaughtExceptionHook) {
             RootedValue exc(cx);
             if (!cx->getPendingException(&exc))
                 return JSTRAP_ERROR;
             cx->clearPendingException();
             RootedValue fval(cx, ObjectValue(*uncaughtExceptionHook));
             RootedValue rv(cx);
-            if (Invoke(cx, ObjectValue(*object), fval, 1, exc.address(), &rv))
-                return vp ? parseResumptionValue(ac, true, rv, *vp, false) : JSTRAP_CONTINUE;
+            if (Invoke(cx, ObjectValue(*object), fval, 1, exc.address(), &rv)) {
+                return vp ? parseResumptionValueHelper(ac, true, rv, thisVForCheck, frame, *vp, false)
+                          : JSTRAP_CONTINUE;
+            }
         }
 
         if (cx->isExceptionPending()) {
             /*
              * We want to report the pending exception, but we want to let the
              * embedding handle it however it wants to.  So pretend like we're
              * starting a new script execution on our current compartment (which
              * is the debugger compartment, so reported errors won't get
@@ -1199,25 +1205,26 @@ Debugger::handleUncaughtExceptionHelper(
             cx->clearPendingException();
         }
     }
     ac.reset();
     return JSTRAP_ERROR;
 }
 
 JSTrapStatus
-Debugger::handleUncaughtException(Maybe<AutoCompartment>& ac, MutableHandleValue vp, bool callHook)
-{
-    return handleUncaughtExceptionHelper(ac, &vp, callHook);
+Debugger::handleUncaughtException(Maybe<AutoCompartment>& ac, MutableHandleValue vp, bool callHook,
+                                  const Maybe<HandleValue>& thisVForCheck, AbstractFramePtr frame)
+{
+    return handleUncaughtExceptionHelper(ac, &vp, callHook, thisVForCheck, frame);
 }
 
 JSTrapStatus
 Debugger::handleUncaughtException(Maybe<AutoCompartment>& ac, bool callHook)
 {
-    return handleUncaughtExceptionHelper(ac, nullptr, callHook);
+    return handleUncaughtExceptionHelper(ac, nullptr, callHook, mozilla::Nothing(), NullFramePtr());
 }
 
 /* static */ void
 Debugger::resultToCompletion(JSContext* cx, bool ok, const Value& rv,
                              JSTrapStatus* status, MutableHandleValue value)
 {
     MOZ_ASSERT_IF(ok, !cx->isExceptionPending());
 
@@ -1324,51 +1331,105 @@ ParseResumptionValueAsObject(JSContext* 
     if (hits != 1) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_RESUMPTION);
         return false;
     }
     return true;
 }
 
 JSTrapStatus
-Debugger::parseResumptionValue(Maybe<AutoCompartment>& ac, bool ok, const Value& rv, MutableHandleValue vp,
-                               bool callHook)
+Debugger::parseResumptionValueHelper(Maybe<AutoCompartment>& ac, bool ok, const Value& rv,
+                                     const Maybe<HandleValue>& thisVForCheck, AbstractFramePtr frame,
+                                     MutableHandleValue vp, bool callHook)
 {
     vp.setUndefined();
     if (!ok)
-        return handleUncaughtException(ac, vp, callHook);
+        return handleUncaughtException(ac, vp, callHook, thisVForCheck, frame);
     if (rv.isUndefined()) {
         ac.reset();
         return JSTRAP_CONTINUE;
     }
     if (rv.isNull()) {
         ac.reset();
         return JSTRAP_ERROR;
     }
 
     JSContext* cx = ac->context()->asJSContext();
     JSTrapStatus status = JSTRAP_CONTINUE;
     RootedValue v(cx);
     RootedValue rvRoot(cx, rv);
+
     if (!ParseResumptionValueAsObject(cx, rvRoot, &status, &v) ||
         !unwrapDebuggeeValue(cx, &v))
     {
-        return handleUncaughtException(ac, vp, callHook);
+        return handleUncaughtException(ac, vp, callHook, thisVForCheck, frame);
+    }
+
+    if (status == JSTRAP_RETURN && thisVForCheck.isSome() && v.isPrimitive()) {
+        if (v.isUndefined()) {
+            if (thisVForCheck.ref().isMagic(JS_UNINITIALIZED_LEXICAL)) {
+                MOZ_ALWAYS_FALSE(ThrowUninitializedThis(cx, frame));
+                return handleUncaughtException(ac, vp, callHook, thisVForCheck, frame);
+            }
+
+            v = thisVForCheck.ref();
+        } else {
+            ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, v, nullptr);
+            return handleUncaughtException(ac, vp, callHook, thisVForCheck, frame);
+        }
     }
 
     ac.reset();
     if (!cx->compartment()->wrap(cx, &v)) {
         vp.setUndefined();
         return JSTRAP_ERROR;
     }
     vp.set(v);
 
     return status;
 }
 
+JSTrapStatus
+Debugger::parseResumptionValue(Maybe<AutoCompartment>& ac, bool ok, const Value& rv,
+                               AbstractFramePtr frame, jsbytecode* pc, MutableHandleValue vp,
+                               bool callHook)
+{
+    JSContext* cx = ac->context()->asJSContext();
+    RootedValue rootThis(cx);
+    Maybe<HandleValue> thisArg;
+    if (frame.debuggerNeedsCheckPrimitiveReturn()) {
+        bool success;
+        {
+            AutoCompartment ac2(cx, frame.scopeChain());
+            success = GetThisValueForDebuggerMaybeOptimizedOut(cx, frame, pc, &rootThis);
+        }
+        if (!success || !cx->compartment()->wrap(cx, &rootThis)) {
+            ac.reset();
+            return JSTRAP_ERROR;
+        }
+        MOZ_ASSERT_IF(rootThis.isMagic(), rootThis.isMagic(JS_UNINITIALIZED_LEXICAL));
+        thisArg.emplace(HandleValue(rootThis));
+    }
+    return parseResumptionValueHelper(ac, ok, rv, thisArg, frame, vp, callHook);
+}
+
+JSTrapStatus
+Debugger::parseResumptionValue(Maybe<AutoCompartment>& ac, bool ok, const Value& rv,
+                               const Value& thisV, AbstractFramePtr frame, MutableHandleValue vp,
+                               bool callHook)
+{
+    JSContext* cx = ac->context()->asJSContext();
+    RootedValue rootThis(cx, thisV);
+    Maybe<HandleValue> thisArg;
+    if (frame.debuggerNeedsCheckPrimitiveReturn())
+        thisArg.emplace(rootThis);
+
+    return parseResumptionValueHelper(ac, ok, rv, thisArg, frame, vp, callHook);
+}
+
 static bool
 CallMethodIfPresent(JSContext* cx, HandleObject obj, const char* name, int argc, Value* argv,
                     MutableHandleValue rval)
 {
     rval.setUndefined();
     JSAtom* atom = Atomize(cx, name, strlen(name));
     if (!atom)
         return false;
@@ -1391,17 +1452,17 @@ Debugger::fireDebuggerStatement(JSContex
 
     ScriptFrameIter iter(cx);
     RootedValue scriptFrame(cx);
     if (!getScriptFrame(cx, iter, &scriptFrame))
         return handleUncaughtException(ac, false);
 
     RootedValue rv(cx);
     bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptFrame.address(), &rv);
-    return parseResumptionValue(ac, ok, rv, vp);
+    return parseResumptionValue(ac, ok, rv, iter.abstractFramePtr(), iter.pc(), vp);
 }
 
 JSTrapStatus
 Debugger::fireExceptionUnwind(JSContext* cx, MutableHandleValue vp)
 {
     RootedObject hook(cx, getHook(OnExceptionUnwind));
     MOZ_ASSERT(hook);
     MOZ_ASSERT(hook->isCallable());
@@ -1419,17 +1480,17 @@ Debugger::fireExceptionUnwind(JSContext*
     argv[1].set(exc);
 
     ScriptFrameIter iter(cx);
     if (!getScriptFrame(cx, iter, argv[0]) || !wrapDebuggeeValue(cx, argv[1]))
         return handleUncaughtException(ac, false);
 
     RootedValue rv(cx);
     bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 2, argv.begin(), &rv);
-    JSTrapStatus st = parseResumptionValue(ac, ok, rv, vp);
+    JSTrapStatus st = parseResumptionValue(ac, ok, rv, iter.abstractFramePtr(), iter.pc(), vp);
     if (st == JSTRAP_CONTINUE)
         cx->setPendingException(exc);
     return st;
 }
 
 JSTrapStatus
 Debugger::fireEnterFrame(JSContext* cx, AbstractFramePtr frame, MutableHandleValue vp)
 {
@@ -1441,17 +1502,18 @@ Debugger::fireEnterFrame(JSContext* cx, 
     ac.emplace(cx, object);
 
     RootedValue scriptFrame(cx);
     if (!getScriptFrame(cx, frame, &scriptFrame))
         return handleUncaughtException(ac, false);
 
     RootedValue rv(cx);
     bool ok = Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, scriptFrame.address(), &rv);
-    return parseResumptionValue(ac, ok, rv, vp);
+
+    return parseResumptionValue(ac, ok, rv, MagicValue(JS_UNINITIALIZED_LEXICAL), frame, vp);
 }
 
 void
 Debugger::fireNewScript(JSContext* cx, HandleScript script)
 {
     RootedObject hook(cx, getHook(OnNewScript));
     MOZ_ASSERT(hook);
     MOZ_ASSERT(hook->isCallable());
@@ -1672,17 +1734,18 @@ Debugger::onTrap(JSContext* cx, MutableH
             EnterDebuggeeNoExecute nx(cx, *dbg);
 
             RootedValue scriptFrame(cx);
             if (!dbg->getScriptFrame(cx, iter, &scriptFrame))
                 return dbg->handleUncaughtException(ac, false);
             RootedValue rv(cx);
             Rooted<JSObject*> handler(cx, bp->handler);
             bool ok = CallMethodIfPresent(cx, handler, "hit", 1, scriptFrame.address(), &rv);
-            JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rv, vp, true);
+            JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rv,  iter.abstractFramePtr(),
+                                                        iter.pc(), vp, true);
             if (st != JSTRAP_CONTINUE)
                 return st;
 
             /* Calling JS code invalidates site. Reload it. */
             site = script->getBreakpointSite(pc);
         }
     }
 
@@ -1762,18 +1825,20 @@ Debugger::onSingleStep(JSContext* cx, Mu
         Debugger* dbg = Debugger::fromChildJSObject(frame);
         EnterDebuggeeNoExecute nx(cx, *dbg);
 
         Maybe<AutoCompartment> ac;
         ac.emplace(cx, dbg->object);
 
         const Value& handler = frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
         RootedValue rval(cx);
+
         bool ok = Invoke(cx, ObjectValue(*frame), handler, 0, nullptr, &rval);
-        JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rval, vp);
+        JSTrapStatus st = dbg->parseResumptionValue(ac, ok, rval, iter.abstractFramePtr(),
+                                                    iter.pc(), vp);
         if (st != JSTRAP_CONTINUE)
             return st;
     }
 
     vp.setUndefined();
     if (exceptionPending)
         cx->setPendingException(exception);
     return JSTRAP_CONTINUE;
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -474,20 +474,23 @@ class Debugger : private mozilla::Linked
      * If there is no uncaughtExceptionHook, or if it fails, report and clear
      * the pending exception on ac.context and return JSTRAP_ERROR.
      *
      * This always calls ac.leave(); ac is a parameter because this method must
      * do some things in the debugger compartment and some things in the
      * debuggee compartment.
      */
     JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment>& ac, bool callHook);
-    JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment>& ac, MutableHandleValue vp, bool callHook);
+    JSTrapStatus handleUncaughtException(mozilla::Maybe<AutoCompartment>& ac, MutableHandleValue vp, bool callHook,
+                                         const mozilla::Maybe<HandleValue>& thisVForCheck = mozilla::Nothing(),
+                                         AbstractFramePtr frame = NullFramePtr());
 
     JSTrapStatus handleUncaughtExceptionHelper(mozilla::Maybe<AutoCompartment>& ac,
-                                               MutableHandleValue* vp, bool callHook);
+                                               MutableHandleValue* vp, bool callHook,
+                                               const mozilla::Maybe<HandleValue>& thisVForCheck, AbstractFramePtr frame);
 
     /*
      * Handle the result of a hook that is expected to return a resumption
      * value <https://wiki.mozilla.org/Debugger#Resumption_Values>. This is called
      * when we return from a debugging hook to debuggee code. The interpreter wants
      * a (JSTrapStatus, Value) pair telling it how to proceed.
      *
      * Precondition: ac is entered. We are in the debugger compartment.
@@ -504,19 +507,35 @@ class Debugger : private mozilla::Linked
      *         unwrap value. Store the result in *vp and return JSTRAP_RETURN
      *         or JSTRAP_THROW. The interpreter will force the current frame to
      *         return or throw an exception.
      *     null - Return JSTRAP_ERROR to terminate the debuggee with an
      *         uncatchable error.
      *     anything else - Make a new TypeError the pending exception and
      *         return handleUncaughtException(ac, vp, callHook).
      */
-    JSTrapStatus parseResumptionValue(mozilla::Maybe<AutoCompartment>& ac, bool ok, const Value& rv,
+    JSTrapStatus parseResumptionValue(mozilla::Maybe<AutoCompartment>& ac, bool OK, const Value& rv,
+                                      AbstractFramePtr frame, jsbytecode* pc, MutableHandleValue vp,
+                                      bool callHook = true);
+
+    /*
+     * When we run the onEnterFrame hook, the |this| slot hasn't been fully
+     * initialized, because the initialzation happens in the function's
+     * prologue. To combat this, we pass the this for the primitive return
+     * check directly. When bug 1249193 is fixed, this overload should be
+     * removed.
+     */
+    JSTrapStatus parseResumptionValue(mozilla::Maybe<AutoCompartment>& ac, bool OK, const Value& rv,
+                                      const Value& thisVForCheck, AbstractFramePtr frame,
                                       MutableHandleValue vp, bool callHook = true);
 
+    JSTrapStatus parseResumptionValueHelper(mozilla::Maybe<AutoCompartment>& ac, bool ok, const Value& rv,
+                                            const mozilla::Maybe<HandleValue>& thisVForCheck, AbstractFramePtr frame,
+                                            MutableHandleValue vp, bool callHook);
+
     GlobalObject* unwrapDebuggeeArgument(JSContext* cx, const Value& v);
 
     static void traceObject(JSTracer* trc, JSObject* obj);
     void trace(JSTracer* trc);
     static void finalize(FreeOp* fop, JSObject* obj);
     void markCrossCompartmentEdges(JSTracer* tracer);
 
     static const Class jsclass;
@@ -612,17 +631,17 @@ class Debugger : private mozilla::Linked
     bool updateObservesCoverageOnDebuggees(JSContext* cx, IsObserving observing);
     void updateObservesAsmJSOnDebuggees(IsObserving observing);
 
     JSObject* getHook(Hook hook) const;
     bool hasAnyLiveHooks() const;
 
     static bool slowPathCheckNoExecute(JSContext* cx, HandleScript script);
     static JSTrapStatus slowPathOnEnterFrame(JSContext* cx, AbstractFramePtr frame);
-    static bool slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, bool ok);
+    static bool slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool ok);
     static JSTrapStatus slowPathOnDebuggerStatement(JSContext* cx, AbstractFramePtr frame);
     static JSTrapStatus slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame);
     static void slowPathOnNewScript(JSContext* cx, HandleScript script);
     static void slowPathOnNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
     static bool slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
                                             double when, GlobalObject::DebuggerVector& dbgs);
     static void slowPathPromiseHook(JSContext* cx, Hook hook, HandleObject promise);
     static void slowPathOnIonCompilation(JSContext* cx, Handle<ScriptVector> scripts,
@@ -780,17 +799,17 @@ class Debugger : private mozilla::Linked
      * in behavior the hooks request, if any. Return the new error/success value.
      *
      * This function may be called twice for the same outgoing frame; only the
      * first call has any effect. (Permitting double calls simplifies some
      * cases where an onPop handler's resumption value changes a return to a
      * throw, or vice versa: we can redirect to a complete copy of the
      * alternative path, containing its own call to onLeaveFrame.)
      */
-    static inline bool onLeaveFrame(JSContext* cx, AbstractFramePtr frame, bool ok);
+    static inline bool onLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool ok);
 
     static inline void onNewScript(JSContext* cx, HandleScript script);
     static inline void onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
     static inline bool onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame,
                                            double when);
     static inline bool observesIonCompilation(JSContext* cx);
     static inline void onIonCompilation(JSContext* cx, Handle<ScriptVector> scripts,
                                         LSprinter& graph);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1016,17 +1016,17 @@ js::UnwindScopeToTryPc(JSScript* script,
         MOZ_ASSERT(*pc == JSOP_TRY);
     }
     return pc;
 }
 
 static bool
 ForcedReturn(JSContext* cx, ScopeIter& si, InterpreterRegs& regs, bool frameOk = true)
 {
-    bool ok = Debugger::onLeaveFrame(cx, regs.fp(), frameOk);
+    bool ok = Debugger::onLeaveFrame(cx, regs.fp(), regs.pc, frameOk);
     UnwindAllScopesInFrame(cx, si);
     // Point the frame to the end of the script, regardless of error. The
     // caller must jump to the correct continuation depending on 'ok'.
     regs.setToEndOfScript();
     return ok;
 }
 
 static bool
@@ -1202,17 +1202,17 @@ HandleError(JSContext* cx, InterpreterRe
             // No need to increment the PCCounts number of execution here, as
             // the interpreter increments any PCCounts if present.
             MOZ_ASSERT_IF(regs.fp()->script()->hasScriptCounts(),
                           regs.fp()->script()->maybeGetPCCounts(regs.pc));
             return res;
         }
 
         ok = HandleClosingGeneratorReturn(cx, regs.fp(), ok);
-        ok = Debugger::onLeaveFrame(cx, regs.fp(), ok);
+        ok = Debugger::onLeaveFrame(cx, regs.fp(), regs.pc, ok);
     } else {
         // We may be propagating a forced return from the interrupt
         // callback, which cannot easily force a return.
         if (MOZ_UNLIKELY(cx->isPropagatingForcedReturn())) {
             cx->clearPropagatingForcedReturn();
             if (!ForcedReturn(cx, si, regs))
                 return ErrorReturnContinuation;
             return SuccessfulReturnContinuation;
@@ -1902,17 +1902,17 @@ CASE(JSOP_RETRVAL)
 
   return_continuation:
     if (activation.entryFrame() != REGS.fp()) {
         // Stop the engine. (No details about which engine exactly, could be
         // interpreter, Baseline or IonMonkey.)
         TraceLogStopEvent(logger, TraceLogger_Engine);
         TraceLogStopEvent(logger, TraceLogger_Scripts);
 
-        interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), interpReturnOK);
+        interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), REGS.pc, interpReturnOK);
 
         REGS.fp()->epilogue(cx);
 
   jit_return_pop_frame:
 
         activation.popInlineFrame(REGS.fp());
         SET_SCRIPT(REGS.fp()->script());
 
@@ -3988,17 +3988,17 @@ DEFAULT()
         cx->clearPendingException();
       }
       ADVANCE_AND_DISPATCH(0);
     }
 
     MOZ_CRASH("Invalid HandleError continuation");
 
   exit:
-    interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), interpReturnOK);
+    interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), REGS.pc, interpReturnOK);
 
     REGS.fp()->epilogue(cx);
 
     gc::MaybeVerifyBarriers(cx, true);
 
     TraceLogStopEvent(logger, TraceLogger_Engine);
     TraceLogStopEvent(logger, scriptEvent);
 
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -845,16 +845,22 @@ AbstractFramePtr::popWith(JSContext* cx)
 {
     if (isInterpreterFrame()) {
         asInterpreterFrame()->popWith(cx);
         return;
     }
     asBaselineFrame()->popWith(cx);
 }
 
+inline bool
+AbstractFramePtr::debuggerNeedsCheckPrimitiveReturn() const
+{
+    return script()->isDerivedClassConstructor();
+}
+
 ActivationEntryMonitor::~ActivationEntryMonitor()
 {
     if (entryMonitor_)
         entryMonitor_->Exit(cx_);
 
     cx_->runtime()->entryMonitor = entryMonitor_;
 }
 
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -211,16 +211,18 @@ class AbstractFramePtr
 
     inline JSScript* script() const;
     inline JSFunction* callee() const;
     inline Value calleev() const;
     inline Value& thisArgument() const;
 
     inline Value newTarget() const;
 
+    inline bool debuggerNeedsCheckPrimitiveReturn() const;
+
     inline bool isFunctionFrame() const;
     inline bool isNonStrictDirectEvalFrame() const;
     inline bool isStrictEvalFrame() const;
 
     inline unsigned numActualArgs() const;
     inline unsigned numFormalArgs() const;
 
     inline Value* argv() const;