mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 767274: New expression decompiler. r=luke
This commit is contained in:
parent
cd12683392
commit
cc4d676247
96
js/src/jit-test/tests/basic/expression-autopsy.js
Normal file
96
js/src/jit-test/tests/basic/expression-autopsy.js
Normal file
@ -0,0 +1,96 @@
|
||||
function check_one(expected, f, err) {
|
||||
var failed = true;
|
||||
try {
|
||||
f();
|
||||
failed = false;
|
||||
} catch (ex) {
|
||||
var s = ex.toString();
|
||||
assertEq(s.slice(0, 11), "TypeError: ");
|
||||
assertEq(s.slice(-err.length), err);
|
||||
assertEq(s.slice(11, -err.length), expected);
|
||||
}
|
||||
if (!failed)
|
||||
throw new Error("didn't fail");
|
||||
}
|
||||
ieval = eval;
|
||||
function check(expr, expected=expr) {
|
||||
var end, err;
|
||||
for ([end, err] of [[".random_prop", " is undefined"], ["()", " is not a function"]]) {
|
||||
var statement = "o = {};" + expr + end, f;
|
||||
var cases = [
|
||||
// Global scope
|
||||
function () {
|
||||
ieval("var o, undef;\n" + statement);
|
||||
},
|
||||
// Function scope
|
||||
Function("o", "undef", statement),
|
||||
// Function scope with variables
|
||||
Function("var o, undef;\n" + statement),
|
||||
// Function scope with some different arugments
|
||||
Function("arg1", "arg2", "var o, undef;\n" + statement),
|
||||
// Deoptimized function scope
|
||||
Function("o", "undef", "with (Object) {}\n" + statement),
|
||||
// Inside with
|
||||
Function("with (Object) { " + statement + " }"),
|
||||
// Closure
|
||||
Function("o", "undef", "function myfunc() { return o + undef; }\n" + statement),
|
||||
// Let definitions in a block
|
||||
Function("{ let o, undef;\n" + statement + "}"),
|
||||
// Let block
|
||||
Function("let (o, undef) { " + statement + " }"),
|
||||
// Let block with some other variables
|
||||
Function("var v1, v2; let (o, undef) { " + statement + " }"),
|
||||
// Shadowed let block
|
||||
Function("o", "undef", "let (o, undef) { " + statement + " }"),
|
||||
// Let in a switch
|
||||
Function("var x = 4; switch (x) { case 4: let o, undef;" + statement + "\ncase 6: break;}"),
|
||||
// Let in for-in
|
||||
Function("var undef, o; for (let z in [1, 2]) { " + statement + " }"),
|
||||
// The more lets the merrier
|
||||
Function("let (x=4, y=5) { x + y; }\nlet (a, b, c) { a + b - c; }\nlet (o, undef) {" + statement + " }"),
|
||||
// Let destructuring
|
||||
Function("o", "undef", "let ([] = 4) {} let (o, undef) { " + statement + " }"),
|
||||
// Try-catch blocks
|
||||
Function("o", "undef", "try { let q = 4; try { let p = 4; } catch (e) {} } catch (e) {} let (o, undef) { " + statement + " }")
|
||||
];
|
||||
for (var f of cases) {
|
||||
check_one(expected, f, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
check("undef");
|
||||
check("o.b");
|
||||
check("o.length");
|
||||
check("o[true]");
|
||||
check("o[false]");
|
||||
check("o[null]");
|
||||
check("o[0]");
|
||||
check("o[1]");
|
||||
check("o[3]");
|
||||
check("o[256]");
|
||||
check("o[65536]");
|
||||
check("o[268435455]");
|
||||
check("o['1.1']");
|
||||
check("o[4 + 'h']", "o['4h']");
|
||||
check("this.x");
|
||||
check("ieval(undef)", "ieval(...)");
|
||||
check("ieval.call()", "ieval.call(...)");
|
||||
|
||||
for (let tok of ["|", "^", "&", "==", "!==", "===", "!==", "<", "<=", ">", ">=",
|
||||
">>", "<<", ">>>", "+", "-", "*", "/", "%"]) {
|
||||
check("o[(undef " + tok + " 4)]");
|
||||
}
|
||||
|
||||
check("o[!(o)]");
|
||||
check("o[~(o)]");
|
||||
check("o[+ (o)]");
|
||||
check("o[- (o)]");
|
||||
|
||||
// A few one off tests
|
||||
check_one("6", (function () { 6() }), " is not a function");
|
||||
check_one("Array.prototype.reverse.call(...)", (function () { Array.prototype.reverse.call('123'); }), " is read-only");
|
||||
check_one("null", function () { var [{ x }] = [null, {}]; }, " has no properties");
|
||||
|
||||
// Check fallback behavior
|
||||
check_one("undefined", (function () { for (let x of undefined) {} }), " has no properties");
|
@ -6,4 +6,4 @@ try {
|
||||
} catch (e) {
|
||||
msg = e.toString();
|
||||
}
|
||||
assertEq(msg, "TypeError: (void 0) is not an object or null");
|
||||
assertEq(msg, "TypeError: undefined is not an object or null");
|
||||
|
@ -5,7 +5,7 @@ try {
|
||||
a = ""
|
||||
for each(x in [0, 0, 0, 0]) {
|
||||
a %= x
|
||||
} ( - a)()
|
||||
} ( let (a=-a) a)()
|
||||
} catch (e) {
|
||||
actual = '' + e;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ assertEq(q[4294967295], 2);
|
||||
|
||||
try {
|
||||
[1,2,3,{a:0,b:1}].foo.bar;
|
||||
} catch (e) { assertEq(e.message, "[1, 2, 3, {a:0, b:1}].foo is undefined"); }
|
||||
} catch (e) { assertEq(e.message.search("\.foo is undefined") != -1, true); }
|
||||
|
||||
var a = [1 + 1, 3 * 2, 6 - 5, 14 % 6, 15 / 5, 1 << 3,
|
||||
8 >> 2, 5 | 2, 5 ^ 3, ~3, -3,"a" + "b", !true, !false];
|
||||
|
@ -1331,7 +1331,7 @@ CopyDecompiledTextForDecomposedOp(JSPrinter *jp, jsbytecode *pc)
|
||||
*/
|
||||
static int
|
||||
ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc,
|
||||
jsbytecode **pcstack, jsbytecode **lastDecomposedPC);
|
||||
jsbytecode **pcstack);
|
||||
|
||||
#define FAILED_EXPRESSION_DECOMPILER ((char *) 1)
|
||||
|
||||
@ -5734,105 +5734,493 @@ js_DecompileFunction(JSPrinter *jp)
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
char *
|
||||
js_DecompileValueGenerator(JSContext *cx, int spindex, jsval v,
|
||||
JSString *fallback)
|
||||
static JSObject *
|
||||
GetBlockChainAtPC(JSContext *cx, JSScript *script, jsbytecode *pc)
|
||||
{
|
||||
StackFrame *fp;
|
||||
JSScript *script;
|
||||
jsbytecode *pc;
|
||||
jsbytecode *start = script->main();
|
||||
|
||||
JS_ASSERT(pc >= start && pc < script->code + script->length);
|
||||
|
||||
JSObject *blockChain = NULL;
|
||||
for (jsbytecode *p = start; p < pc; p += GetBytecodeLength(p)) {
|
||||
JSOp op = JSOp(*p);
|
||||
|
||||
switch (op) {
|
||||
case JSOP_ENTERBLOCK:
|
||||
case JSOP_ENTERLET0:
|
||||
case JSOP_ENTERLET1: {
|
||||
JSObject *child = script->getObject(p);
|
||||
JS_ASSERT_IF(blockChain, child->asBlock().stackDepth() >= blockChain->asBlock().stackDepth());
|
||||
blockChain = child;
|
||||
break;
|
||||
}
|
||||
case JSOP_LEAVEBLOCK:
|
||||
case JSOP_LEAVEBLOCKEXPR:
|
||||
case JSOP_LEAVEFORLETIN: {
|
||||
// Some LEAVEBLOCK instructions are due to early exits via
|
||||
// return/break/etc. from block-scoped loops and functions. We
|
||||
// should ignore these instructions, since they don't really signal
|
||||
// the end of the block.
|
||||
jssrcnote *sn = js_GetSrcNote(script, p);
|
||||
if (!(sn && SN_TYPE(sn) == SRC_HIDDEN)) {
|
||||
JS_ASSERT(blockChain);
|
||||
blockChain = blockChain->asStaticBlock().enclosingBlock();
|
||||
JS_ASSERT_IF(blockChain, blockChain->isBlock());
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return blockChain;
|
||||
}
|
||||
|
||||
class PCStack
|
||||
{
|
||||
JSContext *cx;
|
||||
jsbytecode **stack;
|
||||
int depth_;
|
||||
|
||||
public:
|
||||
explicit PCStack(JSContext *cx)
|
||||
: cx(cx),
|
||||
stack(NULL),
|
||||
depth_(0)
|
||||
{}
|
||||
~PCStack();
|
||||
bool init(JSContext *cx, JSScript *script, jsbytecode *pc);
|
||||
int depth() const { return depth_; }
|
||||
jsbytecode *operator[](int i) const;
|
||||
};
|
||||
|
||||
PCStack::~PCStack()
|
||||
{
|
||||
cx->free_(stack);
|
||||
}
|
||||
|
||||
bool
|
||||
PCStack::init(JSContext *cx, JSScript *script, jsbytecode *pc)
|
||||
{
|
||||
stack = static_cast<jsbytecode **>(cx->malloc_(StackDepth(script) * sizeof(*stack)));
|
||||
if (!stack)
|
||||
return false;
|
||||
depth_ = ReconstructPCStack(cx, script, pc, stack);
|
||||
JS_ASSERT(depth_ >= 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Indexes the pcstack. */
|
||||
jsbytecode *
|
||||
PCStack::operator[](int i) const
|
||||
{
|
||||
if (i < 0) {
|
||||
i += depth_;
|
||||
JS_ASSERT(i >= 0);
|
||||
}
|
||||
JS_ASSERT(i < depth_);
|
||||
return stack[i];
|
||||
}
|
||||
|
||||
/*
|
||||
* The expression decompiler is invoked by error handling code to produce a
|
||||
* string representation of the erroring expression. As it's only a debugging
|
||||
* tool, it only supports basic expressions. For anything complicated, it simply
|
||||
* puts "(intermediate value)" into the error result.
|
||||
*
|
||||
* Here's the basic algorithm:
|
||||
*
|
||||
* 1. Find the stack location of the value whose expression we wish to
|
||||
* decompile. The error handler can explicitly pass this as an
|
||||
* argument. Otherwise, we search backwards down the stack for the offending
|
||||
* value.
|
||||
*
|
||||
* 2. Call ReconstructPCStack with the current frame's pc. This creates a stack
|
||||
* of pcs parallel to the interpreter stack; given an interpreter stack
|
||||
* location, the corresponding pc stack location contains the opcode that pushed
|
||||
* the value in the interpreter. Now, with the result of step 1, we have the
|
||||
* opcode responsible for pushing the value we want to decompile.
|
||||
*
|
||||
* 3. Pass the opcode to decompilePC. decompilePC is the main decompiler
|
||||
* routine, responsible for a string representation of the expression that
|
||||
* generated a certain stack location. decompilePC looks at one opcode and
|
||||
* returns the JS source equivalent of that opcode.
|
||||
*
|
||||
* 4. Expressions can, of course, contain subexpressions. For example, the
|
||||
* literals "4" and "5" are subexpressions of the addition operator in "4 +
|
||||
* 5". If we need to decompile a subexpression, we call decompilePC (step 2)
|
||||
* recursively on the operands' pcs. The result is a depth-first traversal of
|
||||
* the expression tree.
|
||||
*
|
||||
*/
|
||||
struct ExpressionDecompiler
|
||||
{
|
||||
JSContext *cx;
|
||||
StackFrame *fp;
|
||||
RootedScript script;
|
||||
RootedFunction fun;
|
||||
BindingVector *localNames;
|
||||
Sprinter sprinter;
|
||||
|
||||
ExpressionDecompiler(JSContext *cx, JSScript *script, JSFunction *fun)
|
||||
: cx(cx),
|
||||
script(cx, script),
|
||||
fun(cx, fun),
|
||||
localNames(NULL),
|
||||
sprinter(cx)
|
||||
{}
|
||||
~ExpressionDecompiler();
|
||||
bool init();
|
||||
bool decompilePC(jsbytecode *pc);
|
||||
JSAtom *getVar(unsigned slot);
|
||||
JSAtom *getArg(unsigned slot);
|
||||
JSAtom *findLetVar(jsbytecode *pc, unsigned depth);
|
||||
JSAtom *loadAtom(jsbytecode *pc);
|
||||
bool quote(JSString *s, uint32_t quote);
|
||||
bool write(const char *s);
|
||||
bool write(JSString *s);
|
||||
bool getOutput(char **out);
|
||||
};
|
||||
|
||||
bool
|
||||
ExpressionDecompiler::decompilePC(jsbytecode *pc)
|
||||
{
|
||||
JS_ASSERT(script->code <= pc && pc < script->code + script->length);
|
||||
|
||||
PCStack pcstack(cx);
|
||||
if (!pcstack.init(cx, script, pc))
|
||||
return NULL;
|
||||
|
||||
JSOp op = (JSOp)*pc;
|
||||
|
||||
// None of these stack-writing ops generates novel values.
|
||||
JS_ASSERT(op != JSOP_CASE && op != JSOP_DUP && op != JSOP_DUP2);
|
||||
|
||||
if (const char *token = CodeToken[op]) {
|
||||
// Handle simple cases of binary and unary operators.
|
||||
switch (js_CodeSpec[op].nuses) {
|
||||
case 2: {
|
||||
jssrcnote *sn = js_GetSrcNote(script, pc);
|
||||
if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP)
|
||||
return write("(") &&
|
||||
decompilePC(pcstack[-2]) &&
|
||||
write(" ") &&
|
||||
write(token) &&
|
||||
write(" ") &&
|
||||
decompilePC(pcstack[-1]) &&
|
||||
write(")");
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
return write(token) &&
|
||||
write("(") &&
|
||||
decompilePC(pcstack[-1]) &&
|
||||
write(")");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case JSOP_GETGNAME:
|
||||
case JSOP_CALLGNAME:
|
||||
case JSOP_NAME:
|
||||
case JSOP_CALLNAME:
|
||||
return write(loadAtom(pc));
|
||||
case JSOP_GETARG:
|
||||
case JSOP_CALLARG: {
|
||||
unsigned slot = GET_ARGNO(pc);
|
||||
JSAtom *atom = getArg(slot);
|
||||
if (!atom)
|
||||
break; // Destructuring
|
||||
return write(atom);
|
||||
}
|
||||
case JSOP_GETLOCAL:
|
||||
case JSOP_CALLLOCAL: {
|
||||
unsigned i = GET_SLOTNO(pc);
|
||||
JSAtom *atom;
|
||||
if (i >= script->nfixed) {
|
||||
i -= script->nfixed;
|
||||
JS_ASSERT(i < unsigned(pcstack.depth()));
|
||||
atom = findLetVar(pc, i);
|
||||
if (!atom)
|
||||
return decompilePC(pcstack[i]); // Destructing temporary
|
||||
} else {
|
||||
atom = getVar(i);
|
||||
}
|
||||
JS_ASSERT(atom);
|
||||
return write(atom);
|
||||
}
|
||||
case JSOP_CALLALIASEDVAR:
|
||||
case JSOP_GETALIASEDVAR: {
|
||||
JSAtom *atom = ScopeCoordinateName(cx->runtime, script, pc);
|
||||
JS_ASSERT(atom);
|
||||
return write(atom);
|
||||
}
|
||||
case JSOP_LENGTH:
|
||||
case JSOP_GETPROP:
|
||||
case JSOP_CALLPROP: {
|
||||
JSAtom *prop = (op == JSOP_LENGTH) ? cx->runtime->atomState.lengthAtom : loadAtom(pc);
|
||||
if (!decompilePC(pcstack[-1]))
|
||||
return false;
|
||||
if (IsIdentifier(prop))
|
||||
return write(".") &&
|
||||
quote(prop, '\0');
|
||||
else
|
||||
return write("[") &&
|
||||
quote(prop, '\'') &&
|
||||
write("]") >= 0;
|
||||
return true;
|
||||
}
|
||||
case JSOP_GETELEM:
|
||||
case JSOP_CALLELEM:
|
||||
return decompilePC(pcstack[-2]) &&
|
||||
write("[") &&
|
||||
decompilePC(pcstack[-1]) &&
|
||||
write("]");
|
||||
case JSOP_NULL:
|
||||
return write(js_null_str);
|
||||
case JSOP_TRUE:
|
||||
return write(js_true_str);
|
||||
case JSOP_FALSE:
|
||||
return write(js_false_str);
|
||||
case JSOP_ZERO:
|
||||
case JSOP_ONE:
|
||||
case JSOP_INT8:
|
||||
case JSOP_UINT16:
|
||||
case JSOP_UINT24:
|
||||
case JSOP_INT32: {
|
||||
int32_t i;
|
||||
switch (op) {
|
||||
case JSOP_ZERO:
|
||||
i = 0;
|
||||
break;
|
||||
case JSOP_ONE:
|
||||
i = 1;
|
||||
break;
|
||||
case JSOP_INT8:
|
||||
i = GET_INT8(pc);
|
||||
break;
|
||||
case JSOP_UINT16:
|
||||
i = GET_UINT16(pc);
|
||||
break;
|
||||
case JSOP_UINT24:
|
||||
i = GET_UINT24(pc);
|
||||
break;
|
||||
case JSOP_INT32:
|
||||
i = GET_INT32(pc);
|
||||
break;
|
||||
default:
|
||||
JS_NOT_REACHED("wat?");
|
||||
}
|
||||
return sprinter.printf("%d", i) >= 0;
|
||||
}
|
||||
case JSOP_STRING:
|
||||
return quote(loadAtom(pc), '"');
|
||||
case JSOP_UNDEFINED:
|
||||
return write(js_undefined_str);
|
||||
case JSOP_THIS:
|
||||
// |this| could convert to a very long object initialiser, so cite it by
|
||||
// its keyword name.
|
||||
return write(js_this_str);
|
||||
case JSOP_CALL:
|
||||
case JSOP_FUNCALL:
|
||||
return decompilePC(pcstack[-(GET_ARGC(pc) + 2)]) &&
|
||||
write("(...)");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return write("(intermediate value)");
|
||||
}
|
||||
|
||||
ExpressionDecompiler::~ExpressionDecompiler()
|
||||
{
|
||||
cx->delete_<BindingVector>(localNames);
|
||||
}
|
||||
|
||||
bool
|
||||
ExpressionDecompiler::init()
|
||||
{
|
||||
if (!sprinter.init())
|
||||
return false;
|
||||
|
||||
localNames = cx->new_<BindingVector>(cx);
|
||||
if (!localNames)
|
||||
return false;
|
||||
if (!GetOrderedBindings(cx, script->bindings, localNames))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ExpressionDecompiler::write(const char *s)
|
||||
{
|
||||
return sprinter.put(s) >= 0;
|
||||
}
|
||||
|
||||
bool
|
||||
ExpressionDecompiler::write(JSString *s)
|
||||
{
|
||||
return sprinter.putString(s) >= 0;
|
||||
}
|
||||
|
||||
bool
|
||||
ExpressionDecompiler::quote(JSString *s, uint32_t quote)
|
||||
{
|
||||
return QuoteString(&sprinter, s, quote) >= 0;
|
||||
}
|
||||
|
||||
JSAtom *
|
||||
ExpressionDecompiler::loadAtom(jsbytecode *pc)
|
||||
{
|
||||
return script->getAtom(GET_UINT32_INDEX(pc));
|
||||
}
|
||||
|
||||
JSAtom *
|
||||
ExpressionDecompiler::findLetVar(jsbytecode *pc, unsigned depth)
|
||||
{
|
||||
if (script->hasObjects()) {
|
||||
JSObject *chain = GetBlockChainAtPC(cx, script, pc);
|
||||
if (!chain)
|
||||
return NULL;
|
||||
JS_ASSERT(chain->isBlock());
|
||||
do {
|
||||
BlockObject &block = chain->asBlock();
|
||||
uint32_t blockDepth = block.stackDepth();
|
||||
uint32_t blockCount = block.slotCount();
|
||||
if (uint32_t(depth - blockDepth) < uint32_t(blockCount)) {
|
||||
for (Shape::Range r(block.lastProperty()); !r.empty(); r.popFront()) {
|
||||
const Shape &shape = r.front();
|
||||
if (shape.shortid() == int(depth - blockDepth)) {
|
||||
// !JSID_IS_ATOM(shape.propid()) happens for empty
|
||||
// destructuring variables in lets. They can be safely
|
||||
// ignored.
|
||||
if (JSID_IS_ATOM(shape.propid()))
|
||||
return JSID_TO_ATOM(shape.propid());
|
||||
}
|
||||
}
|
||||
}
|
||||
chain = chain->enclosingScope();
|
||||
} while (chain->isBlock());
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JSAtom *
|
||||
ExpressionDecompiler::getArg(unsigned slot)
|
||||
{
|
||||
JS_ASSERT(fun);
|
||||
JS_ASSERT(slot < script->bindings.count());
|
||||
return (*localNames)[slot].maybeName;
|
||||
}
|
||||
|
||||
JSAtom *
|
||||
ExpressionDecompiler::getVar(unsigned slot)
|
||||
{
|
||||
JS_ASSERT(fun);
|
||||
slot += fun->nargs;
|
||||
JS_ASSERT(slot < script->bindings.count());
|
||||
return (*localNames)[slot].maybeName;
|
||||
}
|
||||
|
||||
bool
|
||||
ExpressionDecompiler::getOutput(char **res)
|
||||
{
|
||||
ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0);
|
||||
*res = static_cast<char *>(cx->malloc_(len + 1));
|
||||
if (!*res)
|
||||
return false;
|
||||
js_memcpy(*res, sprinter.stringAt(0), len);
|
||||
(*res)[len] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
FindStartPC(JSContext *cx, JSScript *script, int spindex, Value v, jsbytecode **valuepc)
|
||||
{
|
||||
jsbytecode *current = *valuepc;
|
||||
|
||||
if (spindex == JSDVG_IGNORE_STACK)
|
||||
return true;
|
||||
|
||||
*valuepc = NULL;
|
||||
|
||||
PCStack pcstack(cx);
|
||||
if (!pcstack.init(cx, script, current))
|
||||
return false;
|
||||
|
||||
if (spindex == JSDVG_SEARCH_STACK) {
|
||||
// We search from fp->sp to base to find the most recently calculated
|
||||
// value matching v under assumption that it is it that caused
|
||||
// exception.
|
||||
Value *stackBase = cx->regs().spForStackDepth(0);
|
||||
Value *sp = cx->regs().sp;
|
||||
do {
|
||||
if (sp == stackBase)
|
||||
return true;
|
||||
} while (*--sp != v);
|
||||
if (sp < stackBase + pcstack.depth())
|
||||
*valuepc = pcstack[sp - stackBase];
|
||||
} else {
|
||||
*valuepc = pcstack[spindex];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
DecompileExpressionFromStack(JSContext *cx, int spindex, Value v, char **res)
|
||||
{
|
||||
JS_ASSERT(spindex < 0 ||
|
||||
spindex == JSDVG_IGNORE_STACK ||
|
||||
spindex == JSDVG_SEARCH_STACK);
|
||||
|
||||
*res = NULL;
|
||||
|
||||
if (!cx->hasfp() || !cx->fp()->isScriptFrame())
|
||||
goto do_fallback;
|
||||
return true;
|
||||
StackFrame *fp = js_GetTopStackFrame(cx, FRAME_EXPAND_ALL);
|
||||
JSScript *script = fp->script();
|
||||
jsbytecode *valuepc = cx->regs().pc;
|
||||
JSFunction *fun = fp->maybeFun();
|
||||
JS_ASSERT(script->code <= valuepc && valuepc < script->code + script->length);
|
||||
|
||||
fp = js_GetTopStackFrame(cx, FRAME_EXPAND_ALL);
|
||||
script = fp->script();
|
||||
pc = cx->regs().pc;
|
||||
JS_ASSERT(script->code <= pc && pc < script->code + script->length);
|
||||
// Give up if in prologue.
|
||||
if (valuepc < script->main())
|
||||
return true;
|
||||
|
||||
if (pc < script->main())
|
||||
goto do_fallback;
|
||||
if (!FindStartPC(cx, script, spindex, v, &valuepc))
|
||||
return false;
|
||||
if (!valuepc)
|
||||
return true;
|
||||
|
||||
if (spindex != JSDVG_IGNORE_STACK) {
|
||||
jsbytecode **pcstack;
|
||||
ExpressionDecompiler ea(cx, script, fun);
|
||||
if (!ea.init())
|
||||
return false;
|
||||
if (!ea.decompilePC(valuepc))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Prepare computing pcstack containing pointers to opcodes that
|
||||
* populated interpreter's stack with its current content.
|
||||
*/
|
||||
pcstack = (jsbytecode **)
|
||||
cx->malloc_(StackDepth(script) * sizeof *pcstack);
|
||||
if (!pcstack)
|
||||
return NULL;
|
||||
jsbytecode *lastDecomposedPC = NULL;
|
||||
int pcdepth = ReconstructPCStack(cx, script, pc, pcstack, &lastDecomposedPC);
|
||||
if (pcdepth < 0)
|
||||
goto release_pcstack;
|
||||
|
||||
if (spindex != JSDVG_SEARCH_STACK) {
|
||||
JS_ASSERT(spindex < 0);
|
||||
pcdepth += spindex;
|
||||
if (pcdepth < 0)
|
||||
goto release_pcstack;
|
||||
pc = pcstack[pcdepth];
|
||||
} else {
|
||||
/*
|
||||
* We search from fp->sp to base to find the most recently
|
||||
* calculated value matching v under assumption that it is
|
||||
* it that caused exception, see bug 328664.
|
||||
*/
|
||||
Value *stackBase = cx->regs().spForStackDepth(0);
|
||||
Value *sp = cx->regs().sp;
|
||||
do {
|
||||
if (sp == stackBase) {
|
||||
pcdepth = -1;
|
||||
goto release_pcstack;
|
||||
}
|
||||
} while (*--sp != v);
|
||||
|
||||
/*
|
||||
* The value may have come from beyond stackBase + pcdepth, meaning
|
||||
* that it came from a temporary slot pushed by the interpreter or
|
||||
* arguments pushed for an Invoke call. Only update pc if beneath
|
||||
* stackBase + pcdepth. If above, the value may or may not be
|
||||
* produced by the current pc. Since it takes a fairly contrived
|
||||
* combination of calls to produce a situation where this is not
|
||||
* what we want, we just use the current pc.
|
||||
*
|
||||
* If we are in the middle of a decomposed opcode, use the outer
|
||||
* 'fat' opcode itself. Any source notes for the operation which
|
||||
* are needed during decompilation will be associated with the
|
||||
* outer opcode.
|
||||
*/
|
||||
if (sp < stackBase + pcdepth) {
|
||||
pc = pcstack[sp - stackBase];
|
||||
if (lastDecomposedPC) {
|
||||
size_t len = GetDecomposeLength(lastDecomposedPC,
|
||||
js_CodeSpec[*lastDecomposedPC].length);
|
||||
if (unsigned(pc - lastDecomposedPC) < len)
|
||||
pc = lastDecomposedPC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
release_pcstack:
|
||||
cx->free_(pcstack);
|
||||
if (pcdepth < 0)
|
||||
goto do_fallback;
|
||||
}
|
||||
return ea.getOutput(res);
|
||||
}
|
||||
|
||||
char *
|
||||
js_DecompileValueGenerator(JSContext *cx, int spindex, jsval v,
|
||||
JSString *fallback)
|
||||
{
|
||||
{
|
||||
char *name = DecompileExpression(cx, script, fp->maybeFun(), pc);
|
||||
if (name != FAILED_EXPRESSION_DECOMPILER)
|
||||
return name;
|
||||
char *result;
|
||||
if (!DecompileExpressionFromStack(cx, spindex, v, &result))
|
||||
return NULL;
|
||||
if (result) {
|
||||
if (strcmp(result, "(intermediate value)"))
|
||||
return result;
|
||||
cx->free_(result);
|
||||
}
|
||||
}
|
||||
|
||||
do_fallback:
|
||||
if (!fallback) {
|
||||
if (v.isUndefined())
|
||||
return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)"
|
||||
fallback = js_ValueToSource(cx, v);
|
||||
if (!fallback)
|
||||
return NULL;
|
||||
@ -5927,7 +6315,7 @@ DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun,
|
||||
if (!g.pcstack)
|
||||
return NULL;
|
||||
|
||||
int pcdepth = ReconstructPCStack(cx, script, begin, g.pcstack, NULL);
|
||||
int pcdepth = ReconstructPCStack(cx, script, begin, g.pcstack);
|
||||
if (pcdepth < 0)
|
||||
return FAILED_EXPRESSION_DECOMPILER;
|
||||
|
||||
@ -5946,7 +6334,7 @@ DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun,
|
||||
unsigned
|
||||
js_ReconstructStackDepth(JSContext *cx, JSScript *script, jsbytecode *pc)
|
||||
{
|
||||
return ReconstructPCStack(cx, script, pc, NULL, NULL);
|
||||
return ReconstructPCStack(cx, script, pc, NULL);
|
||||
}
|
||||
|
||||
#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, -1);
|
||||
@ -6008,7 +6396,7 @@ SimulateOp(JSContext *cx, JSScript *script, JSOp op, const JSCodeSpec *cs,
|
||||
|
||||
static int
|
||||
ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *target,
|
||||
jsbytecode **pcstack, jsbytecode **lastDecomposedPC)
|
||||
jsbytecode **pcstack)
|
||||
{
|
||||
/*
|
||||
* Walk forward from script->main and compute the stack depth and stack of
|
||||
@ -6025,15 +6413,10 @@ ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *target,
|
||||
for (; pc < target; pc += oplen) {
|
||||
JSOp op = JSOp(*pc);
|
||||
const JSCodeSpec *cs = &js_CodeSpec[op];
|
||||
oplen = cs->length;
|
||||
if (oplen < 0)
|
||||
oplen = js_GetVariableBytecodeLength(pc);
|
||||
oplen = GetBytecodeLength(pc);
|
||||
|
||||
if (cs->format & JOF_DECOMPOSE) {
|
||||
if (lastDecomposedPC)
|
||||
*lastDecomposedPC = pc;
|
||||
if (cs->format & JOF_DECOMPOSE)
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* A (C ? T : E) expression requires skipping either T (if target is in
|
||||
|
@ -14,7 +14,7 @@ var expect = '';
|
||||
printBugNumber(BUGNUMBER);
|
||||
START(summary);
|
||||
|
||||
expect = 'TypeError: <x/>.hasOwnProperty is not a constructor';
|
||||
expect = 'TypeError: (intermediate value).hasOwnProperty is not a constructor';
|
||||
actual = '';
|
||||
|
||||
try
|
||||
|
@ -15,7 +15,7 @@ enterFunc (String (BUGNUMBER));
|
||||
|
||||
// should throw an error in strict mode
|
||||
var actual = '';
|
||||
var expect = 's.length is read-only';
|
||||
var expect = '"length" is read-only';
|
||||
var status = summary + ': Throw if STRICT and WERROR is enabled';
|
||||
|
||||
if (!options().match(/strict/))
|
||||
|
@ -21,7 +21,7 @@ function test()
|
||||
printStatus (summary);
|
||||
|
||||
print('See Also bug 365891');
|
||||
expect = /TypeError: a\(1\) (has no properties|is null)/;
|
||||
expect = /TypeError: a\(.+\) (has no properties|is null)/;
|
||||
try
|
||||
{
|
||||
function a(){return null;} a(1)[0];
|
||||
@ -32,7 +32,7 @@ function test()
|
||||
}
|
||||
reportMatch(expect, actual, summary);
|
||||
|
||||
expect = /TypeError: \/a\/\.exec\("b"\) (has no properties|is null)/;
|
||||
expect = /TypeError: \(intermediate value\).exec\(.+\) (has no properties|is null)/;
|
||||
try
|
||||
{
|
||||
/a/.exec("b")[0];
|
||||
|
@ -29,7 +29,7 @@ reportCompare(expect, actual, summary + ': join');
|
||||
|
||||
// reverse
|
||||
value = '123';
|
||||
expect = 'TypeError: Array.prototype.reverse.call(value) is read-only';
|
||||
expect = 'TypeError: Array.prototype.reverse.call(...) is read-only';
|
||||
try
|
||||
{
|
||||
actual = Array.prototype.reverse.call(value) + '';
|
||||
@ -42,7 +42,7 @@ reportCompare(expect, actual, summary + ': reverse');
|
||||
|
||||
// sort
|
||||
value = 'cba';
|
||||
expect = 'TypeError: Array.prototype.sort.call(value) is read-only';
|
||||
expect = 'TypeError: Array.prototype.sort.call(...) is read-only';
|
||||
try
|
||||
{
|
||||
actual = Array.prototype.sort.call(value) + '';
|
||||
@ -69,7 +69,7 @@ reportCompare('abc', value, summary + ': push');
|
||||
|
||||
// pop
|
||||
value = 'abc';
|
||||
expect = "TypeError: property Array.prototype.pop.call(value) is non-configurable and can't be deleted";
|
||||
expect = "TypeError: property Array.prototype.pop.call(...) is non-configurable and can't be deleted";
|
||||
try
|
||||
{
|
||||
actual = Array.prototype.pop.call(value);
|
||||
@ -83,7 +83,7 @@ reportCompare('abc', value, summary + ': pop');
|
||||
|
||||
// unshift
|
||||
value = 'def';
|
||||
expect = 'TypeError: Array.prototype.unshift.call(value, "a", "b", "c") is read-only';
|
||||
expect = 'TypeError: Array.prototype.unshift.call(...) is read-only';
|
||||
try
|
||||
{
|
||||
actual = Array.prototype.unshift.call(value, 'a', 'b', 'c');
|
||||
@ -97,7 +97,7 @@ reportCompare('def', value, summary + ': unshift');
|
||||
|
||||
// shift
|
||||
value = 'abc';
|
||||
expect = 'TypeError: Array.prototype.shift.call(value) is read-only';
|
||||
expect = 'TypeError: Array.prototype.shift.call(...) is read-only';
|
||||
try
|
||||
{
|
||||
actual = Array.prototype.shift.call(value);
|
||||
@ -111,7 +111,7 @@ reportCompare('abc', value, summary + ': shift');
|
||||
|
||||
// splice
|
||||
value = 'abc';
|
||||
expect = 'TypeError: Array.prototype.splice.call(value, 1, 1) is read-only';
|
||||
expect = 'TypeError: Array.prototype.splice.call(...) is read-only';
|
||||
try
|
||||
{
|
||||
actual = Array.prototype.splice.call(value, 1, 1) + '';
|
||||
|
@ -20,7 +20,7 @@ function test()
|
||||
printBugNumber(BUGNUMBER);
|
||||
printStatus (summary);
|
||||
|
||||
expect = 'TypeError: y.a = [2 for each (p in [])] is not a function';
|
||||
expect = 'TypeError: [] is not a function';
|
||||
try
|
||||
{
|
||||
eval('y = {}; (y.a = [2 for each (p in [])])();');
|
||||
|
@ -19,7 +19,7 @@ function test()
|
||||
printBugNumber(BUGNUMBER);
|
||||
printStatus (summary);
|
||||
|
||||
expect = /TypeError: (\(let \(b = 1\) 2\).c is not a function|Cannot find function c.)/;
|
||||
expect = /TypeError: (.+\.c is not a function|Cannot find function c.)/;
|
||||
actual = 'No Error';
|
||||
try
|
||||
{
|
||||
@ -32,7 +32,7 @@ function test()
|
||||
|
||||
reportMatch(expect, actual, summary + ': 1');
|
||||
|
||||
expect = /TypeError: (\(let \(b = 1, d = 2\) 2\).c is not a function|Cannot find function c.)/;
|
||||
expect = /TypeError: (.+\.c is not a function|Cannot find function c.)/;
|
||||
actual = 'No Error';
|
||||
try
|
||||
{
|
||||
@ -45,7 +45,7 @@ function test()
|
||||
|
||||
reportMatch(expect, actual, summary + ': 2');
|
||||
|
||||
expect = /TypeError: (\(let \(b = 1, d = 2\) 2\).c is not a function|Cannot find function c.)/;
|
||||
expect = /TypeError: (.+\.c is not a function|Cannot find function c.)/;
|
||||
actual = 'No Error';
|
||||
try
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ function test()
|
||||
printBugNumber(BUGNUMBER);
|
||||
printStatus (summary);
|
||||
|
||||
expect = /TypeError: NaN is not a function/;
|
||||
expect = /TypeError: .+ is not a function/;
|
||||
actual = 'No Error';
|
||||
try
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ function test()
|
||||
printBugNumber(BUGNUMBER);
|
||||
printStatus (summary);
|
||||
|
||||
expect = /TypeError: NaN is not a function/;
|
||||
expect = /TypeError: .+ is not a function/;
|
||||
actual = 'No Error';
|
||||
try
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ function test()
|
||||
printBugNumber(BUGNUMBER);
|
||||
printStatus (summary);
|
||||
|
||||
expect = /TypeError: NaN is not a function/;
|
||||
expect = /TypeError: .+ is not a function/;
|
||||
actual = 'No Error';
|
||||
try
|
||||
{
|
||||
|
@ -21,7 +21,7 @@ function test()
|
||||
printBugNumber(BUGNUMBER);
|
||||
printStatus (summary);
|
||||
|
||||
expect = /TypeError: \[1, 2, 3, 4\].g (has no properties|is undefined)/;
|
||||
expect = /TypeError: .+\.g (has no properties|is undefined)/;
|
||||
actual = '';
|
||||
try
|
||||
{
|
||||
|
@ -22,7 +22,7 @@ function test()
|
||||
printBugNumber(BUGNUMBER);
|
||||
printStatus (summary);
|
||||
|
||||
expect = 'TypeError: [].__proto__ is not a function';
|
||||
expect = 'TypeError: (intermediate value).__proto__ is not a function';
|
||||
|
||||
jit(true);
|
||||
|
||||
|
@ -20,7 +20,7 @@ function test()
|
||||
printBugNumber(BUGNUMBER);
|
||||
printStatus (summary);
|
||||
|
||||
expect = /TypeError: let \(a = undefined\) a (is undefined|has no properties)/;
|
||||
expect = "TypeError: undefined has no properties";
|
||||
try
|
||||
{
|
||||
(let (a=undefined) a).b = 3;
|
||||
@ -30,7 +30,7 @@ function test()
|
||||
actual = ex + '';
|
||||
}
|
||||
|
||||
reportMatch(expect, actual, summary);
|
||||
reportCompare(expect, actual, summary);
|
||||
|
||||
exitFunc ('test');
|
||||
}
|
||||
|
@ -9,6 +9,6 @@ try {
|
||||
err = e;
|
||||
}
|
||||
assertEq(err instanceof TypeError, true);
|
||||
assertEq(err.message, "[][j] is undefined");
|
||||
assertEq(err.message, "(intermediate value)[j] is undefined");
|
||||
|
||||
reportCompare(0, 0, 'ok');
|
||||
|
Loading…
Reference in New Issue
Block a user