mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 12:37:37 +00:00
Add Script.prototype.getAllOffsets and getLineOffsets.
This commit is contained in:
parent
1432aa3dfd
commit
b0d27328b3
@ -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_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_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_OFFSET, 279, 0, JSEXN_TYPEERR, "invalid script offset")
|
||||||
|
MSG_DEF(JSMSG_DEBUG_BAD_LINE, 280, 0, JSEXN_TYPEERR, "invalid line number")
|
||||||
|
@ -341,6 +341,32 @@ IndexToId(JSContext* cx, JSObject* obj, jsdouble index, JSBool* hole, jsid* idp,
|
|||||||
return ReallyBigIndexToId(cx, index, 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
|
* 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
|
* pointed by vp and set *hole to false. Otherwise set *hole to true and *vp
|
||||||
|
249
js/src/jsdbg.cpp
249
js/src/jsdbg.cpp
@ -48,6 +48,7 @@
|
|||||||
#include "jswrapper.h"
|
#include "jswrapper.h"
|
||||||
#include "jsinterpinlines.h"
|
#include "jsinterpinlines.h"
|
||||||
#include "jsobjinlines.h"
|
#include "jsobjinlines.h"
|
||||||
|
#include "jsopcodeinlines.h"
|
||||||
#include "vm/Stack-inl.h"
|
#include "vm/Stack-inl.h"
|
||||||
|
|
||||||
using namespace js;
|
using namespace js;
|
||||||
@ -1307,6 +1308,252 @@ DebugScript_getOffsetLine(JSContext *cx, uintN argc, Value *vp)
|
|||||||
return true;
|
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
|
static JSBool
|
||||||
DebugScript_construct(JSContext *cx, uintN argc, Value *vp)
|
DebugScript_construct(JSContext *cx, uintN argc, Value *vp)
|
||||||
{
|
{
|
||||||
@ -1320,6 +1567,8 @@ static JSPropertySpec DebugScript_properties[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static JSFunctionSpec DebugScript_methods[] = {
|
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_FN("getOffsetLine", DebugScript_getOffsetLine, 0, 0),
|
||||||
JS_FS_END
|
JS_FS_END
|
||||||
};
|
};
|
||||||
|
@ -809,6 +809,13 @@ struct JSObject : js::gc::Cell {
|
|||||||
|
|
||||||
JSBool makeDenseArraySlow(JSContext *cx);
|
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:
|
public:
|
||||||
inline js::ArgumentsObject *asArguments();
|
inline js::ArgumentsObject *asArguments();
|
||||||
inline js::NormalArgumentsObject *asNormalArguments();
|
inline js::NormalArgumentsObject *asNormalArguments();
|
||||||
|
@ -545,6 +545,14 @@ GetBytecodeLength(JSContext *cx, JSScript *script, jsbytecode *pc);
|
|||||||
extern bool
|
extern bool
|
||||||
IsValidBytecodeOffset(JSContext *cx, JSScript *script, size_t offset);
|
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
|
#endif
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user