Bug 1271650 - Implement a C++ interface for DebuggerFrame.onStep.
☠☠ backed out by 056e578d03bb ☠ ☠
authorEddy Bruel <ejpbruel@mozilla.com
Wed, 14 Sep 2016 18:23:12 +0200
changeset 355248 4bb39c7c98dda7397546e975853af152dcd9f77b
parent 355247 e03332b2656bfafaa97bfbfa260d1ff3eb30b662
child 355249 9e4e13f55217a91681cbe2b0e5a9e82d3fe7ecae
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1271650
milestone51.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 1271650 - Implement a C++ interface for DebuggerFrame.onStep.
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -58,16 +58,17 @@ using mozilla::Some;
 using mozilla::Nothing;
 using mozilla::Variant;
 using mozilla::AsVariant;
 
 
 /*** Forward declarations, ClassOps and Classes **************************************************/
 
 static void DebuggerFrame_finalize(FreeOp* fop, JSObject* obj);
+static void DebuggerFrame_trace(JSTracer* trc, JSObject* obj);
 static void DebuggerEnv_trace(JSTracer* trc, JSObject* obj);
 static void DebuggerObject_trace(JSTracer* trc, JSObject* obj);
 static void DebuggerScript_trace(JSTracer* trc, JSObject* obj);
 static void DebuggerSource_trace(JSTracer* trc, JSObject* obj);
 
 enum {
     JSSLOT_DEBUGFRAME_OWNER,
     JSSLOT_DEBUGFRAME_ARGUMENTS,
@@ -83,17 +84,17 @@ const ClassOps DebuggerFrame::classOps_ 
     nullptr,    /* setProperty */
     nullptr,    /* enumerate   */
     nullptr,    /* resolve     */
     nullptr,    /* mayResolve  */
     DebuggerFrame_finalize,
     nullptr,    /* call        */
     nullptr,    /* hasInstance */
     nullptr,    /* construct   */
-    nullptr,    /* trace   */
+    DebuggerFrame_trace
 };
 
 const Class DebuggerFrame::class_ = {
     "Frame",
     JSCLASS_HAS_PRIVATE |
     JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT) |
     JSCLASS_BACKGROUND_FINALIZE,
     &DebuggerFrame::classOps_
@@ -2038,32 +2039,32 @@ Debugger::onSingleStep(JSContext* cx, Mu
         }
         MOZ_ASSERT(stepperCount == trappingScript->stepModeCount());
     }
 #endif
 
     // Call onStep for frames that have the handler set.
     for (size_t i = 0; i < frames.length(); i++) {
         HandleDebuggerFrame frame = frames[i];
-        if (frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
+        OnStepHandler* handler = frame->onStepHandler();
+        if (!handler)
             continue;
 
         Debugger* dbg = Debugger::fromChildJSObject(frame);
         EnterDebuggeeNoExecute nx(cx, *dbg);
 
         Maybe<AutoCompartment> ac;
         ac.emplace(cx, dbg->object);
 
-        RootedValue fval(cx, frame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER));
-        RootedValue rval(cx);
-        bool ok = js::Call(cx, fval, frame, &rval);
-        JSTrapStatus st = dbg->processHandlerResult(ac, ok, rval, iter.abstractFramePtr(),
-                                                    iter.pc(), vp);
-        if (st != JSTRAP_CONTINUE)
-            return st;
+        JSTrapStatus status = JSTRAP_CONTINUE;
+        bool success = handler->onStep(cx, frame, status, vp);
+        status = dbg->processParsedHandlerResult(ac, iter.abstractFramePtr(), iter.pc(), success,
+                                                 status, vp);
+        if (status != JSTRAP_CONTINUE)
+            return status;
     }
 
     vp.setUndefined();
     if (exceptionPending)
         cx->setPendingException(exception);
     return JSTRAP_CONTINUE;
 }
 
@@ -7189,16 +7190,51 @@ static const JSPropertySpec DebuggerSour
 
 static const JSFunctionSpec DebuggerSource_methods[] = {
     JS_FS_END
 };
 
 
 /*** Debugger.Frame ******************************************************************************/
 
+ScriptedOnStepHandler::ScriptedOnStepHandler(JSObject* object)
+  : object_(object)
+{
+    MOZ_ASSERT(object_->isCallable());
+}
+
+JSObject*
+ScriptedOnStepHandler::object() const
+{
+    return object_;
+}
+
+void
+ScriptedOnStepHandler::drop()
+{
+}
+
+void
+ScriptedOnStepHandler::trace(JSTracer* tracer)
+{
+    TraceEdge(tracer, &object_, "OnStepHandlerFunction.object");
+}
+
+bool
+ScriptedOnStepHandler::onStep(JSContext* cx, HandleDebuggerFrame frame, JSTrapStatus& statusp,
+                              MutableHandleValue vp)
+{
+    RootedValue fval(cx, ObjectValue(*object_));
+    RootedValue rval(cx);
+    if (!js::Call(cx, fval, frame, &rval))
+        return false;
+
+    return ParseResumptionValue(cx, rval, statusp, vp);
+};
+
 /* static */ NativeObject*
 DebuggerFrame::initClass(JSContext* cx, HandleObject dbgCtor, HandleObject obj)
 {
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
     RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
 
     return InitClass(cx, dbgCtor, objProto, &class_, construct, 0, properties_,
                      methods_, nullptr, nullptr);
@@ -7423,16 +7459,50 @@ DebuggerFrame::getImplementation(HandleD
 
     if (referent.isBaselineFrame())
         return DebuggerFrameImplementation::Baseline;
     else if (referent.isRematerializedFrame())
         return DebuggerFrameImplementation::Ion;
     return DebuggerFrameImplementation::Interpreter;
 }
 
+/* static */ bool
+DebuggerFrame::setOnStepHandler(JSContext* cx, HandleDebuggerFrame frame, OnStepHandler* handler)
+{
+    MOZ_ASSERT(frame->isLive());
+
+    AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+
+    OnStepHandler* prior = frame->onStepHandler();
+    if (prior && handler != prior) {
+        prior->drop();
+    }
+
+    if (handler && !prior) {
+        // Single stepping toggled off->on.
+        AutoCompartment ac(cx, referent.environmentChain());
+        // Ensure observability *before* incrementing the step mode
+        // count. Calling this function after calling incrementStepModeCount
+        // will make it a no-op.
+        Debugger* dbg = frame->owner();
+        if (!dbg->ensureExecutionObservabilityOfScript(cx, referent.script()))
+            return false;
+        if (!referent.script()->incrementStepModeCount(cx))
+            return false;
+    } else if (!handler && prior) {
+        // Single stepping toggled on->off.
+        referent.script()->decrementStepModeCount(cx->runtime()->defaultFreeOp());
+    }
+
+    /* Now that the step mode switch has succeeded, we can install the handler. */
+    frame->setReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER,
+                           handler ? PrivateValue(handler) : UndefinedValue());
+    return true;
+}
+
 /*
  * Evaluate |chars[0..length-1]| in the environment |env|, treating that
  * source as appearing starting at |lineno| in |filename|. Store the return
  * value in |*rval|. Use |thisv| as the 'this' value.
  *
  * If |frame| is non-nullptr, evaluate as for a direct eval in that frame; |env|
  * must be either |frame|'s DebugScopeObject, or some extension of that
  * environment; either way, |frame|'s scope is where newly declared variables
@@ -7597,16 +7667,23 @@ DebuggerFrame::eval(JSContext* cx, Handl
 }
 
 /* statuc */ bool
 DebuggerFrame::isLive() const
 {
     return !!getPrivate();
 }
 
+OnStepHandler*
+DebuggerFrame::onStepHandler() const
+{
+    Value value = getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
+    return value.isUndefined() ? nullptr : static_cast<OnStepHandler*>(value.toPrivate());
+}
+
 /* static */ bool
 DebuggerFrame::requireLive(JSContext* cx, HandleDebuggerFrame frame)
 {
     if (!frame->isLive()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
                              "Debugger.Frame");
         return false;
     }
@@ -7663,16 +7740,27 @@ DebuggerFrame_maybeDecrementFrameScriptS
         frame.script()->decrementStepModeCount(fop);
 }
 
 static void
 DebuggerFrame_finalize(FreeOp* fop, JSObject* obj)
 {
     MOZ_ASSERT(fop->maybeOffMainThread());
     DebuggerFrame_freeScriptFrameIterData(fop, obj);
+    OnStepHandler* handler = obj->as<DebuggerFrame>().onStepHandler();
+    if (handler)
+       handler->drop();
+}
+
+static void
+DebuggerFrame_trace(JSTracer* trc, JSObject* obj)
+{
+    OnStepHandler* handler = obj->as<DebuggerFrame>().onStepHandler();
+    if (handler) 
+        handler->trace(trc);
 }
 
 /* static */ DebuggerFrame*
 DebuggerFrame::checkThis(JSContext* cx, const CallArgs& args, const char* fnname, bool checkLive)
 {
     JSObject* thisobj = NonNullObject(cx, args.thisv());
     if (!thisobj)
         return nullptr;
@@ -8074,57 +8162,49 @@ DebuggerFrame::liveGetter(JSContext* cx,
 }
 
 static bool
 IsValidHook(const Value& v)
 {
     return v.isUndefined() || (v.isObject() && v.toObject().isCallable());
 }
 
-static bool
-DebuggerFrame_getOnStep(JSContext* cx, unsigned argc, Value* vp)
-{
-    THIS_FRAME(cx, argc, vp, "get onStep", args, thisobj, frame);
-    (void) frame;  // Silence GCC warning
-    RootedValue handler(cx, thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER));
-    MOZ_ASSERT(IsValidHook(handler));
-    args.rval().set(handler);
-    return true;
-}
-
-static bool
-DebuggerFrame_setOnStep(JSContext* cx, unsigned argc, Value* vp)
-{
-    THIS_FRAME(cx, argc, vp, "set onStep", args, thisobj, frame);
+/* static */ bool
+DebuggerFrame::onStepGetter(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGGER_FRAME(cx, argc, vp, "get onStep", args, frame);
+
+    OnStepHandler* handler = frame->onStepHandler();
+    RootedValue value(cx, handler ? ObjectValue(*handler->object()) : UndefinedValue());
+    MOZ_ASSERT(IsValidHook(value));
+    args.rval().set(value);
+    return true;
+}
+
+/* static */ bool
+DebuggerFrame::onStepSetter(JSContext* cx, unsigned argc, Value* vp)
+{
+    THIS_DEBUGGER_FRAME(cx, argc, vp, "set onStep", args, frame);
     if (!args.requireAtLeast(cx, "Debugger.Frame.set onStep", 1))
         return false;
     if (!IsValidHook(args[0])) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
         return false;
     }
 
-    Value prior = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
-    if (!args[0].isUndefined() && prior.isUndefined()) {
-        // Single stepping toggled off->on.
-        AutoCompartment ac(cx, frame.environmentChain());
-        // Ensure observability *before* incrementing the step mode
-        // count. Calling this function after calling incrementStepModeCount
-        // will make it a no-op.
-        Debugger* dbg = Debugger::fromChildJSObject(thisobj);
-        if (!dbg->ensureExecutionObservabilityOfScript(cx, frame.script()))
-            return false;
-        if (!frame.script()->incrementStepModeCount(cx))
-            return false;
-    } else if (args[0].isUndefined() && !prior.isUndefined()) {
-        // Single stepping toggled on->off.
-        frame.script()->decrementStepModeCount(cx->runtime()->defaultFreeOp());
-    }
-
-    /* Now that the step mode switch has succeeded, we can install the handler. */
-    thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER, args[0]);
+    ScriptedOnStepHandler* handler = nullptr;
+    if (!args[0].isUndefined()) {
+        handler = cx->new_<ScriptedOnStepHandler>(&args[0].toObject());
+        if (!handler)
+            return false;
+    }
+
+    if (!DebuggerFrame::setOnStepHandler(cx, frame, handler))
+        return false;
+
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 DebuggerFrame_getOnPop(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_FRAME(cx, argc, vp, "get onPop", args, thisobj, frame);
@@ -8223,17 +8303,17 @@ const JSPropertySpec DebuggerFrame::prop
     JS_PSG("generator", DebuggerFrame::generatorGetter, 0),
     JS_PSG("live", DebuggerFrame::liveGetter, 0),
     JS_PSG("offset", DebuggerFrame::offsetGetter, 0),
     JS_PSG("older", DebuggerFrame::olderGetter, 0),
     JS_PSG("script", DebuggerFrame_getScript, 0),
     JS_PSG("this", DebuggerFrame::thisGetter, 0),
     JS_PSG("type", DebuggerFrame::typeGetter, 0),
     JS_PSG("implementation", DebuggerFrame::implementationGetter, 0),
-    JS_PSGS("onStep", DebuggerFrame_getOnStep, DebuggerFrame_setOnStep, 0),
+    JS_PSGS("onStep", DebuggerFrame::onStepGetter, DebuggerFrame::onStepSetter, 0),
     JS_PSGS("onPop", DebuggerFrame_getOnPop, DebuggerFrame_setOnPop, 0),
     JS_PS_END
 };
 
 const JSFunctionSpec DebuggerFrame::methods_[] = {
     JS_FN("eval", DebuggerFrame::evalMethod, 1, 0),
     JS_FN("evalWithBindings", DebuggerFrame::evalWithBindingsMethod, 1, 0),
     JS_FS_END
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -31,20 +31,22 @@
 enum JSTrapStatus {
     JSTRAP_ERROR,
     JSTRAP_CONTINUE,
     JSTRAP_RETURN,
     JSTRAP_THROW,
     JSTRAP_LIMIT
 };
 
+
 namespace js {
 
 class Breakpoint;
 class DebuggerMemory;
+class ScriptedOnStepHandler;
 class WasmInstanceObject;
 
 typedef HashSet<ReadBarrieredGlobalObject,
                 MovableCellHasher<ReadBarrieredGlobalObject>,
                 RuntimeAllocPolicy> WeakGlobalObjectSet;
 
 /*
  * A weakmap from GC thing keys to JSObject values that supports the keys being
@@ -245,16 +247,17 @@ typedef mozilla::Variant<JSScript*, Wasm
 // Either a ScriptSourceObject, for ordinary JS, or a WasmInstanceObject,
 // denoting the synthesized source of a wasm module.
 typedef mozilla::Variant<ScriptSourceObject*, WasmInstanceObject*> DebuggerSourceReferent;
 
 class Debugger : private mozilla::LinkedListElement<Debugger>
 {
     friend class Breakpoint;
     friend class DebuggerMemory;
+    friend class ScriptedOnStepHandler;
     friend class SavedStacks;
     friend class mozilla::LinkedListElement<Debugger>;
     friend class mozilla::LinkedList<Debugger>;
     friend bool (::JS_DefineDebuggerObject)(JSContext* cx, JS::HandleObject obj);
     friend bool (::JS::dbg::IsDebugger)(JSObject&);
     friend bool (::JS::dbg::GetDebuggeeGlobals)(JSContext*, JSObject&, AutoObjectVector&);
     friend void JS::dbg::onNewPromise(JSContext* cx, HandleObject promise);
     friend void JS::dbg::onPromiseSettled(JSContext* cx, HandleObject promise);
@@ -1149,18 +1152,85 @@ enum class DebuggerFrameType {
 };
 
 enum class DebuggerFrameImplementation {
     Interpreter,
     Baseline,
     Ion
 };
 
+/*
+ * A Handler represents a reference to a handler function. These handler
+ * functions are called by the Debugger API to notify the user of certain
+ * events. For each event type, we define a separate subclass of Handler. This
+ * allows users to define a single reference to an object that implements
+ * multiple handlers, by inheriting from the appropriate subclasses.
+ * 
+ * A Handler can be stored on a reflection object, in which case the reflection
+ * object becomes responsible for managing the lifetime of the Handler. To aid
+ * with this, the Handler base class defines several methods, which are to be
+ * called by the reflection object at the appropriate time (see below).
+ */
+struct Handler {
+    virtual ~Handler() {}
+
+    /*
+     * If the Handler is a reference to a callable JSObject, this method returns
+     * the latter. This allows the Handler to be used from JS. Otherwise, this
+     * method returns nullptr.
+     */
+    virtual JSObject* object() const = 0;
+
+    /*
+     * Drops the reference to the handler. This method will be called by the
+     * reflection object on which the reference is stored when the former is
+     * finalized, or the latter replaced.
+     */
+    virtual void drop() = 0;
+
+    /*
+     * Traces the reference to the handler. This method will be called
+     * by the reflection object on which the reference is stored whenever the
+     * former is traced.
+     */
+    virtual void trace(JSTracer* tracer) = 0;
+};
+
+/*
+ * An OnStepHandler represents a handler function that is called when a small
+ * amount of progress is made in a frame.
+ */
+struct OnStepHandler : Handler {
+    /*
+     * If we have made a small amount of progress in a frame, this method is
+     * called with the frame as argument. If succesful, this method should
+     * return true, with `statusp` and `vp` set to a resumption value
+     * specifiying how execution should continue.
+     */
+    virtual bool onStep(JSContext* cx, HandleDebuggerFrame frame, JSTrapStatus& statusp,
+                        MutableHandleValue vp) = 0;
+};
+
+class ScriptedOnStepHandler final : public OnStepHandler {
+  public:
+    explicit ScriptedOnStepHandler(JSObject* object);
+    virtual JSObject* object() const override;
+    virtual void drop() override;
+    virtual void trace(JSTracer* tracer) override;
+    virtual bool onStep(JSContext* cx, HandleDebuggerFrame frame, JSTrapStatus& statusp,
+                        MutableHandleValue vp) override;
+
+  private:
+    HeapPtr<JSObject*> object_;
+};
+
 class DebuggerFrame : public NativeObject
 {
+    friend class ScriptedOnStepHandler;
+
   public:
     enum {
         OWNER_SLOT
     };
 
     static const unsigned RESERVED_SLOTS = 1;
 
     static const Class class_;
@@ -1180,23 +1250,26 @@ class DebuggerFrame : public NativeObjec
     static bool getIsGenerator(HandleDebuggerFrame frame);
     static MOZ_MUST_USE bool getOffset(JSContext* cx, HandleDebuggerFrame frame, size_t& result);
     static MOZ_MUST_USE bool getOlder(JSContext* cx, HandleDebuggerFrame frame,
                                       MutableHandleDebuggerFrame result);
     static MOZ_MUST_USE bool getThis(JSContext* cx, HandleDebuggerFrame frame,
                                      MutableHandleValue result);
     static DebuggerFrameType getType(HandleDebuggerFrame frame);
     static DebuggerFrameImplementation getImplementation(HandleDebuggerFrame frame);
+    static MOZ_MUST_USE bool setOnStepHandler(JSContext* cx, HandleDebuggerFrame frame,
+                                              OnStepHandler* handler);
 
     static MOZ_MUST_USE bool eval(JSContext* cx, HandleDebuggerFrame frame,
                                   mozilla::Range<const char16_t> chars, HandleObject bindings,
                                   const EvalOptions& options, JSTrapStatus& status,
                                   MutableHandleValue value);
 
     bool isLive() const;
+    OnStepHandler* onStepHandler() const;
 
   private:
     static const ClassOps classOps_;
 
     static const JSPropertySpec properties_[];
     static const JSFunctionSpec methods_[];
 
     static MOZ_MUST_USE bool requireLive(JSContext* cx, HandleDebuggerFrame frame);
@@ -1211,16 +1284,18 @@ class DebuggerFrame : public NativeObjec
     static MOZ_MUST_USE bool environmentGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool generatorGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool liveGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool offsetGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool olderGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool thisGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool typeGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool implementationGetter(JSContext* cx, unsigned argc, Value* vp);
+    static MOZ_MUST_USE bool onStepGetter(JSContext* cx, unsigned argc, Value* vp);
+    static MOZ_MUST_USE bool onStepSetter(JSContext* cx, unsigned argc, Value* vp);
 
     static MOZ_MUST_USE bool evalMethod(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool evalWithBindingsMethod(JSContext* cx, unsigned argc, Value* vp);
 
     Debugger* owner() const;
 };
 
 class DebuggerObject : public NativeObject