author | Brian Hackett <bhackett1024@gmail.com> |
Mon, 23 Jul 2018 21:44:04 +0000 | |
changeset 822114 | cfb449f0184cb01ebd640c14abd7995845626509 |
parent 822113 | 7d63e1235defe4c6a80d246658140470fc25c98a |
child 822115 | 818c1a79b41dc6d18c9dbe2c600fe02905f3e7d0 |
push id | 117296 |
push user | bmo:gl@mozilla.com |
push date | Tue, 24 Jul 2018 20:28:07 +0000 |
reviewers | jorendorff |
bugs | 1470795 |
milestone | 63.0a1 |
--- a/js/src/doc/Debugger/Debugger.Script.md +++ b/js/src/doc/Debugger/Debugger.Script.md @@ -171,16 +171,22 @@ from its prototype: `sourceLength` : **If the instance refers to a `JSScript`**, the length, in characters, of this script's code within the [`Debugger.Source`][source] instance given by `source`. **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 runs. The result refers to the global directly, not via a wrapper or a `WindowProxy` ("outer window", in Firefox). **If the instance refers to WebAssembly code**, throw a `TypeError`. @@ -282,16 +288,28 @@ methods of other kinds of objects. * lineNumber: the line number for which offset is an entry point * columnNumber: the column number for which offset is an entry point * 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 the array are objects, each of which describes a single opcode, and contains the following properties: * lineNumber: the line number of the current opcode.
--- a/js/src/doc/Debugger/Debugger.md +++ b/js/src/doc/Debugger/Debugger.md @@ -501,8 +501,14 @@ other kinds of objects. The functions described below are not called with a `this` value. <code id="isCompilableUnit">isCompilableUnit(<i>source</i>)</code> : Given a string of source code, designated by <i>source</i>, return false if the string might become a valid JavaScript statement with the addition of 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.
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getSuccessorOrPredecessorOffsets-01.js @@ -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); +}
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-mainOffset-01.js @@ -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);
--- a/js/src/vm/BytecodeUtil.cpp +++ b/js/src/vm/BytecodeUtil.cpp @@ -2984,8 +2984,60 @@ js::GetCodeCoverageSummary(JSContext* cx return nullptr; js_memcpy(res, out.string(), len); res[len] = 0; if (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; +}
--- a/js/src/vm/BytecodeUtil.h +++ b/js/src/vm/BytecodeUtil.h @@ -831,16 +831,21 @@ class PCCounts }; static inline jsbytecode* 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. */ extern MOZ_MUST_USE bool Disassemble(JSContext* cx, JS::Handle<JSScript*> script, bool lines, Sprinter* sp); unsigned
--- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -5063,16 +5063,37 @@ Debugger::isCompilableUnit(JSContext* cx cx->clearPendingException(); } JS::SetWarningReporter(cx, older); args.rval().setBoolean(result); 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) { THIS_DEBUGGER(cx, argc, vp, "adoptDebuggeeValue", args, dbg); if (!args.requireAtLeast(cx, "Debugger.adoptDebuggeeValue", 1)) return false; RootedValue v(cx, args[0]); @@ -5132,16 +5153,17 @@ const JSFunctionSpec Debugger::methods[] JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0), JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1, 0), JS_FN("adoptDebuggeeValue", Debugger::adoptDebuggeeValue, 1, 0), JS_FS_END }; const JSFunctionSpec Debugger::static_methods[] { JS_FN("isCompilableUnit", Debugger::isCompilableUnit, 1, 0), + JS_FN("recordReplayProcessKind", Debugger::recordReplayProcessKind, 0, 0), JS_FS_END }; /*** Debugger.Script *****************************************************************************/ // Get the Debugger.Script referent as bare Cell. This should only be used for // GC operations like tracing. Please use GetScriptReferent below. static inline gc::Cell* @@ -5498,16 +5520,24 @@ static bool DebuggerScript_getSourceLength(JSContext* cx, unsigned argc, Value* vp) { THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get sourceEnd)", args, obj, script); args.rval().setNumber(uint32_t(script->sourceEnd() - script->sourceStart())); 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) { THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get global)", args, obj, script); Debugger* dbg = Debugger::fromChildJSObject(obj); RootedValue v(cx, ObjectValue(script->global())); if (!dbg->wrapDebuggeeValue(cx, &v)) return false; @@ -5888,16 +5918,94 @@ DebuggerScript_getOffsetLocation(JSConte DebuggerScriptGetOffsetLocationMatcher matcher(cx, offset, &result); if (!referent.match(matcher)) return false; args.rval().setObject(*result); 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) { THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllOffsets", args, obj, script); // First pass: determine which offsets in this script are jump targets and // which line numbers jump to them. FlowGraphSummary flowData(cx); @@ -6688,27 +6796,30 @@ static const JSPropertySpec DebuggerScri JS_PSG("isAsyncFunction", DebuggerScript_getIsAsyncFunction, 0), JS_PSG("displayName", DebuggerScript_getDisplayName, 0), JS_PSG("url", DebuggerScript_getUrl, 0), JS_PSG("startLine", DebuggerScript_getStartLine, 0), JS_PSG("lineCount", DebuggerScript_getLineCount, 0), 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 }; static const JSFunctionSpec DebuggerScript_methods[] = { JS_FN("getChildScripts", DebuggerScript_getChildScripts, 0, 0), JS_FN("getAllOffsets", DebuggerScript_getAllOffsets, 0, 0), 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), JS_FN("clearAllBreakpoints", DebuggerScript_clearAllBreakpoints, 0, 0), JS_FN("isInCatchScope", DebuggerScript_isInCatchScope, 1, 0), JS_FN("getOffsetsCoverage", DebuggerScript_getOffsetsCoverage, 0, 0), JS_FS_END };
--- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -716,16 +716,17 @@ class Debugger : private mozilla::Linked static bool findObjects(JSContext* cx, unsigned argc, Value* vp); static bool findAllGlobals(JSContext* cx, unsigned argc, Value* vp); static bool makeGlobalObjectReference(JSContext* cx, unsigned argc, Value* vp); static bool setupTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp); static bool drainTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp); 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); #endif static bool adoptDebuggeeValue(JSContext* cx, unsigned argc, Value* vp); static bool construct(JSContext* cx, unsigned argc, Value* vp); static const JSPropertySpec properties[]; static const JSFunctionSpec methods[];