bug=421806 r=brendan a1.9=blockin1.9 fixing decompiler regressions with interpreter stack modeling

This commit is contained in:
igor@mir2.org 2008-03-12 16:03:29 -07:00
parent 1c996a05b3
commit 54cd9bd2fe
3 changed files with 166 additions and 149 deletions

View File

@ -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;

View File

@ -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;
}

View File

@ -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