зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1470795 Part 1 - Debugger changes for recording/replaying, r=jorendorff.
--HG-- extra : rebase_source : f449f369a920483be4adb6701abf2c499b8e19a0
This commit is contained in:
Родитель
e030fb5f6c
Коммит
965d96a199
|
@ -176,6 +176,12 @@ from its prototype:
|
|||
|
||||
**If the instance refers to WebAssembly code**, throw a `TypeError`.
|
||||
|
||||
`mainOffset`
|
||||
: **If the instance refers to a `JSScript`**, the offset of the main
|
||||
entry point of the script, excluding any prologue.
|
||||
|
||||
**If the instance refers to WebAssembly code**, throw a `TypeError`.
|
||||
|
||||
`global`
|
||||
: **If the instance refers to a `JSScript`**, a [`Debugger.Object`][object]
|
||||
instance referring to the global object in whose scope this script
|
||||
|
@ -287,6 +293,18 @@ methods of other kinds of objects.
|
|||
* isEntryPoint: true if the offset is a column entry point, as
|
||||
would be reported by getAllColumnOffsets(); otherwise false.
|
||||
|
||||
<code>getSuccessorOffsets(<i>offset</i>)</code>
|
||||
: **If the instance refers to a `JSScript`**, return an array
|
||||
containing the offsets of all bytecodes in the script which are
|
||||
immediate successors of <i>offset</i> via non-exceptional control
|
||||
flow paths.
|
||||
|
||||
<code>getPredecessorOffsets(<i>offset</i>)</code>
|
||||
: **If the instance refers to a `JSScript`**, return an array
|
||||
containing the offsets of all bytecodes in the script for which
|
||||
<i>offset</i> is an immediate successor via non-exceptional
|
||||
control flow paths.
|
||||
|
||||
`getOffsetsCoverage()`:
|
||||
: **If the instance refers to a `JSScript`**, return `null` or an array which
|
||||
contains informations about the coverage of all opcodes. The elements of
|
||||
|
|
|
@ -506,3 +506,9 @@ The functions described below are not called with a `this` value.
|
|||
more lines. Otherwise return true. The intent is to support interactive
|
||||
compilation - accumulate lines in a buffer until isCompilableUnit is true,
|
||||
then pass it to the compiler.
|
||||
|
||||
<code id="recordReplayProcessKind">recordReplayProcessKind()</code>
|
||||
: Return the kind of record/replay firefox process that is currently
|
||||
running: the string "RecordingReplaying" if this is a recording or
|
||||
replaying process, the string "Middleman" if this is a middleman
|
||||
process, or undefined for normal firefox content or UI processes.
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
|
||||
// An offset A of a script should have successor B iff B has predecessor A.
|
||||
// Offsets reached while stepping through a script should be successors of
|
||||
// each other.
|
||||
|
||||
var scripts = [
|
||||
"n = 1",
|
||||
"if (n == 0) return; else n = 2",
|
||||
"while (n < 5) n++",
|
||||
"switch (n) { case 4: break; case 5: return 0; }",
|
||||
];
|
||||
|
||||
var g = newGlobal();
|
||||
for (var n in scripts) {
|
||||
g.eval("function f" + n + "() { " + scripts[n] + " }");
|
||||
}
|
||||
|
||||
var dbg = Debugger(g);
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
for (var n in scripts) {
|
||||
var compares = 0;
|
||||
var script = frame.eval("f" + n).return.script;
|
||||
var worklist = [script.mainOffset];
|
||||
var seen = [];
|
||||
while (worklist.length) {
|
||||
var offset = worklist.pop();
|
||||
if (seen.includes(offset))
|
||||
continue;
|
||||
seen.push(offset);
|
||||
var succs = script.getSuccessorOffsets(offset);
|
||||
for (var succ of succs) {
|
||||
compares++;
|
||||
var preds = script.getPredecessorOffsets(succ);
|
||||
assertEq(preds.includes(offset), true);
|
||||
worklist.push(succ);
|
||||
}
|
||||
}
|
||||
assertEq(compares != 0, true);
|
||||
compares = 0;
|
||||
for (var offset of seen) {
|
||||
var preds = script.getPredecessorOffsets(offset);
|
||||
for (var pred of preds) {
|
||||
var succs = script.getSuccessorOffsets(pred);
|
||||
compares++;
|
||||
assertEq(succs.includes(offset), true);
|
||||
}
|
||||
}
|
||||
assertEq(compares != 0, true);
|
||||
script.setBreakpoint(script.mainOffset, { hit: mainBreakpointHandler });
|
||||
}
|
||||
};
|
||||
g.eval("debugger");
|
||||
|
||||
function mainBreakpointHandler(frame) {
|
||||
frame.lastOffset = frame.script.mainOffset;
|
||||
frame.onStep = onStepHandler;
|
||||
}
|
||||
|
||||
function onStepHandler() {
|
||||
steps++;
|
||||
var succs = this.script.getSuccessorOffsets(this.lastOffset);
|
||||
if (!succs.includes(this.offset)) {
|
||||
// The onStep handler might skip over opcodes, even when running in the
|
||||
// interpreter. Check transitive successors of the last offset.
|
||||
var found = false;
|
||||
for (var succ of succs) {
|
||||
if (this.script.getSuccessorOffsets(succ).includes(this.offset))
|
||||
found = true;
|
||||
}
|
||||
assertEq(found, true);
|
||||
}
|
||||
this.lastOffset = this.offset;
|
||||
}
|
||||
|
||||
for (var n in scripts) {
|
||||
var steps = 0;
|
||||
g.eval("f" + n + "()");
|
||||
assertEq(steps != 0, true);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
// The main offset of a script should be hit before it performs any actions.
|
||||
|
||||
var g = newGlobal();
|
||||
g.eval("var n = 0; function foo() { n = 1; }");
|
||||
var dbg = Debugger(g);
|
||||
|
||||
var hits = 0;
|
||||
function breakpointHit(frame) {
|
||||
hits++;
|
||||
assertEq(frame.eval("n").return, 0);
|
||||
}
|
||||
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
var script = frame.eval("foo").return.script;
|
||||
script.setBreakpoint(script.mainOffset, { hit: breakpointHit });
|
||||
};
|
||||
g.eval("debugger; foo()");
|
||||
assertEq(g.eval("n"), 1);
|
||||
assertEq(hits, 1);
|
|
@ -2989,3 +2989,55 @@ js::GetCodeCoverageSummary(JSContext* cx, size_t* length)
|
|||
*length = len;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
js::GetSuccessorBytecodes(jsbytecode* pc, PcVector& successors)
|
||||
{
|
||||
JSOp op = (JSOp)*pc;
|
||||
if (FlowsIntoNext(op)) {
|
||||
if (!successors.append(GetNextPc(pc)))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CodeSpec[op].type() == JOF_JUMP) {
|
||||
if (!successors.append(pc + GET_JUMP_OFFSET(pc)))
|
||||
return false;
|
||||
} else if (op == JSOP_TABLESWITCH) {
|
||||
if (!successors.append(pc + GET_JUMP_OFFSET(pc)))
|
||||
return false;
|
||||
jsbytecode* npc = pc + JUMP_OFFSET_LEN;
|
||||
|
||||
int32_t low = GET_JUMP_OFFSET(npc);
|
||||
npc += JUMP_OFFSET_LEN;
|
||||
int ncases = GET_JUMP_OFFSET(npc) - low + 1;
|
||||
npc += JUMP_OFFSET_LEN;
|
||||
|
||||
for (int i = 0; i < ncases; i++) {
|
||||
if (!successors.append(pc + GET_JUMP_OFFSET(npc)))
|
||||
return false;
|
||||
npc += JUMP_OFFSET_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::GetPredecessorBytecodes(JSScript* script, jsbytecode* pc, PcVector& predecessors)
|
||||
{
|
||||
jsbytecode* end = script->code() + script->length();
|
||||
MOZ_ASSERT(pc >= script->code() && pc < end);
|
||||
for (jsbytecode* npc = script->code(); npc < end; npc = GetNextPc(npc)) {
|
||||
PcVector successors;
|
||||
if (!GetSuccessorBytecodes(npc, successors))
|
||||
return false;
|
||||
for (size_t i = 0; i < successors.length(); i++) {
|
||||
if (successors[i] == pc) {
|
||||
if (!predecessors.append(npc))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -836,6 +836,11 @@ GetNextPc(jsbytecode* pc)
|
|||
return pc + GetBytecodeLength(pc);
|
||||
}
|
||||
|
||||
typedef Vector<jsbytecode*, 4, SystemAllocPolicy> PcVector;
|
||||
|
||||
bool GetSuccessorBytecodes(jsbytecode* pc, PcVector& successors);
|
||||
bool GetPredecessorBytecodes(JSScript* script, jsbytecode* pc, PcVector& predecessors);
|
||||
|
||||
#if defined(DEBUG) || defined(JS_JITSPEW)
|
||||
/*
|
||||
* Disassemblers, for debugging only.
|
||||
|
|
|
@ -5068,6 +5068,27 @@ Debugger::isCompilableUnit(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
Debugger::recordReplayProcessKind(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
if (mozilla::recordreplay::IsMiddleman()) {
|
||||
JSString* str = JS_NewStringCopyZ(cx, "Middleman");
|
||||
if (!str)
|
||||
return false;
|
||||
args.rval().setString(str);
|
||||
} else if (mozilla::recordreplay::IsRecordingOrReplaying()) {
|
||||
JSString* str = JS_NewStringCopyZ(cx, "RecordingReplaying");
|
||||
if (!str)
|
||||
return false;
|
||||
args.rval().setString(str);
|
||||
} else {
|
||||
args.rval().setUndefined();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Debugger::adoptDebuggeeValue(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
|
@ -5137,6 +5158,7 @@ const JSFunctionSpec Debugger::methods[] = {
|
|||
|
||||
const JSFunctionSpec Debugger::static_methods[] {
|
||||
JS_FN("isCompilableUnit", Debugger::isCompilableUnit, 1, 0),
|
||||
JS_FN("recordReplayProcessKind", Debugger::recordReplayProcessKind, 0, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
@ -5502,6 +5524,14 @@ DebuggerScript_getSourceLength(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
DebuggerScript_getMainOffset(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get mainOffset)", args, obj, script);
|
||||
args.rval().setNumber(uint32_t(script->mainOffset()));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
DebuggerScript_getGlobal(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
|
@ -5893,6 +5923,84 @@ DebuggerScript_getOffsetLocation(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
class DebuggerScriptGetSuccessorOrPredecessorOffsetsMatcher
|
||||
{
|
||||
JSContext* cx_;
|
||||
size_t offset_;
|
||||
bool successor_;
|
||||
MutableHandleObject result_;
|
||||
|
||||
public:
|
||||
DebuggerScriptGetSuccessorOrPredecessorOffsetsMatcher(JSContext* cx, size_t offset,
|
||||
bool successor,
|
||||
MutableHandleObject result)
|
||||
: cx_(cx), offset_(offset), successor_(successor), result_(result) { }
|
||||
using ReturnType = bool;
|
||||
ReturnType match(HandleScript script) {
|
||||
PcVector adjacent;
|
||||
if (successor_) {
|
||||
if (!GetSuccessorBytecodes(script->code() + offset_, adjacent)) {
|
||||
ReportOutOfMemory(cx_);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!GetPredecessorBytecodes(script, script->code() + offset_, adjacent)) {
|
||||
ReportOutOfMemory(cx_);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result_.set(NewDenseEmptyArray(cx_));
|
||||
if (!result_)
|
||||
return false;
|
||||
|
||||
for (jsbytecode* pc : adjacent) {
|
||||
if (!NewbornArrayPush(cx_, result_, NumberValue(pc - script->code())))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ReturnType match(Handle<WasmInstanceObject*> instance) {
|
||||
JS_ReportErrorASCII(cx_, "getSuccessorOrPredecessorOffsets NYI on wasm instances");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
static bool
|
||||
DebuggerScript_getSuccessorOrPredecessorOffsets(JSContext* cx, unsigned argc, Value* vp,
|
||||
const char* name, bool successor)
|
||||
{
|
||||
THIS_DEBUGSCRIPT_REFERENT(cx, argc, vp, name, args, obj, referent);
|
||||
if (!args.requireAtLeast(cx, name, 1))
|
||||
return false;
|
||||
size_t offset;
|
||||
if (!ScriptOffset(cx, args[0], &offset))
|
||||
return false;
|
||||
|
||||
RootedObject result(cx);
|
||||
DebuggerScriptGetSuccessorOrPredecessorOffsetsMatcher matcher(cx, offset, successor, &result);
|
||||
if (!referent.match(matcher))
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*result);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
DebuggerScript_getSuccessorOffsets(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
return DebuggerScript_getSuccessorOrPredecessorOffsets(cx, argc, vp,
|
||||
"getSuccessorOffsets", true);
|
||||
}
|
||||
|
||||
static bool
|
||||
DebuggerScript_getPredecessorOffsets(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
return DebuggerScript_getSuccessorOrPredecessorOffsets(cx, argc, vp,
|
||||
"getPredecessorOffsets", false);
|
||||
}
|
||||
|
||||
static bool
|
||||
DebuggerScript_getAllOffsets(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
|
@ -6693,6 +6801,7 @@ static const JSPropertySpec DebuggerScript_properties[] = {
|
|||
JS_PSG("source", DebuggerScript_getSource, 0),
|
||||
JS_PSG("sourceStart", DebuggerScript_getSourceStart, 0),
|
||||
JS_PSG("sourceLength", DebuggerScript_getSourceLength, 0),
|
||||
JS_PSG("mainOffset", DebuggerScript_getMainOffset, 0),
|
||||
JS_PSG("global", DebuggerScript_getGlobal, 0),
|
||||
JS_PSG("format", DebuggerScript_getFormat, 0),
|
||||
JS_PS_END
|
||||
|
@ -6704,6 +6813,8 @@ static const JSFunctionSpec DebuggerScript_methods[] = {
|
|||
JS_FN("getAllColumnOffsets", DebuggerScript_getAllColumnOffsets, 0, 0),
|
||||
JS_FN("getLineOffsets", DebuggerScript_getLineOffsets, 1, 0),
|
||||
JS_FN("getOffsetLocation", DebuggerScript_getOffsetLocation, 0, 0),
|
||||
JS_FN("getSuccessorOffsets", DebuggerScript_getSuccessorOffsets, 1, 0),
|
||||
JS_FN("getPredecessorOffsets", DebuggerScript_getPredecessorOffsets, 1, 0),
|
||||
JS_FN("setBreakpoint", DebuggerScript_setBreakpoint, 2, 0),
|
||||
JS_FN("getBreakpoints", DebuggerScript_getBreakpoints, 1, 0),
|
||||
JS_FN("clearBreakpoint", DebuggerScript_clearBreakpoint, 1, 0),
|
||||
|
|
|
@ -721,6 +721,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
|||
static bool startTraceLogger(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool endTraceLogger(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool isCompilableUnit(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool recordReplayProcessKind(JSContext* cx, unsigned argc, Value* vp);
|
||||
#ifdef NIGHTLY_BUILD
|
||||
static bool setupTraceLogger(JSContext* cx, unsigned argc, Value* vp);
|
||||
static bool drainTraceLogger(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
|
Загрузка…
Ссылка в новой задаче