diff --git a/js/src/jsiter.c b/js/src/jsiter.c index 549be152831a..3b3007a462ee 100644 --- a/js/src/jsiter.c +++ b/js/src/jsiter.c @@ -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; diff --git a/js/src/jsopcode.c b/js/src/jsopcode.c index 32be82432afb..6b02426f64c5 100644 --- a/js/src/jsopcode.c +++ b/js/src/jsopcode.c @@ -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; } diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index 6072dc638748..8cbbb9cccaf7 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -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