зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1475417 - Part 2: Fire onEnterFrame when resuming a generator or async function. r=jandem, r=jimb
This commit is contained in:
Родитель
5cd444cdaa
Коммит
071656e082
|
@ -0,0 +1,53 @@
|
|||
// frame.live is false for generator frames popped due to exception or termination.
|
||||
|
||||
load(libdir + "/asserts.js");
|
||||
|
||||
function test(when, what) {
|
||||
let g = newGlobal();
|
||||
g.eval("function* f(x) { yield x; }");
|
||||
|
||||
let dbg = new Debugger;
|
||||
let gw = dbg.addDebuggee(g);
|
||||
let fw = gw.getOwnPropertyDescriptor("f").value;
|
||||
|
||||
let t = 0;
|
||||
let poppedFrame;
|
||||
|
||||
function tick(frame) {
|
||||
if (frame.callee == fw) {
|
||||
if (t == when) {
|
||||
poppedFrame = frame;
|
||||
dbg.onEnterFrame = undefined;
|
||||
frame.onPop = undefined;
|
||||
return what;
|
||||
}
|
||||
t++;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
dbg.onDebuggerStatement = frame => {
|
||||
dbg.onEnterFrame = frame => {
|
||||
frame.onPop = function() {
|
||||
return tick(this);
|
||||
};
|
||||
return tick(frame);
|
||||
};
|
||||
let result = frame.eval("for (let _ of f(0)) {}");
|
||||
assertDeepEq(result, what);
|
||||
};
|
||||
g.eval("debugger;");
|
||||
|
||||
assertEq(t, when);
|
||||
assertEq(poppedFrame.live, false);
|
||||
assertErrorMessage(() => poppedFrame.older,
|
||||
Error,
|
||||
"Debugger.Frame is not live");
|
||||
}
|
||||
|
||||
for (let when = 0; when < 6; when++) {
|
||||
for (let what of [null, {throw: "fit"}]) {
|
||||
test(when, what);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Stepping into the `.next()` method of a generator works as expected.
|
||||
|
||||
let g = newGlobal();
|
||||
g.eval(`\
|
||||
function* nums() { // line 1
|
||||
yield 1; // 2
|
||||
yield 2; // 3
|
||||
} // 4
|
||||
function f() { // 5
|
||||
let gen = nums(); // 6
|
||||
gen.next(); // 7
|
||||
gen.next(); // 8
|
||||
gen.next(); // 9
|
||||
} // 10
|
||||
`);
|
||||
|
||||
let log = [];
|
||||
let previousLine = -1;
|
||||
let dbg = new Debugger(g);
|
||||
dbg.onEnterFrame = frame => {
|
||||
frame.onStep = () => {
|
||||
let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
|
||||
if (previousLine != line) { // We stepped to a new line.
|
||||
log.push(line);
|
||||
previousLine = line;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
g.f();
|
||||
assertEq(log.join(" "), "5 6 1 6 7 1 2 7 8 2 3 8 9 3 9 10");
|
|
@ -0,0 +1,44 @@
|
|||
// Stepping into the `.throw()` method of a generator with no relevant catch block.
|
||||
//
|
||||
// The debugger fires onEnterFrame and then frame.onPop for the generator frame when
|
||||
// `gen.throw()` is called.
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
let g = newGlobal();
|
||||
g.eval(`\
|
||||
function* z() { // line 1
|
||||
yield 1; // 2
|
||||
yield 2; // 3
|
||||
} // 4
|
||||
function f() { // 5
|
||||
let gen = z(); // 6
|
||||
gen.next(); // 7
|
||||
gen.throw("fit"); // 8
|
||||
} // 9
|
||||
`);
|
||||
|
||||
let log = "";
|
||||
let previousLine = -1;
|
||||
let dbg = new Debugger(g);
|
||||
dbg.onEnterFrame = frame => {
|
||||
log += frame.callee.name + "{";
|
||||
frame.onStep = () => {
|
||||
let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
|
||||
if (previousLine != line) { // We stepped to a new line.
|
||||
log += line;
|
||||
previousLine = line;
|
||||
}
|
||||
};
|
||||
frame.onPop = completion => {
|
||||
if ("throw" in completion)
|
||||
log += "!";
|
||||
log += "}";
|
||||
}
|
||||
};
|
||||
|
||||
assertThrowsValue(() => g.f(), "fit");
|
||||
// z{1} is the initial generator setup.
|
||||
// z{12} is the first .next() call, running to `yield 1` on line 2
|
||||
// The final `z{!}` is for the .throw() call.
|
||||
assertEq(log, "f{56z{1}67z{12}78z{!}!}");
|
|
@ -0,0 +1,45 @@
|
|||
// Stepping into the `.throw()` method of a generator with a relevant catch block.
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
let g = newGlobal();
|
||||
g.eval(`\
|
||||
function* z() { // line 1
|
||||
try { // 2
|
||||
yield 1; // 3
|
||||
} catch (exc) { // 4
|
||||
yield 2; // 5
|
||||
} // 6
|
||||
} // 7
|
||||
function f() { // 8
|
||||
let gen = z(); // 9
|
||||
gen.next(); // 10
|
||||
gen.throw("fit"); // 11
|
||||
} // 12
|
||||
`);
|
||||
|
||||
let log = [];
|
||||
let previousLine = -1;
|
||||
let dbg = new Debugger(g);
|
||||
dbg.onEnterFrame = frame => {
|
||||
log.push(frame.callee.name + " in");
|
||||
frame.onStep = () => {
|
||||
let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
|
||||
if (previousLine != line) { // We stepped to a new line.
|
||||
log.push(line);
|
||||
previousLine = line;
|
||||
}
|
||||
};
|
||||
frame.onPop = completion => {
|
||||
log.push(frame.callee.name + " out");
|
||||
};
|
||||
};
|
||||
|
||||
g.f();
|
||||
assertEq(
|
||||
log.join(", "),
|
||||
"f in, 8, 9, z in, 1, z out, " +
|
||||
"9, 10, z in, 1, 2, 3, z out, " +
|
||||
"10, 11, z in, 2, 4, 5, z out, " + // not sure why we hit line 2 here, source notes bug maybe
|
||||
"11, 12, f out"
|
||||
);
|
|
@ -0,0 +1,36 @@
|
|||
// A Debugger can {throw:} from onEnterFrame in an async function.
|
||||
// The resulting promise (if any) is rejected with the thrown error value.
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
let g = newGlobal();
|
||||
g.eval(`
|
||||
async function f() { await 1; }
|
||||
var err = new TypeError("object too hairy");
|
||||
`);
|
||||
|
||||
let dbg = new Debugger;
|
||||
let gw = dbg.addDebuggee(g);
|
||||
let errw = gw.makeDebuggeeValue(g.err);
|
||||
|
||||
// Repeat the test for each onEnterFrame event.
|
||||
// It fires up to three times:
|
||||
// - when the async function g.f is called;
|
||||
// - when we enter it to run to `await 1`;
|
||||
// - when we resume after the await to run to the end.
|
||||
for (let when = 0; when < 3; when++) {
|
||||
let hits = 0;
|
||||
dbg.onEnterFrame = frame => {
|
||||
return hits++ < when ? undefined : {throw: errw};
|
||||
};
|
||||
|
||||
let result = undefined;
|
||||
g.f()
|
||||
.then(value => { result = {returned: value}; })
|
||||
.catch(err => { result = {threw: err}; });
|
||||
|
||||
drainJobQueue();
|
||||
|
||||
assertEq(hits, when + 1);
|
||||
assertDeepEq(result, {threw: g.err});
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// A Debugger can {return:} from onEnterFrame at any resume point in an async function.
|
||||
// The async function's promise is resolved with the returned value.
|
||||
|
||||
let g = newGlobal();
|
||||
g.eval(`async function f(x) { await x; }`);
|
||||
|
||||
let dbg = new Debugger(g);
|
||||
function test(when) {
|
||||
let hits = 0;
|
||||
dbg.onEnterFrame = frame => {
|
||||
if (frame.type == "call" && frame.callee.name === "f") {
|
||||
if (hits++ == when) {
|
||||
return {return: "exit"};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let result = undefined;
|
||||
let finished = false;
|
||||
g.f("hello").then(value => { result = value; finished = true; });
|
||||
drainJobQueue();
|
||||
assertEq(finished, true);
|
||||
assertEq(hits, when + 1);
|
||||
assertEq(result, "exit");
|
||||
}
|
||||
|
||||
// onEnterFrame with hits==0 is not a resume point; {return:} behaves differently there
|
||||
// (see onEnterFrame-async-resumption-02.js).
|
||||
test(1); // force return from first resume point, immediately after the initial suspend
|
||||
test(2); // force return from second resume point, immediately after the await instruction
|
|
@ -0,0 +1,81 @@
|
|||
// Frame properties and methods work in generator-resuming onEnterFrame events.
|
||||
// Also tests onPop events, for good measure.
|
||||
|
||||
let g = newGlobal();
|
||||
g.eval(`\
|
||||
function* gen(lo, hi) {
|
||||
var a = 1/2;
|
||||
yield a;
|
||||
yield a * a;
|
||||
}
|
||||
`);
|
||||
let dbg = new Debugger;
|
||||
let gw = dbg.addDebuggee(g);
|
||||
|
||||
let hits = 0;
|
||||
let savedScript = null;
|
||||
let savedEnv = null;
|
||||
let savedOffsets = new Set;
|
||||
|
||||
function check(frame) {
|
||||
assertEq(frame.type, "call");
|
||||
assertEq(frame.constructing, false);
|
||||
assertEq(frame.callee, gw.makeDebuggeeValue(g.gen));
|
||||
|
||||
// `arguments` elements don't work in resumed generator frames,
|
||||
// because generators don't keep the arguments around.
|
||||
// The first onEnterFrame and onPop events can see them.
|
||||
assertEq(frame.arguments.length, hits < 2 ? args.length : 0);
|
||||
for (var i = 0; i < frame.arguments.length; i++) {
|
||||
assertEq(frame.arguments.hasOwnProperty(i), true);
|
||||
|
||||
if (hits < 2)
|
||||
assertEq(frame.arguments[i], gw.makeDebuggeeValue(args[i]), `arguments[${i}]`);
|
||||
else
|
||||
assertEq(frame.arguments[i], undefined);
|
||||
}
|
||||
|
||||
if (savedEnv === null) {
|
||||
savedEnv = frame.environment;
|
||||
assertEq(savedScript, null);
|
||||
savedScript = frame.script;
|
||||
} else {
|
||||
assertEq(frame.environment, savedEnv);
|
||||
assertEq(frame.script, savedScript);
|
||||
}
|
||||
let a_expected = hits < 3 ? undefined : 1/2;
|
||||
assertEq(savedEnv.getVariable("a"), a_expected);
|
||||
|
||||
assertEq(frame.generator, true);
|
||||
assertEq(frame.live, true);
|
||||
|
||||
let pc = frame.offset;
|
||||
assertEq(savedOffsets.has(pc), false);
|
||||
savedOffsets.add(pc);
|
||||
|
||||
assertEq(frame.older, null);
|
||||
assertEq(frame.this, gw);
|
||||
assertEq(typeof frame.implementation, "string");
|
||||
|
||||
// And the moment of truth:
|
||||
assertEq(frame.eval("2 + 2").return, 4);
|
||||
assertEq(frame.eval("a").return, a_expected);
|
||||
assertEq(frame.eval("if (a !== undefined) { assertEq(a < (lo + hi) / 2, true); } 7;").return, 7);
|
||||
}
|
||||
|
||||
dbg.onEnterFrame = frame => {
|
||||
if (frame.type === "eval")
|
||||
return;
|
||||
check(frame);
|
||||
hits++;
|
||||
frame.onPop = completion => {
|
||||
check(frame);
|
||||
hits++;
|
||||
};
|
||||
};
|
||||
|
||||
// g.gen ignores the arguments passed to it, but we use them to test
|
||||
// frame.arguments.
|
||||
let args = [0, 10, g, dbg];
|
||||
for (let v of g.gen(...args)) {}
|
||||
assertEq(hits, 8);
|
|
@ -0,0 +1,27 @@
|
|||
// onEnterFrame fires after the [[GeneratorState]] is set to "executing".
|
||||
//
|
||||
// This test checks that Debugger doesn't accidentally make it possible to
|
||||
// reenter a generator frame that's already on the stack. (Also tests a fun
|
||||
// corner case in baseline debug-mode OSR.)
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
let g = newGlobal();
|
||||
g.eval('function* f() { yield 1; yield 2; }');
|
||||
let dbg = Debugger(g);
|
||||
let genObj = null;
|
||||
let hits = 0;
|
||||
dbg.onEnterFrame = frame => {
|
||||
// The first time onEnterFrame fires, there is no generator object, so
|
||||
// there's nothing to test. The generator object doesn't exist until
|
||||
// JSOP_GENERATOR is reached, right before the initial yield.
|
||||
if (genObj !== null) {
|
||||
dbg.removeDebuggee(g); // avoid the DebuggeeWouldRun exception
|
||||
assertThrowsInstanceOf(() => genObj.next(), g.TypeError);
|
||||
dbg.addDebuggee(g);
|
||||
hits++;
|
||||
}
|
||||
};
|
||||
genObj = g.f();
|
||||
for (let x of genObj) {}
|
||||
assertEq(hits, 3);
|
|
@ -0,0 +1,25 @@
|
|||
// If onEnterFrame terminates a generator, the Frame is left in a sane but inactive state.
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
let g = newGlobal();
|
||||
g.eval("function* f(x) { yield x; }");
|
||||
let dbg = new Debugger;
|
||||
let gw = dbg.addDebuggee(g);
|
||||
|
||||
let genFrame = null;
|
||||
dbg.onDebuggerStatement = frame => {
|
||||
dbg.onEnterFrame = frame => {
|
||||
if (frame.callee == gw.getOwnPropertyDescriptor("f").value) {
|
||||
genFrame = frame;
|
||||
return null;
|
||||
}
|
||||
};
|
||||
assertEq(frame.eval("f(0);"), null);
|
||||
};
|
||||
|
||||
g.eval("debugger;");
|
||||
|
||||
assertEq(genFrame instanceof Debugger.Frame, true);
|
||||
assertEq(genFrame.live, false);
|
||||
assertThrowsInstanceOf(() => genFrame.callee, Error);
|
|
@ -0,0 +1,36 @@
|
|||
// A debugger can {throw:} from onEnterFrame at any resume point in a generator.
|
||||
// It closes the generator.
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
let g = newGlobal();
|
||||
g.eval(`
|
||||
function* f() { yield 1; }
|
||||
var exn = new TypeError("object too hairy");
|
||||
`);
|
||||
|
||||
let dbg = new Debugger;
|
||||
let gw = dbg.addDebuggee(g);
|
||||
|
||||
// Repeat the test for each onEnterFrame event.
|
||||
// It fires up to three times:
|
||||
// - when the generator g.f is called;
|
||||
// - when we enter it to run to `yield 1`;
|
||||
// - when we resume after the yield to run to the end.
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let hits = 0;
|
||||
dbg.onEnterFrame = frame => {
|
||||
return hits++ < i ? undefined : {throw: gw.makeDebuggeeValue(g.exn)};
|
||||
};
|
||||
let genObj;
|
||||
assertThrowsValue(
|
||||
() => {
|
||||
genObj = g.f();
|
||||
for (let x of genObj) {}
|
||||
},
|
||||
g.exn
|
||||
);
|
||||
assertEq(hits, i + 1);
|
||||
if (hits > 1)
|
||||
assertEq(genObj.next().done, true);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// A Debugger can {return:} from onEnterFrame at any resume point in a generator.
|
||||
// Force-returning closes the generator.
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
let g = newGlobal();
|
||||
g.values = [1, 2, 3];
|
||||
g.eval(`function* f() { yield* values; }`);
|
||||
|
||||
let dbg = Debugger(g);
|
||||
|
||||
// onEnterFrame will fire up to 5 times.
|
||||
// - once for the initial call to g.f();
|
||||
// - four times at resume points:
|
||||
// - initial resume at the top of the generator body
|
||||
// - resume after yielding 1
|
||||
// - resume after yielding 2
|
||||
// - resume after yielding 3 (this resumption will run to the end).
|
||||
// This test ignores the initial call and focuses on resume points.
|
||||
for (let i = 1; i < 5; i++) {
|
||||
let hits = 0;
|
||||
dbg.onEnterFrame = frame => {
|
||||
return hits++ < i ? undefined : {return: "we're done here"};
|
||||
};
|
||||
|
||||
let genObj = g.f();
|
||||
let actual = [];
|
||||
while (true) {
|
||||
let r = genObj.next();
|
||||
if (r.done) {
|
||||
assertDeepEq(r, {value: "we're done here", done: true});
|
||||
break;
|
||||
}
|
||||
actual.push(r.value);
|
||||
}
|
||||
assertEq(hits, i + 1);
|
||||
assertDeepEq(actual, g.values.slice(0, i - 1));
|
||||
assertDeepEq(genObj.next(), {value: undefined, done: true});
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Returning {throw:} from onEnterFrame when resuming inside a try block in a
|
||||
// generator causes control to jump to the catch block.
|
||||
|
||||
let g = newGlobal();
|
||||
g.eval(`
|
||||
function* gen() {
|
||||
try {
|
||||
yield 0;
|
||||
return "fail";
|
||||
} catch (exc) {
|
||||
assertEq(exc, "fit");
|
||||
return "ok";
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
let dbg = new Debugger(g);
|
||||
let hits = 0;
|
||||
dbg.onEnterFrame = frame => {
|
||||
assertEq(frame.callee.name, "gen");
|
||||
if (++hits == 3) {
|
||||
// First hit is when calling gen();
|
||||
// second hit is resuming at the implicit initial yield;
|
||||
// third hit is resuming inside the try block.
|
||||
return {throw: "fit"};
|
||||
}
|
||||
};
|
||||
|
||||
let it = g.gen();
|
||||
let result = it.next();
|
||||
assertEq(result.done, false);
|
||||
assertEq(result.value, 0);
|
||||
result = it.next();
|
||||
assertEq(result.done, true);
|
||||
assertEq(result.value, "ok");
|
|
@ -4647,7 +4647,7 @@ BaselineCompiler::emit_JSOP_AWAIT()
|
|||
return emit_JSOP_YIELD();
|
||||
}
|
||||
|
||||
typedef bool (*DebugAfterYieldFn)(JSContext*, BaselineFrame*);
|
||||
typedef bool (*DebugAfterYieldFn)(JSContext*, BaselineFrame*, jsbytecode*, bool*);
|
||||
static const VMFunction DebugAfterYieldInfo =
|
||||
FunctionInfo<DebugAfterYieldFn>(jit::DebugAfterYield, "DebugAfterYield");
|
||||
|
||||
|
@ -4660,8 +4660,21 @@ BaselineCompiler::emit_JSOP_DEBUGAFTERYIELD()
|
|||
frame.assertSyncedStack();
|
||||
masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
|
||||
prepareVMCall();
|
||||
pushArg(ImmPtr(pc));
|
||||
pushArg(R0.scratchReg());
|
||||
return callVM(DebugAfterYieldInfo);
|
||||
if (!callVM(DebugAfterYieldInfo))
|
||||
return false;
|
||||
|
||||
icEntries_.back().setFakeKind(ICEntry::Kind_DebugAfterYield);
|
||||
|
||||
Label done;
|
||||
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done);
|
||||
{
|
||||
masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand);
|
||||
masm.jump(&return_);
|
||||
}
|
||||
masm.bind(&done);
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef bool (*FinalSuspendFn)(JSContext*, HandleObject, jsbytecode*);
|
||||
|
|
|
@ -103,6 +103,7 @@ struct DebugModeOSREntry
|
|||
frameKind == ICEntry::Kind_EarlyStackCheck ||
|
||||
frameKind == ICEntry::Kind_DebugTrap ||
|
||||
frameKind == ICEntry::Kind_DebugPrologue ||
|
||||
frameKind == ICEntry::Kind_DebugAfterYield ||
|
||||
frameKind == ICEntry::Kind_DebugEpilogue;
|
||||
}
|
||||
|
||||
|
@ -307,6 +308,8 @@ ICEntryKindToString(ICEntry::Kind kind)
|
|||
return "debug trap";
|
||||
case ICEntry::Kind_DebugPrologue:
|
||||
return "debug prologue";
|
||||
case ICEntry::Kind_DebugAfterYield:
|
||||
return "debug after yield";
|
||||
case ICEntry::Kind_DebugEpilogue:
|
||||
return "debug epilogue";
|
||||
default:
|
||||
|
@ -367,6 +370,7 @@ PatchBaselineFramesForDebugMode(JSContext* cx,
|
|||
// - All the ways above.
|
||||
// C. From the debug trap handler.
|
||||
// D. From the debug prologue.
|
||||
// K. From a JSOP_DEBUGAFTERYIELD instruction.
|
||||
// E. From the debug epilogue.
|
||||
//
|
||||
// Cycles (On to Off to On)+ or (Off to On to Off)+:
|
||||
|
@ -470,6 +474,7 @@ PatchBaselineFramesForDebugMode(JSContext* cx,
|
|||
kind == ICEntry::Kind_EarlyStackCheck ||
|
||||
kind == ICEntry::Kind_DebugTrap ||
|
||||
kind == ICEntry::Kind_DebugPrologue ||
|
||||
kind == ICEntry::Kind_DebugAfterYield ||
|
||||
kind == ICEntry::Kind_DebugEpilogue);
|
||||
|
||||
// We will have allocated a new recompile info, so delete the
|
||||
|
@ -546,6 +551,17 @@ PatchBaselineFramesForDebugMode(JSContext* cx,
|
|||
popFrameReg = true;
|
||||
break;
|
||||
|
||||
case ICEntry::Kind_DebugAfterYield:
|
||||
// Case K above.
|
||||
//
|
||||
// Resume at the next instruction.
|
||||
MOZ_ASSERT(*pc == JSOP_DEBUGAFTERYIELD);
|
||||
recompInfo->resumeAddr = bl->nativeCodeForPC(script,
|
||||
pc + JSOP_DEBUGAFTERYIELD_LENGTH,
|
||||
&recompInfo->slotInfo);
|
||||
popFrameReg = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Case E above.
|
||||
//
|
||||
|
@ -945,9 +961,9 @@ HasForcedReturn(BaselineDebugModeOSRInfo* info, bool rv)
|
|||
if (kind == ICEntry::Kind_DebugEpilogue)
|
||||
return true;
|
||||
|
||||
// |rv| is the value in ReturnReg. If true, in the case of the prologue,
|
||||
// it means a forced return.
|
||||
if (kind == ICEntry::Kind_DebugPrologue)
|
||||
// |rv| is the value in ReturnReg. If true, in the case of the prologue or
|
||||
// after yield, it means a forced return.
|
||||
if (kind == ICEntry::Kind_DebugPrologue || kind == ICEntry::Kind_DebugAfterYield)
|
||||
return rv;
|
||||
|
||||
// N.B. The debug trap handler handles its own forced return, so no
|
||||
|
|
|
@ -256,8 +256,9 @@ class ICEntry
|
|||
Kind_DebugTrap,
|
||||
|
||||
// A fake IC entry for returning from a callVM to
|
||||
// Debug{Prologue,Epilogue}.
|
||||
// Debug{Prologue,AfterYield,Epilogue}.
|
||||
Kind_DebugPrologue,
|
||||
Kind_DebugAfterYield,
|
||||
Kind_DebugEpilogue,
|
||||
|
||||
Kind_Invalid
|
||||
|
|
|
@ -957,12 +957,19 @@ InterpretResume(JSContext* cx, HandleObject obj, HandleValue val, HandleProperty
|
|||
}
|
||||
|
||||
bool
|
||||
DebugAfterYield(JSContext* cx, BaselineFrame* frame)
|
||||
DebugAfterYield(JSContext* cx, BaselineFrame* frame, jsbytecode* pc, bool* mustReturn)
|
||||
{
|
||||
*mustReturn = false;
|
||||
|
||||
// The BaselineFrame has just been constructed by JSOP_RESUME in the
|
||||
// caller. We need to set its debuggee flag as necessary.
|
||||
if (frame->script()->isDebuggee())
|
||||
//
|
||||
// If a breakpoint is set on JSOP_DEBUGAFTERYIELD, or stepping is enabled,
|
||||
// we may already have done this work. Don't fire onEnterFrame again.
|
||||
if (frame->script()->isDebuggee() && !frame->isDebuggee()) {
|
||||
frame->setIsDebuggee();
|
||||
return DebugPrologue(cx, frame, pc, mustReturn);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -975,13 +982,19 @@ GeneratorThrowOrReturn(JSContext* cx, BaselineFrame* frame, Handle<GeneratorObje
|
|||
// the exception handler where we will clear the pc.
|
||||
JSScript* script = frame->script();
|
||||
uint32_t offset = script->yieldAndAwaitOffsets()[genObj->yieldAndAwaitIndex()];
|
||||
frame->setOverridePc(script->offsetToPC(offset));
|
||||
jsbytecode* pc = script->offsetToPC(offset);
|
||||
frame->setOverridePc(pc);
|
||||
|
||||
// In the interpreter, GeneratorObject::resume marks the generator as running,
|
||||
// so we do the same.
|
||||
genObj->setRunning();
|
||||
|
||||
MOZ_ALWAYS_TRUE(DebugAfterYield(cx, frame));
|
||||
bool mustReturn = false;
|
||||
if (!DebugAfterYield(cx, frame, pc, &mustReturn))
|
||||
return false;
|
||||
if (mustReturn)
|
||||
resumeKind = GeneratorObject::RETURN;
|
||||
|
||||
MOZ_ALWAYS_FALSE(js::GeneratorThrowOrReturn(cx, frame, genObj, arg, resumeKind));
|
||||
return false;
|
||||
}
|
||||
|
@ -1091,11 +1104,15 @@ HandleDebugTrap(JSContext* cx, BaselineFrame* frame, uint8_t* retAddr, bool* mus
|
|||
jsbytecode* pc = script->baselineScript()->icEntryFromReturnAddress(retAddr).pc(script);
|
||||
|
||||
if (*pc == JSOP_DEBUGAFTERYIELD) {
|
||||
// JSOP_DEBUGAFTERYIELD will set the frame's debuggee flag, but if we
|
||||
// set a breakpoint there we have to do it now.
|
||||
// JSOP_DEBUGAFTERYIELD will set the frame's debuggee flag and call the
|
||||
// onEnterFrame handler, but if we set a breakpoint there we have to do
|
||||
// it now.
|
||||
MOZ_ASSERT(!frame->isDebuggee());
|
||||
if (!DebugAfterYield(cx, frame))
|
||||
|
||||
if (!DebugAfterYield(cx, frame, pc, mustReturn))
|
||||
return false;
|
||||
if (*mustReturn)
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(frame->isDebuggee());
|
||||
|
|
|
@ -785,7 +785,7 @@ MOZ_MUST_USE bool
|
|||
InterpretResume(JSContext* cx, HandleObject obj, HandleValue val, HandlePropertyName kind,
|
||||
MutableHandleValue rval);
|
||||
MOZ_MUST_USE bool
|
||||
DebugAfterYield(JSContext* cx, BaselineFrame* frame);
|
||||
DebugAfterYield(JSContext* cx, BaselineFrame* frame, jsbytecode* pc, bool* mustReturn);
|
||||
MOZ_MUST_USE bool
|
||||
GeneratorThrowOrReturn(JSContext* cx, BaselineFrame* frame, Handle<GeneratorObject*> genObj,
|
||||
HandleValue arg, uint32_t resumeKind);
|
||||
|
|
|
@ -1817,6 +1817,15 @@ Debugger::fireEnterFrame(JSContext* cx, MutableHandleValue vp)
|
|||
RootedValue scriptFrame(cx);
|
||||
|
||||
FrameIter iter(cx);
|
||||
|
||||
#if DEBUG
|
||||
// Assert that the hook won't be able to re-enter the generator.
|
||||
if (iter.hasScript() && *iter.pc() == JSOP_DEBUGAFTERYIELD) {
|
||||
GeneratorObject* genObj = GetGeneratorObjectForFrame(cx, iter.abstractFramePtr());
|
||||
MOZ_ASSERT(genObj->isRunning() || genObj->isClosing());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!getFrame(cx, iter, &scriptFrame))
|
||||
return reportUncaughtException(ar);
|
||||
|
||||
|
|
|
@ -154,6 +154,10 @@ class GeneratorObject : public NativeObject
|
|||
MOZ_ASSERT_IF(yieldAndAwaitIndex == 0,
|
||||
getFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT).isUndefined());
|
||||
MOZ_ASSERT_IF(yieldAndAwaitIndex != 0, isRunning() || isClosing());
|
||||
setYieldAndAwaitIndexNoAssert(yieldAndAwaitIndex);
|
||||
}
|
||||
// Debugger has to flout the state machine rules a bit.
|
||||
void setYieldAndAwaitIndexNoAssert(uint32_t yieldAndAwaitIndex) {
|
||||
MOZ_ASSERT(yieldAndAwaitIndex < uint32_t(YIELD_AND_AWAIT_INDEX_CLOSING));
|
||||
setFixedSlot(YIELD_AND_AWAIT_INDEX_SLOT, Int32Value(yieldAndAwaitIndex));
|
||||
MOZ_ASSERT(isSuspended());
|
||||
|
|
|
@ -4286,6 +4286,20 @@ CASE(JSOP_RESUME)
|
|||
TraceLogStartEvent(logger, scriptEvent);
|
||||
TraceLogStartEvent(logger, TraceLogger_Interpreter);
|
||||
|
||||
switch (Debugger::onEnterFrame(cx, REGS.fp())) {
|
||||
case ResumeMode::Continue:
|
||||
break;
|
||||
case ResumeMode::Throw:
|
||||
case ResumeMode::Terminate:
|
||||
goto error;
|
||||
case ResumeMode::Return:
|
||||
MOZ_ASSERT_IF(REGS.fp()->callee().isGenerator(), // as opposed to an async function
|
||||
gen->isClosed());
|
||||
if (!ForcedReturn(cx, REGS))
|
||||
goto error;
|
||||
goto successful_return_continuation;
|
||||
}
|
||||
|
||||
switch (resumeKind) {
|
||||
case GeneratorObject::NEXT:
|
||||
break;
|
||||
|
|
Загрузка…
Ссылка в новой задаче