Bug 1016523 - Part 1: Have Debugger treat invoking the interrupt handler as a step in the interpreter. (r=jimb)
authorShu-yu Guo <shu@rfrn.org>
Thu, 05 Jun 2014 15:10:32 -0700
changeset 206197 6f0ea3b13b103303c527d499511b29b3f9b5dd72
parent 206196 845420c232561f630ea030bd9e21b1c9642319b8
child 206198 f54319e70aa95b966d407978b175532fe583c2c4
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [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 - Part 1: Have Debugger treat invoking the interrupt handler as a step in the interpreter. (r=jimb)
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/Interpreter.cpp
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -38,16 +38,17 @@
 #include "jstypes.h"
 #include "jswatchpoint.h"
 
 #include "gc/Marking.h"
 #ifdef JS_ION
 #include "jit/Ion.h"
 #endif
 #include "js/CharacterEncoding.h"
+#include "vm/Debugger.h"
 #include "js/OldDebugAPI.h"
 #include "vm/HelperThreads.h"
 #include "vm/Shape.h"
 
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/Stack-inl.h"
@@ -1027,19 +1028,46 @@ js::InvokeInterruptCallback(JSContext *c
     // compilation.
     jit::AttachFinishedCompilations(cx);
 #endif
 
     // Important: Additional callbacks can occur inside the callback handler
     // if it re-enters the JS engine. The embedding must ensure that the
     // callback is disconnected before attempting such re-entry.
     JSInterruptCallback cb = cx->runtime()->interruptCallback;
-    if (!cb || cb(cx))
+    if (!cb)
         return true;
 
+    if (cb(cx)) {
+        // Debugger treats invoking the interrupt callback as a "step", so
+        // invoke the onStep handler.
+        if (cx->compartment()->debugMode()) {
+            ScriptFrameIter iter(cx);
+            if (iter.script()->stepModeEnabled()) {
+                RootedValue rval(cx);
+                switch (Debugger::onSingleStep(cx, &rval)) {
+                  case JSTRAP_ERROR:
+                    return false;
+                  case JSTRAP_CONTINUE:
+                    return true;
+                  case JSTRAP_RETURN:
+                    // See note in Debugger::propagateForcedReturn.
+                    Debugger::propagateForcedReturn(cx, iter.abstractFramePtr(), rval);
+                    return false;
+                  case JSTRAP_THROW:
+                    cx->setPendingException(rval);
+                    return false;
+                  default:;
+                }
+            }
+        }
+
+        return true;
+    }
+
     // No need to set aside any pending exception here: ComputeStackString
     // already does that.
     Rooted<JSString*> stack(cx, ComputeStackString(cx));
     const jschar *chars = stack ? stack->getCharsZ(cx) : nullptr;
     if (!chars)
         chars = MOZ_UTF16("(stack not available)");
     JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_WARNING, js_GetErrorMessage, nullptr,
                                    JSMSG_TERMINATED, chars);
@@ -1090,16 +1118,17 @@ ThreadSafeContext::recoverFromOutOfMemor
     }
 }
 
 JSContext::JSContext(JSRuntime *rt)
   : ExclusiveContext(rt, &rt->mainThread, Context_JS),
     throwing(false),
     unwrappedException_(UndefinedValue()),
     options_(),
+    propagatingForcedReturn_(false),
     reportGranularity(JS_DEFAULT_JITREPORT_GRANULARITY),
     resolvingList(nullptr),
     generatingError(false),
     savedFrameChains_(),
     defaultCompartmentObject_(nullptr),
     cycleDetectorSet(MOZ_THIS_IN_INITIALIZER_LIST()),
     errorReporter(nullptr),
     data(nullptr),
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -417,16 +417,20 @@ struct JSContext : public js::ExclusiveC
   private:
     /* Exception state -- the exception member is a GC root by definition. */
     bool                throwing;            /* is there a pending exception? */
     js::Value           unwrappedException_; /* most-recently-thrown exception */
 
     /* Per-context options. */
     JS::ContextOptions  options_;
 
+    // True if propagating a forced return from an interrupt handler during
+    // debug mode.
+    bool                propagatingForcedReturn_;
+
   public:
     int32_t             reportGranularity;  /* see vm/Probes.h */
 
     js::AutoResolving   *resolvingList;
 
     /* True if generating an error, to prevent runaway recursion. */
     bool                generatingError;
 
@@ -565,16 +569,20 @@ struct JSContext : public js::ExclusiveC
 
     void setPendingException(js::Value v);
 
     void clearPendingException() {
         throwing = false;
         unwrappedException_.setUndefined();
     }
 
+    bool isPropagatingForcedReturn() const { return propagatingForcedReturn_; }
+    void setPropagatingForcedReturn() { propagatingForcedReturn_ = true; }
+    void clearPropagatingForcedReturn() { propagatingForcedReturn_ = false; }
+
 #ifdef DEBUG
     /*
      * Controls whether a quadratic-complexity assertion is performed during
      * stack iteration; defaults to true.
      */
     bool stackIterAssertionEnabled;
 #endif
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3671,16 +3671,33 @@ Debugger::handleIonBailout(JSContext *cx
     // reconstructed during bailout from the Ion frame corresponding to
     // |from|.
     ScriptFrameIter iter(cx);
     while (iter.abstractFramePtr() != to)
         ++iter;
     return replaceFrameGuts(cx, from, to, iter);
 }
 
+/* static */ void
+Debugger::propagateForcedReturn(JSContext *cx, AbstractFramePtr frame, HandleValue rval)
+{
+    // Invoking the interrupt handler is considered a step and invokes the
+    // youngest frame's onStep handler, if any. However, we cannot handle
+    // { return: ... } resumption values straightforwardly from the interrupt
+    // handler. Instead, we set the intended return value in the frame's rval
+    // slot and set the propagating-forced-return flag on the JSContext.
+    //
+    // The interrupt handler then returns false with no exception set,
+    // signaling an uncatchable exception. In the exception handlers, we then
+    // check for the special propagating-forced-return flag.
+    MOZ_ASSERT(!cx->isExceptionPending());
+    cx->setPropagatingForcedReturn();
+    frame.setReturnValue(rval);
+}
+
 static bool
 DebuggerScript_setBreakpoint(JSContext *cx, unsigned argc, Value *vp)
 {
     REQUIRE_ARGC("Debugger.Script.setBreakpoint", 2);
     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "setBreakpoint", args, obj, script);
     Debugger *dbg = Debugger::fromChildJSObject(obj);
 
     if (!dbg->observesScript(script)) {
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -437,16 +437,17 @@ class Debugger : private mozilla::Linked
     static inline JSTrapStatus onExceptionUnwind(JSContext *cx, MutableHandleValue vp);
     static inline void onNewScript(JSContext *cx, HandleScript script,
                                    GlobalObject *compileAndGoGlobal);
     static inline void onNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global);
     static JSTrapStatus onTrap(JSContext *cx, MutableHandleValue vp);
     static JSTrapStatus onSingleStep(JSContext *cx, MutableHandleValue vp);
     static bool handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to);
     static bool handleIonBailout(JSContext *cx, jit::RematerializedFrame *from, jit::BaselineFrame *to);
+    static void propagateForcedReturn(JSContext *cx, AbstractFramePtr frame, HandleValue rval);
 
     /************************************* Functions for use by Debugger.cpp. */
 
     inline bool observesEnterFrame() const;
     inline bool observesNewScript() const;
     inline bool observesNewGlobalObject() const;
     inline bool observesGlobal(GlobalObject *global) const;
     bool observesFrame(AbstractFramePtr frame) const;
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1049,16 +1049,24 @@ HandleError(JSContext *cx, InterpreterRe
 
             if (exception.isMagic(JS_GENERATOR_CLOSING)) {
                 cx->clearPendingException();
                 ok = true;
                 regs.fp()->clearReturnValue();
             }
         }
     } 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();
+            ForcedReturn(cx, si, regs);
+            return SuccessfulReturnContinuation;
+        }
+
         UnwindForUncatchableException(cx, regs);
     }
 
     ForcedReturn(cx, si, regs);
     return ok ? SuccessfulReturnContinuation : ErrorReturnContinuation;
 }
 
 #define REGS                     (activation.regs())