Bug 1251921 - Do not call debugger hooks with half-initialized frame if InterpeterFrame::prologue fails. (r=jorendorff)
authorEric Faust <efaustbmo@gmail.com>
Mon, 14 Mar 2016 14:29:12 -0700
changeset 288637 9d99253b3e008d7fad83a89f51002ee07f5220a2
parent 288636 14092b3cacbeaab373eb88792f90800e21d2f1f3
child 288638 e79cc51e486ceae0084e34ee3918e54e2942cf7e
push id30087
push usercbook@mozilla.com
push dateTue, 15 Mar 2016 09:43:43 +0000
treeherdermozilla-central@5e14887312d4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1251921
milestone48.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 1251921 - Do not call debugger hooks with half-initialized frame if InterpeterFrame::prologue fails. (r=jorendorff)
js/src/jit-test/tests/debug/prologueFailure-01.js
js/src/jit-test/tests/debug/prologueFailure-02.js
js/src/vm/Interpreter.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/prologueFailure-01.js
@@ -0,0 +1,32 @@
+g = newGlobal();
+g.parent = this;
+
+function installHook() {
+    let calledTimes = 0;
+    function hook() {
+        calledTimes++;
+
+        // Allow the new.target.prototype get to throw.
+        if (calledTimes === 1)
+            return undefined;
+
+        return {
+            return: undefined
+        };
+    }
+
+    Debugger(parent).onExceptionUnwind = hook;
+}
+
+
+g.eval("(" + installHook + ")()");
+
+var handler = {
+    get(t, p) {
+        throw new TypeError;
+    }
+};
+
+
+var f = new Proxy(function(){}, handler);
+new f();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/prologueFailure-02.js
@@ -0,0 +1,49 @@
+g = newGlobal();
+g.parent = this;
+
+function installHook() {
+    let calledTimes = 0;
+    function hook(frame) {
+        calledTimes++;
+        switch (calledTimes) {
+          case 1:
+            // Proxy get trap
+            assertEq(frame.type, "call");
+            assertEq(frame.script.displayName.includes("get"), true);
+            break;
+          case 2:
+            // wrapper function. There is no entry for notRun
+            assertEq(frame.type, "call");
+            assertEq(frame.script.displayName.includes("wrapper"), true);
+            break;
+          case 3:
+            assertEq(frame.type, "global");
+            // Force the top-level to return cleanly, so that we can tell
+            // assertion failures from the intended throwing.
+            return { return: undefined };
+
+          default:
+            // that's the whole chain.
+            assertEq(false, true);
+        }
+    }
+
+    Debugger(parent).onExceptionUnwind = hook;
+}
+
+
+g.eval("(" + installHook + ")()");
+
+var handler = {
+    get(t, p) {
+        throw new TypeError;
+    }
+};
+
+function notRun() {}
+
+function wrapper() {
+    var f = new Proxy(notRun, handler);
+    new f();
+}
+wrapper();
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1660,19 +1660,20 @@ Interpret(JSContext* cx, RunState& state
     RootedPropertyName rootName0(cx);
     RootedId rootId0(cx);
     RootedShape rootShape0(cx);
     RootedScript rootScript0(cx);
     DebugOnly<uint32_t> blockDepth;
 
     /* State communicated between non-local jumps: */
     bool interpReturnOK;
+    bool frameHalfInitialized;
 
     if (!activation.entryFrame()->prologue(cx))
-        goto error;
+        goto prologue_error;
 
     switch (Debugger::onEnterFrame(cx, activation.entryFrame())) {
       case JSTRAP_CONTINUE:
         break;
       case JSTRAP_RETURN:
         if (!ForcedReturn(cx, REGS))
             goto error;
         goto successful_return_continuation;
@@ -1896,25 +1897,31 @@ CASE(JSOP_RETRVAL)
      * false after the inline_return label.
      */
     CHECK_BRANCH();
 
   successful_return_continuation:
     interpReturnOK = true;
 
   return_continuation:
+    frameHalfInitialized = false;
+
+  prologue_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(), REGS.pc, interpReturnOK);
-
-        REGS.fp()->epilogue(cx);
+        if (MOZ_LIKELY(!frameHalfInitialized)) {
+            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());
 
   jit_return:
 
@@ -2867,17 +2874,17 @@ CASE(JSOP_FUNCALL)
 
     {
         TraceLoggerEvent event(logger, TraceLogger_Scripts, script);
         TraceLogStartEvent(logger, event);
         TraceLogStartEvent(logger, TraceLogger_Interpreter);
     }
 
     if (!REGS.fp()->prologue(cx))
-        goto error;
+        goto prologue_error;
 
     switch (Debugger::onEnterFrame(cx, REGS.fp())) {
       case JSTRAP_CONTINUE:
         break;
       case JSTRAP_RETURN:
         if (!ForcedReturn(cx, REGS))
             goto error;
         goto successful_return_continuation;
@@ -3990,35 +3997,42 @@ DEFAULT()
         cx->clearPendingException();
       }
       ADVANCE_AND_DISPATCH(0);
     }
 
     MOZ_CRASH("Invalid HandleError continuation");
 
   exit:
-    interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), REGS.pc, interpReturnOK);
-
-    REGS.fp()->epilogue(cx);
+    if (MOZ_LIKELY(!frameHalfInitialized)) {
+        interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), REGS.pc, interpReturnOK);
+
+        REGS.fp()->epilogue(cx);
+    }
 
     gc::MaybeVerifyBarriers(cx, true);
 
     TraceLogStopEvent(logger, TraceLogger_Engine);
     TraceLogStopEvent(logger, scriptEvent);
 
     /*
      * This path is used when it's guaranteed the method can be finished
      * inside the JIT.
      */
   leave_on_safe_point:
 
     if (interpReturnOK)
         state.setReturnValue(activation.entryFrame()->returnValue());
 
     return interpReturnOK;
+
+  prologue_error:
+    interpReturnOK = false;
+    frameHalfInitialized = true;
+    goto prologue_return_continuation;
 }
 
 bool
 js::Throw(JSContext* cx, HandleValue v)
 {
     MOZ_ASSERT(!cx->isExceptionPending());
     cx->setPendingException(v);
     return false;