Add Script.prototype.getAllOffsets and getLineOffsets.

This commit is contained in:
Jason Orendorff 2011-06-28 14:46:00 -05:00
parent 1432aa3dfd
commit b0d27328b3
5 changed files with 296 additions and 5 deletions

View File

@ -359,3 +359,4 @@ MSG_DEF(JSMSG_DEBUG_OBJECT_PROTO, 276, 0, JSEXN_TYPEERR, "Debug.Object.proto
MSG_DEF(JSMSG_DEBUG_LOOP, 277, 0, JSEXN_TYPEERR, "cannot debug an object in same compartment as debugger or a compartment that is already debugging the debugger")
MSG_DEF(JSMSG_DEBUG_NOT_IDLE, 278, 0, JSEXN_ERR, "can't start debugging: a debuggee script is on the stack")
MSG_DEF(JSMSG_DEBUG_BAD_OFFSET, 279, 0, JSEXN_TYPEERR, "invalid script offset")
MSG_DEF(JSMSG_DEBUG_BAD_LINE, 280, 0, JSEXN_TYPEERR, "invalid line number")

View File

@ -341,6 +341,32 @@ IndexToId(JSContext* cx, JSObject* obj, jsdouble index, JSBool* hole, jsid* idp,
return ReallyBigIndexToId(cx, index, idp);
}
bool
JSObject::arrayGetOwnDataElement(JSContext *cx, size_t i, Value *vp)
{
JS_ASSERT(isArray());
if (isDenseArray()) {
if (i >= getArrayLength())
vp->setMagic(JS_ARRAY_HOLE);
else
*vp = getDenseArrayElement(uint32(i));
return true;
}
JSBool hole;
jsid id;
if (!IndexToId(cx, this, i, &hole, &id))
return false;
const Shape *shape = nativeLookup(id);
if (!shape || !shape->isDataDescriptor())
vp->setMagic(JS_ARRAY_HOLE);
else
*vp = getSlot(shape->slot);
return true;
}
/*
* If the property at the given index exists, get its value into location
* pointed by vp and set *hole to false. Otherwise set *hole to true and *vp

View File

@ -48,6 +48,7 @@
#include "jswrapper.h"
#include "jsinterpinlines.h"
#include "jsobjinlines.h"
#include "jsopcodeinlines.h"
#include "vm/Stack-inl.h"
using namespace js;
@ -1307,6 +1308,252 @@ DebugScript_getOffsetLine(JSContext *cx, uintN argc, Value *vp)
return true;
}
class BytecodeRangeWithLineNumbers : private BytecodeRange
{
public:
using BytecodeRange::empty;
using BytecodeRange::frontPC;
using BytecodeRange::frontOpcode;
using BytecodeRange::frontOffset;
BytecodeRangeWithLineNumbers(JSContext *cx, JSScript *script)
: BytecodeRange(cx, script), lineno(script->lineno), sn(script->notes()), snpc(script->code) {
if (!SN_IS_TERMINATOR(sn))
snpc += SN_DELTA(sn);
updateLine();
}
void popFront() {
BytecodeRange::popFront();
if (!empty())
updateLine();
}
size_t frontLineNumber() const { return lineno; }
private:
void updateLine() {
// Determine the current line number by reading all source notes up to
// and including the current offset.
while (!SN_IS_TERMINATOR(sn) && snpc <= frontPC()) {
JSSrcNoteType type = (JSSrcNoteType) SN_TYPE(sn);
if (type == SRC_SETLINE)
lineno = size_t(js_GetSrcNoteOffset(sn, 0));
else if (type == SRC_NEWLINE)
lineno++;
sn = SN_NEXT(sn);
snpc += SN_DELTA(sn);
}
}
size_t lineno;
jssrcnote *sn;
jsbytecode *snpc;
};
static const size_t NoEdges = -1;
static const size_t MultipleEdges = -2;
/*
* FlowGraphSummary::populate(cx, script) computes a summary of script's
* control flow graph used by DebugScript_{getAllOffsets,getLineOffsets}.
*
* jumpData[offset] is:
* - NoEdges if offset isn't the offset of an instruction, or if the
* instruction is apparently unreachable;
* - MultipleEdges if you can arrive at that instruction from
* instructions on multiple different lines OR it's the first
* instruction of the script;
* - otherwise, the (unique) line number of all instructions that can
* precede the instruction at offset.
*
* The generated graph does not contain edges for JSOP_RETSUB, which appears at
* the end of finally blocks. The algorithm that uses this information works
* anyway, because in non-exception cases, JSOP_RETSUB always returns to a
* !FlowsIntoNext instruction (JSOP_GOTO/GOTOX or JSOP_RETRVAL) which generates
* an edge if needed.
*/
class FlowGraphSummary : public Vector<size_t> {
public:
typedef Vector<size_t> Base;
FlowGraphSummary(JSContext *cx) : Base(cx) {}
void addEdge(size_t sourceLine, size_t targetOffset) {
FlowGraphSummary &self = *this;
if (self[targetOffset] == NoEdges)
self[targetOffset] = sourceLine;
else if (self[targetOffset] != sourceLine)
self[targetOffset] = MultipleEdges;
}
void addEdgeFromAnywhere(size_t targetOffset) {
(*this)[targetOffset] = MultipleEdges;
}
bool populate(JSContext *cx, JSScript *script) {
if (!growBy(script->length))
return false;
FlowGraphSummary &self = *this;
self[0] = MultipleEdges;
for (size_t i = 1; i < script->length; i++)
self[i] = NoEdges;
size_t prevLine = script->lineno;
JSOp prevOp = JSOP_NOP;
for (BytecodeRangeWithLineNumbers r(cx, script); !r.empty(); r.popFront()) {
size_t lineno = r.frontLineNumber();
JSOp op = r.frontOpcode();
if (FlowsIntoNext(prevOp))
addEdge(prevLine, r.frontOffset());
if (js_CodeSpec[op].type() == JOF_JUMP) {
addEdge(lineno, r.frontOffset() + GET_JUMP_OFFSET(r.frontPC()));
} else if (js_CodeSpec[op].type() == JOF_JUMPX) {
addEdge(lineno, r.frontOffset() + GET_JUMPX_OFFSET(r.frontPC()));
} else if (op == JSOP_TABLESWITCH || op == JSOP_TABLESWITCHX ||
op == JSOP_LOOKUPSWITCH || op == JSOP_LOOKUPSWITCHX) {
bool table = op == JSOP_TABLESWITCH || op == JSOP_TABLESWITCHX;
bool big = op == JSOP_TABLESWITCHX || op == JSOP_LOOKUPSWITCHX;
jsbytecode *pc = r.frontPC();
size_t offset = r.frontOffset();
ptrdiff_t step = big ? JUMPX_OFFSET_LEN : JUMP_OFFSET_LEN;
size_t defaultOffset = offset + (big ? GET_JUMPX_OFFSET(pc) : GET_JUMP_OFFSET(pc));
pc += step;
addEdge(lineno, defaultOffset);
jsint ncases;
if (table) {
jsint low = GET_JUMP_OFFSET(pc);
pc += JUMP_OFFSET_LEN;
ncases = GET_JUMP_OFFSET(pc) - low + 1;
pc += JUMP_OFFSET_LEN;
} else {
ncases = (jsint) GET_UINT16(pc);
pc += UINT16_LEN;
JS_ASSERT(ncases > 0);
}
for (jsint i = 0; i < ncases; i++) {
if (!table)
pc += INDEX_LEN;
size_t target = offset + (big ? GET_JUMPX_OFFSET(pc) : GET_JUMP_OFFSET(pc));
addEdge(lineno, target);
pc += step;
}
}
prevOp = op;
prevLine = lineno;
}
return true;
}
};
static JSBool
DebugScript_getAllOffsets(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, vp, "getAllOffsets", obj, script);
// First pass: determine which offsets in this script are jump targets and
// which line numbers jump to them.
FlowGraphSummary flowData(cx);
if (!flowData.populate(cx, script))
return false;
// Second pass: build the result array.
JSObject *result = NewDenseEmptyArray(cx);
if (!result)
return false;
for (BytecodeRangeWithLineNumbers r(cx, script); !r.empty(); r.popFront()) {
size_t offset = r.frontOffset();
size_t lineno = r.frontLineNumber();
// Make a note, if the current instruction is an entry point for the current line.
if (flowData[offset] != NoEdges && flowData[offset] != lineno) {
// Get the offsets array for this line.
JSObject *offsets;
Value offsetsv;
if (!result->arrayGetOwnDataElement(cx, lineno, &offsetsv))
return false;
jsid id;
if (offsetsv.isObject()) {
offsets = &offsetsv.toObject();
} else {
JS_ASSERT(offsetsv.isMagic(JS_ARRAY_HOLE));
// Create an empty offsets array for this line.
// Store it in the result array.
offsets = NewDenseEmptyArray(cx);
if (!offsets ||
!ValueToId(cx, NumberValue(lineno), &id) ||
!result->defineProperty(cx, id, ObjectValue(*offsets)))
{
return false;
}
}
// Append the current offset to the offsets array.
if (!js_ArrayCompPush(cx, offsets, NumberValue(offset)))
return false;
}
}
vp->setObject(*result);
return true;
}
static JSBool
DebugScript_getLineOffsets(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, vp, "getAllOffsets", obj, script);
REQUIRE_ARGC("Debug.Script.getLineOffsets", 1);
// Parse lineno argument.
size_t lineno;
bool ok = false;
if (vp[2].isNumber()) {
jsdouble d = vp[2].toNumber();
lineno = size_t(d);
ok = (lineno == d);
}
if (!ok) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_BAD_LINE);
return false;
}
// First pass: determine which offsets in this script are jump targets and
// which line numbers jump to them.
FlowGraphSummary flowData(cx);
if (!flowData.populate(cx, script))
return false;
// Second pass: build the result array.
JSObject *result = NewDenseEmptyArray(cx);
if (!result)
return false;
for (BytecodeRangeWithLineNumbers r(cx, script); !r.empty(); r.popFront()) {
size_t offset = r.frontOffset();
// If the op at offset is an entry point, append offset to result.
if (r.frontLineNumber() == lineno &&
flowData[offset] != NoEdges &&
flowData[offset] != lineno)
{
if (!js_ArrayCompPush(cx, result, NumberValue(offset)))
return false;
}
}
vp->setObject(*result);
return true;
}
static JSBool
DebugScript_construct(JSContext *cx, uintN argc, Value *vp)
{
@ -1320,6 +1567,8 @@ static JSPropertySpec DebugScript_properties[] = {
};
static JSFunctionSpec DebugScript_methods[] = {
JS_FN("getAllOffsets", DebugScript_getAllOffsets, 0, 0),
JS_FN("getLineOffsets", DebugScript_getLineOffsets, 1, 0),
JS_FN("getOffsetLine", DebugScript_getOffsetLine, 0, 0),
JS_FS_END
};

View File

@ -809,6 +809,13 @@ struct JSObject : js::gc::Cell {
JSBool makeDenseArraySlow(JSContext *cx);
/*
* If this array object has a data property with index i, set *vp to its
* value and return true. If not, do vp->setMagic(JS_ARRAY_HOLE) and return
* true. On OOM, report it and return false.
*/
bool arrayGetOwnDataElement(JSContext *cx, size_t i, js::Value *vp);
public:
inline js::ArgumentsObject *asArguments();
inline js::NormalArgumentsObject *asNormalArguments();

View File

@ -545,6 +545,14 @@ GetBytecodeLength(JSContext *cx, JSScript *script, jsbytecode *pc);
extern bool
IsValidBytecodeOffset(JSContext *cx, JSScript *script, size_t offset);
inline bool
FlowsIntoNext(JSOp op)
{
// JSOP_YIELD is considered to flow into the next instruction, like JSOP_CALL.
return op != JSOP_STOP && op != JSOP_RETURN && op != JSOP_RETRVAL && op != JSOP_THROW &&
op != JSOP_GOTO && op != JSOP_GOTOX && op != JSOP_RETSUB;
}
}
#endif