Bug 1570178 - Part 3: Support setting onStep when the frame is not live. r=jimb

We want to be able to set onStep when generators are suspended, and since that
already requires changes, it is easy to expand that and allow setting onStep
even after a frame is entirely dead. This simplifies the implementation,
and better matches user expectations around object properties not throwing
or vanishing due to actions outside their control.

Differential Revision: https://phabricator.services.mozilla.com/D50431

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Logan Smyth 2019-10-28 05:53:13 +00:00
Родитель dd33864eb6
Коммит 5eb14d59ba
3 изменённых файлов: 145 добавлений и 12 удалений

Просмотреть файл

@ -679,25 +679,39 @@ DebuggerFrameImplementation DebuggerFrame::getImplementation(
/* static */ /* static */
bool DebuggerFrame::setOnStepHandler(JSContext* cx, HandleDebuggerFrame frame, bool DebuggerFrame::setOnStepHandler(JSContext* cx, HandleDebuggerFrame frame,
OnStepHandler* handler) { OnStepHandler* handler) {
MOZ_ASSERT(frame->isLive());
OnStepHandler* prior = frame->onStepHandler(); OnStepHandler* prior = frame->onStepHandler();
if (handler == prior) { if (handler == prior) {
return true; return true;
} }
JSFreeOp* fop = cx->defaultFreeOp(); JSFreeOp* fop = cx->defaultFreeOp();
AbstractFramePtr referent = DebuggerFrame::getReferent(frame); if (frame->isLive()) {
AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
// Adjust execution observability and step counts on whatever code (JS or // Adjust execution observability and step counts on whatever code (JS or
// Wasm) this frame is running. // Wasm) this frame is running.
if (handler) { if (handler) {
if (!frame->maybeIncrementStepperCounter(cx, referent)) { if (!frame->maybeIncrementStepperCounter(cx, referent)) {
return false; return false;
}
} else {
frame->maybeDecrementStepperCounter(cx->runtime()->defaultFreeOp(),
referent);
}
} else if (frame->hasGenerator()) {
RootedScript script(cx, frame->generatorInfo()->generatorScript());
if (handler) {
if (!frame->maybeIncrementStepperCounter(cx, script)) {
return false;
}
} else {
frame->maybeDecrementStepperCounter(cx->runtime()->defaultFreeOp(),
script);
} }
} else { } else {
frame->maybeDecrementStepperCounter(cx->runtime()->defaultFreeOp(), // If the frame is entirely dead, we still allow setting the onStep
referent); // handler, but it has no effect.
} }
// Now that the stepper counts and observability are set correctly, we can // Now that the stepper counts and observability are set correctly, we can
@ -1215,8 +1229,10 @@ bool DebuggerFrame::CallData::ToNative(JSContext* cx, unsigned argc,
Value* vp) { Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp); CallArgs args = CallArgsFromVp(argc, vp);
// All accessors/methods require a live frame, except for the live getter. // These methods do not require liveness.
bool checkLive = MyMethod != &CallData::liveGetter; bool checkLive = MyMethod != &CallData::liveGetter &&
MyMethod != &CallData::onStepGetter &&
MyMethod != &CallData::onStepSetter;
RootedDebuggerFrame frame(cx, RootedDebuggerFrame frame(cx,
DebuggerFrame::check(cx, args.thisv(), checkLive)); DebuggerFrame::check(cx, args.thisv(), checkLive));

Просмотреть файл

@ -0,0 +1,49 @@
// Changing onStep while the function is dead is allowed.
load(libdir + "asserts.js");
const g = newGlobal({ newCompartment: true });
const dbg = new Debugger(g);
let steps = new Set();
dbg.onDebuggerStatement = function(frame) {
// Setting 'onStep' while alive is allowed.
steps.add("debugger 1");
assertEq(frame.live, true);
frame.onStep = function() {
steps.add("onstep 1");
};
dbg.onDebuggerStatement = function() {
// Clear the 'onStep' while dead.
steps.add("debugger 2");
assertEq(frame.live, false);
// Clearing 'onStep' while dead is allowed.
frame.onStep = undefined;
// Setting 'onStep' while dead is allowed.
frame.onStep = function() {
steps.add("onstep 2");
};
};
};
g.eval(
`
function myGen() {
debugger;
print("make sure we have a place to step to inside the frame");
}
const g = myGen();
debugger;
`
);
assertDeepEq(Array.from(steps), [
"debugger 1",
"onstep 1",
"debugger 2",
]);

Просмотреть файл

@ -0,0 +1,68 @@
// Changing onStep while the generator is suspended/dead is allowed.
load(libdir + "asserts.js");
const g = newGlobal({ newCompartment: true });
const dbg = new Debugger(g);
let steps = new Set();
dbg.onDebuggerStatement = function(frame) {
// Setting 'onStep' while alive is allowed.
steps.add("debugger 1");
assertEq(frame.live, true);
frame.onStep = function() {
steps.add("onstep 1");
};
dbg.onDebuggerStatement = function() {
// Clear the 'onStep' while suspended.
steps.add("debugger 2");
assertEq(frame.live, false);
// Clearing 'onStep' while suspended is allowed.
frame.onStep = undefined;
// Setting 'onStep' while suspended is allowed.
frame.onStep = function() {
steps.add("onstep 2");
};
dbg.onDebuggerStatement = function() {
steps.add("debugger 3");
assertEq(frame.live, false);
// Clearing 'onStep' while dead is allowed.
frame.onStep = undefined;
// Setting 'onStep' while dead is allowed.
frame.onStep = function() {
steps.add("onstep 3");
};
};
};
};
g.eval(
`
function* myGen() {
debugger;
yield;
}
const g = myGen();
g.next();
debugger;
g.next();
debugger;
`
);
assertDeepEq(Array.from(steps), [
"debugger 1",
"onstep 1",
"debugger 2",
"onstep 2",
"debugger 3",
]);