mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 08:35:26 +00:00
bug=421806 r=brendan a1.9=blockin1.9 fixing decompiler regressions with interpreter stack modeling
This commit is contained in:
parent
1c996a05b3
commit
54cd9bd2fe
@ -720,7 +720,7 @@ JSObject *
|
||||
js_NewGenerator(JSContext *cx, JSStackFrame *fp)
|
||||
{
|
||||
JSObject *obj;
|
||||
uintN argc, nargs, nvars, depth, nslots;
|
||||
uintN argc, nargs, nvars, nslots;
|
||||
JSGenerator *gen;
|
||||
jsval *newsp;
|
||||
|
||||
@ -733,8 +733,7 @@ js_NewGenerator(JSContext *cx, JSStackFrame *fp)
|
||||
argc = fp->argc;
|
||||
nargs = JS_MAX(argc, fp->fun->nargs);
|
||||
nvars = fp->nvars;
|
||||
depth = fp->script->depth;
|
||||
nslots = 2 + nargs + nvars + 2 * depth;
|
||||
nslots = 2 + nargs + nvars + fp->script->depth;
|
||||
|
||||
/* Allocate obj's private data struct. */
|
||||
gen = (JSGenerator *)
|
||||
@ -795,7 +794,7 @@ js_NewGenerator(JSContext *cx, JSStackFrame *fp)
|
||||
gen->frame.pc = fp->pc;
|
||||
|
||||
/* Allocate generating pc and operand stack space. */
|
||||
gen->frame.spbase = gen->frame.sp = newsp + depth;
|
||||
gen->frame.spbase = gen->frame.sp = newsp;
|
||||
|
||||
/* Copy remaining state (XXX sharp* and xml* should be local vars). */
|
||||
gen->frame.sharpDepth = 0;
|
||||
|
@ -615,7 +615,8 @@ struct JSPrinter {
|
||||
JSPackedBool pretty; /* pretty-print: indent, use newlines */
|
||||
JSPackedBool grouped; /* in parenthesized expression context */
|
||||
JSScript *script; /* script being printed */
|
||||
jsbytecode *dvgfence; /* js_DecompileValueGenerator fencepost */
|
||||
jsbytecode *dvgfence; /* DecompileExpression fencepost */
|
||||
jsbytecode **pcstack; /* DecompileExpression modelled stack */
|
||||
JSFunction *fun; /* interpreted function */
|
||||
jsuword *localNames; /* argument and variable names */
|
||||
};
|
||||
@ -644,6 +645,7 @@ JS_NEW_PRINTER(JSContext *cx, const char *name, JSFunction *fun,
|
||||
jp->grouped = (indent & JS_IN_GROUP_CONTEXT) != 0;
|
||||
jp->script = NULL;
|
||||
jp->dvgfence = NULL;
|
||||
jp->pcstack = NULL;
|
||||
jp->fun = fun;
|
||||
jp->localNames = NULL;
|
||||
if (fun && FUN_INTERPRETED(fun) && JS_GET_LOCAL_NAME_COUNT(fun)) {
|
||||
@ -763,12 +765,39 @@ typedef struct SprintStack {
|
||||
JSPrinter *printer; /* permanent output goes here */
|
||||
} SprintStack;
|
||||
|
||||
/*
|
||||
* Find the depth of the operand stack when the interpreter reaches the given
|
||||
* pc in script. pcstack must have space for least script->depth elements. On
|
||||
* return it will contain pointers to opcodes that populated the interpreter's
|
||||
* current operand stack.
|
||||
*
|
||||
* This function cannot raise an exception or error. However, due to a risk of
|
||||
* potential bugs when modeling the stack, the function returns -1 if it
|
||||
* detects an inconsistency in the model. Such an inconsistency triggers an
|
||||
* assert in a debug build.
|
||||
*/
|
||||
static intN
|
||||
ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc,
|
||||
jsbytecode **pcstack);
|
||||
|
||||
#define FAILED_EXPRESSION_DECOMPILER ((char *) 1)
|
||||
|
||||
/*
|
||||
* Decompile a part of expression up to the given pc. The function returns
|
||||
* NULL on out-of-memory, or the FAILED_EXPRESSION_DECOMPILER sentinel when
|
||||
* the decompiler fails due to a bug and/or unimplemented feature, or the
|
||||
* decompiled string on success.
|
||||
*/
|
||||
static char *
|
||||
DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun,
|
||||
jsbytecode *pc);
|
||||
|
||||
/*
|
||||
* Get a stacked offset from ss->sprinter.base, or if the stacked value |off|
|
||||
* is negative, lazily fetch the generating pc at |spindex = 1 + off| and try
|
||||
* to decompile the code that generated the missing value. This is used when
|
||||
* is negative, fetch the generating pc from printer->pcstack[-2 - off] and
|
||||
* decompile the code that generated the missing value. This is used when
|
||||
* reporting errors, where the model stack will lack |pcdepth| non-negative
|
||||
* offsets (see js_DecompileValueGenerator and js_DecompileCode).
|
||||
* offsets (see DecompileExpression and DecompileCode).
|
||||
*
|
||||
* If the stacked offset is -1, return 0 to index the NUL padding at the start
|
||||
* of ss->sprinter.base. If this happens, it means there is a decompiler bug
|
||||
@ -778,30 +807,35 @@ static ptrdiff_t
|
||||
GetOff(SprintStack *ss, uintN i)
|
||||
{
|
||||
ptrdiff_t off;
|
||||
jsbytecode *pc;
|
||||
char *bytes;
|
||||
|
||||
off = ss->offsets[i];
|
||||
if (off < 0) {
|
||||
#if defined DEBUG_brendan || defined DEBUG_mrbkap || defined DEBUG_crowder
|
||||
JS_ASSERT(off < -1);
|
||||
#endif
|
||||
if (++off == 0) {
|
||||
if (!ss->sprinter.base && SprintPut(&ss->sprinter, "", 0) >= 0)
|
||||
memset(ss->sprinter.base, 0, ss->sprinter.offset);
|
||||
return 0;
|
||||
}
|
||||
if (off >= 0)
|
||||
return off;
|
||||
|
||||
bytes = js_DecompileValueGenerator(ss->sprinter.context, off,
|
||||
JSVAL_NULL, NULL);
|
||||
JS_ASSERT(off <= -2);
|
||||
JS_ASSERT(ss->printer->pcstack);
|
||||
if (off < -2 && ss->printer->pcstack) {
|
||||
pc = ss->printer->pcstack[-2 - off];
|
||||
bytes = DecompileExpression(ss->sprinter.context, ss->printer->script,
|
||||
ss->printer->fun, pc);
|
||||
if (!bytes)
|
||||
return 0;
|
||||
off = SprintCString(&ss->sprinter, bytes);
|
||||
if (off < 0)
|
||||
off = 0;
|
||||
ss->offsets[i] = off;
|
||||
JS_free(ss->sprinter.context, bytes);
|
||||
if (bytes != FAILED_EXPRESSION_DECOMPILER) {
|
||||
off = SprintCString(&ss->sprinter, bytes);
|
||||
if (off < 0)
|
||||
off = 0;
|
||||
ss->offsets[i] = off;
|
||||
JS_free(ss->sprinter.context, bytes);
|
||||
return off;
|
||||
}
|
||||
if (!ss->sprinter.base && SprintPut(&ss->sprinter, "", 0) >= 0) {
|
||||
memset(ss->sprinter.base, 0, ss->sprinter.offset);
|
||||
ss->offsets[i] = -1;
|
||||
}
|
||||
}
|
||||
return off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *
|
||||
@ -4520,9 +4554,9 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
|
||||
return pc;
|
||||
}
|
||||
|
||||
JSBool
|
||||
js_DecompileCode(JSPrinter *jp, JSScript *script, jsbytecode *pc, uintN len,
|
||||
uintN pcdepth)
|
||||
static JSBool
|
||||
DecompileCode(JSPrinter *jp, JSScript *script, jsbytecode *pc, uintN len,
|
||||
uintN pcdepth)
|
||||
{
|
||||
uintN depth, i;
|
||||
SprintStack ss;
|
||||
@ -4555,28 +4589,9 @@ js_DecompileCode(JSPrinter *jp, JSScript *script, jsbytecode *pc, uintN len,
|
||||
*/
|
||||
ss.top = pcdepth;
|
||||
if (pcdepth != 0) {
|
||||
JSStackFrame *fp;
|
||||
ptrdiff_t top;
|
||||
|
||||
for (fp = cx->fp; fp && !fp->script; fp = fp->down)
|
||||
continue;
|
||||
top = fp ? fp->sp - fp->spbase : 0;
|
||||
for (i = 0; i < pcdepth; i++) {
|
||||
ss.offsets[i] = -1;
|
||||
ss.opcodes[i] = JSOP_NOP;
|
||||
}
|
||||
if (fp && fp->pc == pc && (uintN)top == pcdepth) {
|
||||
for (i = 0; i < pcdepth; i++) {
|
||||
ptrdiff_t off;
|
||||
jsbytecode *genpc;
|
||||
|
||||
off = (intN)i - (intN)depth;
|
||||
genpc = (jsbytecode *) fp->spbase[off];
|
||||
if (JS_UPTRDIFF(genpc, script->code) < script->length) {
|
||||
ss.offsets[i] += (ptrdiff_t)i - top;
|
||||
ss.opcodes[i] = *genpc;
|
||||
}
|
||||
}
|
||||
ss.offsets[i] = -2 - i;
|
||||
ss.opcodes[i] = *jp->pcstack[i];
|
||||
}
|
||||
}
|
||||
|
||||
@ -4603,7 +4618,7 @@ out:
|
||||
JSBool
|
||||
js_DecompileScript(JSPrinter *jp, JSScript *script)
|
||||
{
|
||||
return js_DecompileCode(jp, script, script->code, (uintN)script->length, 0);
|
||||
return DecompileCode(jp, script, script->code, (uintN)script->length, 0);
|
||||
}
|
||||
|
||||
static const char native_code_str[] = "\t[native code]\n";
|
||||
@ -4621,7 +4636,7 @@ js_DecompileFunctionBody(JSPrinter *jp)
|
||||
}
|
||||
|
||||
script = jp->fun->u.i.script;
|
||||
return js_DecompileCode(jp, script, script->code, (uintN)script->length, 0);
|
||||
return DecompileCode(jp, script, script->code, (uintN)script->length, 0);
|
||||
}
|
||||
|
||||
JSBool
|
||||
@ -4747,7 +4762,7 @@ js_DecompileFunction(JSPrinter *jp)
|
||||
}
|
||||
|
||||
len = fun->u.i.script->code + fun->u.i.script->length - pc;
|
||||
ok = js_DecompileCode(jp, fun->u.i.script, pc, (uintN)len, 0);
|
||||
ok = DecompileCode(jp, fun->u.i.script, pc, (uintN)len, 0);
|
||||
if (!ok)
|
||||
return JS_FALSE;
|
||||
|
||||
@ -4763,35 +4778,15 @@ js_DecompileFunction(JSPrinter *jp)
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the depth of the operand stack when the interpreter reaches the given
|
||||
* pc in script. When pcstack is not null, it must be an array with space for
|
||||
* at least script->depth elements. On return the array will contain pointers
|
||||
* to opcodes that populated the interpreter's current operand stack.
|
||||
*
|
||||
* This function cannot raise an exception or error. However, due to a risk of
|
||||
* potential bugs when modeling the stack, the function returns -1 if it
|
||||
* detects an inconsistency in the model. Such an inconsistency triggers an
|
||||
* assert in a debug build.
|
||||
*/
|
||||
static intN
|
||||
ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc,
|
||||
jsbytecode **pcstack);
|
||||
|
||||
char *
|
||||
js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v,
|
||||
JSString *fallback)
|
||||
{
|
||||
JSStackFrame *fp;
|
||||
jsbytecode *pc, *begin, *end;
|
||||
jsbytecode *pc;
|
||||
JSScript *script;
|
||||
JSOp op;
|
||||
const JSCodeSpec *cs;
|
||||
jssrcnote *sn;
|
||||
ptrdiff_t len;
|
||||
intN pcdepth;
|
||||
jsval *sp;
|
||||
JSPrinter *jp;
|
||||
char *name;
|
||||
|
||||
JS_ASSERT(spindex < 0 ||
|
||||
@ -4866,10 +4861,35 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v,
|
||||
goto do_fallback;
|
||||
}
|
||||
|
||||
/*
|
||||
* We know the address of the opcode that triggered the diagnostic. Find
|
||||
* the decompilation limits for the opcode and its stack depth.
|
||||
*/
|
||||
name = DecompileExpression(cx, script, fp->fun, pc);
|
||||
if (name != FAILED_EXPRESSION_DECOMPILER)
|
||||
return name;
|
||||
|
||||
do_fallback:
|
||||
if (!fallback) {
|
||||
fallback = js_ValueToSource(cx, v);
|
||||
if (!fallback)
|
||||
return NULL;
|
||||
}
|
||||
return js_DeflateString(cx, JSSTRING_CHARS(fallback),
|
||||
JSSTRING_LENGTH(fallback));
|
||||
}
|
||||
|
||||
static char *
|
||||
DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun,
|
||||
jsbytecode *pc)
|
||||
{
|
||||
JSOp op;
|
||||
const JSCodeSpec *cs;
|
||||
jsbytecode *begin, *end;
|
||||
jssrcnote *sn;
|
||||
ptrdiff_t len;
|
||||
jsbytecode **pcstack;
|
||||
intN pcdepth;
|
||||
JSPrinter *jp;
|
||||
char *name;
|
||||
|
||||
JS_ASSERT(script->main <= pc && pc < script->code + script->length);
|
||||
op = (JSOp) *pc;
|
||||
if (op == JSOP_TRAP)
|
||||
op = JS_GetTrapOpcode(cx, script, pc);
|
||||
@ -4893,7 +4913,7 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v,
|
||||
* fall back to the base object.
|
||||
*/
|
||||
if (op == JSOP_BINDNAME)
|
||||
goto do_fallback;
|
||||
return FAILED_EXPRESSION_DECOMPILER;
|
||||
|
||||
/* NAME ops are self-contained, others require left or right context. */
|
||||
cs = &js_CodeSpec[op];
|
||||
@ -4906,7 +4926,7 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v,
|
||||
case 0:
|
||||
sn = js_GetSrcNote(script, pc);
|
||||
if (!sn)
|
||||
goto do_fallback;
|
||||
return FAILED_EXPRESSION_DECOMPILER;
|
||||
switch (SN_TYPE(sn)) {
|
||||
case SRC_PCBASE:
|
||||
begin -= js_GetSrcNoteOffset(sn, 0);
|
||||
@ -4916,39 +4936,41 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v,
|
||||
begin += cs->length;
|
||||
break;
|
||||
default:
|
||||
goto do_fallback;
|
||||
return FAILED_EXPRESSION_DECOMPILER;
|
||||
}
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
len = PTRDIFF(end, begin, jsbytecode);
|
||||
if (len <= 0)
|
||||
goto do_fallback;
|
||||
return FAILED_EXPRESSION_DECOMPILER;
|
||||
|
||||
pcdepth = ReconstructPCStack(cx, script, begin, NULL);
|
||||
if (pcdepth < 0)
|
||||
goto do_fallback;
|
||||
pcstack = (jsbytecode **) JS_malloc(cx, script->depth * sizeof *pcstack);
|
||||
if (!pcstack)
|
||||
return NULL;
|
||||
|
||||
/* From this point the control must flow through the label out. */
|
||||
pcdepth = ReconstructPCStack(cx, script, begin, pcstack);
|
||||
if (pcdepth < 0) {
|
||||
name = FAILED_EXPRESSION_DECOMPILER;
|
||||
goto out;
|
||||
}
|
||||
|
||||
name = NULL;
|
||||
jp = JS_NEW_PRINTER(cx, "js_DecompileValueGenerator", fp->fun, 0, JS_FALSE);
|
||||
jp = JS_NEW_PRINTER(cx, "js_DecompileValueGenerator", fun, 0, JS_FALSE);
|
||||
if (jp) {
|
||||
jp->dvgfence = end;
|
||||
if (js_DecompileCode(jp, script, begin, (uintN)len, (uintN)pcdepth)) {
|
||||
jp->pcstack = pcstack;
|
||||
if (DecompileCode(jp, script, begin, (uintN) len, (uintN) pcdepth)) {
|
||||
name = (jp->sprinter.base) ? jp->sprinter.base : (char *) "";
|
||||
name = JS_strdup(cx, name);
|
||||
}
|
||||
js_DestroyPrinter(jp);
|
||||
}
|
||||
return name;
|
||||
|
||||
do_fallback:
|
||||
if (!fallback) {
|
||||
fallback = js_ValueToSource(cx, v);
|
||||
if (!fallback)
|
||||
return NULL;
|
||||
}
|
||||
return js_DeflateString(cx, JSSTRING_CHARS(fallback),
|
||||
JSSTRING_LENGTH(fallback));
|
||||
out:
|
||||
JS_free(cx, pcstack);
|
||||
return name;
|
||||
}
|
||||
|
||||
static intN
|
||||
@ -4962,6 +4984,8 @@ ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc,
|
||||
ptrdiff_t oplen;
|
||||
jssrcnote *sn;
|
||||
uint32 type;
|
||||
jsbytecode *pc2;
|
||||
intN i;
|
||||
|
||||
#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, -1);
|
||||
|
||||
@ -5026,8 +5050,7 @@ ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc,
|
||||
case JOF_TABLESWITCH:
|
||||
case JOF_TABLESWITCHX:
|
||||
{
|
||||
jsint jmplen, i, low, high;
|
||||
jsbytecode *pc2;
|
||||
jsint jmplen, low, high;
|
||||
|
||||
jmplen = (type == JOF_TABLESWITCH) ? JUMP_OFFSET_LEN
|
||||
: JUMPX_OFFSET_LEN;
|
||||
@ -5047,7 +5070,6 @@ ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc,
|
||||
case JOF_LOOKUPSWITCHX:
|
||||
{
|
||||
jsint jmplen;
|
||||
jsbytecode *pc2;
|
||||
jsatomid npairs;
|
||||
|
||||
jmplen = (type == JOF_LOOKUPSWITCH) ? JUMP_OFFSET_LEN
|
||||
@ -5101,56 +5123,56 @@ ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc,
|
||||
}
|
||||
|
||||
LOCAL_ASSERT((uintN)(pcdepth + ndefs) <= script->depth);
|
||||
if (pcstack) {
|
||||
intN i;
|
||||
jsbytecode *pc2;
|
||||
|
||||
/*
|
||||
* Fill the slots that the opcode defines withs its pc unless it just
|
||||
* reshuffle the stack. In the latter case we want to preserve the
|
||||
* opcode that generated the original value.
|
||||
*/
|
||||
switch (op) {
|
||||
default:
|
||||
for (i = 0; i != ndefs; ++i)
|
||||
pcstack[pcdepth + i] = pc;
|
||||
break;
|
||||
|
||||
case JSOP_CASE:
|
||||
case JSOP_CASEX:
|
||||
/* Keep the switch value. */
|
||||
JS_ASSERT(ndefs == 1);
|
||||
break;
|
||||
|
||||
case JSOP_DUP:
|
||||
JS_ASSERT(ndefs == 2);
|
||||
pcstack[pcdepth + 1] = pcstack[pcdepth];
|
||||
break;
|
||||
|
||||
case JSOP_DUP2:
|
||||
JS_ASSERT(ndefs == 4);
|
||||
pcstack[pcdepth + 2] = pcstack[pcdepth];
|
||||
pcstack[pcdepth + 3] = pcstack[pcdepth + 1];
|
||||
break;
|
||||
|
||||
case JSOP_SWAP:
|
||||
JS_ASSERT(ndefs == 2);
|
||||
pc2 = pcstack[pcdepth];
|
||||
pcstack[pcdepth] = pcstack[pcdepth + 1];
|
||||
pcstack[pcdepth + 1] = pc2;
|
||||
break;
|
||||
|
||||
case JSOP_LEAVEBLOCKEXPR:
|
||||
/*
|
||||
* Fill the slots that the opcode defines withs its pc unless it
|
||||
* just reshuffle the stack. In the latter case we want to
|
||||
* preserve the opcode that generated the original value.
|
||||
* The decompiler wants to see [leaveblockexpr] on pcstack, not
|
||||
* [enterblock] or the pc that ended a simulated let expression
|
||||
* when [enterblock] defines zero locals as in:
|
||||
*
|
||||
* let ([] = []) expr
|
||||
*/
|
||||
switch (op) {
|
||||
default:
|
||||
for (i = 0; i != ndefs; ++i)
|
||||
pcstack[pcdepth + i] = pc;
|
||||
break;
|
||||
|
||||
case JSOP_CASE:
|
||||
case JSOP_CASEX:
|
||||
/* Keep the switch value. */
|
||||
JS_ASSERT(ndefs == 1);
|
||||
break;
|
||||
|
||||
case JSOP_DUP:
|
||||
JS_ASSERT(ndefs == 2);
|
||||
pcstack[pcdepth + 1] = pcstack[pcdepth];
|
||||
break;
|
||||
|
||||
case JSOP_DUP2:
|
||||
JS_ASSERT(ndefs == 4);
|
||||
pcstack[pcdepth + 2] = pcstack[pcdepth];
|
||||
pcstack[pcdepth + 3] = pcstack[pcdepth + 1];
|
||||
break;
|
||||
|
||||
case JSOP_SWAP:
|
||||
JS_ASSERT(ndefs == 2);
|
||||
pc2 = pcstack[pcdepth];
|
||||
pcstack[pcdepth] = pcstack[pcdepth + 1];
|
||||
pcstack[pcdepth + 1] = pc2;
|
||||
break;
|
||||
|
||||
case JSOP_LEAVEBLOCKEXPR:
|
||||
/*
|
||||
* The decompiler wants [leaveblockexpr], not [enterblock], to
|
||||
* be left on pcstack after a simulated let expression.
|
||||
*/
|
||||
JS_ASSERT(ndefs == 0);
|
||||
LOCAL_ASSERT(pcdepth >= 1);
|
||||
LOCAL_ASSERT(*pcstack[pcdepth - 1] == JSOP_ENTERBLOCK);
|
||||
pcstack[pcdepth - 1] = pc;
|
||||
break;
|
||||
}
|
||||
JS_ASSERT(ndefs == 0);
|
||||
LOCAL_ASSERT(pcdepth >= 1);
|
||||
LOCAL_ASSERT(nuses == 0 ||
|
||||
*pcstack[pcdepth - 1] == JSOP_ENTERBLOCK);
|
||||
pcstack[pcdepth - 1] = pc;
|
||||
break;
|
||||
}
|
||||
pcdepth += ndefs;
|
||||
}
|
||||
|
@ -362,10 +362,6 @@ js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc, uintN loc,
|
||||
* Decompilers, for script, function, and expression pretty-printing.
|
||||
*/
|
||||
extern JSBool
|
||||
js_DecompileCode(JSPrinter *jp, JSScript *script, jsbytecode *pc, uintN len,
|
||||
uintN pcdepth);
|
||||
|
||||
extern JSBool
|
||||
js_DecompileScript(JSPrinter *jp, JSScript *script);
|
||||
|
||||
extern JSBool
|
||||
|
Loading…
Reference in New Issue
Block a user