Bug 1387115 - Allow debugger to eval using WebAssembly frames. r?luke
MozReview-Commit-ID: EXUk5VqT5kp
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());