Bug 1470795 Part 1 - Debugger changes for recording/replaying, r=jorendorff.

--HG--
extra : rebase_source : f449f369a920483be4adb6701abf2c499b8e19a0
This commit is contained in:
Brian Hackett 2018-07-23 21:44:04 +00:00
Родитель e030fb5f6c
Коммит 965d96a199
8 изменённых файлов: 293 добавлений и 0 удалений

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

@ -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);