Bug 1286948 - onEnterFrame/onLeaveFrame wasm events and callstack. r=shu

Handles onEnterFrame and onLeaveFrame trap handling. The FrameIter is used in
the DebuggerFrame instead of ScriptFrameIter. The debug wasm frame is created
and can be found on callstack during those event.

MozReview-Commit-ID: 8oyFkrINc0A

--HG--
extra : rebase_source : 5057e1d3a952572129643082cfe8507beb549878
This commit is contained in:
Yury Delendik 2017-01-07 10:40:38 -06:00
parent 76fa09762a
commit bb1f768e9f
10 changed files with 569 additions and 87 deletions

View File

@ -111,6 +111,8 @@ its prototype:
* `"module"`: a frame running code at the top level of a module.
* `"wasmcall"`: a frame running a WebAssembly function call.
* `"debugger"`: a frame for a call to user code invoked by the debugger
(see the `eval` method below).
@ -124,8 +126,11 @@ its prototype:
* `"ion"`: a frame running in the optimizing JIT.
* `"wasm"`: a frame running in WebAssembly baseline JIT.
`this`
: The value of `this` for this frame (a debuggee value).
: The value of `this` for this frame (a debuggee value). For a `wasmcall`
frame, this property throws a `TypeError`.
`older`
: The next-older visible frame, in which control will resume when this
@ -149,6 +154,7 @@ its prototype:
`offset`
: The offset of the bytecode instruction currently being executed in
`script`, or `undefined` if the frame's `script` property is `null`.
For a `wasmcall` frame, this property throws a `TypeError`.
`environment`
: The lexical environment within which evaluation is taking place (a
@ -268,9 +274,9 @@ methods of other kinds of objects.
<code id="eval">eval(<i>code</i>, [<i>options</i>])</code>
: Evaluate <i>code</i> in the execution context of this frame, and return
a [completion value][cv] describing how it completed. <i>Code</i> is a
string. If this frame's `environment` property is `null`, throw a
`TypeError`. All extant handler methods, breakpoints, and
so on remain active during the call. This function follows the
string. If this frame's `environment` property is `null` or `type` property
is `wasmcall`, throw a `TypeError`. All extant handler methods, breakpoints,
and so on remain active during the call. This function follows the
[invocation function conventions][inv fr].
<i>Code</i> is interpreted as strict mode code when it contains a Use
@ -326,3 +332,5 @@ methods of other kinds of objects.
The <i>options</i> argument is as for
[`Debugger.Frame.prototype.eval`][fr eval], described above.
Also like `eval`, if this frame's `environment` property is `null` or
`type` property is `wasmcall`, throw a `TypeError`.

View File

@ -0,0 +1,20 @@
// |jit-test| test-also-wasm-baseline; exitstatus: 3
// Checking resumption values for 'null' at onEnterFrame.
load(libdir + "asserts.js");
if (!wasmIsSupported())
quit(3);
var g = newGlobal('');
var dbg = new Debugger();
dbg.addDebuggee(g);
sandbox.eval(`
var wasm = wasmTextToBinary('(module (func (nop)) (export "test" 0))');
var m = new WebAssembly.Instance(new WebAssembly.Module(wasm));`);
dbg.onEnterFrame = function (frame) {
if (frame.type !== "wasmcall") return;
return null;
};
g.eval("m.exports.test()");
assertEq(false, true);

View File

@ -0,0 +1,22 @@
// |jit-test| test-also-wasm-baseline; exitstatus: 3
// Checking resumption values for 'null' at frame's onPop.
load(libdir + "asserts.js");
if (!wasmIsSupported())
quit(3);
var g = newGlobal('');
var dbg = new Debugger();
dbg.addDebuggee(g);
sandbox.eval(`
var wasm = wasmTextToBinary('(module (func (nop)) (export "test" 0))');
var m = new WebAssembly.Instance(new WebAssembly.Module(wasm));`);
dbg.onEnterFrame = function (frame) {
if (frame.type !== "wasmcall") return;
frame.onPop = function () {
return null;
};
};
g.eval("m.exports.test()");
assertEq(false, true);

View File

@ -0,0 +1,335 @@
// |jit-test| test-also-wasm-baseline; error: TestComplete
// Tests that wasm module scripts raises onEnterFrame and onLeaveFrame events.
load(libdir + "asserts.js");
if (!wasmIsSupported())
throw "TestComplete";
function runWasmWithDebugger(wast, lib, init, done) {
let g = newGlobal('');
let dbg = new Debugger(g);
g.eval(`
var wasm = wasmTextToBinary('${wast}');
var lib = ${lib || 'undefined'};
var m = new WebAssembly.Instance(new WebAssembly.Module(wasm), lib);`);
init(dbg, g);
let result = undefined, error = undefined;
try {
result = g.eval("m.exports.test()");
} catch (ex) {
error = ex;
}
done(dbg, result, error, g);
}
// Checking if onEnterFrame is fired for wasm frames and verifying the content
// of the frame and environment properties.
var onEnterFrameCalled, onLeaveFrameCalled, onExceptionUnwindCalled, testComplete;
runWasmWithDebugger(
'(module (func (result i32) (i32.const 42)) (export "test" 0))', undefined,
function (dbg) {
var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0];
assertEq(!!wasmScript, true);
onEnterFrameCalled = 0;
onLeaveFrameCalled = 0;
testComplete = false;
var evalFrame;
dbg.onEnterFrame = function (frame) {
if (frame.type !== 'wasmcall') {
if (frame.type === 'eval')
evalFrame = frame;
return;
}
onEnterFrameCalled++;
assertEq(frame.script, wasmScript);
assertEq(frame.older, evalFrame);
assertEq(frame.type, 'wasmcall');
let env = frame.environment;
assertEq(env instanceof Object, true);
assertEq(env.inspectable, true);
assertEq(env.parent !== null, true);
assertEq(env.type, 'declarative');
assertEq(env.callee, null);
assertEq(Array.isArray(env.names()), true);
assertEq(env.names().length, 0);
frame.onPop = function() {
onLeaveFrameCalled++;
testComplete = true;
};
};
},
function (dbg, result, error) {
assertEq(testComplete, true);
assertEq(onEnterFrameCalled, 1);
assertEq(onLeaveFrameCalled, 1);
assertEq(result, 42);
assertEq(error, undefined);
}
);
// Checking the dbg.getNewestFrame() and frame.older.
runWasmWithDebugger(
'(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
'{env: { ex: () => { }}}',
function (dbg) {
onEnterFrameCalled = 0;
onLeaveFrameCalled = 0;
testComplete = false;
var evalFrame, wasmFrame;
dbg.onEnterFrame = function (frame) {
onEnterFrameCalled++;
assertEq(dbg.getNewestFrame(), frame);
switch (frame.type) {
case 'eval':
evalFrame = frame;
break;
case 'wasmcall':
wasmFrame = frame;
break;
case 'call':
assertEq(frame.older, wasmFrame);
assertEq(frame.older.older, evalFrame);
assertEq(frame.older.older.older, null);
testComplete = true;
break;
}
frame.onPop = function() {
onLeaveFrameCalled++;
};
};
},
function (dbg, result, error) {
assertEq(testComplete, true);
assertEq(onEnterFrameCalled, 3);
assertEq(onLeaveFrameCalled, 3);
assertEq(error, undefined);
}
);
// Checking if we can enumerate frames and find 'wasmcall' one.
runWasmWithDebugger(
'(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
'{env: { ex: () => { debugger; }}}',
function (dbg) {
testComplete = false;
dbg.onDebuggerStatement = function (frame) {
assertEq(frame.type, 'call');
assertEq(frame.older.type, 'wasmcall');
assertEq(frame.older.older.type, 'eval');
assertEq(frame.older.older.older, null);
testComplete = true;
}
},
function (dbg, result, error) {
assertEq(testComplete, true);
assertEq(error, undefined);
}
);
// Checking if onPop works without onEnterFrame handler.
runWasmWithDebugger(
'(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
'{env: { ex: () => { debugger; }}}',
function (dbg) {
onLeaveFrameCalled = 0;
dbg.onDebuggerStatement = function (frame) {
if (!frame.older || frame.older.type != 'wasmcall')
return;
frame.older.onPop = function () {
onLeaveFrameCalled++;
};
}
},
function (dbg, result, error) {
assertEq(onLeaveFrameCalled, 1);
assertEq(error, undefined);
}
);
// Checking if function return values are not changed.
runWasmWithDebugger(
'(module (func (result f64) (f64.const 0.42)) (export "test" 0))', undefined,
function (dbg) {
dbg.onEnterFrame = function (frame) {
dbg.onPop = function () {};
};
},
function (dbg, result, error) {
assertEq(result, 0.42);
assertEq(error, undefined);
}
);
runWasmWithDebugger(
'(module (func (result f32) (f32.const 4.25)) (export "test" 0))', undefined,
function (dbg) {
dbg.onEnterFrame = function (frame) {
dbg.onPop = function () {};
};
},
function (dbg, result, error) {
assertEq(result, 4.25);
assertEq(error, undefined);
}
);
// Checking if onEnterFrame/onExceptionUnwind work during exceptions --
// `unreachable` causes wasm to throw WebAssembly.RuntimeError exception.
runWasmWithDebugger(
'(module (func (unreachable)) (export "test" 0))', undefined,
function (dbg) {
onEnterFrameCalled = 0;
onLeaveFrameCalled = 0;
onExceptionUnwindCalled = 0;
dbg.onEnterFrame = function (frame) {
if (frame.type !== "wasmcall") return;
onEnterFrameCalled++;
frame.onPop = function() {
onLeaveFrameCalled++;
};
};
dbg.onExceptionUnwind = function (frame) {
if (frame.type !== "wasmcall") return;
onExceptionUnwindCalled++;
};
},
function (dbg, result, error, g) {
assertEq(onEnterFrameCalled, 1);
assertEq(onLeaveFrameCalled, 1);
assertEq(onExceptionUnwindCalled, 1);
assertEq(error instanceof g.WebAssembly.RuntimeError, true);
}
);
// Checking if onEnterFrame/onExceptionUnwind work during exceptions
// originated in the JavaScript import call.
runWasmWithDebugger(
'(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
'{env: { ex: () => { throw new Error(); }}}',
function (dbg) {
onEnterFrameCalled = 0;
onLeaveFrameCalled = 0;
onExceptionUnwindCalled = 0;
dbg.onEnterFrame = function (frame) {
if (frame.type !== "wasmcall") return;
onEnterFrameCalled++;
frame.onPop = function() {
onLeaveFrameCalled++;
};
};
dbg.onExceptionUnwind = function (frame) {
if (frame.type !== "wasmcall") return;
onExceptionUnwindCalled++;
};
},
function (dbg, result, error, g) {
assertEq(onEnterFrameCalled, 1);
assertEq(onLeaveFrameCalled, 1);
assertEq(onExceptionUnwindCalled, 1);
assertEq(error instanceof g.Error, true);
}
);
// Checking throwing in the handler.
runWasmWithDebugger(
'(module (func (unreachable)) (export "test" 0))', undefined,
function (dbg) {
dbg.uncaughtExceptionHook = function (value) {
assertEq(value instanceof Error, true);
return {throw: 'test'};
};
dbg.onEnterFrame = function (frame) {
if (frame.type !== "wasmcall") return;
throw new Error();
};
},
function (dbg, result, error) {
assertEq(error, 'test');
}
);
runWasmWithDebugger(
'(module (func (unreachable)) (export "test" 0))', undefined,
function (dbg) {
dbg.uncaughtExceptionHook = function (value) {
assertEq(value instanceof Error, true);
return {throw: 'test'};
};
dbg.onEnterFrame = function (frame) {
if (frame.type !== "wasmcall") return;
frame.onPop = function () {
throw new Error();
}
};
},
function (dbg, result, error) {
assertEq(error, 'test');
}
);
// Checking resumption values for JS_THROW.
runWasmWithDebugger(
'(module (func (nop)) (export "test" 0))', undefined,
function (dbg, g) {
dbg.onEnterFrame = function (frame) {
if (frame.type !== "wasmcall") return;
return {throw: 'test'};
};
},
function (dbg, result, error, g) {
assertEq(error, 'test');
}
);
runWasmWithDebugger(
'(module (func (nop)) (export "test" 0))', undefined,
function (dbg, g) {
dbg.onEnterFrame = function (frame) {
if (frame.type !== "wasmcall") return;
frame.onPop = function () {
return {throw: 'test'};
}
};
},
function (dbg, result, error, g) {
assertEq(error, 'test');
}
);
// Checking resumption values for JS_RETURN (not implemented by wasm baseline).
runWasmWithDebugger(
'(module (func (unreachable)) (export "test" 0))', undefined,
function (dbg) {
dbg.onEnterFrame = function (frame) {
if (frame.type !== "wasmcall") return;
return {return: 2};
};
},
function (dbg, result, error) {
assertEq(result, undefined, 'NYI: result == 2, if JS_RETURN is implemented');
assertEq(error != undefined, true, 'NYI: error == undefined, if JS_RETURN is implemented');
}
);
runWasmWithDebugger(
'(module (func (unreachable)) (export "test" 0))', undefined,
function (dbg) {
dbg.onEnterFrame = function (frame) {
if (frame.type !== "wasmcall") return;
frame.onPop = function () {
return {return: 2};
}
};
},
function (dbg, result, error) {
assertEq(result, undefined, 'NYI: result == 2, if JS_RETURN is implemented');
assertEq(error != undefined, true, 'NYI: error == undefined, if JS_RETURN is implemented');
}
);
throw "TestComplete";

View File

@ -344,6 +344,7 @@
macro(variable, variable, "variable") \
macro(void0, void0, "(void 0)") \
macro(wasm, wasm, "wasm") \
macro(wasmcall, wasmcall, "wasmcall") \
macro(watch, watch, "watch") \
macro(WeakMapConstructorInit, WeakMapConstructorInit, "WeakMapConstructorInit") \
macro(WeakSetConstructorInit, WeakSetConstructorInit, "WeakSetConstructorInit") \

View File

@ -15,7 +15,7 @@
js::Debugger::onLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool ok)
{
MOZ_ASSERT_IF(frame.isInterpreterFrame(), frame.asInterpreterFrame() == cx->interpreterFrame());
MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(), frame.isDebuggee());
/* Traps must be cleared from eval frames, see slowPathOnLeaveFrame. */
mozilla::DebugOnly<bool> evalTraps = frame.isEvalFrame() &&
frame.script()->hasAnyBreakpointsOrStepMode();
@ -44,7 +44,7 @@ js::Debugger::checkNoExecute(JSContext* cx, HandleScript script)
/* static */ JSTrapStatus
js::Debugger::onEnterFrame(JSContext* cx, AbstractFramePtr frame)
{
MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(), frame.isDebuggee());
if (!frame.isDebuggee())
return JSTRAP_CONTINUE;
return slowPathOnEnterFrame(cx, frame);
@ -74,7 +74,7 @@ js::Debugger::onNewWasmInstance(JSContext* cx, Handle<WasmInstanceObject*> wasmI
}
inline bool
js::Debugger::getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
js::Debugger::getScriptFrame(JSContext* cx, const FrameIter& iter,
MutableHandle<DebuggerFrame*> result)
{
return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, result);

View File

@ -739,7 +739,7 @@ Debugger::memory() const
bool
Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr referent,
const ScriptFrameIter* maybeIter, MutableHandleValue vp)
const FrameIter* maybeIter, MutableHandleValue vp)
{
RootedDebuggerFrame result(cx);
if (!Debugger::getScriptFrameWithIter(cx, referent, maybeIter, &result))
@ -751,13 +751,13 @@ Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr referent,
bool
Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr referent,
const ScriptFrameIter* maybeIter,
const FrameIter* maybeIter,
MutableHandleDebuggerFrame result)
{
MOZ_ASSERT_IF(maybeIter, maybeIter->abstractFramePtr() == referent);
MOZ_ASSERT(!referent.script()->selfHosted());
MOZ_ASSERT_IF(referent.hasScript(), !referent.script()->selfHosted());
if (!referent.script()->ensureHasAnalyzedArgsUsage(cx))
if (referent.hasScript() && !referent.script()->ensureHasAnalyzedArgsUsage(cx))
return false;
FrameMap::AddPtr p = frames.lookupForAdd(referent);
@ -1015,7 +1015,7 @@ Debugger::slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame)
return JSTRAP_CONTINUE;
// The Debugger API mustn't muck with frames from self-hosted scripts.
if (frame.script()->selfHosted())
if (frame.hasScript() && frame.script()->selfHosted())
return JSTRAP_CONTINUE;
RootedValue rval(cx);
@ -1771,7 +1771,7 @@ Debugger::fireExceptionUnwind(JSContext* cx, MutableHandleValue vp)
RootedValue scriptFrame(cx);
RootedValue wrappedExc(cx, exc);
ScriptFrameIter iter(cx);
FrameIter iter(cx);
if (!getScriptFrame(cx, iter, &scriptFrame) || !wrapDebuggeeValue(cx, &wrappedExc))
return reportUncaughtException(ac);
@ -1796,7 +1796,7 @@ Debugger::fireEnterFrame(JSContext* cx, MutableHandleValue vp)
RootedValue scriptFrame(cx);
ScriptFrameIter iter(cx);
FrameIter iter(cx);
if (!getScriptFrame(cx, iter, &scriptFrame))
return reportUncaughtException(ac);
@ -2348,9 +2348,10 @@ class MOZ_RAII ExecutionObservableCompartments : public Debugger::ExecutionObser
bool shouldRecompileOrInvalidate(JSScript* script) const {
return script->hasBaselineScript() && compartments_.has(script->compartment());
}
bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
// AbstractFramePtr can't refer to non-remateralized Ion frames, so if
// iter refers to one such, we know we don't match.
bool shouldMarkAsDebuggee(FrameIter& iter) const {
// AbstractFramePtr can't refer to non-remateralized Ion frames or
// non-debuggee wasm frames, so if iter refers to one such, we know we
// don't match.
return iter.hasUsableAbstractFramePtr() && compartments_.has(iter.compartment());
}
@ -2402,16 +2403,17 @@ class MOZ_RAII ExecutionObservableFrame : public Debugger::ExecutionObservableSe
if (!script->hasBaselineScript())
return false;
if (script == frame_.script())
if (frame_.hasScript() && script == frame_.script())
return true;
return frame_.isRematerializedFrame() &&
script == frame_.asRematerializedFrame()->outerScript();
}
bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
// AbstractFramePtr can't refer to non-remateralized Ion frames, so if
// iter refers to one such, we know we don't match.
bool shouldMarkAsDebuggee(FrameIter& iter) const {
// AbstractFramePtr can't refer to non-remateralized Ion frames or
// non-debuggee wasm frames, so if iter refers to one such, we know we
// don't match.
//
// We never use this 'has' overload for frame invalidation, only for
// frame debuggee marking; so this overload doesn't need a parallel to
@ -2439,7 +2441,7 @@ class MOZ_RAII ExecutionObservableScript : public Debugger::ExecutionObservableS
bool shouldRecompileOrInvalidate(JSScript* script) const {
return script->hasBaselineScript() && script == script_;
}
bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
bool shouldMarkAsDebuggee(FrameIter& iter) const {
// AbstractFramePtr can't refer to non-remateralized Ion frames, and
// while a non-rematerialized Ion frame may indeed be running script_,
// we cannot mark them as debuggees until they bail out.
@ -2449,6 +2451,9 @@ class MOZ_RAII ExecutionObservableScript : public Debugger::ExecutionObservableS
// debuggee. This is correct in that the only other way a frame may be
// marked as debuggee is via Debugger.Frame reflection, which would
// have rematerialized any Ion frames.
//
// Also AbstractFramePtr can't refer to non-debuggee wasm frames, so if
// iter refers to one such, we know we don't match.
return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr().script() == script_;
}
@ -2470,7 +2475,7 @@ Debugger::updateExecutionObservabilityOfFrames(JSContext* cx, const ExecutionObs
}
AbstractFramePtr oldestEnabledFrame;
for (ScriptFrameIter iter(cx);
for (FrameIter iter(cx);
!iter.done();
++iter)
{
@ -2480,6 +2485,8 @@ Debugger::updateExecutionObservabilityOfFrames(JSContext* cx, const ExecutionObs
oldestEnabledFrame = iter.abstractFramePtr();
oldestEnabledFrame.setIsDebuggee();
}
if (iter.abstractFramePtr().isWasmDebugFrame())
iter.abstractFramePtr().asWasmDebugFrame()->observeFrame(cx);
} else {
#ifdef DEBUG
// Debugger.Frame lifetimes are managed by the debug epilogue,
@ -2588,6 +2595,17 @@ UpdateExecutionObservabilityOfScriptsInZone(JSContext* cx, Zone* zone,
FinishDiscardBaselineScript(fop, scripts[i]);
}
// Iterate through all wasm instances to find ones that need to be updated.
for (JSCompartment* c : zone->compartments) {
for (wasm::Instance* instance : c->wasm.instances()) {
if (!instance->debugEnabled())
continue;
bool enableTrap = observing == Debugger::IsObserving::Observing;
instance->ensureEnterFrameTrapsState(cx, enableTrap);
}
}
return true;
}
@ -2611,7 +2629,7 @@ template <typename FrameFn>
/* static */ void
Debugger::forEachDebuggerFrame(AbstractFramePtr frame, FrameFn fn)
{
GlobalObject* global = &frame.script()->global();
GlobalObject* global = frame.global();
if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
Debugger* dbg = *p;
@ -2670,7 +2688,8 @@ Debugger::ensureExecutionObservabilityOfOsrFrame(JSContext* cx, InterpreterFrame
/* static */ bool
Debugger::ensureExecutionObservabilityOfFrame(JSContext* cx, AbstractFramePtr frame)
{
MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(), frame.isDebuggee());
MOZ_ASSERT_IF(frame.isWasmDebugFrame(), frame.wasmInstance()->debugEnabled());
if (frame.isDebuggee())
return true;
ExecutionObservableFrame obs(frame);
@ -2776,7 +2795,7 @@ Debugger::updateObservesCoverageOnDebuggees(JSContext* cx, IsObserving observing
// If any frame on the stack belongs to the debuggee, then we cannot update
// the ScriptCounts, because this would imply to invalidate a Debugger.Frame
// to recompile it with/without ScriptCount support.
for (ScriptFrameIter iter(cx);
for (FrameIter iter(cx);
!iter.done();
++iter)
{
@ -3745,15 +3764,15 @@ Debugger::getNewestFrame(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg);
/* Since there may be multiple contexts, use AllScriptFramesIter. */
for (AllScriptFramesIter i(cx); !i.done(); ++i) {
/* Since there may be multiple contexts, use AllFramesIter. */
for (AllFramesIter i(cx); !i.done(); ++i) {
if (dbg->observesFrame(i)) {
// Ensure that Ion frames are rematerialized. Only rematerialized
// Ion frames may be used as AbstractFramePtrs.
if (i.isIon() && !i.ensureHasRematerializedFrame(cx))
return false;
AbstractFramePtr frame = i.abstractFramePtr();
ScriptFrameIter iter(i.activation()->cx());
FrameIter iter(i.activation()->cx());
while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != frame)
++iter;
return dbg->getScriptFrame(cx, iter, args.rval());
@ -4016,7 +4035,7 @@ Debugger::removeDebuggeeGlobal(FreeOp* fop, GlobalObject* global,
for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) {
AbstractFramePtr frame = e.front().key();
NativeObject* frameobj = e.front().value();
if (&frame.script()->global() == global) {
if (frame.global() == global) {
DebuggerFrame_freeScriptFrameIterData(fop, frameobj);
DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj);
e.removeFront();
@ -6296,9 +6315,9 @@ Debugger::observesScript(JSScript* script) const
bool
Debugger::observesWasm(wasm::Instance* instance) const
{
if (!enabled || !instance->code().metadata().debugEnabled)
if (!enabled || !instance->debugEnabled())
return false;
return false; // TODO check global
return observesGlobal(&instance->object()->global());
}
/* static */ bool
@ -7330,7 +7349,7 @@ DebuggerFrame::initClass(JSContext* cx, HandleObject dbgCtor, HandleObject obj)
/* static */ DebuggerFrame*
DebuggerFrame::create(JSContext* cx, HandleObject proto, AbstractFramePtr referent,
const ScriptFrameIter* maybeIter, HandleNativeObject debugger)
const FrameIter* maybeIter, HandleNativeObject debugger)
{
JSObject* obj = NewObjectWithGivenProto(cx, &DebuggerFrame::class_, proto);
if (!obj)
@ -7338,7 +7357,7 @@ DebuggerFrame::create(JSContext* cx, HandleObject proto, AbstractFramePtr refere
DebuggerFrame& frame = obj->as<DebuggerFrame>();
// Eagerly copy ScriptFrameIter data if we've already walked the stack.
// Eagerly copy FrameIter data if we've already walked the stack.
if (maybeIter) {
AbstractFramePtr data = maybeIter->copyDataAsAbstractFramePtr();
if (!data)
@ -7376,10 +7395,10 @@ DebuggerFrame::getIsConstructing(JSContext* cx, HandleDebuggerFrame frame, bool&
{
MOZ_ASSERT(frame->isLive());
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
FrameIter& iter = *maybeIter;
result = iter.isFunctionFrame() && iter.isConstructing();
return true;
@ -7435,10 +7454,10 @@ DebuggerFrame::getEnvironment(JSContext* cx, HandleDebuggerFrame frame,
Debugger* dbg = frame->owner();
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
FrameIter& iter = *maybeIter;
Rooted<Env*> env(cx);
{
@ -7455,18 +7474,21 @@ DebuggerFrame::getEnvironment(JSContext* cx, HandleDebuggerFrame frame,
/* static */ bool
DebuggerFrame::getIsGenerator(HandleDebuggerFrame frame)
{
return DebuggerFrame::getReferent(frame).script()->isGenerator();
AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
return referent.hasScript() && referent.script()->isGenerator();
}
/* static */ bool
DebuggerFrame::getOffset(JSContext* cx, HandleDebuggerFrame frame, size_t& result)
{
MOZ_ASSERT(frame->isLive());
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
if (!requireScriptReferent(cx, frame))
return false;
ScriptFrameIter& iter = *maybeIter;
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
FrameIter& iter = *maybeIter;
JSScript* script = iter.script();
UpdateFrameIterPc(iter);
@ -7483,10 +7505,10 @@ DebuggerFrame::getOlder(JSContext* cx, HandleDebuggerFrame frame,
Debugger* dbg = frame->owner();
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
FrameIter& iter = *maybeIter;
for (++iter; !iter.done(); ++iter) {
if (dbg->observesFrame(iter)) {
@ -7504,13 +7526,15 @@ DebuggerFrame::getOlder(JSContext* cx, HandleDebuggerFrame frame,
DebuggerFrame::getThis(JSContext* cx, HandleDebuggerFrame frame, MutableHandleValue result)
{
MOZ_ASSERT(frame->isLive());
if (!requireScriptReferent(cx, frame))
return false;
Debugger* dbg = frame->owner();
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
FrameIter& iter = *maybeIter;
{
AbstractFramePtr frame = iter.abstractFramePtr();
@ -7542,6 +7566,8 @@ DebuggerFrame::getType(HandleDebuggerFrame frame)
return DebuggerFrameType::Call;
else if (referent.isModuleFrame())
return DebuggerFrameType::Module;
else if (referent.isWasmDebugFrame())
return DebuggerFrameType::WasmCall;
MOZ_CRASH("Unknown frame type");
}
@ -7554,6 +7580,8 @@ DebuggerFrame::getImplementation(HandleDebuggerFrame frame)
return DebuggerFrameImplementation::Baseline;
else if (referent.isRematerializedFrame())
return DebuggerFrameImplementation::Ion;
else if (referent.isWasmDebugFrame())
return DebuggerFrameImplementation::Wasm;
return DebuggerFrameImplementation::Interpreter;
}
@ -7568,6 +7596,10 @@ DebuggerFrame::setOnStepHandler(JSContext* cx, HandleDebuggerFrame frame, OnStep
MOZ_ASSERT(frame->isLive());
AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
if (referent.isWasmDebugFrame()) {
MOZ_CRASH();
return true;
}
OnStepHandler* prior = frame->onStepHandler();
if (prior && handler != prior) {
@ -7688,7 +7720,7 @@ static bool
DebuggerGenericEval(JSContext* cx, const mozilla::Range<const char16_t> chars,
HandleObject bindings, const EvalOptions& options,
JSTrapStatus& status, MutableHandleValue value,
Debugger* dbg, HandleObject envArg, ScriptFrameIter* iter)
Debugger* dbg, HandleObject envArg, FrameIter* iter)
{
/* Either we're specifying the frame, or a global. */
MOZ_ASSERT_IF(iter, !envArg);
@ -7779,13 +7811,15 @@ DebuggerFrame::eval(JSContext* cx, HandleDebuggerFrame frame, mozilla::Range<con
MutableHandleValue value)
{
MOZ_ASSERT(frame->isLive());
if (!requireScriptReferent(cx, frame))
return false;
Debugger* dbg = frame->owner();
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
FrameIter& iter = *maybeIter;
UpdateFrameIterPc(iter);
@ -7842,22 +7876,22 @@ DebuggerFrame::getReferent(HandleDebuggerFrame frame)
{
AbstractFramePtr referent = AbstractFramePtr::FromRaw(frame->getPrivate());
if (referent.isScriptFrameIterData()) {
ScriptFrameIter iter(*(ScriptFrameIter::Data*)(referent.raw()));
FrameIter iter(*(FrameIter::Data*)(referent.raw()));
referent = iter.abstractFramePtr();
}
return referent;
}
/* static */ bool
DebuggerFrame::getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
Maybe<ScriptFrameIter>& result)
DebuggerFrame::getFrameIter(JSContext* cx, HandleDebuggerFrame frame,
Maybe<FrameIter>& result)
{
AbstractFramePtr referent = AbstractFramePtr::FromRaw(frame->getPrivate());
if (referent.isScriptFrameIterData()) {
result.emplace(*reinterpret_cast<ScriptFrameIter::Data*>(referent.raw()));
result.emplace(*reinterpret_cast<FrameIter::Data*>(referent.raw()));
} else {
result.emplace(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
ScriptFrameIter& iter = *result;
result.emplace(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
FrameIter& iter = *result;
while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != referent)
++iter;
AbstractFramePtr data = iter.copyDataAsAbstractFramePtr();
@ -7868,12 +7902,26 @@ DebuggerFrame::getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
return true;
}
/* static */ bool
DebuggerFrame::requireScriptReferent(JSContext* cx, HandleDebuggerFrame frame)
{
AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
if (!referent.hasScript()) {
RootedValue frameobj(cx, ObjectValue(*frame));
ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_BAD_REFERENT,
JSDVG_SEARCH_STACK, frameobj, nullptr,
"a script frame", nullptr);
return false;
}
return true;
}
static void
DebuggerFrame_freeScriptFrameIterData(FreeOp* fop, JSObject* obj)
{
AbstractFramePtr frame = AbstractFramePtr::FromRaw(obj->as<NativeObject>().getPrivate());
if (frame.isScriptFrameIterData())
fop->delete_((ScriptFrameIter::Data*) frame.raw());
fop->delete_((FrameIter::Data*) frame.raw());
obj->as<NativeObject>().setPrivate(nullptr);
}
@ -7948,19 +7996,19 @@ DebuggerFrame_checkThis(JSContext* cx, const CallArgs& args, const char* fnname,
/*
* To make frequently fired hooks like onEnterFrame more performant,
* Debugger.Frame methods should not create a ScriptFrameIter unless it
* Debugger.Frame methods should not create a FrameIter unless it
* absolutely needs to. That is, unless the method has to call a method on
* ScriptFrameIter that's otherwise not available on AbstractFramePtr.
* FrameIter that's otherwise not available on AbstractFramePtr.
*
* When a Debugger.Frame is first created, its private slot is set to the
* AbstractFramePtr itself. The first time the users asks for a
* ScriptFrameIter, we construct one, have it settle on the frame pointed to
* FrameIter, we construct one, have it settle on the frame pointed to
* by the AbstractFramePtr and cache its internal Data in the Debugger.Frame
* object's private slot. Subsequent uses of the Debugger.Frame object will
* always create a ScriptFrameIter from the cached Data.
* always create a FrameIter from the cached Data.
*
* Methods that only need the AbstractFramePtr should use THIS_FRAME.
* Methods that need a ScriptFrameIterator should use THIS_FRAME_ITER.
* Methods that need a FrameIterator should use THIS_FRAME_ITER.
*/
#define THIS_DEBUGGER_FRAME(cx, argc, vp, fnname, args, frame) \
@ -7979,20 +8027,20 @@ DebuggerFrame_checkThis(JSContext* cx, const CallArgs& args, const char* fnname,
THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \
AbstractFramePtr frame = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
if (frame.isScriptFrameIterData()) { \
ScriptFrameIter iter(*(ScriptFrameIter::Data*)(frame.raw())); \
FrameIter iter(*(FrameIter::Data*)(frame.raw())); \
frame = iter.abstractFramePtr(); \
}
#define THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter) \
THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \
Maybe<ScriptFrameIter> maybeIter; \
Maybe<FrameIter> maybeIter; \
{ \
AbstractFramePtr f = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
if (f.isScriptFrameIterData()) { \
maybeIter.emplace(*(ScriptFrameIter::Data*)(f.raw())); \
maybeIter.emplace(*(FrameIter::Data*)(f.raw())); \
} else { \
maybeIter.emplace(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK); \
ScriptFrameIter& iter = *maybeIter; \
maybeIter.emplace(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK); \
FrameIter& iter = *maybeIter; \
while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != f) \
++iter; \
AbstractFramePtr data = iter.copyDataAsAbstractFramePtr(); \
@ -8001,7 +8049,7 @@ DebuggerFrame_checkThis(JSContext* cx, const CallArgs& args, const char* fnname,
thisobj->setPrivate(data.raw()); \
} \
} \
ScriptFrameIter& iter = *maybeIter
FrameIter& iter = *maybeIter
#define THIS_FRAME_OWNER(cx, argc, vp, fnname, args, thisobj, frame, dbg) \
THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame); \
@ -8032,6 +8080,9 @@ DebuggerFrame::typeGetter(JSContext* cx, unsigned argc, Value* vp)
case DebuggerFrameType::Module:
str = cx->names().module;
break;
case DebuggerFrameType::WasmCall:
str = cx->names().wasmcall;
break;
default:
MOZ_CRASH("bad DebuggerFrameType value");
}
@ -8058,6 +8109,9 @@ DebuggerFrame::implementationGetter(JSContext* cx, unsigned argc, Value* vp)
case DebuggerFrameImplementation::Interpreter:
s = "interpreter";
break;
case DebuggerFrameImplementation::Wasm:
s = "wasm";
break;
default:
MOZ_CRASH("bad DebuggerFrameImplementation value");
}
@ -8273,6 +8327,11 @@ DebuggerFrame_getScript(JSContext* cx, unsigned argc, Value* vp)
if (!scriptObject)
return false;
}
} else if (frame.isWasmDebugFrame()) {
RootedWasmInstanceObject instance(cx, frame.wasmInstance()->object());
scriptObject = debug->wrapWasmScript(cx, instance);
if (!scriptObject)
return false;
} else {
/*
* We got eval, JS_Evaluate*, or JS_ExecuteScript non-function script

View File

@ -303,7 +303,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
virtual const HashSet<Zone*>* zones() const { return nullptr; }
virtual bool shouldRecompileOrInvalidate(JSScript* script) const = 0;
virtual bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const = 0;
virtual bool shouldMarkAsDebuggee(FrameIter& iter) const = 0;
};
// This enum is converted to and compare with bool values; NotObserving
@ -775,10 +775,10 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
* its data if we need to make a new Debugger.Frame.
*/
MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
const ScriptFrameIter* maybeIter,
const FrameIter* maybeIter,
MutableHandleValue vp);
MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
const ScriptFrameIter* maybeIter,
const FrameIter* maybeIter,
MutableHandleDebuggerFrame result);
inline Breakpoint* firstBreakpoint() const;
@ -1002,11 +1002,11 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
* frame, in which case the cost of walking the stack has already been
* paid.
*/
MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const FrameIter& iter,
MutableHandleValue vp) {
return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, vp);
}
MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const FrameIter& iter,
MutableHandleDebuggerFrame result);
@ -1151,13 +1151,15 @@ enum class DebuggerFrameType {
Eval,
Global,
Call,
Module
Module,
WasmCall
};
enum class DebuggerFrameImplementation {
Interpreter,
Baseline,
Ion
Ion,
Wasm
};
/*
@ -1285,7 +1287,7 @@ class DebuggerFrame : public NativeObject
static NativeObject* initClass(JSContext* cx, HandleObject dbgCtor, HandleObject objProto);
static DebuggerFrame* create(JSContext* cx, HandleObject proto, AbstractFramePtr referent,
const ScriptFrameIter* maybeIter, HandleNativeObject debugger);
const FrameIter* maybeIter, HandleNativeObject debugger);
static MOZ_MUST_USE bool getArguments(JSContext* cx, HandleDebuggerFrame frame,
MutableHandleDebuggerArguments result);
@ -1323,8 +1325,9 @@ class DebuggerFrame : public NativeObject
static const JSFunctionSpec methods_[];
static AbstractFramePtr getReferent(HandleDebuggerFrame frame);
static MOZ_MUST_USE bool getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
mozilla::Maybe<ScriptFrameIter>& result);
static MOZ_MUST_USE bool getFrameIter(JSContext* cx, HandleDebuggerFrame frame,
mozilla::Maybe<FrameIter>& result);
static MOZ_MUST_USE bool requireScriptReferent(JSContext* cx, HandleDebuggerFrame frame);
static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);

View File

@ -422,6 +422,8 @@ AbstractFramePtr::returnValue() const
{
if (isInterpreterFrame())
return asInterpreterFrame()->returnValue();
if (isWasmDebugFrame())
return UndefinedHandleValue;
return asBaselineFrame()->returnValue();
}
@ -436,6 +438,12 @@ AbstractFramePtr::setReturnValue(const Value& rval) const
asBaselineFrame()->setReturnValue(rval);
return;
}
if (isWasmDebugFrame()) {
// TODO handle wasm function return value
// The function is called from Debugger::slowPathOnLeaveFrame --
// ignoring value for wasm.
return;
}
asRematerializedFrame()->setReturnValue(rval);
}
@ -889,6 +897,8 @@ AbstractFramePtr::newTarget() const
inline bool
AbstractFramePtr::debuggerNeedsCheckPrimitiveReturn() const
{
if (isWasmDebugFrame())
return false;
return script()->isDerivedClassConstructor();
}

View File

@ -32,6 +32,7 @@
#include "wasm/WasmSerialize.h"
#include "wasm/WasmSignalHandlers.h"
#include "vm/Debugger-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
@ -115,13 +116,21 @@ WasmHandleDebugTrap()
frame->setIsDebuggee();
frame->observeFrame(cx);
// TODO call onEnterFrame
return true;
JSTrapStatus status = Debugger::onEnterFrame(cx, frame);
if (status == JSTRAP_RETURN) {
// Ignoring forced return (JSTRAP_RETURN) -- changing code execution
// order is not yet implemented in the wasm baseline.
// TODO properly handle JSTRAP_RETURN and resume wasm execution.
JS_ReportErrorASCII(cx, "Unexpected resumption value from onEnterFrame");
return false;
}
return status == JSTRAP_CONTINUE;
}
if (site->kind() == CallSite::LeaveFrame) {
DebugFrame* frame = iter.debugFrame();
// TODO call onLeaveFrame
bool ok = Debugger::onLeaveFrame(cx, frame, nullptr, true);
frame->leaveFrame(cx);
return true;
return ok;
}
// TODO baseline debug traps
MOZ_CRASH();
@ -139,7 +148,22 @@ WasmHandleDebugThrow()
continue;
DebugFrame* frame = iter.debugFrame();
// TODO call onExceptionUnwind and onLeaveFrame
JSTrapStatus status = Debugger::onExceptionUnwind(cx, frame);
if (status == JSTRAP_RETURN) {
// Unexpected trap return -- raising error since throw recovery
// is not yet implemented in the wasm baseline.
// TODO properly handle JSTRAP_RETURN and resume wasm execution.
JS_ReportErrorASCII(cx, "Unexpected resumption value from onExceptionUnwind");
}
bool ok = Debugger::onLeaveFrame(cx, frame, nullptr, false);
if (ok) {
// Unexpected success from the handler onLeaveFrame -- raising error
// since throw recovery is not yet implemented in the wasm baseline.
// TODO properly handle success and resume wasm execution.
JS_ReportErrorASCII(cx, "Unexpected success from onLeaveFrame");
}
frame->leaveFrame(cx);
}
}