Bug 1016523 - Test. (r=jimb)
authorShu-yu Guo <shu@rfrn.org>
Thu, 05 Jun 2014 15:10:33 -0700
changeset 207283 4c794b0e6e9cba4324152562377df20d2d0d5931
parent 207282 f54319e70aa95b966d407978b175532fe583c2c4
child 207284 bb9909b6258ae69817f17134495bf249483850ba
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs1016523
milestone32.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 1016523 - Test. (r=jimb)
js/src/jit-test/tests/debug/Frame-onStep-resumption-05.js
js/src/jsapi.cpp
js/src/jsapi.h
js/src/shell/js.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-05.js
@@ -0,0 +1,55 @@
+// Test that invoking the interrupt callback counts as a step.
+
+function testResumptionVal(resumptionVal, turnOffDebugMode) {
+  var g = newGlobal();
+  var dbg = new Debugger;
+  g.log = "";
+  g.resumptionVal = resumptionVal;
+
+  setInterruptCallback(function () {
+    g.log += "i";
+    dbg.addDebuggee(g);
+    var frame = dbg.getNewestFrame();
+    frame.onStep = function () {
+      g.log += "s";
+      frame.onStep = undefined;
+
+      if (turnOffDebugMode)
+        dbg.removeDebuggee(g);
+
+      return resumptionVal;
+    };
+    return true;
+  });
+
+  try {
+    return g.eval("(" + function f() {
+      log += "f";
+      invokeInterruptCallback(function (interruptRv) {
+        log += "r";
+        assertEq(interruptRv, resumptionVal == undefined);
+      });
+      log += "a";
+      return 42;
+    } + ")();");
+  } finally {
+    assertEq(g.log, resumptionVal == undefined ? "fisra" : "fisr");
+  }
+}
+
+assertEq(testResumptionVal(undefined), 42);
+assertEq(testResumptionVal({ return: "not 42" }), "not 42");
+try {
+  testResumptionVal({ throw: "thrown 42" });
+} catch (e) {
+  assertEq(e, "thrown 42");
+}
+
+assertEq(testResumptionVal(undefined, true), 42);
+assertEq(testResumptionVal({ return: "not 42" }, true), "not 42");
+
+try {
+  testResumptionVal({ throw: "thrown 42" }, true);
+} catch (e) {
+  assertEq(e, "thrown 42");
+}
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6085,36 +6085,44 @@ JS_ReportPendingException(JSContext *cx)
 
     // This can only fail due to oom.
     bool ok = js_ReportUncaughtException(cx);
     JS_ASSERT(!cx->isExceptionPending());
     return ok;
 }
 
 JS::AutoSaveExceptionState::AutoSaveExceptionState(JSContext *cx)
-    : context(cx), wasThrowing(cx->throwing), exceptionValue(cx)
-{
-    AssertHeapIsIdle(cx);
-    CHECK_REQUEST(cx);
+  : context(cx),
+    wasPropagatingForcedReturn(cx->propagatingForcedReturn_),
+    wasThrowing(cx->throwing),
+    exceptionValue(cx)
+{
+    AssertHeapIsIdle(cx);
+    CHECK_REQUEST(cx);
+    if (wasPropagatingForcedReturn)
+        cx->clearPropagatingForcedReturn();
     if (wasThrowing) {
         exceptionValue = cx->unwrappedException_;
         cx->clearPendingException();
     }
 }
 
 void
 JS::AutoSaveExceptionState::restore()
 {
+    context->propagatingForcedReturn_ = wasPropagatingForcedReturn;
     context->throwing = wasThrowing;
     context->unwrappedException_ = exceptionValue;
     drop();
 }
 
 JS::AutoSaveExceptionState::~AutoSaveExceptionState()
 {
+    if (wasPropagatingForcedReturn && !context->isPropagatingForcedReturn())
+        context->setPropagatingForcedReturn();
     if (wasThrowing && !context->isExceptionPending()) {
         context->throwing = true;
         context->unwrappedException_ = exceptionValue;
     }
 }
 
 struct JSExceptionState {
     bool throwing;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4655,16 +4655,17 @@ namespace JS {
  *     AutoSaveExceptionState savedExc(cx);
  *     ... cleanup that might re-enter JS ...
  *     return ok;
  */
 class JS_PUBLIC_API(AutoSaveExceptionState)
 {
   private:
     JSContext *context;
+    bool wasPropagatingForcedReturn;
     bool wasThrowing;
     RootedValue exceptionValue;
 
   public:
     /*
      * Take a snapshot of cx's current exception state. Then clear any current
      * pending exception in cx.
      */
@@ -4676,16 +4677,17 @@ class JS_PUBLIC_API(AutoSaveExceptionSta
      */
     ~AutoSaveExceptionState();
 
     /*
      * Discard any stored exception state.
      * If this is called, the destructor is a no-op.
      */
     void drop() {
+        wasPropagatingForcedReturn = false;
         wasThrowing = false;
         exceptionValue.setUndefined();
     }
 
     /*
      * Replace cx's exception state with the stored exception state. Then
      * discard the stored exception state. If this is called, the
      * destructor is a no-op.
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3421,16 +3421,46 @@ InterruptIf(JSContext *cx, unsigned argc
         JS_RequestInterruptCallback(cx->runtime());
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
+InvokeInterruptCallbackWrapper(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (args.length() != 1) {
+        JS_ReportError(cx, "Wrong number of arguments");
+        return false;
+    }
+
+    gServiceInterrupt = true;
+    JS_RequestInterruptCallback(cx->runtime());
+    bool interruptRv = CheckForInterrupt(cx);
+
+    // The interrupt handler could have set a pending exception. Since we call
+    // back into JS, don't have it see the pending exception. If we have an
+    // uncatchable exception that's not propagating a debug mode forced
+    // return, return.
+    if (!interruptRv && !cx->isExceptionPending() && !cx->isPropagatingForcedReturn())
+        return false;
+
+    JS::AutoSaveExceptionState savedExc(cx);
+    Value argv[1] = { BooleanValue(interruptRv) };
+    RootedValue rv(cx);
+    if (!Invoke(cx, UndefinedValue(), args[0], 1, argv, &rv))
+        return false;
+
+    args.rval().setUndefined();
+    return interruptRv;
+}
+
+static bool
 SetInterruptCallback(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() != 1) {
         JS_ReportError(cx, "Wrong number of arguments");
         return false;
     }
@@ -4723,16 +4753,23 @@ static const JSFunctionSpecWithHelp shel
 "  If a second argument is provided, it will be invoked when the timer elapses.\n"
 "  Calling this function will replace any callback set by |setInterruptCallback|.\n"),
 
     JS_FN_HELP("interruptIf", InterruptIf, 1, 0,
 "interruptIf(cond)",
 "  Requests interrupt callback if cond is true. If a callback function is set via\n"
 "  |timeout| or |setInterruptCallback|, it will be called. No-op otherwise."),
 
+    JS_FN_HELP("invokeInterruptCallback", InvokeInterruptCallbackWrapper, 0, 0,
+"invokeInterruptCallback(fun)",
+"  Forcefully set the interrupt flag and invoke the interrupt handler. If a\n"
+"  callback function is set via |timeout| or |setInterruptCallback|, it will\n"
+"  be called. Before returning, fun is called with the return value of the\n"
+"  interrupt handler."),
+
     JS_FN_HELP("setInterruptCallback", SetInterruptCallback, 1, 0,
 "setInterruptCallback(func)",
 "  Sets func as the interrupt callback function.\n"
 "  Calling this function will replace any callback set by |timeout|.\n"),
 
     JS_FN_HELP("elapsed", Elapsed, 0, 0,
 "elapsed()",
 "  Execution time elapsed for the current context."),