Bug 1387115 - Allow debugger to eval using WebAssembly frames. r?luke draft
authorYury Delendik <ydelendik@mozilla.com>
Thu, 03 Aug 2017 11:00:40 -0500
changeset 621473 a635d61dd33bbd5a17ece3c7ea845bfa8f77bd79
parent 621472 05a2fd6f71a0f396de5f948f0b3989e01b38cf5d
child 724682 6cacde4cf22797d03405778875f20f68eae5b1bc
push id72392
push userydelendik@mozilla.com
push dateFri, 04 Aug 2017 22:06:55 +0000
reviewersluke
bugs1387115
milestone57.0a1
Bug 1387115 - Allow debugger to eval using WebAssembly frames. r?luke MozReview-Commit-ID: EXUk5VqT5kp
js/src/jit-test/tests/debug/wasm-jseval.js
js/src/vm/Debugger.cpp
js/src/vm/Stack.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-jseval.js
@@ -0,0 +1,45 @@
+// |jit-test| test-also-wasm-baseline
+// Tests that JS can be evaluated on wasm module scripts frames.
+
+load(libdir + "wasm.js");
+
+wasmRunWithDebugger(
+    '(module (memory 1 1)\
+     (global (mut f64) (f64.const 0.5))\
+     (global f32 (f32.const 3.5))\
+     (func (param i32) (local f64) (f64.const 1.0) (tee_local 1) (set_global 0) (nop))\
+     (export "test" 0)\
+     (data (i32.const 0) "Abc\\x2A"))',
+    undefined,
+    function ({dbg}) {
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type != 'wasmcall') return;
+
+            var memoryContent = frame.eval('new DataView(memory0.buffer).getUint8(3)').return;
+            assertEq(memoryContent, 42, 'valid memory content is expected (0x2A)');
+
+            var global1AndParamSum = frame.eval('global1 + var0').return;
+            assertEq(global1AndParamSum, 3.5);
+
+            var stepNumber = 0;
+            frame.onStep = function () {
+                switch (stepNumber) {
+                  case 1: // after i64.const 1.0
+                    assertEq(frame.eval('global0').return, 0.5);
+                    assertEq(frame.eval('var1').return, 0.0);
+                    break;
+                  case 2: // after tee_local $var1
+                    assertEq(frame.eval('var1').return, 1.0);
+                    break;
+                  case 3: // after set_global $global0
+                    assertEq(frame.eval('global0').return, 1.0);
+                    break;
+                }
+                stepNumber++;
+            };
+        };
+  },
+  function ({error}) {
+      assertEq(error, undefined);
+  }
+);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -8165,30 +8165,29 @@ DebuggerFrame::getArguments(JSContext *c
  *
  * 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
  * go. In this case, |frame| must have a computed 'this' value, equal to |thisv|.
  */
 static bool
 EvaluateInEnv(JSContext* cx, Handle<Env*> env, AbstractFramePtr frame,
-              jsbytecode* pc, mozilla::Range<const char16_t> chars, const char* filename,
+              mozilla::Range<const char16_t> chars, const char* filename,
               unsigned lineno, MutableHandleValue rval)
 {
     assertSameCompartment(cx, env, frame);
-    MOZ_ASSERT_IF(frame, pc);
 
     CompileOptions options(cx);
     options.setIsRunOnce(true)
            .setNoScriptRval(false)
            .setFileAndLine(filename, lineno)
            .setCanLazilyParse(false)
            .setIntroductionType("debugger eval")
-           .maybeMakeStrictMode(frame ? frame.script()->strict() : false);
-    RootedScript callerScript(cx, frame ? frame.script() : nullptr);
+           .maybeMakeStrictMode(frame && frame.hasScript() ? frame.script()->strict() : false);
+    RootedScript callerScript(cx, frame && frame.hasScript() ? frame.script() : nullptr);
     SourceBufferHolder srcBuf(chars.begin().get(), chars.length(), SourceBufferHolder::NoOwnership);
     RootedScript script(cx);
 
     ScopeKind scopeKind;
     if (IsGlobalLexicalEnvironment(env))
         scopeKind = ScopeKind::Global;
     else
         scopeKind = ScopeKind::NonSyntactic;
@@ -8293,34 +8292,31 @@ DebuggerGenericEval(JSContext* cx, const
 
         env = newEnv;
     }
 
     /* Run the code and produce the completion value. */
     LeaveDebuggeeNoExecute nnx(cx);
     RootedValue rval(cx);
     AbstractFramePtr frame = iter ? iter->abstractFramePtr() : NullFramePtr();
-    jsbytecode* pc = iter ? iter->pc() : nullptr;
-
-    bool ok = EvaluateInEnv(cx, env, frame, pc, chars,
+
+    bool ok = EvaluateInEnv(cx, env, frame, chars,
                             options.filename() ? options.filename() : "debugger eval code",
                             options.lineno(), &rval);
     Debugger::resultToCompletion(cx, ok, rval, &status, value);
     ac.reset();
     return dbg->wrapDebuggeeValue(cx, value);
 }
 
 /* static */ bool
 DebuggerFrame::eval(JSContext* cx, HandleDebuggerFrame frame, mozilla::Range<const char16_t> chars,
                     HandleObject bindings, const EvalOptions& options, JSTrapStatus& status,
                     MutableHandleValue value)
 {
     MOZ_ASSERT(frame->isLive());
-    if (!requireScriptReferent(cx, frame))
-        return false;
 
     Debugger* dbg = frame->owner();
 
     Maybe<FrameIter> maybeIter;
     if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
         return false;
     FrameIter& iter = *maybeIter;
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -40,24 +40,25 @@ InterpreterFrame::initExecuteFrame(JSCon
     flags_ = 0;
     script_ = script;
 
     // newTarget = NullValue is an initial sentinel for "please fill me in from the stack".
     // It should never be passed from Ion code.
     RootedValue newTarget(cx, newTargetValue);
     if (script->isDirectEvalInFunction()) {
         FrameIter iter(cx);
-        MOZ_ASSERT(!iter.isWasm());
         if (newTarget.isNull() &&
+            iter.hasScript() &&
             iter.script()->bodyScope()->hasOnChain(ScopeKind::Function))
         {
             newTarget = iter.newTarget();
         }
     } else if (evalInFramePrev) {
         if (newTarget.isNull() &&
+            evalInFramePrev.hasScript() &&
             evalInFramePrev.script()->bodyScope()->hasOnChain(ScopeKind::Function))
         {
             newTarget = evalInFramePrev.newTarget();
         }
     }
 
     Value* dstvp = (Value*)this - 1;
     dstvp[0] = newTarget;
@@ -1187,26 +1188,27 @@ FrameIter::unaliasedActual(unsigned i, M
     return abstractFramePtr().unaliasedActual(i, checkAliasing);
 }
 
 JSObject*
 FrameIter::environmentChain(JSContext* cx) const
 {
     switch (data_.state_) {
       case DONE:
-      case WASM:
         break;
       case JIT:
         if (data_.jitFrames_.isIonScripted()) {
             jit::MaybeReadFallback recover(cx, activation()->asJit(), &data_.jitFrames_);
             return ionInlineFrames_.environmentChain(recover);
         }
         return data_.jitFrames_.baselineFrame()->environmentChain();
       case INTERP:
         return interpFrame()->environmentChain();
+      case WASM:
+        return data_.wasmFrames_.debugFrame()->environmentChain();
     }
     MOZ_CRASH("Unexpected state");
 }
 
 CallObject&
 FrameIter::callObj(JSContext* cx) const
 {
     MOZ_ASSERT(calleeTemplate()->needsCallObject());