From ba2bb88b35d4e8e570c66c767af0f504c4a8a9ce Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Fri, 1 Feb 2013 00:19:00 -0500 Subject: [PATCH] Bug 718969 - Perform an exorcism; remove the decompiler. r=luke --HG-- extra : rebase_source : 64df8f9a7ac230e1462a8f6e134ac3e86258d6f8 --- js/src/jsopcode.cpp | 4577 +------------------------------------------ js/src/jsopcode.h | 45 - 2 files changed, 9 insertions(+), 4613 deletions(-) diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 99a1f36dbbc..87482800c46 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* - * JS bytecode descriptors, disassemblers, and decompilers. + * JS bytecode descriptors, disassemblers, and (expression) decompilers. */ #include "mozilla/FloatingPoint.h" @@ -109,14 +109,6 @@ const char *js_CodeName[] = { #define COUNTS_LEN 16 -typedef Vector DupBuffer; - -static bool -Dup(const char *chars, DupBuffer *cb) -{ - return cb->append(chars, strlen(chars) + 1); -} - size_t js_GetVariableBytecodeLength(jsbytecode *pc) { @@ -1073,411 +1065,7 @@ js_QuoteString(JSContext *cx, JSString *str, jschar quote) /************************************************************************/ -/* - * Information for associating the decompilation of each opcode in a script - * with the place where it appears in the text for the decompilation of the - * entire script (or the function containing the script). - */ -struct DecompiledOpcode -{ - /* Decompiled text of this opcode. */ - const char *text; - - /* Bytecode into which this opcode was nested, or NULL. */ - jsbytecode *parent; - - /* - * Offset into the parent's decompiled text of the decompiled text of this - * opcode. For opcodes with a NULL parent, this was emitted directly into - * the permanent output at the given offset. - */ - int32_t parentOffset; - - /* - * Surrounded by parentheses when printed, which parentOffset does not - * account for. - */ - bool parenthesized; - - DecompiledOpcode() - : text(NULL), parent(NULL), parentOffset(-1), parenthesized(false) - {} -}; - -struct JSPrinter -{ - Sprinter sprinter; /* base class state */ - LifoAlloc pool; /* string allocation pool */ - unsigned indent; /* indentation in spaces */ - bool pretty; /* pretty-print: indent, use newlines */ - bool grouped; /* in parenthesized expression context */ - bool strict; /* in code marked strict */ - JSScript *script; /* script being printed */ - jsbytecode *dvgfence; /* DecompileExpression fencepost */ - jsbytecode **pcstack; /* DecompileExpression modeled stack */ - JSFunction *fun; /* interpreted function */ - BindingVector *localNames; /* argument and variable names */ - Vector *decompiledOpcodes; /* optional state for decompiled ops */ - - DecompiledOpcode &decompiled(jsbytecode *pc) { - JS_ASSERT(decompiledOpcodes); - return (*decompiledOpcodes)[pc - script->code]; - } -}; - -static bool -SetPrinterLocalNames(JSContext *cx, JSScript *script, JSPrinter *jp) -{ - BindingVector *localNames = NULL; - if (script->bindings.count() > 0) { - localNames = cx->new_(cx); - RootedScript script_(cx, script); - if (!localNames || !FillBindingVector(script_, localNames)) - return false; - } - jp->localNames = localNames; - return true; -} - -JSPrinter * -js_NewPrinter(JSContext *cx, const char *name, JSFunction *fun, - unsigned indent, JSBool pretty, JSBool grouped, JSBool strict) -{ - JSPrinter *jp = cx->pod_malloc(); - if (!jp) - return NULL; - new (&jp->sprinter) Sprinter(cx); - if (!jp->sprinter.init()) - return NULL; - new (&jp->pool) LifoAlloc(1024); - jp->indent = indent; - jp->pretty = !!pretty; - jp->grouped = !!grouped; - jp->strict = !!strict; - jp->script = NULL; - jp->dvgfence = NULL; - jp->pcstack = NULL; - jp->fun = fun; - jp->localNames = NULL; - jp->decompiledOpcodes = NULL; - if (fun && fun->hasScript()) { - if (!SetPrinterLocalNames(cx, fun->nonLazyScript(), jp)) { - js_DestroyPrinter(jp); - return NULL; - } - } - return jp; -} - -void -js_DestroyPrinter(JSPrinter *jp) -{ - jp->pool.freeAll(); - js_delete(jp->localNames); - jp->sprinter.Sprinter::~Sprinter(); - js_free(jp); -} - -JSString * -js_GetPrinterOutput(JSPrinter *jp) -{ - JSContext *cx = jp->sprinter.context; - return JS_NewStringCopyZ(cx, jp->sprinter.string()); -} - -/* Mark the parent and offset into the parent's text for a printed opcode. */ -static inline void -UpdateDecompiledParent(JSPrinter *jp, jsbytecode *pc, jsbytecode *parent, size_t offset) -{ - if (jp->decompiledOpcodes && pc) { - jp->decompiled(pc).parent = parent; - jp->decompiled(pc).parentOffset = offset; - } -} - -/* - * NB: Indexed by SRC_DECL_* defines from frontend/BytecodeEmitter.h. - */ -static const char * const var_prefix[] = {"var ", "const ", "let "}; - -static const char * -VarPrefix(jssrcnote *sn) -{ - if (sn && (SN_TYPE(sn) == SRC_DECL || SN_TYPE(sn) == SRC_GROUPASSIGN)) { - ptrdiff_t type = js_GetSrcNoteOffset(sn, 0); - if ((unsigned)type <= SRC_DECL_LET) - return var_prefix[type]; - } - return ""; -} - -int -js_printf(JSPrinter *jp, const char *format, ...) -{ - va_list ap; - char *bp, *fp; - int cc; - - if (*format == '\0') - return 0; - - va_start(ap, format); - - /* If pretty-printing, expand magic tab into a run of jp->indent spaces. */ - if (*format == '\t') { - format++; - if (jp->pretty && Sprint(&jp->sprinter, "%*s", jp->indent, "") < 0) { - va_end(ap); - return -1; - } - } - - /* Suppress newlines (must be once per format, at the end) if not pretty. */ - fp = NULL; - if (!jp->pretty && format[cc = strlen(format) - 1] == '\n') { - fp = JS_strdup(jp->sprinter.context, format); - if (!fp) { - va_end(ap); - return -1; - } - fp[cc] = '\0'; - format = fp; - } - - /* Allocate temp space, convert format, and put. */ - bp = JS_vsmprintf(format, ap); /* XXX vsaprintf */ - if (fp) { - js_free(fp); - format = NULL; - } - if (!bp) { - jp->sprinter.reportOutOfMemory(); - va_end(ap); - return -1; - } - - cc = strlen(bp); - if (jp->sprinter.put(bp, (size_t)cc) < 0) - cc = -1; - js_free(bp); - - va_end(ap); - return cc; -} - -JSBool -js_puts(JSPrinter *jp, const char *s) -{ - return jp->sprinter.put(s) >= 0; -} - -/************************************************************************/ - -struct SprintStack -{ - Sprinter sprinter; /* sprinter for postfix to infix buffering */ - ptrdiff_t *offsets; /* stack of postfix string offsets */ - jsbytecode *opcodes; /* parallel stack of JS opcodes */ - jsbytecode **bytecodes; /* actual script bytecode pushing the value */ - unsigned top; /* top of stack index */ - unsigned inArrayInit; /* array initialiser/comprehension level */ - JSBool inGenExp; /* in generator expression */ - JSPrinter *printer; /* permanent output goes here */ - - explicit SprintStack(JSContext *cx) - : sprinter(cx), offsets(NULL), - opcodes(NULL), bytecodes(NULL), top(0), inArrayInit(0), - inGenExp(JS_FALSE), printer(NULL) - { } -}; - -/* - * Set the decompiled text of an opcode, according to an offset into the - * print stack's sprinter buffer. - */ -static inline bool -UpdateDecompiledText(SprintStack *ss, jsbytecode *pc, ptrdiff_t todo) -{ - JSPrinter *jp = ss->printer; - - if (jp->decompiledOpcodes && jp->decompiled(pc).text == NULL) { - const char *text = ss->sprinter.stringAt(todo); - size_t len = strlen(text) + 1; - - char *ntext = ss->printer->pool.newArrayUninitialized(len); - if (!ntext) { - ss->sprinter.reportOutOfMemory(); - return false; - } - - js_memcpy(ntext, text, len); - jp->decompiled(pc).text = const_cast(ntext); - } - - return true; -} - -static inline const char * -SprintDupeStr(SprintStack *ss, const char *str) -{ - size_t len = strlen(str) + 1; - - const char *nstr = ss->printer->pool.newArrayUninitialized(len); - if (nstr) { - js_memcpy((char *) nstr, str, len); - } else { - ss->sprinter.reportOutOfMemory(); - nstr = ""; - } - - return nstr; -} - -/* Place an opcode's decompiled text into a printer's permanent output. */ -static inline void -SprintOpcodePermanent(JSPrinter *jp, const char *str, jsbytecode *pc) -{ - ptrdiff_t offset = jp->sprinter.getOffset(); - UpdateDecompiledParent(jp, pc, NULL, offset); - js_printf(jp, "%s", str); -} - -/* - * Place an opcode's decompiled text into the printed output for another - * opcode parentpc, where startOffset indicates the printer offset for the - * start of parentpc. - */ -static inline void -SprintOpcode(SprintStack *ss, const char *str, jsbytecode *pc, - jsbytecode *parentpc, ptrdiff_t startOffset) -{ - if (startOffset < 0) { - JS_ASSERT(ss->sprinter.hadOutOfMemory()); - return; - } - ptrdiff_t offset = ss->sprinter.getOffset(); - UpdateDecompiledParent(ss->printer, pc, parentpc, offset - startOffset); - ss->sprinter.put(str); -} - -/* - * Copy the decompiled text for an opcode to all other ops which it was - * decomposed into. - */ -static inline void -CopyDecompiledTextForDecomposedOp(JSPrinter *jp, jsbytecode *pc) -{ - JS_ASSERT(js_CodeSpec[*pc].format & JOF_DECOMPOSE); - - if (jp->decompiledOpcodes) { - size_t len = GetDecomposeLength(pc, js_CodeSpec[*pc].length); - - const char *text = jp->decompiled(pc).text; - - jsbytecode *pc2 = pc + GetBytecodeLength(pc); - for (; pc2 < pc + len; pc2 += GetBytecodeLength(pc2)) { - jp->decompiled(pc2).text = text; - jp->decompiled(pc2).parent = pc; - jp->decompiled(pc2).parentOffset = 0; - } - } -} - -/* - * 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 int -ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc, jsbytecode **pcstack); - -#define FAILED_EXPRESSION_DECOMPILER ((char *) 1) - -static JSBool DecompileFunction(JSPrinter *jp); - -/* - * 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, 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 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 - * to fix, but it won't violate memory safety. - */ -static ptrdiff_t -GetOff(SprintStack *ss, unsigned i) -{ - ptrdiff_t off; - jsbytecode *pc; - char *bytes; - - off = ss->offsets[i]; - if (off >= 0) - return off; - - 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; - if (bytes != FAILED_EXPRESSION_DECOMPILER) { - off = ss->sprinter.put(bytes); - if (off < 0) - off = 0; - ss->offsets[i] = off; - js_free(bytes); - return off; - } - - if (!*ss->sprinter.string()) { - memset(ss->sprinter.stringAt(0), 0, ss->sprinter.getOffset()); - ss->offsets[i] = -1; - } - } - return 0; -} - -static const char * -GetStr(SprintStack *ss, unsigned i) -{ - ptrdiff_t off = GetOff(ss, i); - return ss->sprinter.stringAt(off); -} - -/* - * Gap between stacked strings to allow for insertion of parens and commas - * when auto-parenthesizing expressions and decompiling array initialisers. - */ -#define PAREN_SLOP (2 + 1) - -/* Fake opcodes (see jsopcode.h) must not overflow unsigned 8-bit space. */ -JS_STATIC_ASSERT(JSOP_FAKE_LIMIT <= 255); - -static inline void -AddParenSlop(SprintStack *ss) -{ - ss->sprinter.reserveAndClear(PAREN_SLOP); -} +static int ReconstructPCStack(JSContext*, JSScript*, jsbytecode*, jsbytecode**); static unsigned StackDepth(JSScript *script) @@ -1485,4056 +1073,6 @@ StackDepth(JSScript *script) return script->nslots - script->nfixed; } -static JSBool -PushOff(SprintStack *ss, ptrdiff_t off, JSOp op, jsbytecode *pc = NULL) -{ - unsigned top; - - /* ss->top points to the next free slot; be paranoid about overflow. */ - top = ss->top; - JS_ASSERT(top < StackDepth(ss->printer->script)); - if (top >= StackDepth(ss->printer->script)) { - ss->sprinter.reportOutOfMemory(); - return JS_FALSE; - } - - /* The opcodes stack must contain real bytecodes that index js_CodeSpec. */ - ss->offsets[top] = off; - ss->opcodes[top] = jsbytecode((op == JSOP_GETPROP2) ? JSOP_GETPROP - : (op == JSOP_GETELEM2) ? JSOP_GETELEM - : op); - ss->bytecodes[top] = pc; - ss->top = ++top; - - AddParenSlop(ss); - return JS_TRUE; -} - -static bool -PushStr(SprintStack *ss, const char *str, JSOp op) -{ - ptrdiff_t off = ss->sprinter.put(str); - if (off < 0) - return false; - return PushOff(ss, off, op); -} - -static ptrdiff_t -PopOffPrec(SprintStack *ss, uint8_t prec, jsbytecode **ppc = NULL) -{ - unsigned top; - const JSCodeSpec *topcs; - ptrdiff_t off; - - if (ppc) - *ppc = NULL; - - /* ss->top points to the next free slot; be paranoid about underflow. */ - top = ss->top; - JS_ASSERT(top != 0); - if (top == 0) - return 0; - - ss->top = --top; - off = GetOff(ss, top); - - int op = ss->opcodes[top]; - if (op >= JSOP_LIMIT) - op = JSOP_NOP; - topcs = &js_CodeSpec[op]; - - jsbytecode *pc = ss->bytecodes[top]; - if (ppc) - *ppc = pc; - - if (topcs->prec != 0 && topcs->prec < prec) { - ss->offsets[top] = off - 2; - ss->sprinter.setOffset(off - 2); - off = Sprint(&ss->sprinter, "(%s)", ss->sprinter.stringAt(off)); - /* If allocation failed, return any safe string. */ - if (off < 0) - off = 0; - if (ss->printer->decompiledOpcodes && pc) - ss->printer->decompiled(pc).parenthesized = true; - } else { - ss->sprinter.setOffset(off); - } - return off; -} - -static const char * -PopStrPrec(SprintStack *ss, uint8_t prec, jsbytecode **ppc = NULL) -{ - ptrdiff_t off; - - off = PopOffPrec(ss, prec, ppc); - return ss->sprinter.stringAt(off); -} - -/* - * As for PopStrPrec, but duplicates the string into the printer's arena. - * Strings returned by PopStrPrec are otherwise invalidated if any new text - * is printed into ss. - */ -static const char * -PopStrPrecDupe(SprintStack *ss, uint8_t prec, jsbytecode **ppc = NULL) -{ - const char *str = PopStrPrec(ss, prec, ppc); - return SprintDupeStr(ss, str); -} - -static ptrdiff_t -PopOff(SprintStack *ss, JSOp op, jsbytecode **ppc = NULL) -{ - return PopOffPrec(ss, js_CodeSpec[op].prec, ppc); -} - -static const char * -PopStr(SprintStack *ss, JSOp op, jsbytecode **ppc = NULL) -{ - return PopStrPrec(ss, js_CodeSpec[op].prec, ppc); -} - -static const char * -PopStrDupe(SprintStack *ss, JSOp op, jsbytecode **ppc = NULL) -{ - return PopStrPrecDupe(ss, js_CodeSpec[op].prec, ppc); -} - -/* - * Pop a condition expression for if/while. JSOP_IFEQ's precedence forces - * extra parens around assignment, which avoids a strict-mode warning. - */ -static const char * -PopCondStr(SprintStack *ss, jsbytecode **ppc = NULL) -{ - JSOp op = (js_CodeSpec[ss->opcodes[ss->top - 1]].format & JOF_SET) - ? JSOP_IFEQ - : JSOP_NOP; - return PopStr(ss, op, ppc); -} - -static inline bool -IsInitializerOp(unsigned char op) -{ - return op == JSOP_NEWINIT || op == JSOP_NEWARRAY || op == JSOP_NEWOBJECT; -} - -struct TableEntry { - jsval key; - ptrdiff_t offset; - JSAtom *label; - int order; /* source order for stable tableswitch sort */ -}; - -inline bool -CompareTableEntries(const TableEntry &a, const TableEntry &b, bool *lessOrEqualp) -{ - *lessOrEqualp = (a.offset != b.offset) ? a.offset <= b.offset : a.order <= b.order; - return true; -} - -static ptrdiff_t -SprintDoubleValue(Sprinter *sp, jsval v, JSOp *opp) -{ - double d; - ptrdiff_t todo; - char *s; - - JS_ASSERT(JSVAL_IS_DOUBLE(v)); - d = JSVAL_TO_DOUBLE(v); - if (MOZ_DOUBLE_IS_NEGATIVE_ZERO(d)) { - todo = sp->put("-0"); - *opp = JSOP_NEG; - } else if (!MOZ_DOUBLE_IS_FINITE(d)) { - /* Don't use Infinity and NaN, as local variables may shadow them. */ - todo = sp->put(MOZ_DOUBLE_IS_NaN(d) - ? "0 / 0" - : (d < 0) - ? "1 / -0" - : "1 / 0"); - *opp = JSOP_DIV; - } else { - ToCStringBuf cbuf; - s = NumberToCString(sp->context, &cbuf, d); - if (!s) { - sp->reportOutOfMemory(); - return -1; - } - JS_ASSERT(strcmp(s, "Infinity") && - (*s != '-' || - strcmp(s + 1, "Infinity")) && - strcmp(s, "NaN")); - todo = Sprint(sp, s); - } - return todo; -} - -static jsbytecode * -Decompile(SprintStack *ss, jsbytecode *pc, int nb); - -static JSBool -DecompileSwitch(SprintStack *ss, TableEntry *table, unsigned tableLength, - jsbytecode *pc, ptrdiff_t switchLength, - ptrdiff_t defaultOffset, JSBool isCondSwitch) -{ - JSContext *cx; - JSPrinter *jp; - ptrdiff_t off, off2, diff, caseExprOff, todo; - const char *rval; - unsigned i; - JSString *str; - - cx = ss->sprinter.context; - jp = ss->printer; - - RootedValue key(cx); - jsbytecode *lvalpc; - const char *lval = PopStr(ss, JSOP_NOP, &lvalpc); - - /* JSOP_CONDSWITCH doesn't pop, unlike JSOP_{LOOKUP,TABLE}SWITCH. */ - if (isCondSwitch) - ss->top++; - - js_printf(jp, "\tswitch ("); - SprintOpcodePermanent(jp, lval, lvalpc); - js_printf(jp, ") {\n"); - - if (tableLength) { - diff = table[0].offset - defaultOffset; - if (diff > 0) { - jp->indent += 2; - js_printf(jp, "\t%s:\n", js_default_str); - jp->indent += 2; - if (!Decompile(ss, pc + defaultOffset, diff)) - return JS_FALSE; - jp->indent -= 4; - } - - caseExprOff = isCondSwitch ? JSOP_CONDSWITCH_LENGTH : 0; - - for (i = 0; i < tableLength; i++) { - off = table[i].offset; - off2 = (i + 1 < tableLength) ? table[i + 1].offset : switchLength; - - key = table[i].key; - if (isCondSwitch) { - ptrdiff_t nextCaseExprOff; - - /* - * key encodes the JSOP_CASE bytecode's offset from switchtop. - * The next case expression follows immediately, unless we are - * at the last case. - */ - nextCaseExprOff = (ptrdiff_t)JSVAL_TO_INT(key); - nextCaseExprOff += js_CodeSpec[pc[nextCaseExprOff]].length; - jp->indent += 2; - if (!Decompile(ss, pc + caseExprOff, nextCaseExprOff - caseExprOff)) - return JS_FALSE; - caseExprOff = nextCaseExprOff; - - /* Balance the stack as if this JSOP_CASE matched. */ - --ss->top; - } else { - /* - * key comes from an atom, not the decompiler, so we need to - * quote it if it's a string literal. But if table[i].label - * is non-null, key was constant-propagated and label is the - * name of the const we should show as the case label. We set - * key to undefined so this identifier is escaped, if required - * by non-ASCII characters, but not quoted, by QuoteString. - */ - todo = -1; - if (table[i].label) { - str = table[i].label; - key = JSVAL_VOID; - } else if (JSVAL_IS_DOUBLE(key)) { - JSOp junk; - - todo = SprintDoubleValue(&ss->sprinter, key, &junk); - if (todo < 0) - return JS_FALSE; - str = NULL; - } else { - str = ToString(cx, key); - if (!str) - return JS_FALSE; - } - if (todo >= 0) { - rval = ss->sprinter.stringAt(todo); - } else { - rval = QuoteString(&ss->sprinter, str, (jschar) - (JSVAL_IS_STRING(key) ? '"' : 0)); - if (!rval) - return JS_FALSE; - } - ss->sprinter.setOffset(rval); - jp->indent += 2; - js_printf(jp, "\tcase %s:\n", rval); - } - - jp->indent += 2; - if (off <= defaultOffset && defaultOffset < off2) { - diff = defaultOffset - off; - if (diff != 0) { - if (!Decompile(ss, pc + off, diff)) - return JS_FALSE; - off = defaultOffset; - } - jp->indent -= 2; - js_printf(jp, "\t%s:\n", js_default_str); - jp->indent += 2; - } - if (!Decompile(ss, pc + off, off2 - off)) - return JS_FALSE; - jp->indent -= 4; - - /* Re-balance as if last JSOP_CASE or JSOP_DEFAULT mismatched. */ - if (isCondSwitch) - ++ss->top; - } - } - - if (defaultOffset == switchLength) { - jp->indent += 2; - js_printf(jp, "\t%s:;\n", js_default_str); - jp->indent -= 2; - } - js_printf(jp, "\t}\n"); - - /* By the end of a JSOP_CONDSWITCH, the discriminant has been popped. */ - if (isCondSwitch) - --ss->top; - return JS_TRUE; -} - -#define LOCAL_ASSERT_CUSTOM(expr, BAD_EXIT) \ - JS_BEGIN_MACRO \ - JS_ASSERT(expr); \ - if (!(expr)) { BAD_EXIT; } \ - JS_END_MACRO - -#define LOCAL_ASSERT_RV(expr, rv) \ - LOCAL_ASSERT_CUSTOM(expr, return (rv)) - -static JSAtom * -GetArgOrVarAtom(JSPrinter *jp, unsigned slot) -{ - LOCAL_ASSERT_RV(jp->fun, NULL); - LOCAL_ASSERT_RV(slot < jp->script->bindings.count(), NULL); - LOCAL_ASSERT_RV(jp->script == jp->fun->nonLazyScript(), NULL); - JSAtom *name = (*jp->localNames)[slot].name(); -#if !JS_HAS_DESTRUCTURING - LOCAL_ASSERT_RV(name, NULL); -#endif - return name; -} - -#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, "") - -static const char * -GetLocalInSlot(SprintStack *ss, int i, int slot, JSObject *obj) -{ - for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) { - Shape &shape = r.front(); - - if (shape.shortid() == slot) { - /* Ignore the empty destructuring dummy. */ - if (!JSID_IS_ATOM(shape.propid())) - continue; - - JSAtom *atom = JSID_TO_ATOM(shape.propid()); - const char *rval = QuoteString(&ss->sprinter, atom, 0); - if (!rval) - return NULL; - - ss->sprinter.setOffset(rval); - return rval; - } - } - - return GetStr(ss, i); -} - -const char * -GetLocal(SprintStack *ss, int i) -{ - ptrdiff_t off = ss->offsets[i]; - if (off >= 0) - return ss->sprinter.stringAt(off); - - /* - * We must be called from js_DecompileValueGenerator (via Decompile) when - * dereferencing a local that's undefined or null. Search script->objects - * for the block containing this local by its stack index, i. - * - * In case of destructuring's use of JSOP_GETLOCAL, however, there may be - * no such local. This could mean no blocks (no script objects at all, or - * none of the script's object literals are blocks), or the stack slot i is - * not in a block. In either case, return GetStr(ss, i). - */ - JSScript *script = ss->printer->script; - if (!script->hasObjects()) - return GetStr(ss, i); - - // In case of a let variable, the stack points to a JSOP_ENTERBLOCK opcode. - // Get the object number from the block instead of iterating all objects and - // hoping the right object is found. - if (off <= -2 && ss->printer->pcstack) { - jsbytecode *pc = ss->printer->pcstack[-2 - off]; - - JS_ASSERT(ss->printer->script->code <= pc); - JS_ASSERT(pc < (ss->printer->script->code + ss->printer->script->length)); - - if (JSOP_ENTERBLOCK == (JSOp)*pc) { - JSObject *obj = script->getObject(GET_UINT32_INDEX(pc)); - - if (obj->isBlock()) { - uint32_t depth = obj->asBlock().stackDepth(); - uint32_t count = obj->asBlock().slotCount(); - if (uint32_t(i - depth) < uint32_t(count)) - return GetLocalInSlot(ss, i, int(i - depth), obj); - } - } - } - - // Iterate over all objects. - for (jsatomid j = 0, n = script->objects()->length; j != n; j++) { - JSObject *obj = script->getObject(j); - - if (obj->isBlock()) { - uint32_t depth = obj->asBlock().stackDepth(); - uint32_t count = obj->asBlock().slotCount(); - if (uint32_t(i - depth) < uint32_t(count)) - return GetLocalInSlot(ss, i, int(i - depth), obj); - } - } - - return GetStr(ss, i); -} - -#undef LOCAL_ASSERT - -/* - * If IsVarSlot returns true, the var's atom is returned in *varAtom. - * If IsVarSlot returns false (indicating that this is a get of a let binding), - * the stack depth of the associated slot is returned in *localSlot. - */ -static bool -IsVarSlot(JSPrinter *jp, jsbytecode *pc, JSAtom **varAtom, int *localSlot) -{ - *localSlot = -1; - - if (JOF_OPTYPE(*pc) == JOF_SCOPECOORD) { - *varAtom = ScopeCoordinateName(jp->sprinter.context, jp->script, pc); - LOCAL_ASSERT_RV(*varAtom, false); - return true; - } - - unsigned slot = GET_SLOTNO(pc); - if (slot < jp->script->nfixed) { - *varAtom = GetArgOrVarAtom(jp, jp->fun->nargs + slot); - LOCAL_ASSERT_RV(*varAtom, false); - return true; - } - - /* We have a local which index is relative to the stack base. */ - slot -= jp->script->nfixed; - JS_ASSERT(slot < StackDepth(jp->script)); - *localSlot = slot; - return false; -} - -#define LOAD_ATOM(PCOFF) (atom = (jp->script->getAtom(GET_UINT32_INDEX((pc) + PCOFF)))) - -typedef Vector AtomVector; -typedef AtomVector::Range AtomRange; - -#if JS_HAS_DESTRUCTURING - -#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, NULL) -#define LOAD_OP_DATA(pc) (oplen = (cs = &js_CodeSpec[op=(JSOp)*pc])->length) - -static jsbytecode * -DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, - AtomRange *letNames = NULL); - -/* - * Decompile a single element of a compound {}/[] destructuring lhs, sprinting - * the result in-place (without pushing/popping the stack) and advancing the pc - * to either the next element or the final pop. - * - * For normal (SRC_DESTRUCT) destructuring, the names of assigned/initialized - * variables are read from their slots. However, for SRC_DESTRUCTLET, the slots - * have not been pushed yet; the caller must pass the names to use via - * 'letNames'. Each variable initialized in this destructuring lhs results in - * popping a name from 'letNames'. - */ -static jsbytecode * -DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, JSBool *hole, - AtomRange *letNames = NULL) -{ - JSPrinter *jp; - JSOp op; - const JSCodeSpec *cs; - unsigned oplen; - int i; - const char *lval, *xval; - JSAtom *atom; - - *hole = JS_FALSE; - jp = ss->printer; - LOAD_OP_DATA(pc); - - switch (op) { - case JSOP_POP: - *hole = JS_TRUE; - if (ss->sprinter.put(", ", 2) < 0) - return NULL; - break; - - case JSOP_PICK: - /* - * For 'let ([x, y] = y)', the emitter generates - * - * push evaluation of y - * dup - * 1 one - * 2 getelem - * 3 pick - * 4 two - * getelem - * pick - * pop - * - * Thus 'x' consists of 1 - 3. The caller (DecompileDestructuring or - * DecompileGroupAssignment) will have taken care of 1 - 2, so pc is - * now pointing at 3. The pick indicates a primitive let var init so - * pop a name and advance the pc to 4. - */ - LOCAL_ASSERT(letNames && !letNames->empty()); - if (!QuoteString(&ss->sprinter, letNames->popCopyFront(), 0)) - return NULL; - break; - - case JSOP_DUP: - { - /* Compound lhs, e.g., '[x,y]' in 'let [[x,y], z] = a;'. */ - pc = DecompileDestructuring(ss, pc, endpc, letNames); - if (!pc) - return NULL; - if (pc == endpc) - return pc; - LOAD_OP_DATA(pc); - - /* - * By its post-condition, DecompileDestructuring pushed one string - * containing the whole decompiled lhs. Our post-condition is to sprint - * in-place so pop/concat this pushed string. - */ - lval = PopStr(ss, JSOP_NOP); - if (ss->sprinter.put(lval) < 0) - return NULL; - - LOCAL_ASSERT(*pc == JSOP_POP); - - /* - * To put block slots in the right place, the emitter follows a - * compound lhs with a pick (if at least one slot was pushed). The pick - * is not part of the compound lhs so DecompileDestructuring did not - * advance over it but it is part of the lhs so advance over it here. - */ - jsbytecode *nextpc = pc + JSOP_POP_LENGTH; - LOCAL_ASSERT(nextpc <= endpc); - if (letNames && *nextpc == JSOP_PICK) { - LOCAL_ASSERT(nextpc < endpc); - pc = nextpc; - LOAD_OP_DATA(pc); - } - break; - } - - case JSOP_SETALIASEDVAR: - case JSOP_SETARG: - case JSOP_SETLOCAL: - LOCAL_ASSERT(!letNames); - LOCAL_ASSERT(pc[oplen] == JSOP_POP || pc[oplen] == JSOP_POPN); - if (op == JSOP_SETARG) { - atom = GetArgOrVarAtom(jp, GET_SLOTNO(pc)); - LOCAL_ASSERT(atom); - if (!QuoteString(&ss->sprinter, atom, 0)) - return NULL; - } else if (IsVarSlot(jp, pc, &atom, &i)) { - if (!QuoteString(&ss->sprinter, atom, 0)) - return NULL; - } else { - lval = GetLocal(ss, i); - if (!lval || ss->sprinter.put(lval) < 0) - return NULL; - } - pc += oplen; - if (pc == endpc) - return pc; - LOAD_OP_DATA(pc); - if (op == JSOP_POPN) - return pc; - LOCAL_ASSERT(op == JSOP_POP); - break; - - default: { - LOCAL_ASSERT(!letNames); - /* - * We may need to auto-parenthesize the left-most value decompiled - * here, so add back PAREN_SLOP temporarily. Then decompile until the - * opcode that would reduce the stack depth to (ss->top-1), which we - * pass to Decompile encoded as -(ss->top-1) - 1 or just -ss->top for - * the nb parameter. - */ - ptrdiff_t todo = ss->sprinter.getOffset(); - ss->sprinter.reserve(PAREN_SLOP); - pc = Decompile(ss, pc, -((int)ss->top)); - if (!pc) - return NULL; - if (pc == endpc) - return pc; - LOAD_OP_DATA(pc); - LOCAL_ASSERT(op == JSOP_ENUMELEM || op == JSOP_ENUMCONSTELEM); - xval = PopStr(ss, JSOP_NOP); - lval = PopStr(ss, JSOP_GETPROP); - ss->sprinter.setOffset(todo); - if (*lval == '\0') { - /* lval is from JSOP_BINDNAME, so just print xval. */ - todo = ss->sprinter.put(xval); - } else if (*xval == '\0') { - /* xval is from JSOP_SETCALL, so print lval. */ - todo = ss->sprinter.put(lval); - } else { - todo = Sprint(&ss->sprinter, "%s[%s]", lval, xval); - } - if (todo < 0) - return NULL; - break; - } - } - - LOCAL_ASSERT(pc < endpc); - pc += oplen; - return pc; -} - -/* - * Decompile a destructuring lhs object or array initialiser, including nested - * destructuring initialisers. On return a single string is pushed containing - * the entire lhs (regardless of how many variables were bound). Thus, the - * caller must take care of fixing up the decompiler stack. - * - * See DecompileDestructuringLHS for description of 'letNames'. - */ -static jsbytecode * -DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, - AtomRange *letNames) -{ - LOCAL_ASSERT(*pc == JSOP_DUP); - pc += JSOP_DUP_LENGTH; - - JSContext *cx = ss->sprinter.context; - JSPrinter *jp = ss->printer; - jsbytecode *startpc = pc; - - /* - * Set head so we can rewrite '[' to '{' as needed. Back up PAREN_SLOP - * chars so the destructuring decompilation accumulates contiguously in - * ss->sprinter starting with "[". - */ - ptrdiff_t head = ss->sprinter.put("[", 1); - if (head < 0 || !PushOff(ss, head, JSOP_NOP)) - return NULL; - ss->sprinter.setOffset(ss->sprinter.getOffset() - PAREN_SLOP); - LOCAL_ASSERT(head == ss->sprinter.getOffset() - 1); - LOCAL_ASSERT(ss->sprinter[head] == '['); - - int lasti = -1; - - while (pc < endpc) { -#if JS_HAS_DESTRUCTURING_SHORTHAND - ptrdiff_t nameoff = -1; -#endif - - const JSCodeSpec *cs; - unsigned oplen; - JSOp op; - LOAD_OP_DATA(pc); - - int i; - double d; - switch (op) { - case JSOP_POP: - /* Empty destructuring lhs. */ - LOCAL_ASSERT(startpc == pc); - pc += oplen; - goto out; - - /* Handle the optimized number-pushing opcodes. */ - case JSOP_ZERO: d = i = 0; goto do_getelem; - case JSOP_ONE: d = i = 1; goto do_getelem; - case JSOP_UINT16: d = i = GET_UINT16(pc); goto do_getelem; - case JSOP_UINT24: d = i = GET_UINT24(pc); goto do_getelem; - case JSOP_INT8: d = i = GET_INT8(pc); goto do_getelem; - case JSOP_INT32: d = i = GET_INT32(pc); goto do_getelem; - - case JSOP_DOUBLE: - d = jp->script->getConst(GET_UINT32_INDEX(pc)).toDouble(); - LOCAL_ASSERT(MOZ_DOUBLE_IS_FINITE(d) && !MOZ_DOUBLE_IS_NEGATIVE_ZERO(d)); - i = (int)d; - - do_getelem: - { - jssrcnote *sn = js_GetSrcNote(cx, jp->script, pc); - pc += oplen; - if (pc == endpc) - return pc; - LOAD_OP_DATA(pc); - LOCAL_ASSERT(op == JSOP_GETELEM); - - /* Distinguish object from array by opcode or source note. */ - if (sn && SN_TYPE(sn) == SRC_INITPROP) { - ss->sprinter[head] = '{'; - if (Sprint(&ss->sprinter, "%g: ", d) < 0) - return NULL; - } else { - /* Sanity check for the gnarly control flow above. */ - LOCAL_ASSERT(i == d); - - /* Fill in any holes (holes at the end don't matter). */ - while (++lasti < i) { - if (ss->sprinter.put(", ", 2) < 0) - return NULL; - } - } - break; - } - - case JSOP_GETPROP: - case JSOP_LENGTH: - { - JSAtom *atom; - LOAD_ATOM(0); - ss->sprinter[head] = '{'; -#if JS_HAS_DESTRUCTURING_SHORTHAND - nameoff = ss->sprinter.getOffset(); -#endif - if (!QuoteString(&ss->sprinter, atom, IsIdentifier(atom) ? 0 : (jschar)'\'')) - return NULL; - if (ss->sprinter.put(": ", 2) < 0) - return NULL; - break; - } - - default: - LOCAL_ASSERT(0); - } - - pc += oplen; - if (pc == endpc) - return pc; - - /* - * Decompile the left-hand side expression whose bytecode starts at pc - * and continues for a bounded number of bytecodes or stack operations - * (and which in any event stops before endpc). - */ - JSBool hole; - pc = DecompileDestructuringLHS(ss, pc, endpc, &hole, letNames); - if (!pc) - return NULL; - -#if JS_HAS_DESTRUCTURING_SHORTHAND - if (nameoff >= 0) { - ptrdiff_t offset, initlen; - - offset = ss->sprinter.getOffset(); - LOCAL_ASSERT(ss->sprinter[offset] == '\0'); - initlen = offset - nameoff; - LOCAL_ASSERT(initlen >= 4); - - /* Early check to rule out odd "name: lval" length. */ - if (((size_t)initlen & 1) == 0) { - size_t namelen; - const char *name; - - /* - * Even "name: lval" string length: check for "x: x" and the - * like, and apply the shorthand if we can. - */ - namelen = (size_t)(initlen - 2) >> 1; - name = ss->sprinter.stringAt(nameoff); - if (!strncmp(name + namelen, ": ", 2) && - !strncmp(name, name + namelen + 2, namelen)) { - offset -= namelen + 2; - ss->sprinter[offset] = '\0'; - ss->sprinter.setOffset(offset); - } - } - } -#endif - - if (pc == endpc || *pc != JSOP_DUP) - break; - - /* - * We should stop if JSOP_DUP is either without notes or its note is - * not SRC_CONTINUE. The former happens when JSOP_DUP duplicates the - * last destructuring reference implementing an op= assignment like in - * '([t] = z).y += x'. In the latter case the note is SRC_DESTRUCT and - * means another destructuring initialiser abuts this one like in - * '[a] = [b] = c'. - */ - jssrcnote *sn = js_GetSrcNote(cx, jp->script, pc); - if (!sn) - break; - if (SN_TYPE(sn) != SRC_CONTINUE) { - LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCT || SN_TYPE(sn) == SRC_DESTRUCTLET); - break; - } - - if (!hole && ss->sprinter.put(", ", 2) < 0) - return NULL; - - pc += JSOP_DUP_LENGTH; - } - -out: - const char *lval = ss->sprinter.stringAt(head); - if (ss->sprinter.put((*lval == '[') ? "]" : "}", 1) < 0) - return NULL; - return pc; -} - -static jsbytecode * -DecompileGroupAssignment(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, - jssrcnote *sn, ptrdiff_t *todop) -{ - JSOp op; - const JSCodeSpec *cs; - unsigned oplen, start, end, i; - ptrdiff_t todo; - JSBool hole; - const char *rval; - - LOAD_OP_DATA(pc); - LOCAL_ASSERT(op == JSOP_GETLOCAL); - - todo = Sprint(&ss->sprinter, "%s[", VarPrefix(sn)); - if (todo < 0 || !PushOff(ss, todo, JSOP_NOP)) - return NULL; - ss->sprinter.setOffset(ss->sprinter.getOffset() - PAREN_SLOP); - - for (;;) { - pc += oplen; - if (pc == endpc) - return pc; - pc = DecompileDestructuringLHS(ss, pc, endpc, &hole); - if (!pc) - return NULL; - if (pc == endpc) - return pc; - LOAD_OP_DATA(pc); - if (op != JSOP_GETLOCAL) - break; - if (!hole && ss->sprinter.put(", ", 2) < 0) - return NULL; - } - - LOCAL_ASSERT(op == JSOP_POPN); - if (ss->sprinter.put("] = [", 5) < 0) - return NULL; - - end = ss->top - 1; - start = end - GET_UINT16(pc); - for (i = start; i < end; i++) { - rval = GetStr(ss, i); - if (Sprint(&ss->sprinter, - (i == start) ? "%s" : ", %s", - (i == end - 1 && *rval == '\0') ? ", " : rval) < 0) { - return NULL; - } - } - - if (ss->sprinter.put("]", 1) < 0) - return NULL; - ss->sprinter.setOffset(ss->offsets[i]); - ss->top = start; - *todop = todo; - return pc; -} - -#undef LOCAL_ASSERT -#undef LOAD_OP_DATA - -#endif /* JS_HAS_DESTRUCTURING */ - -#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, false) - -/* - * The names of the vars of a let block/expr are stored as the ids of the - * shapes of the block object. Shapes are stored in a singly-linked list in - * reverse order of addition. This function takes care of putting the names - * back in declaration order. - */ -static bool -GetBlockNames(JSContext *cx, StaticBlockObject &blockObj, AtomVector *atoms) -{ - size_t numAtoms = blockObj.slotCount(); - LOCAL_ASSERT(numAtoms > 0); - if (!atoms->resize(numAtoms)) - return false; - - unsigned i = numAtoms; - for (Shape::Range r = blockObj.lastProperty()->all(); !r.empty(); r.popFront()) { - Shape &shape = r.front(); - LOCAL_ASSERT(shape.hasShortID()); - --i; - LOCAL_ASSERT((unsigned)shape.shortid() == i); - (*atoms)[i] = JSID_IS_INT(shape.propid()) - ? cx->names().empty - : JSID_TO_ATOM(shape.propid()); - } - - LOCAL_ASSERT(i == 0); - return true; -} - -static bool -PushBlockNames(SprintStack *ss, const AtomVector &atoms) -{ - for (size_t i = 0; i < atoms.length(); i++) { - const char *name = QuoteString(&ss->sprinter, atoms[i], 0); - if (!name || !PushOff(ss, ss->sprinter.getOffsetOf(name), JSOP_ENTERBLOCK)) - return false; - } - return true; -} - -/* - * In the scope of a let, the variables' (decompiler) stack slots must contain - * the corresponding variable's name. This function updates the N top slots - * with the N variable names stored in 'atoms'. - */ -static bool -AssignBlockNamesToPushedSlots(SprintStack *ss, const AtomVector &atoms) -{ - /* For simplicity, just pop and push. */ - LOCAL_ASSERT(atoms.length() <= (unsigned)ss->top); - for (size_t i = 0; i < atoms.length(); ++i) - PopStr(ss, JSOP_NOP); - return PushBlockNames(ss, atoms); -} - -static const char SkipString[] = "/*skip*/"; -static const char DestructuredString[] = "/*destructured*/"; -static const unsigned DestructuredStringLength = ArrayLength(DestructuredString) - 1; - -static ptrdiff_t -SprintLetBody(JSPrinter *jp, SprintStack *ss, jsbytecode *pc, ptrdiff_t bodyLength, - const char *headChars) -{ - if (pc[bodyLength] == JSOP_LEAVEBLOCK) { - js_printf(jp, "\tlet (%s) {\n", headChars); - jp->indent += 4; - if (!Decompile(ss, pc, bodyLength)) - return -1; - jp->indent -= 4; - js_printf(jp, "\t}\n"); - return -2; - } - - LOCAL_ASSERT_RV(pc[bodyLength] == JSOP_LEAVEBLOCKEXPR, -1); - if (!Decompile(ss, pc, bodyLength)) - return -1; - - const char *bodyChars = PopStr(ss, JSOP_SETNAME); - const char *format = *bodyChars == '{' ? "let (%s) (%s)" : "let (%s) %s"; - return Sprint(&ss->sprinter, format, headChars, bodyChars); -} - -/* - * Get the token to prefix the '=' in an assignment operation, checking whether - * the last operation was a getter, setter or compound assignment. For compound - * assignments, marks parents for the lhs and rhs of the operation in the - * compound assign. For an assignment such as 'a += b', the lhs will appear - * twice in the bytecode, in read and write operations. We defer generation of - * the offsets for the initial arithmetic operation until the entire compound - * assign has been processed. - */ -static const char * -GetTokenForAssignment(JSPrinter *jp, jssrcnote *sn, JSOp lastop, - jsbytecode *pc, jsbytecode *rvalpc, - jsbytecode **lastlvalpc, jsbytecode **lastrvalpc) -{ - const char *token; - if (sn && SN_TYPE(sn) == SRC_ASSIGNOP) { - if (lastop == JSOP_GETTER) { - token = js_getter_str; - } else if (lastop == JSOP_SETTER) { - token = js_setter_str; - } else { - token = CodeToken[lastop]; - if (*lastlvalpc && *lastrvalpc) { - UpdateDecompiledParent(jp, *lastlvalpc, pc, 0); - UpdateDecompiledParent(jp, *lastrvalpc, rvalpc, 0); - } - } - } else { - token = ""; - } - *lastlvalpc = NULL; - *lastrvalpc = NULL; - return token; -} - -static ptrdiff_t -SprintNormalFor(JSContext *cx, JSPrinter *jp, SprintStack *ss, const char *initPrefix, - const char *init, jsbytecode *initpc, jsbytecode **ppc, ptrdiff_t *plen) -{ - jsbytecode *pc = *ppc; - jssrcnote *sn = js_GetSrcNote(cx, jp->script, pc); - JS_ASSERT(SN_TYPE(sn) == SRC_FOR); - - /* Print the keyword and the possibly empty init-part. */ - js_printf(jp, "\tfor (%s", initPrefix); - SprintOpcodePermanent(jp, init, initpc); - js_printf(jp, ";"); - - /* Skip the JSOP_NOP or JSOP_POP bytecode. */ - JS_ASSERT(*pc == JSOP_NOP || *pc == JSOP_POP); - pc += JSOP_NOP_LENGTH; - - /* Get the cond, next, and loop-closing tail offsets. */ - ptrdiff_t cond = js_GetSrcNoteOffset(sn, 0); - ptrdiff_t next = js_GetSrcNoteOffset(sn, 1); - ptrdiff_t tail = js_GetSrcNoteOffset(sn, 2); - - /* Find the loop head, skipping over any leading GOTO or NOP. */ - jsbytecode *pc2 = pc; - if (*pc == JSOP_GOTO || *pc == JSOP_NOP) - pc2 += GetBytecodeLength(pc); - LOCAL_ASSERT(tail + GET_JUMP_OFFSET(pc + tail) == pc2 - pc); - - if (cond != tail) { - /* Decompile the loop condition. */ - if (!Decompile(ss, pc + cond, tail - cond)) - return -1; - js_printf(jp, " "); - jsbytecode *condpc; - const char *cond = PopStr(ss, JSOP_NOP, &condpc); - SprintOpcodePermanent(jp, cond, condpc); - } - - /* Need a semicolon whether or not there was a cond. */ - js_puts(jp, ";"); - - if (next != cond) { - /* - * Decompile the loop updater. It may end in a JSOP_POP - * that we skip; or in a JSOP_POPN that we do not skip, - * followed by a JSOP_NOP (skipped as if it's a POP). - * We cope with the difference between these two cases - * by checking for stack imbalance and popping if there - * is an rval. - */ - unsigned saveTop = ss->top; - - if (!Decompile(ss, pc + next, cond - next - JSOP_POP_LENGTH)) - return -1; - LOCAL_ASSERT(ss->top - saveTop <= 1U); - jsbytecode *updatepc = NULL; - const char *update = (ss->top == saveTop) - ? ss->sprinter.stringEnd() - : PopStr(ss, JSOP_NOP, &updatepc); - js_printf(jp, " "); - SprintOpcodePermanent(jp, update, updatepc); - } - - /* Do the loop body. */ - js_printf(jp, ") {\n"); - jp->indent += 4; - next -= pc2 - pc; - if (!Decompile(ss, pc2, next)) - return -1; - jp->indent -= 4; - js_printf(jp, "\t}\n"); - - /* Set len so pc skips over the entire loop. */ - *ppc = pc; - *plen = tail + js_CodeSpec[pc[tail]].length; - return -2; -} - -#undef LOCAL_ASSERT - -static JSBool -InitSprintStack(JSContext *cx, SprintStack *ss, JSPrinter *jp, unsigned depth) -{ - if (!ss->sprinter.init()) - return JS_FALSE; - ss->sprinter.setOffset(PAREN_SLOP); - - /* Allocate the parallel (to avoid padding) offset, opcode and bytecode stacks. */ - size_t offsetsz = depth * sizeof(ptrdiff_t); - size_t opcodesz = depth * sizeof(jsbytecode); - size_t bytecodesz = depth * sizeof(jsbytecode *); - void *space = cx->tempLifoAlloc().alloc(offsetsz + opcodesz + bytecodesz); - if (!space) { - js_ReportOutOfMemory(cx); - return JS_FALSE; - } - ss->offsets = (ptrdiff_t *) space; - ss->opcodes = (jsbytecode *) ((char *)space + offsetsz); - ss->bytecodes = (jsbytecode **) ((char *)space + offsetsz + opcodesz); - - ss->top = ss->inArrayInit = 0; - ss->inGenExp = JS_FALSE; - ss->printer = jp; - return JS_TRUE; -} - -/* - * If nb is non-negative, decompile nb bytecodes starting at pc. Otherwise - * the decompiler starts at pc and continues until it reaches an opcode for - * which decompiling would result in the stack depth equaling -(nb + 1). - */ -static jsbytecode * -Decompile(SprintStack *ss, jsbytecode *pc, int nb) -{ - JSContext *cx = ss->sprinter.context; - JSPrinter *jp, *jp2; - jsbytecode *startpc, *endpc, *pc2, *done, *lvalpc, *rvalpc, *xvalpc; - ptrdiff_t tail, todo, len, oplen, cond, next; - JSOp op, lastop, saveop; - const JSCodeSpec *cs; - jssrcnote *sn, *sn2; - const char *lval, *rval, *xval, *fmt, *token; - unsigned nuses; - int i, argc; - RootedAtom atom(cx); - JSObject *obj; - RootedFunction fun(cx); - JSString *str; - JSBool ok; - JSBool foreach; - JSBool defaultsSwitch = false; - jsval val; - - static const char exception_cookie[] = "/*EXCEPTION*/"; - static const char retsub_pc_cookie[] = "/*RETSUB_PC*/"; - static const char forelem_cookie[] = "/*FORELEM*/"; - static const char with_cookie[] = "/*WITH*/"; - static const char index_format[] = "%s[%s]"; - static const char predot_format[] = "%s%s.%s"; - static const char postdot_format[] = "%s.%s%s"; - static const char preindex_format[] = "%s%s[%s]"; - static const char postindex_format[] = "%s[%s]%s"; - static const char ss_format[] = "%s%s"; - static const char sss_format[] = "%s%s%s"; - - /* Argument and variables decompilation uses the following to share code. */ - JS_STATIC_ASSERT(ARGNO_LEN == SLOTNO_LEN); - -/* - * Local macros - */ -#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, NULL) -#define DECOMPILE_CODE_CLEANUP(pc,nb,cleanup) if (!Decompile(ss, pc, nb)) cleanup -#define DECOMPILE_CODE(pc,nb) DECOMPILE_CODE_CLEANUP(pc,nb,return NULL) -#define TOP_STR() GetStr(ss, ss->top - 1) -#define POP_STR() PopStr(ss, op) -#define POP_STR_PREC(prec) PopStrPrec(ss, prec) - -/* - * Given an atom already fetched from jp->script's atom map, quote/escape its - * string appropriately into rval, and select fmt from the quoted and unquoted - * alternatives. - */ -#define GET_QUOTE_AND_FMT(qfmt, ufmt, rval) \ - JS_BEGIN_MACRO \ - jschar quote_; \ - if (!IsIdentifier(atom)) { \ - quote_ = '\''; \ - fmt = qfmt; \ - } else { \ - quote_ = 0; \ - fmt = ufmt; \ - } \ - rval = QuoteString(&ss->sprinter, atom, quote_); \ - rval = SprintDupeStr(ss, rval); \ - if (!rval) \ - return NULL; \ - JS_END_MACRO - -#define GET_SOURCE_NOTE_ATOM(sn, atom) \ - JS_BEGIN_MACRO \ - jsatomid atomIndex_ = (jsatomid) js_GetSrcNoteOffset((sn), 0); \ - \ - LOCAL_ASSERT(atomIndex_ < jp->script->natoms); \ - (atom) = jp->script->atoms[atomIndex_]; \ - JS_END_MACRO - -/* - * Get atom from jp->script's atom map, quote/escape its string appropriately - * into rval, and select fmt from the quoted and unquoted alternatives. - */ -#define GET_ATOM_QUOTE_AND_FMT(qfmt, ufmt, rval) \ - JS_BEGIN_MACRO \ - LOAD_ATOM(0); \ - GET_QUOTE_AND_FMT(qfmt, ufmt, rval); \ - JS_END_MACRO - -/* - * Per spec, new x(y).z means (new x(y))).z. For example new (x(y).z) must - * decompile with the constructor parenthesized, but new x.z should not. The - * normal rules give x(y).z and x.z identical precedence: both are produced by - * JSOP_GETPROP. - * - * Therefore, we need to know in case JSOP_NEW whether the constructor - * expression contains any unparenthesized function calls. So when building a - * MemberExpression or CallExpression, we set ss->opcodes[n] to JSOP_CALL if - * this is true. x(y).z gets JSOP_CALL, not JSOP_GETPROP. - */ -#define PROPAGATE_CALLNESS() \ - JS_BEGIN_MACRO \ - if (ss->opcodes[ss->top - 1] == JSOP_CALL || \ - ss->opcodes[ss->top - 1] == JSOP_EVAL || \ - ss->opcodes[ss->top - 1] == JSOP_FUNCALL || \ - ss->opcodes[ss->top - 1] == JSOP_FUNAPPLY) { \ - saveop = JSOP_CALL; \ - } \ - JS_END_MACRO - - jsbytecode *lastlvalpc = NULL, *lastrvalpc = NULL; - - JS_CHECK_RECURSION(cx, return NULL); - - jp = ss->printer; - startpc = pc; - endpc = (nb < 0) ? jp->script->code + jp->script->length : pc + nb; - tail = -1; - todo = -2; /* NB: different from Sprint() error return. */ - saveop = JSOP_NOP; - sn = NULL; - rval = NULL; - bool forOf = false; - foreach = false; - - while (nb < 0 || pc < endpc) { - /* - * Move saveop to lastop so prefixed bytecodes can take special action - * while sharing maximal code. Set op and saveop to the new bytecode, - * use op in POP_STR to trigger automatic parenthesization, but push - * saveop at the bottom of the loop if this op pushes. Thus op may be - * set to nop or otherwise mutated to suppress auto-parens. - */ - lastop = saveop; - op = (JSOp) *pc; - cs = &js_CodeSpec[op]; - saveop = op; - len = oplen = cs->length; - nuses = StackUses(jp->script, pc); - - /* - * Here it is possible that nuses > ss->top when the op has a hidden - * source note. But when nb < 0 we assume that the caller knows that - * Decompile would never meet such opcodes. - */ - if (nb < 0) { - LOCAL_ASSERT(ss->top >= nuses); - unsigned ndefs = StackDefs(jp->script, pc); - if ((unsigned) -(nb + 1) == ss->top - nuses + ndefs) - return pc; - } - - /* - * Save source literal associated with JS now before the following - * rewrite changes op. See bug 380197. - */ - token = CodeToken[op]; - - if (pc + oplen == jp->dvgfence) { - /* - * Rewrite non-get ops to their "get" format if the error is in - * the bytecode at pc, or if at an inner opcode of a 'fat' outer - * opcode at pc, so we don't decompile more than the error - * expression. - */ - uint32_t format = cs->format; - bool matchPC = false; - ScriptFrameIter iter(cx); - if (!iter.done()) { - jsbytecode *npc = iter.pc(); - if (pc == npc) { - matchPC = true; - } else if (format & JOF_DECOMPOSE) { - if (unsigned(npc - pc) < GetDecomposeLength(pc, js_CodeSpec[*pc].length)) - matchPC = true; - } - } - if ((matchPC || (pc == startpc && nuses != 0)) && - format & (JOF_SET|JOF_DEL|JOF_INCDEC)) { - uint32_t mode = JOF_MODE(format); - if (mode == JOF_NAME) { - /* - * JOF_NAME does not imply JOF_ATOM, so we must check for - * the QARG and QVAR format types, and translate those to - * JSOP_GETARG or JSOP_GETLOCAL appropriately, instead of - * to JSOP_NAME. - */ - uint32_t type = JOF_TYPE(format); - op = (type == JOF_QARG) - ? JSOP_GETARG - : (type == JOF_LOCAL) - ? JSOP_GETLOCAL - : (type == JOF_SCOPECOORD) - ? JSOP_GETALIASEDVAR - : JSOP_NAME; - - JS_ASSERT(js_CodeSpec[op].nuses >= 0); - i = nuses - js_CodeSpec[op].nuses; - while (--i >= 0) - PopOff(ss, JSOP_NOP); - } else { - /* - * We must replace the faulting pc's bytecode with a - * corresponding JSOP_GET* code. For JSOP_SET{PROP,ELEM}, - * we must use the "2nd" form of JSOP_GET{PROP,ELEM}, to - * throw away the assignment op's right-hand operand and - * decompile it as if it were a GET of its left-hand - * operand. - */ - if (mode == JOF_PROP) { - op = (JSOp) ((format & JOF_SET) - ? JSOP_GETPROP2 - : JSOP_GETPROP); - } else if (mode == JOF_ELEM) { - op = (JSOp) ((format & JOF_SET) - ? JSOP_GETELEM2 - : JSOP_GETELEM); - } else { - /* - * Unknown mode (including mode 0) means that op is - * uncategorized for our purposes, so we must write - * per-op special case code here. - */ - switch (op) { - case JSOP_ENUMELEM: - case JSOP_ENUMCONSTELEM: - op = JSOP_GETELEM; - break; - default: - LOCAL_ASSERT(0); - } - } - } - } - - saveop = op; - if (op >= JSOP_LIMIT) { - if (op == JSOP_GETPROP2) - saveop = JSOP_GETPROP; - else if (op == JSOP_GETELEM2) - saveop = JSOP_GETELEM; - } - - jp->dvgfence = NULL; - } - - jsbytecode *pushpc = pc; - - if (token) { - switch (nuses) { - case 2: - sn = js_GetSrcNote(cx, jp->script, pc); - if (sn && SN_TYPE(sn) == SRC_ASSIGNOP) { - /* - * Avoid over-parenthesizing y in x op= y based on its - * expansion: x = x op y (replace y by z = w to see the - * problem). - */ - op = (JSOp) pc[oplen]; - rval = PopStr(ss, op, &lastrvalpc); - (void)PopStr(ss, op, &lastlvalpc); - - /* Print only the right operand of the assignment-op. */ - todo = ss->sprinter.put(rval); - } else { - rval = PopStrPrecDupe(ss, cs->prec + !!(cs->format & JOF_LEFTASSOC), &rvalpc); - lval = PopStrPrec(ss, cs->prec + !(cs->format & JOF_LEFTASSOC), &lvalpc); - todo = ss->sprinter.getOffset(); - SprintOpcode(ss, lval, lvalpc, pc, todo); - Sprint(&ss->sprinter, " %s ", token); - SprintOpcode(ss, rval, rvalpc, pc, todo); - } - break; - - case 1: - rval = PopStrDupe(ss, op, &rvalpc); - todo = ss->sprinter.put(token); - SprintOpcode(ss, rval, rvalpc, pc, todo); - break; - - case 0: - sn = js_GetSrcNote(cx, jp->script, pc); - if (sn && SN_TYPE(sn) == SRC_CONTINUE) { - /* Hoisted let decl (e.g. 'y' in 'let (x) { let y; }'). */ - todo = ss->sprinter.put(SkipString); - break; - } - todo = ss->sprinter.put(token); - break; - - default: - todo = -2; - break; - } - } else { - switch (op) { - case JSOP_NOP: - /* - * Check for a do-while loop, a for-loop with an empty - * initializer part, a labeled statement, a function - * definition, or try/finally. - */ - sn = js_GetSrcNote(cx, jp->script, pc); - todo = -2; - switch (sn ? SN_TYPE(sn) : SRC_NULL) { - case SRC_WHILE: - /* First instuction (NOP) contains offset to condition */ - ++pc; - /* Second instruction (TRACE) contains offset to JSOP_IFNE */ - sn = js_GetSrcNote(cx, jp->script, pc); - tail = js_GetSrcNoteOffset(sn, 0); - LOCAL_ASSERT(pc[tail] == JSOP_IFNE); - js_printf(jp, "\tdo {\n"); - jp->indent += 4; - DECOMPILE_CODE(pc, tail); - jp->indent -= 4; - js_printf(jp, "\t} while ("); - rval = PopCondStr(ss, &rvalpc); - SprintOpcodePermanent(jp, rval, rvalpc); - js_printf(jp, ");\n"); - pc += tail; - len = js_CodeSpec[*pc].length; - todo = -2; - break; - - case SRC_FOR: - /* for loop with empty initializer. */ - todo = SprintNormalFor(cx, jp, ss, "", "", NULL, &pc, &len); - break; - - case SRC_ENDBRACE: - jp->indent -= 4; - js_printf(jp, "\t}\n"); - break; - - case SRC_FUNCDEF: - fun = jp->script->getFunction(js_GetSrcNoteOffset(sn, 0)); - do_function: - js_puts(jp, "\n"); - jp2 = js_NewPrinter(cx, "nested_function", fun, - jp->indent, jp->pretty, jp->grouped, - jp->strict); - if (!jp2) - return NULL; - ok = DecompileFunction(jp2); - if (ok && !jp2->sprinter.empty()) - js_puts(jp, jp2->sprinter.string()); - js_DestroyPrinter(jp2); - if (!ok) - return NULL; - js_puts(jp, "\n\n"); - break; - - case SRC_BRACE: - js_printf(jp, "\t{\n"); - jp->indent += 4; - len = js_GetSrcNoteOffset(sn, 0); - DECOMPILE_CODE(pc + oplen, len - oplen); - jp->indent -= 4; - js_printf(jp, "\t}\n"); - break; - - default:; - } - break; - - case JSOP_LABEL: - sn = js_GetSrcNote(cx, jp->script, pc); - todo = -2; - switch (sn ? SN_TYPE(sn) : SRC_NULL) { - case SRC_LABEL: - GET_SOURCE_NOTE_ATOM(sn, atom); - jp->indent -= 4; - rval = QuoteString(&ss->sprinter, atom, 0); - if (!rval) - return NULL; - ss->sprinter.setOffset(rval); - js_printf(jp, "\t%s:\n", rval); - jp->indent += 4; - break; - - case SRC_LABELBRACE: - GET_SOURCE_NOTE_ATOM(sn, atom); - rval = QuoteString(&ss->sprinter, atom, 0); - if (!rval) - return NULL; - ss->sprinter.setOffset(rval); - js_printf(jp, "\t%s: {\n", rval); - jp->indent += 4; - break; - - default: - JS_NOT_REACHED("JSOP_LABEL without source note"); - break; - } - break; - - case JSOP_BINDNAME: - case JSOP_BINDGNAME: - todo = Sprint(&ss->sprinter, ""); - break; - - case JSOP_TRY: - js_printf(jp, "\ttry {\n"); - jp->indent += 4; - todo = -2; - break; - - case JSOP_FINALLY: - jp->indent -= 4; - js_printf(jp, "\t} finally {\n"); - jp->indent += 4; - - /* - * We push push the pair of exception/restsub cookies to - * simulate the effects [gosub] or control transfer during - * exception capturing on the stack. - */ - todo = Sprint(&ss->sprinter, exception_cookie); - if (todo < 0 || !PushOff(ss, todo, op)) - return NULL; - todo = Sprint(&ss->sprinter, retsub_pc_cookie); - break; - - case JSOP_RETSUB: - rval = POP_STR(); - LOCAL_ASSERT(strcmp(rval, retsub_pc_cookie) == 0); - lval = POP_STR(); - LOCAL_ASSERT(strcmp(lval, exception_cookie) == 0); - todo = -2; - break; - - case JSOP_GOSUB: - /* - * JSOP_GOSUB has no effect on the decompiler's string stack - * because the next op in bytecode order finds the stack - * balanced by a JSOP_RETSUB executed elsewhere. - */ - todo = -2; - break; - - case JSOP_POPN: - { - unsigned newtop, oldtop; - - /* - * The compiler models operand stack depth and fixes the stack - * pointer on entry to a catch clause based on its depth model. - * The decompiler must match the code generator's model, which - * is why JSOP_FINALLY pushes a cookie that JSOP_RETSUB pops. - */ - oldtop = ss->top; - newtop = oldtop - GET_UINT16(pc); - LOCAL_ASSERT(newtop <= oldtop); - todo = -2; - - sn = js_GetSrcNote(cx, jp->script, pc); - if (sn && SN_TYPE(sn) == SRC_HIDDEN) - break; -#if JS_HAS_DESTRUCTURING - if (sn && SN_TYPE(sn) == SRC_GROUPASSIGN) { - todo = Sprint(&ss->sprinter, "%s[] = [", - VarPrefix(sn)); - if (todo < 0) - return NULL; - for (unsigned i = newtop; i < oldtop; i++) { - rval = ss->sprinter.stringAt(ss->offsets[i]); - if (Sprint(&ss->sprinter, ss_format, - (i == newtop) ? "" : ", ", - (i == oldtop - 1 && *rval == '\0') - ? ", " : rval) < 0) { - return NULL; - } - } - if (ss->sprinter.put("]", 1) < 0) - return NULL; - - /* - * If this is an empty group assignment, we have no stack - * budget into which we can push our result string. Adjust - * ss->sprinter.offset so that our consumer can find the - * empty group assignment decompilation. - */ - if (newtop == oldtop) { - ss->sprinter.setOffset(todo); - } else { - /* - * Kill newtop before the end_groupassignment: label by - * retracting/popping early. - */ - LOCAL_ASSERT(newtop < oldtop); - ss->sprinter.setOffset(GetOff(ss, newtop)); - ss->top = newtop; - } - - end_groupassignment: - LOCAL_ASSERT(*pc == JSOP_POPN); - - /* - * Thread directly to the next opcode if we can, to handle - * the special cases of a group assignment in the first or - * last part of a for(;;) loop head, or in a let block or - * expression head. - * - * NB: todo at this point indexes space in ss->sprinter - * that is liable to be overwritten. The code below knows - * exactly how long rval lives, or else copies it down via - * Sprinter::put. - */ - rval = ss->sprinter.stringAt(todo); - rvalpc = NULL; - todo = -2; - pc2 = pc + oplen; - - if (*pc2 == JSOP_NOP) { - sn = js_GetSrcNote(cx, jp->script, pc2); - if (sn) { - if (SN_TYPE(sn) == SRC_FOR) { - op = JSOP_NOP; - pc = pc2; - todo = SprintNormalFor(cx, jp, ss, "", rval, rvalpc, &pc, &len); - break; - } - } else { - /* - * An unnannotated NOP following a POPN must be the - * third part of for(;;) loop head. If the POPN's - * immediate operand is 0, then we may have no slot - * on the sprint-stack in which to push our result - * string. In this case the result can be recovered - * at ss->sprinter.base + ss->sprinter.offset. - */ - if (GET_UINT16(pc) == 0) - break; - todo = ss->sprinter.put(rval); - saveop = JSOP_NOP; - } - } - - /* - * If control flow reaches this point with todo still -2, - * just print rval as an expression statement. - */ - if (todo == -2) - js_printf(jp, "\t%s;\n", rval); - break; - } -#endif - if (newtop < oldtop) { - ss->sprinter.setOffset(GetOff(ss, newtop)); - ss->top = newtop; - } - break; - } - - case JSOP_EXCEPTION: - /* The catch decompiler handles this op itself. */ - LOCAL_ASSERT(JS_FALSE); - break; - - case JSOP_POP: - /* - * By default, do not automatically parenthesize when popping - * a stacked expression decompilation. We auto-parenthesize - * only when JSOP_POP is annotated with SRC_PCDELTA, meaning - * comma operator. - */ - op = JSOP_POPV; - /* FALL THROUGH */ - - case JSOP_POPV: - sn = js_GetSrcNote(cx, jp->script, pc); - switch (sn ? SN_TYPE(sn) : SRC_NULL) { - case SRC_FOR: - /* Force parens around 'in' expression at 'for' front. */ - if (ss->opcodes[ss->top-1] == JSOP_IN) - op = JSOP_LSH; - rval = PopStr(ss, op, &rvalpc); - todo = SprintNormalFor(cx, jp, ss, "", rval, rvalpc, &pc, &len); - break; - - case SRC_PCDELTA: - /* Comma operator: use JSOP_POP for correct precedence. */ - op = JSOP_POP; - - /* Pop and save to avoid blowing stack depth budget. */ - lval = PopStrDupe(ss, op, &lvalpc); - - /* - * The offset tells distance to the end of the right-hand - * operand of the comma operator. - */ - pushpc = pc; - done = pc + len; - pc += js_GetSrcNoteOffset(sn, 0); - len = 0; - - if (!Decompile(ss, done, pc - done)) - return NULL; - - /* Pop Decompile result and print comma expression. */ - rval = PopStrDupe(ss, op, &rvalpc); - todo = ss->sprinter.getOffset(); - SprintOpcode(ss, lval, lvalpc, pushpc, todo); - ss->sprinter.put(", "); - SprintOpcode(ss, rval, rvalpc, pushpc, todo); - break; - - case SRC_HIDDEN: - /* Hide this pop, it's from a goto in a with or for/in. */ - todo = -2; - break; - - case SRC_CONTINUE: - /* Pop the stack, don't print: end of a for-let-in. */ - (void) PopOff(ss, op); - todo = -2; - break; - - default: - { - /* Turn off parens around a yield statement. */ - if (ss->opcodes[ss->top-1] == JSOP_YIELD) - op = JSOP_NOP; - - jsbytecode *rvalpc; - rval = PopStr(ss, op, &rvalpc); - - /* - * Don't emit decompiler-pushed strings that are not - * handled by other opcodes. They are pushed onto the - * stack to help model the interpreter stack and should - * not appear in the decompiler's output. - */ - if (*rval != '\0' && (rval[0] != '/' || rval[1] != '*')) { - bool parens = - *rval == '{' || - (strncmp(rval, js_function_str, 8) == 0 && - rval[8] == ' '); - js_printf(jp, parens ? "\t(" : "\t"); - SprintOpcodePermanent(jp, rval, rvalpc); - js_printf(jp, parens ? ");\n" : ";\n"); - } else { - LOCAL_ASSERT(*rval == '\0' || - strcmp(rval, exception_cookie) == 0); - } - todo = -2; - break; - } - } - sn = NULL; - break; - - case JSOP_PICK: - { - unsigned i = pc[1]; - LOCAL_ASSERT(ss->top > i + 1); - unsigned bottom = ss->top - (i + 1); - - ptrdiff_t pickedOffset = ss->offsets[bottom]; - memmove(ss->offsets + bottom, ss->offsets + bottom + 1, - i * sizeof(ss->offsets[0])); - ss->offsets[ss->top - 1] = pickedOffset; - - jsbytecode pickedOpcode = ss->opcodes[bottom]; - memmove(ss->opcodes + bottom, ss->opcodes + bottom + 1, - i * sizeof(ss->opcodes[0])); - ss->opcodes[ss->top - 1] = pickedOpcode; - - todo = -2; - break; - } - - case JSOP_ENTERWITH: - LOCAL_ASSERT(!js_GetSrcNote(cx, jp->script, pc)); - rval = PopStr(ss, op, &rvalpc); - js_printf(jp, "\twith ("); - SprintOpcodePermanent(jp, rval, rvalpc); - js_printf(jp, ") {\n"); - jp->indent += 4; - todo = Sprint(&ss->sprinter, with_cookie); - break; - - case JSOP_LEAVEWITH: - sn = js_GetSrcNote(cx, jp->script, pc); - todo = -2; - if (sn && SN_TYPE(sn) == SRC_HIDDEN) - break; - rval = POP_STR(); - LOCAL_ASSERT(strcmp(rval, with_cookie) == 0); - jp->indent -= 4; - js_printf(jp, "\t}\n"); - break; - - case JSOP_ENTERBLOCK: - { - obj = jp->script->getObject(GET_UINT32_INDEX(pc)); - AtomVector atoms(cx); - StaticBlockObject &blockObj = obj->asStaticBlock(); - - if (!GetBlockNames(cx, blockObj, &atoms) || !PushBlockNames(ss, atoms)) - return NULL; - - sn = js_GetSrcNote(cx, jp->script, pc); - switch (sn ? SN_TYPE(sn) : SRC_NULL) { -#if JS_HAS_BLOCK_SCOPE - case SRC_BRACE: - js_printf(jp, "\t{\n"); - jp->indent += 4; - len = js_GetSrcNoteOffset(sn, 0); - if (!Decompile(ss, pc + oplen, len - oplen)) - return NULL; - jp->indent -= 4; - js_printf(jp, "\t}\n"); - break; -#endif - - case SRC_CATCH: - jp->indent -= 4; - js_printf(jp, "\t} catch ("); - - pc2 = pc; - pc += oplen; - LOCAL_ASSERT(*pc == JSOP_EXCEPTION); - pc += JSOP_EXCEPTION_LENGTH; - todo = Sprint(&ss->sprinter, exception_cookie); - if (todo < 0 || !PushOff(ss, todo, JSOP_EXCEPTION)) - return NULL; - - if (*pc == JSOP_DUP) { - sn2 = js_GetSrcNote(cx, jp->script, pc); - if (!sn2 || SN_TYPE(sn2) != SRC_DESTRUCT) { - /* - * This is a dup to save the exception for later. - * It is emitted only when the catch head contains - * an exception guard. - */ - LOCAL_ASSERT(js_GetSrcNoteOffset(sn, 0) != 0); - pc += JSOP_DUP_LENGTH; - todo = Sprint(&ss->sprinter, exception_cookie); - if (todo < 0 || !PushOff(ss, todo, JSOP_EXCEPTION)) - return NULL; - } - } - -#if JS_HAS_DESTRUCTURING - if (*pc == JSOP_DUP) { - pc = DecompileDestructuring(ss, pc, endpc); - if (!pc) - return NULL; - LOCAL_ASSERT(*pc == JSOP_POP); - pc += JSOP_POP_LENGTH; - lval = PopStr(ss, JSOP_NOP); - js_puts(jp, lval); - } else { -#endif - LOCAL_ASSERT(*pc == JSOP_SETLOCAL || *pc == JSOP_SETALIASEDVAR); - pc += js_CodeSpec[*pc].length; - LOCAL_ASSERT(*pc == JSOP_POP); - pc += JSOP_POP_LENGTH; - LOCAL_ASSERT(blockObj.slotCount() >= 1); - if (!QuoteString(&jp->sprinter, atoms[0], 0)) - return NULL; -#if JS_HAS_DESTRUCTURING - } -#endif - - /* - * Pop the exception_cookie (or its dup in the case of a - * guarded catch head) off the stack now. - */ - rval = PopStr(ss, JSOP_NOP); - LOCAL_ASSERT(strcmp(rval, exception_cookie) == 0); - - len = js_GetSrcNoteOffset(sn, 0); - if (len) { - len -= pc - pc2; - LOCAL_ASSERT(len > 0); - js_printf(jp, " if "); - if (!Decompile(ss, pc, len)) - return NULL; - js_printf(jp, "%s", POP_STR()); - pc += len; - LOCAL_ASSERT(*pc == JSOP_IFEQ); - pc += js_CodeSpec[*pc].length; - } - - js_printf(jp, ") {\n"); - jp->indent += 4; - len = 0; - break; - default:; - } - - todo = -2; - } - break; - - case JSOP_LEAVEBLOCK: - case JSOP_LEAVEBLOCKEXPR: - { - unsigned top, depth; - - sn = js_GetSrcNote(cx, jp->script, pc); - todo = -2; - if (op == JSOP_LEAVEBLOCKEXPR) { - LOCAL_ASSERT(SN_TYPE(sn) == SRC_PCBASE); - rval = POP_STR(); - } else if (sn) { - LOCAL_ASSERT(op == JSOP_LEAVEBLOCK); - if (SN_TYPE(sn) == SRC_HIDDEN) - break; - - /* - * This JSOP_LEAVEBLOCK must be for a catch block. If sn's - * offset does not equal the model stack depth, there must - * be a copy of the exception value on the stack due to a - * catch guard (see above, the JSOP_ENTERBLOCK + SRC_CATCH - * case code). - */ - LOCAL_ASSERT(SN_TYPE(sn) == SRC_CATCH); - if ((unsigned)js_GetSrcNoteOffset(sn, 0) != ss->top) { - LOCAL_ASSERT((unsigned)js_GetSrcNoteOffset(sn, 0) - == ss->top - 1); - rval = POP_STR(); - LOCAL_ASSERT(strcmp(rval, exception_cookie) == 0); - } - } - top = ss->top; - depth = GET_UINT16(pc); - LOCAL_ASSERT(top >= depth); - top -= depth; - ss->top = top; - ss->sprinter.setOffset(GetOff(ss, top)); - if (op == JSOP_LEAVEBLOCKEXPR) - todo = ss->sprinter.put(rval); - break; - } - - case JSOP_ENTERLET0: - { - obj = jp->script->getObject(GET_UINT32_INDEX(pc)); - StaticBlockObject &blockObj = obj->asStaticBlock(); - - AtomVector atoms(cx); - if (!GetBlockNames(cx, blockObj, &atoms)) - return NULL; - - sn = js_GetSrcNote(cx, jp->script, pc); - LOCAL_ASSERT(SN_TYPE(sn) == SRC_DECL); - ptrdiff_t letData = js_GetSrcNoteOffset(sn, 0); - bool groupAssign = LetDataToGroupAssign(letData); - unsigned letDepth = blockObj.stackDepth(); - LOCAL_ASSERT(letDepth == (unsigned)ss->top - blockObj.slotCount()); - LOCAL_ASSERT(atoms.length() == blockObj.slotCount()); - - /* - * Build the list of decompiled rhs expressions. Do this before - * sprinting the let-head since GetStr can inject stuff on top - * of the stack (in case js_DecompileValueGenerator). - */ - Vector rhsExprs(cx); - if (!rhsExprs.resize(atoms.length())) - return NULL; - for (size_t i = 0; i < rhsExprs.length(); ++i) - rhsExprs[i] = SprintDupeStr(ss, GetStr(ss, letDepth + i)); - - /* Build the let head starting at headBegin. */ - ptrdiff_t headBegin = ss->sprinter.getOffset(); - - /* - * For group assignment, prepend the '[lhs-vars] = [' here, - * append rhsExprs in the next loop and append ']' after. - */ - if (groupAssign) { - if (Sprint(&ss->sprinter, "[") < 0) - return NULL; - for (size_t i = 0; i < atoms.length(); ++i) { - if (i && Sprint(&ss->sprinter, ", ") < 0) - return NULL; - if (!QuoteString(&ss->sprinter, atoms[i], 0)) - return NULL; - } - if (Sprint(&ss->sprinter, "] = [") < 0) - return NULL; - } - - for (size_t i = 0; i < atoms.length(); ++i) { - const char *rhs = rhsExprs[i]; - if (!strcmp(rhs, SkipString)) - continue; - - if (i && Sprint(&ss->sprinter, ", ") < 0) - return NULL; - - if (groupAssign) { - if (ss->sprinter.put(rhs) < 0) - return NULL; - } else if (!strncmp(rhs, DestructuredString, DestructuredStringLength)) { - if (ss->sprinter.put(rhs + DestructuredStringLength) < 0) - return NULL; - } else { - JS_ASSERT(atoms[i] != cx->names().empty); - if (!QuoteString(&ss->sprinter, atoms[i], 0)) - return NULL; - if (*rhs) { - uint8_t prec = js_CodeSpec[ss->opcodes[letDepth + i]].prec; - const char *fmt = prec && prec < js_CodeSpec[JSOP_SETLOCAL].prec - ? " = (%s)" - : " = %s"; - if (Sprint(&ss->sprinter, fmt, rhs) < 0) - return NULL; - } - } - } - - if (groupAssign && Sprint(&ss->sprinter, "]") < 0) - return NULL; - - /* Clone the let head chars before clobbering the stack. */ - DupBuffer head(cx); - if (!Dup(ss->sprinter.stringAt(headBegin), &head)) - return NULL; - if (!AssignBlockNamesToPushedSlots(ss, atoms)) - return NULL; - - /* Detect 'for (let ...)' desugared into 'let (...) {for}'. */ - jsbytecode *nextpc = pc + JSOP_ENTERLET0_LENGTH; - if (*nextpc == JSOP_NOP) { - jssrcnote *nextsn = js_GetSrcNote(cx, jp->script, nextpc); - if (nextsn && SN_TYPE(nextsn) == SRC_FOR) { - pc = nextpc; - todo = SprintNormalFor(cx, jp, ss, "let ", head.begin(), pc, &pc, &len); - break; - } - } - - /* Decompile the body and then complete the let block/expr. */ - len = LetDataToOffset(letData); - pc = nextpc; - saveop = (JSOp) pc[len]; - todo = SprintLetBody(jp, ss, pc, len, head.begin()); - break; - } - - /* - * With 'for (let lhs in rhs)' and 'switch (c) { let-decl }', - * placeholder slots have already been pushed (by JSOP_UNDEFINED). - * In both the for-let-in and switch-hoisted-let cases: - * - there is a non-let slot on top of the stack (hence enterlet1) - * - there is no further special let-handling required: - * for-let-in will decompile the let head when it decompiles - * the loop body prologue; there is no let head to decompile - * with switch. - * Hence, the only thing to do is update the let vars' slots with - * their names, taking care to preserve the iter/condition value - * on top of the stack. - */ - case JSOP_ENTERLET1: - { - obj = jp->script->getObject(GET_UINT32_INDEX(pc)); - StaticBlockObject &blockObj = obj->asStaticBlock(); - - AtomVector atoms(cx); - if (!GetBlockNames(cx, blockObj, &atoms)) - return NULL; - - LOCAL_ASSERT(js_GetSrcNote(cx, jp->script, pc) == NULL); - LOCAL_ASSERT(ss->top - 1 == blockObj.stackDepth() + blockObj.slotCount()); - jsbytecode *nextpc = pc + JSOP_ENTERLET1_LENGTH; - if (*nextpc == JSOP_GOTO) { - LOCAL_ASSERT(SN_TYPE(js_GetSrcNote(cx, jp->script, nextpc)) == SRC_FOR_IN); - } else { - LOCAL_ASSERT(*nextpc == JSOP_CONDSWITCH || - *nextpc == JSOP_TABLESWITCH); - } - - DupBuffer rhs(cx); - if (!Dup(PopStr(ss, JSOP_NOP), &rhs)) - return NULL; - if (!AssignBlockNamesToPushedSlots(ss, atoms)) - return NULL; - if (!PushStr(ss, rhs.begin(), op)) - return NULL; - todo = -2; - break; - } - - case JSOP_CALLALIASEDVAR: - case JSOP_GETALIASEDVAR: - case JSOP_CALLLOCAL: - case JSOP_GETLOCAL: - if (IsVarSlot(jp, pc, atom.address(), &i)) - goto do_name; - LOCAL_ASSERT((unsigned)i < ss->top); - sn = js_GetSrcNote(cx, jp->script, pc); - -#if JS_HAS_DESTRUCTURING - if (sn && SN_TYPE(sn) == SRC_GROUPASSIGN) { - /* - * Distinguish a js_DecompileValueGenerator call that - * targets op alone, from decompilation of a full group - * assignment sequence, triggered by SRC_GROUPASSIGN - * annotating the first JSOP_GETLOCAL in the sequence. - */ - if (endpc - pc > JSOP_GETLOCAL_LENGTH || pc > startpc) { - pc = DecompileGroupAssignment(ss, pc, endpc, sn, &todo); - if (!pc) - return NULL; - LOCAL_ASSERT(*pc == JSOP_POPN); - len = oplen = JSOP_POPN_LENGTH; - goto end_groupassignment; - } - - /* Null sn to prevent bogus VarPrefix'ing below. */ - sn = NULL; - } -#endif - - rval = GetLocal(ss, i); - todo = Sprint(&ss->sprinter, ss_format, VarPrefix(sn), rval); - break; - - case JSOP_SETALIASEDVAR: - case JSOP_SETLOCAL: - if (IsVarSlot(jp, pc, atom.address(), &i)) - goto do_setname; - lval = GetLocal(ss, i); - rval = PopStrDupe(ss, op, &rvalpc); - goto do_setlval; - - case JSOP_INCALIASEDVAR: - case JSOP_DECALIASEDVAR: - if (IsVarSlot(jp, pc, atom.address(), &i)) - goto do_incatom; - lval = GetLocal(ss, i); - goto do_inclval; - - case JSOP_INCLOCAL: - case JSOP_DECLOCAL: - /* INCLOCAL/DECLOCAL are followed by GETLOCAL containing the slot. */ - JS_ASSERT(pc[JSOP_INCLOCAL_LENGTH] == JSOP_GETLOCAL); - if (IsVarSlot(jp, pc + JSOP_INCLOCAL_LENGTH, atom.address(), &i)) - goto do_incatom; - lval = GetLocal(ss, i); - goto do_inclval; - - case JSOP_ALIASEDVARINC: - case JSOP_ALIASEDVARDEC: - if (IsVarSlot(jp, pc, atom.address(), &i)) - goto do_atominc; - lval = GetLocal(ss, i); - goto do_lvalinc; - - case JSOP_LOCALINC: - case JSOP_LOCALDEC: - /* LOCALINC/LOCALDEC are followed by GETLOCAL containing the slot. */ - JS_ASSERT(pc[JSOP_LOCALINC_LENGTH] == JSOP_GETLOCAL); - if (IsVarSlot(jp, pc + JSOP_LOCALINC_LENGTH, atom.address(), &i)) - goto do_atominc; - lval = GetLocal(ss, i); - goto do_lvalinc; - - case JSOP_RETRVAL: - todo = -2; - break; - - case JSOP_RETURN: - LOCAL_ASSERT(jp->fun); - fun = jp->fun; - if (fun->isExprClosure()) { - /* Turn on parens around comma-expression here. */ - op = JSOP_SETNAME; - rval = PopStr(ss, op, &rvalpc); - bool parens = (*rval == '{'); - if (parens) - js_printf(jp, "("); - SprintOpcodePermanent(jp, rval, rvalpc); - js_printf(jp, parens ? ")%s" : "%s", - (fun->isLambda() || !fun->atom()) - ? "" - : ";"); - todo = -2; - break; - } - /* FALL THROUGH */ - - case JSOP_SETRVAL: - rval = PopStr(ss, op, &rvalpc); - if (*rval != '\0') { - js_printf(jp, "\t%s ", js_return_str); - SprintOpcodePermanent(jp, rval, rvalpc); - js_printf(jp, ";\n"); - } else { - js_printf(jp, "\t%s;\n", js_return_str); - } - todo = -2; - break; - -#if JS_HAS_GENERATORS - case JSOP_YIELD: -#if JS_HAS_GENERATOR_EXPRS - if (!ss->inGenExp || !(sn = js_GetSrcNote(cx, jp->script, pc))) -#endif - { - /* Turn off most parens. */ - op = JSOP_SETNAME; - rval = POP_STR(); - todo = (*rval != '\0') - ? Sprint(&ss->sprinter, - (strncmp(rval, js_yield_str, 5) == 0 && - (rval[5] == ' ' || rval[5] == '\0')) - ? "%s (%s)" - : "%s %s", - js_yield_str, rval) - : ss->sprinter.put(js_yield_str); - break; - } - -#if JS_HAS_GENERATOR_EXPRS - LOCAL_ASSERT(SN_TYPE(sn) == SRC_HIDDEN); - /* FALL THROUGH */ -#endif - - case JSOP_ARRAYPUSH: - { - /* Turn off most parens. */ - op = JSOP_SETNAME; - - /* Pop the expression being pushed or yielded. */ - rval = POP_STR(); - - /* - * Skip the for loop head stacked by JSOP_GOTO:SRC_FOR_IN until - * we hit a block local slot (note empty destructuring patterns - * result in unit-count blocks). - */ - unsigned pos = ss->top; - while (pos != 0) { - op = (JSOp) ss->opcodes[--pos]; - if (op == JSOP_ENTERBLOCK) - break; - } - JS_ASSERT(op == JSOP_ENTERBLOCK); - - /* - * Here, forpos must index the space before the left-most |for| - * in the single string of accumulated |for| heads and optional - * final |if (condition)|. - */ - unsigned forpos = pos + 1; - LOCAL_ASSERT(forpos < ss->top); - - /* - * Now move pos downward over the block's local slots. Even an - * empty destructuring pattern has one (dummy) local. - */ - while (ss->opcodes[pos] == JSOP_ENTERBLOCK) { - if (pos == 0) - break; - --pos; - } - -#if JS_HAS_GENERATOR_EXPRS - if (saveop == JSOP_YIELD) { - /* - * Generator expression: decompile just rval followed by - * the string starting at forpos. Leave the result string - * in ss->offsets[0] so it can be recovered by our caller - * (the JSOP_ANONFUNOBJ with SRC_GENEXP case). Bump the - * top of stack to balance yield, which is an expression - * (so has neutral stack balance). - */ - LOCAL_ASSERT(pos == 0); - xval = ss->sprinter.stringAt(ss->offsets[forpos]); - ss->sprinter.setOffset(PAREN_SLOP); - todo = Sprint(&ss->sprinter, ss_format, rval, xval); - if (todo < 0) - return NULL; - ss->offsets[0] = todo; - ++ss->top; - return pc; - } -#endif /* JS_HAS_GENERATOR_EXPRS */ - - /* - * Array comprehension: retract the sprinter to the beginning - * of the array initialiser and decompile "[ for ...]". - */ - JS_ASSERT(jp->script->nfixed + pos == GET_UINT16(pc)); - LOCAL_ASSERT(ss->opcodes[pos] == JSOP_NEWINIT); - - ptrdiff_t start = ss->offsets[pos]; - LOCAL_ASSERT(ss->sprinter[start] == '[' || - ss->sprinter[start] == '#'); - LOCAL_ASSERT(forpos < ss->top); - xval = ss->sprinter.stringAt(ss->offsets[forpos]); - lval = ss->sprinter.stringAt(start); - ss->sprinter.setOffset(lval); - - todo = Sprint(&ss->sprinter, sss_format, lval, rval, xval); - if (todo < 0) - return NULL; - ss->offsets[pos] = todo; - todo = -2; - break; - } -#endif /* JS_HAS_GENERATORS */ - - case JSOP_THROWING: - todo = -2; - break; - - case JSOP_THROW: - sn = js_GetSrcNote(cx, jp->script, pc); - todo = -2; - if (sn && SN_TYPE(sn) == SRC_HIDDEN) - break; - rval = PopStr(ss, op, &rvalpc); - js_printf(jp, "\t%s ", js_throw_str); - SprintOpcodePermanent(jp, rval, rvalpc); - js_printf(jp, ";\n"); - break; - - case JSOP_ITER: - forOf = (GET_UINT8(pc) == JSITER_FOR_OF); - foreach = (GET_UINT8(pc) & (JSITER_FOREACH | JSITER_KEYVALUE)) == - JSITER_FOREACH; - todo = -2; - break; - - case JSOP_MOREITER: - JS_NOT_REACHED("JSOP_MOREITER"); - break; - - case JSOP_ENDITER: - sn = js_GetSrcNote(cx, jp->script, pc); - todo = -2; - if (sn && SN_TYPE(sn) == SRC_HIDDEN) - break; - (void) PopOff(ss, op); - break; - - case JSOP_GOTO: - sn = js_GetSrcNote(cx, jp->script, pc); - switch (sn ? SN_TYPE(sn) : SRC_NULL) { - case SRC_FOR_IN: - /* - * The bytecode around pc looks like this: - * <> - * iter - * pc: goto C [src_for_in(B, D)] - * A: loophead - * iternext - * <> - * B: pop [maybe a src_decl_var/let] - * <> - * C: moreiter - * ifne A - * enditer - * D: ... - * - * In an array comprehension or generator expression, we - * construct the for-head and store it in the slot pushed - * by JSOP_ITER, then recurse to decompile S. The - * culminating JSOP_ARRAYPUSH or JSOP_YIELD instruction - * (which S must contain, by construction) glues all the - * clauses together. - * - * Otherwise this is a for-in statement. We eagerly output - * the for-head and recurse to decompile the controlled - * statement S. - * - * We never decompile the obligatory JSOP_POP, - * JSOP_MOREITER or JSOP_IFNE, though we do quick asserts - * to check that they are there. - */ - LOCAL_ASSERT(pc[-JSOP_ITER_LENGTH] == JSOP_ITER); - LOCAL_ASSERT(pc[js_CodeSpec[op].length] == JSOP_LOOPHEAD); - LOCAL_ASSERT(pc[js_CodeSpec[op].length + JSOP_LOOPHEAD_LENGTH] == JSOP_ITERNEXT); - cond = GET_JUMP_OFFSET(pc); - next = js_GetSrcNoteOffset(sn, 0); - tail = js_GetSrcNoteOffset(sn, 1); - LOCAL_ASSERT(pc[next] == JSOP_POP); - LOCAL_ASSERT(pc[cond] == JSOP_LOOPENTRY); - cond += JSOP_LOOPENTRY_LENGTH; - LOCAL_ASSERT(pc[cond] == JSOP_MOREITER); - DECOMPILE_CODE(pc + oplen, next - oplen); - lval = POP_STR(); - - /* - * This string "" comes from jsopcode.tbl. It stands - * for the result pushed by JSOP_ITERNEXT. - */ - LOCAL_ASSERT(strcmp(lval + strlen(lval) - 9, " = ") == 0); - const_cast(lval)[strlen(lval) - 9] = '\0'; - LOCAL_ASSERT(ss->top >= 1); - - if (ss->inArrayInit || ss->inGenExp) { - rval = POP_STR(); - if (ss->top >= 1 && ss->opcodes[ss->top - 1] == JSOP_FORLOCAL) { - ss->sprinter.setOffset(ss->offsets[ss->top] - PAREN_SLOP); - if (Sprint(&ss->sprinter, " %s (%s %s %s)", - foreach ? js_for_each_str : js_for_str, - lval, - forOf ? "of" : "in", - rval) < 0) { - return NULL; - } - - /* - * Do not AddParenSlop here, as we will push the - * top-most offset again, which will add paren slop - * for us. We must push to balance the stack budget - * when nesting for heads in a comprehension. - */ - todo = ss->offsets[ss->top - 1]; - } else { - todo = Sprint(&ss->sprinter, " %s (%s %s %s)", - foreach ? js_for_each_str : js_for_str, - lval, - forOf ? "of" : "in", - rval); - } - if (todo < 0 || !PushOff(ss, todo, JSOP_FORLOCAL)) - return NULL; - DECOMPILE_CODE(pc + next + JSOP_POP_LENGTH, cond - next - JSOP_POP_LENGTH); - } else { - /* - * As above, rval or an extension of it must remain - * stacked during loop body decompilation. - */ - rval = GetStr(ss, ss->top - 1); - xval = VarPrefix(js_GetSrcNote(cx, jp->script, pc + next)); - js_printf(jp, "\t%s (%s%s %s %s) {\n", - foreach ? js_for_each_str : js_for_str, - xval, - lval, - forOf ? "of" : "in", - rval); - jp->indent += 4; - DECOMPILE_CODE(pc + next + JSOP_POP_LENGTH, cond - next - JSOP_POP_LENGTH); - jp->indent -= 4; - js_printf(jp, "\t}\n"); - } - - pc += tail; - LOCAL_ASSERT(*pc == JSOP_IFNE); - len = js_CodeSpec[*pc].length; - break; - - case SRC_WHILE: - cond = GET_JUMP_OFFSET(pc); - tail = js_GetSrcNoteOffset(sn, 0); - DECOMPILE_CODE(pc + cond, tail - cond); - js_printf(jp, "\twhile ("); - rval = PopCondStr(ss, &rvalpc); - SprintOpcodePermanent(jp, rval, rvalpc); - js_printf(jp, ") {\n"); - jp->indent += 4; - DECOMPILE_CODE(pc + oplen, cond - oplen); - jp->indent -= 4; - js_printf(jp, "\t}\n"); - pc += tail; - LOCAL_ASSERT(*pc == JSOP_IFNE); - len = js_CodeSpec[*pc].length; - todo = -2; - break; - - case SRC_CONT2LABEL: - GET_SOURCE_NOTE_ATOM(sn, atom); - rval = QuoteString(&ss->sprinter, atom, 0); - if (!rval) - return NULL; - ss->sprinter.setOffset(rval); - js_printf(jp, "\tcontinue %s;\n", rval); - break; - - case SRC_CONTINUE: - js_printf(jp, "\tcontinue;\n"); - break; - - case SRC_BREAK2LABEL: - GET_SOURCE_NOTE_ATOM(sn, atom); - rval = QuoteString(&ss->sprinter, atom, 0); - if (!rval) - return NULL; - ss->sprinter.setOffset(rval); - js_printf(jp, "\tbreak %s;\n", rval); - break; - - case SRC_HIDDEN: - break; - - default: - js_printf(jp, "\tbreak;\n"); - break; - } - todo = -2; - break; - - case JSOP_IFEQ: - { - JSBool elseif = JS_FALSE; - - if_again: - len = GET_JUMP_OFFSET(pc); - sn = js_GetSrcNote(cx, jp->script, pc); - - switch (sn ? SN_TYPE(sn) : SRC_NULL) { - case SRC_IF: - case SRC_IF_ELSE: - rval = PopCondStr(ss, &rvalpc); - if (ss->inArrayInit || ss->inGenExp) { - LOCAL_ASSERT(SN_TYPE(sn) == SRC_IF); - ss->sprinter.setOffset(ss->sprinter.getOffset() - PAREN_SLOP); - if (Sprint(&ss->sprinter, " if (%s)", rval) < 0) - return NULL; - AddParenSlop(ss); - } else { - js_printf(jp, elseif ? " if (" : "\tif ("); - SprintOpcodePermanent(jp, rval, rvalpc); - js_printf(jp, ") {\n"); - jp->indent += 4; - } - - if (SN_TYPE(sn) == SRC_IF) { - DECOMPILE_CODE(pc + oplen, len - oplen); - } else { - LOCAL_ASSERT(!ss->inArrayInit && !ss->inGenExp); - tail = js_GetSrcNoteOffset(sn, 0); - DECOMPILE_CODE(pc + oplen, tail - oplen); - jp->indent -= 4; - pc += tail; - LOCAL_ASSERT(*pc == JSOP_GOTO); - oplen = js_CodeSpec[*pc].length; - len = GET_JUMP_OFFSET(pc); - js_printf(jp, "\t} else"); - - /* - * If the second offset for sn is non-zero, it tells - * the distance from the goto around the else, to the - * ifeq for the if inside the else that forms an "if - * else if" chain. Thus cond spans the condition of - * the second if, so we simply decompile it and start - * over at label if_again. - */ - cond = js_GetSrcNoteOffset(sn, 1); - if (cond != 0) { - cond -= tail; - DECOMPILE_CODE(pc + oplen, cond - oplen); - pc += cond; - oplen = js_CodeSpec[*pc].length; - elseif = JS_TRUE; - goto if_again; - } - - js_printf(jp, " {\n"); - jp->indent += 4; - DECOMPILE_CODE(pc + oplen, len - oplen); - } - - if (!ss->inArrayInit && !ss->inGenExp) { - jp->indent -= 4; - js_printf(jp, "\t}\n"); - } - todo = -2; - break; - - case SRC_COND: - xval = PopStrDupe(ss, op, &xvalpc); - len = js_GetSrcNoteOffset(sn, 0); - DECOMPILE_CODE(pc + oplen, len - oplen); - lval = PopStrDupe(ss, op, &lvalpc); - pushpc = pc; - pc += len; - LOCAL_ASSERT(*pc == JSOP_GOTO); - oplen = js_CodeSpec[*pc].length; - len = GET_JUMP_OFFSET(pc); - DECOMPILE_CODE(pc + oplen, len - oplen); - rval = PopStrDupe(ss, op, &rvalpc); - todo = ss->sprinter.getOffset(); - SprintOpcode(ss, xval, xvalpc, pushpc, todo); - ss->sprinter.put(" ? "); - SprintOpcode(ss, lval, lvalpc, pushpc, todo); - ss->sprinter.put(" : "); - SprintOpcode(ss, rval, rvalpc, pushpc, todo); - break; - - default: - break; - } - break; - } - - case JSOP_IFNE: - LOCAL_ASSERT(0); - break; - - case JSOP_OR: - xval = "||"; - - do_logical_connective: - /* Top of stack is the first clause in a disjunction (||). */ - lval = PopStrDupe(ss, op, &lvalpc); - done = pc + GET_JUMP_OFFSET(pc); - pushpc = pc; - pc += len; - JS_ASSERT(*pc == JSOP_POP); - pc += JSOP_POP_LENGTH; - len = done - pc; - if (!Decompile(ss, pc, len)) - return NULL; - rval = PopStrDupe(ss, op, &rvalpc); - if (!rval) - return NULL; - todo = ss->sprinter.getOffset(); - SprintOpcode(ss, lval, lvalpc, pushpc, todo); - if (jp->pretty && - jp->indent + 4 + strlen(lval) + 4 + strlen(rval) > 75) { - Sprint(&ss->sprinter, " %s\n", xval); - Sprint(&ss->sprinter, "%*s", jp->indent + 4, ""); - } else { - Sprint(&ss->sprinter, " %s ", xval); - } - SprintOpcode(ss, rval, rvalpc, pushpc, todo); - break; - - case JSOP_AND: - xval = "&&"; - goto do_logical_connective; - - case JSOP_ENUMELEM: - case JSOP_ENUMCONSTELEM: - /* - * The stack has the object under the (top) index expression. - * The "rval" property id is underneath those two on the stack. - * The for loop body net and gross lengths can now be adjusted - * to account for the length of the indexing expression that - * came after JSOP_FORELEM and before JSOP_ENUMELEM. - */ - atom = NULL; - op = JSOP_NOP; /* turn off parens around xval */ - xval = POP_STR(); - op = JSOP_GETELEM; /* lval must have high precedence */ - lval = POP_STR(); - op = saveop; - rval = POP_STR(); - LOCAL_ASSERT(strcmp(rval, forelem_cookie) == 0); - if (*xval == '\0') { - todo = ss->sprinter.put(lval); - } else { - todo = Sprint(&ss->sprinter, index_format, lval, xval); - } - break; - - case JSOP_GETTER: - case JSOP_SETTER: - todo = -2; - break; - - case JSOP_DUP2: - rval = GetStr(ss, ss->top-2); - todo = ss->sprinter.put(rval); - if (todo < 0 || !PushOff(ss, todo, - (JSOp) ss->opcodes[ss->top-2])) { - return NULL; - } - /* FALL THROUGH */ - - case JSOP_DUP: -#if JS_HAS_DESTRUCTURING - sn = js_GetSrcNote(cx, jp->script, pc); - if (sn) { - if (SN_TYPE(sn) == SRC_DESTRUCT) { - pc = DecompileDestructuring(ss, pc, endpc); - if (!pc) - return NULL; - - /* Left-hand side never needs parens. */ - JS_ASSERT(js_CodeSpec[JSOP_POP].prec <= 3); - lval = PopStr(ss, JSOP_POP); - - /* Make sure comma-expression on rhs gets parens. */ - JS_ASSERT(js_CodeSpec[JSOP_SETNAME].prec > js_CodeSpec[JSOP_POP].prec); - rval = PopStr(ss, JSOP_SETNAME); - - if (strcmp(rval, forelem_cookie) == 0) { - todo = Sprint(&ss->sprinter, ss_format, - VarPrefix(sn), lval); - - /* Skip POP so the SRC_FOR_IN code can pop for itself. */ - if (*pc == JSOP_POP) - len = JSOP_POP_LENGTH; - } else { - todo = Sprint(&ss->sprinter, "%s%s = %s", - VarPrefix(sn), lval, rval); - } - - op = saveop = JSOP_ENUMELEM; - len = 0; - } else { - LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCTLET); - - ptrdiff_t offsetToLet = js_GetSrcNoteOffset(sn, 0); - LOCAL_ASSERT(*(pc + offsetToLet) == JSOP_ENTERLET0); - - obj = jp->script->getObject(GET_UINT32_INDEX(pc + offsetToLet)); - StaticBlockObject &blockObj = obj->asStaticBlock(); - - uint32_t blockDepth = blockObj.stackDepth(); - LOCAL_ASSERT(blockDepth < ss->top); - LOCAL_ASSERT(ss->top <= blockDepth + blockObj.slotCount()); - - AtomVector atoms(cx); - if (!GetBlockNames(cx, blockObj, &atoms)) - return NULL; - - /* - * Skip any initializers preceding this one. E.g., in - * let (w=1, x=2, [y,z] = a) { ... } - * skip 'w' and 'x' for the JSOP_DUP of '[y,z] = a'. - */ - AtomRange letNames = atoms.all(); - uint32_t curDepth = ss->top - 1 /* initializer */; - for (uint32_t i = blockDepth; i < curDepth; ++i) - letNames.popFront(); - - /* - * Pop and copy the rhs before it gets clobbered. - * Use JSOP_SETLOCAL's precedence since this is =. - */ - DupBuffer rhs(cx); - if (!Dup(PopStr(ss, JSOP_SETLOCAL), &rhs)) - return NULL; - - /* Destructure, tracking how many vars were bound. */ - size_t remainBefore = letNames.remain(); - pc = DecompileDestructuring(ss, pc, endpc, &letNames); - if (!pc) - return NULL; - size_t remainAfter = letNames.remain(); - - /* - * Merge the lhs and rhs and prefix with a cookie to - * tell enterlet0 not to prepend "name = ". - */ - const char *lhs = PopStr(ss, JSOP_NOP); - ptrdiff_t off = Sprint(&ss->sprinter, "%s%s = %s", - DestructuredString, lhs, rhs.begin()); - if (off < 0 || !PushOff(ss, off, JSOP_NOP)) - return NULL; - - /* - * Only one slot has been pushed (holding the entire - * decompiled destructuring expression). However, the - * abstract depth needs one slot per bound var, so push - * empty strings for the remainder. We don't have to - * worry about empty destructuring because the parser - * ensures that there is always at least one pushed - * slot for each destructuring lhs. - */ - LOCAL_ASSERT(remainBefore >= remainAfter); - LOCAL_ASSERT(remainBefore > remainAfter || remainAfter > 0); - for (size_t i = remainBefore - 1; i > remainAfter; --i) { - if (!PushStr(ss, SkipString, JSOP_NOP)) - return NULL; - } - - LOCAL_ASSERT(*pc == JSOP_POP); - pc += JSOP_POP_LENGTH; - - /* Eat up the JSOP_UNDEFINED following empty destructuring. */ - if (remainBefore == remainAfter) { - LOCAL_ASSERT(*pc == JSOP_UNDEFINED); - pc += JSOP_UNDEFINED_LENGTH; - } - - len = 0; - todo = -2; - } - break; - } -#endif - - rval = GetStr(ss, ss->top-1); - saveop = (JSOp) ss->opcodes[ss->top-1]; - todo = ss->sprinter.put(rval); - break; - - case JSOP_SWAP: - Swap(ss->offsets[ss->top-1], ss->offsets[ss->top-2]); - Swap(ss->opcodes[ss->top-1], ss->opcodes[ss->top-2]); - Swap(ss->bytecodes[ss->top-1], ss->bytecodes[ss->top-2]); - todo = -2; - break; - - case JSOP_SETARG: - atom = GetArgOrVarAtom(jp, GET_ARGNO(pc)); - LOCAL_ASSERT(atom); - goto do_setname; - - case JSOP_SETCONST: - case JSOP_SETNAME: - case JSOP_SETGNAME: - LOAD_ATOM(0); - - do_setname: - lval = QuoteString(&ss->sprinter, atom, 0); - if (!lval) - return NULL; - rval = PopStrDupe(ss, op, &rvalpc); - if (op == JSOP_SETNAME || op == JSOP_SETGNAME) - (void) PopOff(ss, op); - - do_setlval: - sn = js_GetSrcNote(cx, jp->script, pc - 1); - todo = ss->sprinter.getOffset(); - if (sn && SN_TYPE(sn) == SRC_ASSIGNOP) { - const char *token = - GetTokenForAssignment(jp, sn, lastop, pc, rvalpc, - &lastlvalpc, &lastrvalpc); - Sprint(&ss->sprinter, "%s %s= ", lval, token); - SprintOpcode(ss, rval, rvalpc, pc, todo); - } else { - sn = js_GetSrcNote(cx, jp->script, pc); - const char *prefix = VarPrefix(sn); - Sprint(&ss->sprinter, "%s%s = ", prefix, lval); - SprintOpcode(ss, rval, rvalpc, pc, todo); - } - break; - - case JSOP_NEW: - case JSOP_CALL: - case JSOP_EVAL: - case JSOP_FUNCALL: - case JSOP_FUNAPPLY: - { - argc = GET_ARGC(pc); - const char **argv = cx->pod_malloc(argc + 1); - if (!argv) - return NULL; - jsbytecode **argbytecodes = cx->pod_malloc(argc + 1); - if (!argbytecodes) { - js_free(argv); - return NULL; - } - - op = JSOP_SETNAME; - for (i = argc; i > 0; i--) - argv[i] = PopStrDupe(ss, op, &argbytecodes[i]); - - /* Skip the JSOP_PUSHOBJ-created empty string. */ - LOCAL_ASSERT(ss->top >= 2); - (void) PopOff(ss, op); - - /* - * Special case: new (x(y)(z)) must be parenthesized like so. - * Same for new (x(y).z) -- contrast with new x(y).z. - * See PROPAGATE_CALLNESS. - */ - op = (JSOp) ss->opcodes[ss->top - 1]; - argv[0] = PopStrDupe(ss, - (saveop == JSOP_NEW && - (op == JSOP_CALL || - op == JSOP_EVAL || - op == JSOP_FUNCALL || - op == JSOP_FUNAPPLY || - op == JSOP_CALLPROP || - op == JSOP_CALLELEM)) - ? JSOP_NAME - : saveop, - &lvalpc); - op = saveop; - - lval = "(", rval = ")"; - todo = ss->sprinter.getOffset(); - if (op == JSOP_NEW) { - if (argc == 0) - lval = rval = ""; - Sprint(&ss->sprinter, "%s ", js_new_str); - } - SprintOpcode(ss, argv[0], lvalpc, pc, todo); - ss->sprinter.put(lval); - - for (i = 1; i <= argc; i++) { - SprintOpcode(ss, argv[i], argbytecodes[i], pc, todo); - if (i < argc) - ss->sprinter.put(", "); - } - ss->sprinter.put(rval); - - js_free(argv); - js_free(argbytecodes); - - break; - } - - case JSOP_SETCALL: - todo = Sprint(&ss->sprinter, ""); - break; - - case JSOP_DELNAME: - LOAD_ATOM(0); - lval = QuoteString(&ss->sprinter, atom, 0); - if (!lval) - return NULL; - ss->sprinter.setOffset(lval); - do_delete_lval: - todo = Sprint(&ss->sprinter, "%s %s", js_delete_str, lval); - break; - - case JSOP_DELPROP: - GET_ATOM_QUOTE_AND_FMT("%s %s[%s]", "%s %s.%s", rval); - op = JSOP_GETPROP; - lval = POP_STR(); - todo = Sprint(&ss->sprinter, fmt, js_delete_str, lval, rval); - break; - - case JSOP_DELELEM: - op = JSOP_NOP; /* turn off parens */ - xval = POP_STR(); - op = JSOP_GETPROP; - lval = POP_STR(); - if (*xval == '\0') - goto do_delete_lval; - todo = Sprint(&ss->sprinter, "%s %s[%s]", js_delete_str, lval, xval); - break; - - case JSOP_TYPEOFEXPR: - case JSOP_TYPEOF: - case JSOP_VOID: - { - const char *prefix = (op == JSOP_VOID) ? js_void_str : js_typeof_str; - rval = PopStrDupe(ss, op, &rvalpc); - todo = ss->sprinter.getOffset(); - Sprint(&ss->sprinter, "%s ", prefix); - SprintOpcode(ss, rval, rvalpc, pc, todo); - break; - } - - case JSOP_INCARG: - case JSOP_DECARG: - /* INCARG/DECARG are followed by GETARG containing the slot. */ - JS_ASSERT(pc[JSOP_INCARG_LENGTH] == JSOP_GETARG); - atom = GetArgOrVarAtom(jp, GET_ARGNO(pc + JSOP_INCARG_LENGTH)); - LOCAL_ASSERT(atom); - goto do_incatom; - - case JSOP_INCNAME: - case JSOP_DECNAME: - case JSOP_INCGNAME: - case JSOP_DECGNAME: - LOAD_ATOM(0); - do_incatom: - lval = QuoteString(&ss->sprinter, atom, 0); - if (!lval) - return NULL; - ss->sprinter.setOffset(lval); - do_inclval: - todo = Sprint(&ss->sprinter, ss_format, - js_incop_strs[!(cs->format & JOF_INC)], lval); - if (js_CodeSpec[*pc].format & JOF_DECOMPOSE) - len += GetDecomposeLength(pc, js_CodeSpec[*pc].length); - break; - - case JSOP_INCPROP: - case JSOP_DECPROP: - GET_ATOM_QUOTE_AND_FMT(preindex_format, predot_format, rval); - - /* - * Force precedence below the numeric literal opcodes, so that - * 42..foo or 10000..toString(16), e.g., decompile with parens - * around the left-hand side of dot. - */ - op = JSOP_GETPROP; - lval = POP_STR(); - todo = Sprint(&ss->sprinter, fmt, - js_incop_strs[!(cs->format & JOF_INC)], - lval, rval); - len += GetDecomposeLength(pc, JSOP_INCPROP_LENGTH); - break; - - case JSOP_INCELEM: - case JSOP_DECELEM: - op = JSOP_NOP; /* turn off parens */ - xval = POP_STR(); - op = JSOP_GETELEM; - lval = POP_STR(); - if (*xval != '\0') { - todo = Sprint(&ss->sprinter, preindex_format, - js_incop_strs[!(cs->format & JOF_INC)], - lval, xval); - } else { - todo = Sprint(&ss->sprinter, ss_format, - js_incop_strs[!(cs->format & JOF_INC)], lval); - } - len += GetDecomposeLength(pc, JSOP_INCELEM_LENGTH); - break; - - case JSOP_ARGINC: - case JSOP_ARGDEC: - /* ARGINC/ARGDEC are followed by GETARG containing the slot. */ - JS_ASSERT(pc[JSOP_ARGINC_LENGTH] == JSOP_GETARG); - atom = GetArgOrVarAtom(jp, GET_ARGNO(pc + JSOP_ARGINC_LENGTH)); - LOCAL_ASSERT(atom); - goto do_atominc; - - case JSOP_NAMEINC: - case JSOP_NAMEDEC: - case JSOP_GNAMEINC: - case JSOP_GNAMEDEC: - LOAD_ATOM(0); - do_atominc: - lval = QuoteString(&ss->sprinter, atom, 0); - if (!lval) - return NULL; - ss->sprinter.setOffset(lval); - do_lvalinc: - todo = Sprint(&ss->sprinter, ss_format, - lval, js_incop_strs[!(cs->format & JOF_INC)]); - if (js_CodeSpec[*pc].format & JOF_DECOMPOSE) - len += GetDecomposeLength(pc, js_CodeSpec[*pc].length); - break; - - case JSOP_PROPINC: - case JSOP_PROPDEC: - GET_ATOM_QUOTE_AND_FMT(postindex_format, postdot_format, rval); - - /* - * Force precedence below the numeric literal opcodes, so that - * 42..foo or 10000..toString(16), e.g., decompile with parens - * around the left-hand side of dot. - */ - op = JSOP_GETPROP; - lval = POP_STR(); - todo = Sprint(&ss->sprinter, fmt, lval, rval, - js_incop_strs[!(cs->format & JOF_INC)]); - len += GetDecomposeLength(pc, JSOP_PROPINC_LENGTH); - break; - - case JSOP_ELEMINC: - case JSOP_ELEMDEC: - op = JSOP_NOP; /* turn off parens */ - xval = POP_STR(); - op = JSOP_GETELEM; - lval = POP_STR(); - if (*xval != '\0') { - todo = Sprint(&ss->sprinter, postindex_format, lval, xval, - js_incop_strs[!(cs->format & JOF_INC)]); - } else { - todo = Sprint(&ss->sprinter, ss_format, - lval, js_incop_strs[!(cs->format & JOF_INC)]); - } - len += GetDecomposeLength(pc, JSOP_ELEMINC_LENGTH); - break; - - case JSOP_GETPROP2: - op = JSOP_GETPROP; - (void) PopOff(ss, lastop); - /* FALL THROUGH */ - - case JSOP_CALLPROP: - case JSOP_GETPROP: - case JSOP_GETXPROP: - case JSOP_LENGTH: - LOAD_ATOM(0); - - GET_QUOTE_AND_FMT("[%s]", ".%s", rval); - PROPAGATE_CALLNESS(); - lval = PopStr(ss, op, &lvalpc); - todo = ss->sprinter.getOffset(); - SprintOpcode(ss, lval, lvalpc, pc, todo); - Sprint(&ss->sprinter, fmt, rval); - break; - - case JSOP_SETPROP: - { - LOAD_ATOM(0); - GET_QUOTE_AND_FMT("[%s] %s= ", ".%s %s= ", xval); - rval = PopStrDupe(ss, op, &rvalpc); - - /* - * Force precedence below the numeric literal opcodes, so that - * 42..foo or 10000..toString(16), e.g., decompile with parens - * around the left-hand side of dot. - */ - op = JSOP_GETPROP; - lval = PopStr(ss, op, &lvalpc); - sn = js_GetSrcNote(cx, jp->script, pc - 1); - const char *token = - GetTokenForAssignment(jp, sn, lastop, pc, rvalpc, - &lastlvalpc, &lastrvalpc); - todo = ss->sprinter.getOffset(); - SprintOpcode(ss, lval, lvalpc, pc, todo); - Sprint(&ss->sprinter, fmt, xval, token); - SprintOpcode(ss, rval, rvalpc, pc, todo); - break; - } - - case JSOP_GETELEM2: - (void) PopOff(ss, lastop); - /* FALL THROUGH */ - case JSOP_CALLELEM: - case JSOP_GETELEM: - op = JSOP_NOP; /* turn off parens */ - xval = PopStrDupe(ss, op, &xvalpc); - op = saveop; - PROPAGATE_CALLNESS(); - lval = PopStr(ss, op, &lvalpc); - todo = ss->sprinter.getOffset(); - SprintOpcode(ss, lval, lvalpc, pc, todo); - if (*xval != '\0') { - ss->sprinter.put("["); - SprintOpcode(ss, xval, xvalpc, pc, todo); - ss->sprinter.put("]"); - } - break; - - case JSOP_SETELEM: - { - rval = PopStrDupe(ss, op, &rvalpc); - op = JSOP_NOP; /* turn off parens */ - xval = PopStrDupe(ss, op, &xvalpc); - cs = &js_CodeSpec[ss->opcodes[ss->top]]; - op = JSOP_GETELEM; /* lval must have high precedence */ - lval = PopStr(ss, op, &lvalpc); - op = saveop; - if (*xval == '\0') - goto do_setlval; - sn = js_GetSrcNote(cx, jp->script, pc - 1); - const char *token = - GetTokenForAssignment(jp, sn, lastop, pc, rvalpc, - &lastlvalpc, &lastrvalpc); - todo = ss->sprinter.getOffset(); - SprintOpcode(ss, lval, lvalpc, pc, todo); - ss->sprinter.put("["); - SprintOpcode(ss, xval, xvalpc, pc, todo); - ss->sprinter.put("]"); - Sprint(&ss->sprinter, " %s= ", token); - SprintOpcode(ss, rval, rvalpc, pc, todo); - break; - } - - case JSOP_CALLARG: - case JSOP_GETARG: - i = GET_ARGNO(pc); - atom = GetArgOrVarAtom(jp, i); -#if JS_HAS_DESTRUCTURING - if (!atom) { - todo = Sprint(&ss->sprinter, "%s[%d]", js_arguments_str, i); - break; - } -#else - LOCAL_ASSERT(atom); -#endif - goto do_name; - - case JSOP_CALLNAME: - case JSOP_NAME: - case JSOP_GETGNAME: - case JSOP_CALLGNAME: - LOAD_ATOM(0); - do_name: - lval = ""; - sn = js_GetSrcNote(cx, jp->script, pc); - rval = QuoteString(&ss->sprinter, atom, 0); - if (!rval) - return NULL; - ss->sprinter.setOffset(rval); - todo = Sprint(&ss->sprinter, sss_format, - VarPrefix(sn), lval, rval); - break; - - case JSOP_UINT16: - i = (int) GET_UINT16(pc); - goto do_sprint_int; - - case JSOP_UINT24: - i = (int) GET_UINT24(pc); - goto do_sprint_int; - - case JSOP_INT8: - i = GET_INT8(pc); - goto do_sprint_int; - - case JSOP_INT32: - i = GET_INT32(pc); - do_sprint_int: - todo = Sprint(&ss->sprinter, "%d", i); - break; - - case JSOP_DOUBLE: - { - val = jp->script->getConst(GET_UINT32_INDEX(pc)); - todo = SprintDoubleValue(&ss->sprinter, val, &saveop); - break; - } - - case JSOP_STRING: - LOAD_ATOM(0); - rval = QuoteString(&ss->sprinter, atom, '"'); - if (!rval) - return NULL; - todo = ss->sprinter.getOffsetOf(rval); - break; - - case JSOP_LAMBDA: -#if JS_HAS_GENERATOR_EXPRS - sn = js_GetSrcNote(cx, jp->script, pc); - if (sn && SN_TYPE(sn) == SRC_GENEXP) { - BindingVector *outerLocalNames; - JSScript *inner; - RootedScript outer(cx); - Vector *decompiledOpcodes; - SprintStack ss2(cx); - RootedFunction outerfun(cx); - - fun = jp->script->getFunction(GET_UINT32_INDEX(pc)); - - /* - * All allocation when decompiling is LIFO, using malloc or, - * more commonly, arena-allocating from cx->tempLifoAlloc - * Therefore after InitSprintStack succeeds, we must release - * to mark before returning. - */ - LifoAllocScope las(&cx->tempLifoAlloc()); - outerLocalNames = jp->localNames; - if (!SetPrinterLocalNames(cx, fun->nonLazyScript(), jp)) - return NULL; - - inner = fun->nonLazyScript(); - if (!InitSprintStack(cx, &ss2, jp, StackDepth(inner))) { - js_delete(jp->localNames); - jp->localNames = outerLocalNames; - return NULL; - } - ss2.inGenExp = JS_TRUE; - - /* - * Recursively decompile this generator function as an - * un-parenthesized generator expression. The ss->inGenExp - * special case of JSOP_YIELD shares array comprehension - * decompilation code that leaves the result as the single - * string pushed on ss2. - */ - outer = jp->script; - outerfun = jp->fun; - decompiledOpcodes = jp->decompiledOpcodes; - LOCAL_ASSERT(UnsignedPtrDiff(pc, outer->code) <= outer->length); - jp->script = inner; - jp->fun = fun; - jp->decompiledOpcodes = NULL; - - /* - * Decompile only the main bytecode, to avoid tripping over - * new prolog ops that have stack effects. - */ - ok = Decompile(&ss2, inner->main(), inner->length - inner->mainOffset) - != NULL; - jp->script = outer; - jp->fun = outerfun; - js_delete(jp->localNames); - jp->localNames = outerLocalNames; - jp->decompiledOpcodes = decompiledOpcodes; - if (!ok) - return NULL; - - /* - * Advance over this op and its global |this| push, and - * arrange to advance over the call to this lambda. - */ - pc += len; - LOCAL_ASSERT(*pc == JSOP_UNDEFINED); - pc += JSOP_UNDEFINED_LENGTH; - LOCAL_ASSERT(*pc == JSOP_NOTEARG); - pc += JSOP_NOTEARG_LENGTH; - LOCAL_ASSERT(*pc == JSOP_CALL); - LOCAL_ASSERT(GET_ARGC(pc) == 0); - len = JSOP_CALL_LENGTH; - - /* - * Arrange to parenthesize this genexp unless: - * - * 1. It is the complete expression consumed by a control - * flow bytecode such as JSOP_TABLESWITCH whose syntax - * always parenthesizes the controlling expression. - * 2. It is the sole argument to a function call. - * - * But if this genexp runs up against endpc, parenthesize - * regardless. (This can happen if we are called from - * DecompileExpression or recursively from case - * JSOP_{NOP,AND,OR}.) - * - * There's no special case for |if (genexp)| because the - * compiler optimizes that to |if (true)|. - */ - pc2 = pc + len; - op = JSOp(*pc2); - if (op == JSOP_LOOPHEAD || op == JSOP_NOP || op == JSOP_NOTEARG) - pc2 += JSOP_NOP_LENGTH; - LOCAL_ASSERT(pc2 < endpc || - endpc < outer->code + outer->length); - LOCAL_ASSERT(ss2.top == 1); - ss2.opcodes[0] = JSOP_POP; - if (pc2 == endpc) { - op = JSOP_SETNAME; - } else { - op = (JSOp) *pc2; - op = ((js_CodeSpec[op].format & JOF_PARENHEAD) || - ((js_CodeSpec[op].format & JOF_INVOKE) && GET_ARGC(pc2) == 1)) - ? JSOP_POP - : JSOP_SETNAME; - - /* - * Stack this result as if it's a name and not an - * anonymous function, so it doesn't get decompiled as - * a generator function in a getter or setter context. - * The precedence level is the same for JSOP_NAME and - * JSOP_LAMBDA. - */ - LOCAL_ASSERT(js_CodeSpec[JSOP_NAME].prec == - js_CodeSpec[saveop].prec); - saveop = JSOP_NAME; - } - - /* - * Alas, we have to malloc a copy of the result left on the - * top of ss2 because both ss and ss2 arena-allocate from - * cx's tempLifoAlloc - */ - rval = JS_strdup(cx, PopStr(&ss2, op)); - las.releaseEarly(); - if (!rval) - return NULL; - todo = ss->sprinter.put(rval); - js_free((void *)rval); - break; - } -#endif /* JS_HAS_GENERATOR_EXPRS */ - else if (sn && SN_TYPE(sn) == SRC_CONTINUE) { - /* - * Local function definitions have a lambda;setlocal;pop - * triple (annotated with SRC_CONTINUE) in the function - * prologue and a nop (annotated with SRC_FUNCDEF) at the - * actual position where the function definition should - * syntactically appear. - */ - jsbytecode *nextpc = pc + JSOP_LAMBDA_LENGTH; - LOCAL_ASSERT(*nextpc == JSOP_SETLOCAL || *nextpc == JSOP_SETALIASEDVAR || *nextpc == JSOP_SETARG); - nextpc += js_CodeSpec[*nextpc].length; - LOCAL_ASSERT(*nextpc == JSOP_POP); - nextpc += JSOP_POP_LENGTH; - len = nextpc - pc; - todo = -2; - break; - } - - /* Otherwise, this is a lambda expression. */ - fun = jp->script->getFunction(GET_UINT32_INDEX(pc)); - { - /* - * Always parenthesize expression closures. We can't force - * saveop to a low-precedence op to arrange for auto-magic - * parenthesization without confusing getter/setter code - * that checks for JSOP_LAMBDA. - */ - bool grouped = !fun->isExprClosure(); - bool strict = jp->script->strict; - str = js_DecompileToString(cx, "lambda", fun, 0, - false, grouped, strict, - DecompileFunction); - if (!str) - return NULL; - } - sprint_string: - todo = ss->sprinter.putString(str); - break; - - case JSOP_CALLEE: - JS_ASSERT(jp->fun && jp->fun->atom()); - todo = ss->sprinter.putString(jp->fun->atom()); - break; - - case JSOP_OBJECT: - obj = jp->script->getObject(GET_UINT32_INDEX(pc)); - str = ValueToSource(cx, ObjectValue(*obj)); - if (!str) - return NULL; - goto sprint_string; - - case JSOP_REGEXP: - obj = jp->script->getRegExp(GET_UINT32_INDEX(pc)); - str = obj->asRegExp().toString(cx); - if (!str) - return NULL; - goto sprint_string; - - case JSOP_TABLESWITCH: - { - ptrdiff_t off, off2; - int32_t j, n, low, high; - TableEntry *table, *tmp; - - if (defaultsSwitch) { - defaultsSwitch = false; - len = GET_JUMP_OFFSET(pc); - if (jp->fun->hasRest()) { - // Jump over rest parameter things. - if (pc[len] == JSOP_SETARG || pc[len] == JSOP_SETALIASEDVAR) - len += GetBytecodeLength(pc + len); - LOCAL_ASSERT(pc[len] == JSOP_POP); - len += GetBytecodeLength(pc + len); - } - todo = -2; - break; - } - - sn = js_GetSrcNote(cx, jp->script, pc); - LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH); - len = js_GetSrcNoteOffset(sn, 0); - off = GET_JUMP_OFFSET(pc); - pc2 = pc + JUMP_OFFSET_LEN; - low = GET_JUMP_OFFSET(pc2); - pc2 += JUMP_OFFSET_LEN; - high = GET_JUMP_OFFSET(pc2); - pc2 += JUMP_OFFSET_LEN; - - n = high - low + 1; - if (n == 0) { - table = NULL; - j = 0; - ok = true; - } else { - table = cx->pod_malloc(n); - if (!table) - return NULL; - for (i = j = 0; i < n; i++) { - table[j].label = NULL; - off2 = GET_JUMP_OFFSET(pc2); - if (off2) { - sn = js_GetSrcNote(cx, jp->script, pc2); - if (sn) { - LOCAL_ASSERT(SN_TYPE(sn) == SRC_LABEL); - GET_SOURCE_NOTE_ATOM(sn, table[j].label); - } - table[j].key = INT_TO_JSVAL(low + i); - table[j].offset = off2; - table[j].order = j; - j++; - } - pc2 += JUMP_OFFSET_LEN; - } - tmp = cx->pod_malloc(j); - if (tmp) { - MergeSort(table, size_t(j), tmp, CompareTableEntries); - js_free(tmp); - ok = true; - } else { - ok = false; - } - } - - if (ok) - ok = DecompileSwitch(ss, table, (unsigned)j, pc, len, off, false); - js_free(table); - if (!ok) - return NULL; - todo = -2; - break; - } - - case JSOP_CONDSWITCH: - { - ptrdiff_t off, off2, caseOff; - int ncases; - TableEntry *table; - - sn = js_GetSrcNote(cx, jp->script, pc); - LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH); - len = js_GetSrcNoteOffset(sn, 0); - off = js_GetSrcNoteOffset(sn, 1); - - /* - * Count the cases using offsets from switch to first case, - * and case to case, stored in srcnote immediates. - */ - pc2 = pc; - off2 = off; - for (ncases = 0; off2 != 0; ncases++) { - pc2 += off2; - LOCAL_ASSERT(*pc2 == JSOP_CASE || *pc2 == JSOP_DEFAULT); - if (*pc2 == JSOP_DEFAULT) { - /* End of cases, but count default as a case. */ - off2 = 0; - } else { - sn = js_GetSrcNote(cx, jp->script, pc2); - LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_PCDELTA); - off2 = js_GetSrcNoteOffset(sn, 0); - } - } - - /* - * Allocate table and rescan the cases using their srcnotes, - * stashing each case's delta from switch top in table[i].key, - * and the distance to its statements in table[i].offset. - */ - table = cx->pod_malloc(ncases); - if (!table) - return NULL; - pc2 = pc; - off2 = off; - for (i = 0; i < ncases; i++) { - pc2 += off2; - LOCAL_ASSERT(*pc2 == JSOP_CASE || *pc2 == JSOP_DEFAULT); - caseOff = pc2 - pc; - table[i].key = INT_TO_JSVAL((int32_t) caseOff); - table[i].offset = caseOff + GET_JUMP_OFFSET(pc2); - if (*pc2 == JSOP_CASE) { - sn = js_GetSrcNote(cx, jp->script, pc2); - LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_PCDELTA); - off2 = js_GetSrcNoteOffset(sn, 0); - } - } - - /* - * Find offset of default code by fetching the default offset - * from the end of table. JSOP_CONDSWITCH always has a default - * case at the end. - */ - off = JSVAL_TO_INT(table[ncases-1].key); - pc2 = pc + off; - off += GET_JUMP_OFFSET(pc2); - - ok = DecompileSwitch(ss, table, (unsigned)ncases, pc, len, off, - JS_TRUE); - js_free(table); - if (!ok) - return NULL; - todo = -2; - break; - } - - case JSOP_CASE: - { - lval = PopStr(ss, op, &lvalpc); - if (!lval) - return NULL; - js_printf(jp, "\tcase "); - SprintOpcodePermanent(jp, lval, lvalpc); - js_printf(jp, ":\n"); - todo = -2; - break; - } - - case JSOP_DEFFUN: - fun = jp->script->getFunction(GET_UINT32_INDEX(pc)); - todo = -2; - goto do_function; - - case JSOP_HOLE: - todo = ss->sprinter.put("", 0); - break; - - case JSOP_NEWINIT: - { - i = GET_UINT8(pc); - LOCAL_ASSERT(i == JSProto_Array || i == JSProto_Object); - - todo = ss->sprinter.getOffset(); - if (i == JSProto_Array) { - ++ss->inArrayInit; - if (ss->sprinter.put("[") < 0) - return NULL; - } else { - if (ss->sprinter.put("{") < 0) - return NULL; - } - break; - } - - case JSOP_NEWARRAY: - { - todo = ss->sprinter.getOffset(); - ++ss->inArrayInit; - if (ss->sprinter.put("[") < 0) - return NULL; - break; - } - - case JSOP_NEWOBJECT: - { - todo = ss->sprinter.getOffset(); - if (ss->sprinter.put("{") < 0) - return NULL; - break; - } - - case JSOP_ENDINIT: - { - JSBool inArray; - - op = JSOP_NOP; /* turn off parens */ - rval = PopStr(ss, op, &rvalpc); - sn = js_GetSrcNote(cx, jp->script, pc); - - /* Skip any #n= prefix to find the opening bracket. */ - for (xval = rval; *xval != '[' && *xval != '{' && *xval; xval++) - continue; - inArray = (*xval == '['); - if (inArray) - --ss->inArrayInit; - todo = ss->sprinter.getOffset(); - SprintOpcode(ss, rval, rvalpc, pc, todo); - Sprint(&ss->sprinter, "%s%c", - (sn && SN_TYPE(sn) == SRC_CONTINUE) ? ", " : "", - inArray ? ']' : '}'); - break; - } - - { - JSBool isFirst; - const char *maybeComma; - const char *maybeSpread; - - case JSOP_INITELEM: - case JSOP_INITELEM_INC: - case JSOP_SPREAD: - JS_ASSERT(ss->top >= 3); - isFirst = IsInitializerOp(ss->opcodes[ss->top - 3]); - - /* Turn off most parens. */ - rval = PopStr(ss, JSOP_SETNAME, &rvalpc); - - /* Turn off all parens for xval and lval, which we control. */ - xval = PopStr(ss, JSOP_NOP, &xvalpc); - lval = PopStr(ss, JSOP_NOP, &lvalpc); - - if (op == JSOP_INITELEM) { - atom = NULL; - goto do_initprop; - } - maybeComma = isFirst ? "" : ", "; - maybeSpread = op == JSOP_SPREAD ? "..." : ""; - todo = Sprint(&ss->sprinter, "%s%s%s", lval, maybeComma, maybeSpread); - SprintOpcode(ss, rval, rvalpc, pc, todo); - if (todo != -1) { - if (!UpdateDecompiledText(ss, pushpc, todo)) - return NULL; - if (!PushOff(ss, todo, saveop, pushpc)) - return NULL; - if (!PushStr(ss, "", JSOP_NOP)) - return NULL; - todo = -2; - } - break; - - case JSOP_INITELEM_ARRAY: - JS_ASSERT(ss->top >= 2); - isFirst = IsInitializerOp(ss->opcodes[ss->top - 2]); - - /* Turn off most parens. */ - rval = PopStr(ss, JSOP_SETNAME, &rvalpc); - - /* Turn off all parens for lval, which we control. */ - lval = PopStr(ss, JSOP_NOP, &lvalpc); - - maybeComma = isFirst ? "" : ", "; - todo = Sprint(&ss->sprinter, "%s%s", lval, maybeComma); - SprintOpcode(ss, rval, rvalpc, pc, todo); - break; - - case JSOP_INITPROP: - LOAD_ATOM(0); - xval = QuoteString(&ss->sprinter, atom, jschar(IsIdentifier(atom) ? 0 : '\'')); - if (!xval) - return NULL; - isFirst = IsInitializerOp(ss->opcodes[ss->top - 2]); - rval = PopStrDupe(ss, op, &rvalpc); - lval = PopStr(ss, op, &lvalpc); - /* fall through */ - - do_initprop: - todo = ss->sprinter.getOffset(); - SprintOpcode(ss, lval, lvalpc, pc, todo); - maybeComma = isFirst ? "" : ", "; - if (lastop == JSOP_GETTER || lastop == JSOP_SETTER) { - const char *end = rval + strlen(rval); - - if (*rval == '(') - ++rval, --end; - LOCAL_ASSERT(strncmp(rval, js_function_str, 8) == 0); - LOCAL_ASSERT(rval[8] == ' '); - rval += 8 + 1; - LOCAL_ASSERT(*end ? *end == ')' : end[-1] == '}'); - Sprint(&ss->sprinter, "%s%s %s%s%.*s", - maybeComma, - (lastop == JSOP_GETTER) - ? js_get_str : js_set_str, - xval, - (rval[0] != '(') ? " " : "", - end - rval, rval); - } else { - Sprint(&ss->sprinter, "%s%s: ", maybeComma, xval); - SprintOpcode(ss, rval, rvalpc, pc, todo); - } - break; - } - - case JSOP_DEBUGGER: - js_printf(jp, "\tdebugger;\n"); - todo = -2; - break; - - case JSOP_REST: - // Ignore bytecode related to handling rest. - pc += GetBytecodeLength(pc); - if (*pc == JSOP_UNDEFINED) - pc += GetBytecodeLength(pc); - if (*pc == JSOP_SETALIASEDVAR || *pc == JSOP_SETARG) { - pc += GetBytecodeLength(pc); - LOCAL_ASSERT(*pc == JSOP_POP); - len = GetBytecodeLength(pc); - } else { - len = 0; - } - todo = -2; - break; - - default: - todo = -2; - break; - } - } - - if (ss->sprinter.hadOutOfMemory()) { - /* OOMs while printing to a string do not immediately return. */ - return NULL; - } - - if (todo < 0) { - /* -2 means "don't push", -1 means reported error. */ - JS_ASSERT(todo == -2); - if (todo == -1) - return NULL; - } else { - if (!UpdateDecompiledText(ss, pushpc, todo)) - return NULL; - if (!PushOff(ss, todo, saveop, pushpc)) - return NULL; - if (js_CodeSpec[*pc].format & JOF_DECOMPOSE) - CopyDecompiledTextForDecomposedOp(jp, pc); - } - - pc += len; - } - -/* - * Undefine local macros. - */ -#undef DECOMPILE_CODE -#undef TOP_STR -#undef POP_STR -#undef POP_STR_PREC -#undef LOCAL_ASSERT -#undef GET_QUOTE_AND_FMT -#undef GET_ATOM_QUOTE_AND_FMT - - return pc; -} - -static JSBool -DecompileCode(JSPrinter *jp, JSScript *script, jsbytecode *pc, unsigned len, - unsigned pcdepth, unsigned initialStackDepth = 0) -{ - JSContext *cx = jp->sprinter.context; - - unsigned depth = StackDepth(script); - JS_ASSERT(pcdepth <= depth); - - /* Initialize a sprinter for use with the offset stack. */ - LifoAllocScope las(&cx->tempLifoAlloc()); - SprintStack ss(cx); - if (!InitSprintStack(cx, &ss, jp, depth)) - return false; - - /* - * If we are called from js_DecompileValueGenerator with a portion of - * script's bytecode that starts with a non-zero model stack depth given - * by pcdepth, attempt to initialize the missing string offsets in ss to - * |spindex| negative indexes from fp->sp for the activation fp in which - * the error arose. - * - * See js_DecompileValueGenerator for how its |spindex| parameter is used, - * and see also GetOff, which makes use of the ss.offsets[i] < -1 that are - * potentially stored below. - */ - ss.top = pcdepth; - if (pcdepth != 0) { - for (unsigned i = 0; i < pcdepth; i++) { - ss.offsets[i] = -2 - (ptrdiff_t)i; - ss.opcodes[i] = *jp->pcstack[i]; - } - } - - for (unsigned i = 0; i < initialStackDepth; i++) { - if (!PushStr(&ss, "", JSOP_NOP)) - return false; - } - - /* Call recursive subroutine to do the hard work. */ - RootedScript oldscript(cx, jp->script); - BindingVector *oldLocalNames = jp->localNames; - if (!SetPrinterLocalNames(cx, script, jp)) - return false; - jp->script = script; - - /* Call recursive subroutine to do the hard work. */ - bool ok = Decompile(&ss, pc, len) != NULL; - - jp->script = oldscript; - js_delete(jp->localNames); - jp->localNames = oldLocalNames; - - /* If the given code didn't empty the stack, do it now. */ - if (ok && ss.top - initialStackDepth) { - const char *last; - do { - last = ss.sprinter.stringAt(PopOff(&ss, JSOP_POP)); - } while (ss.top - initialStackDepth > pcdepth); - js_printf(jp, "%s", last); - } - - return ok; -} - -/* - * Decompile a function body, expression closure expression, or complete - * script. Start at |pc|; go to the end of |script|. Include a directive - * prologue, if appropriate. - */ -static JSBool -DecompileBody(JSPrinter *jp, JSScript *script, jsbytecode *pc) -{ - /* Print a strict mode code directive, if needed. */ - if (script->strict && !jp->strict) { - if (jp->fun && jp->fun->isExprClosure()) { - /* - * We have no syntax for strict function expressions; - * at least give a hint. - */ - js_printf(jp, "\t/* use strict */ \n"); - } else { - js_printf(jp, "\t\"use strict\";\n"); - } - jp->strict = true; - } - - jsbytecode *end = script->code + script->length; - return DecompileCode(jp, script, pc, end - pc, 0); -} - -static JSBool -DecompileScript(JSPrinter *jp, JSScript *script) -{ - return DecompileBody(jp, script, script->code); -} - -JSString * -js_DecompileToString(JSContext *cx, const char *name, JSFunction *fun, - unsigned indent, JSBool pretty, JSBool grouped, JSBool strict, - JSDecompilerPtr decompiler) -{ - JSPrinter *jp; - JSString *str; - - jp = js_NewPrinter(cx, name, fun, indent, pretty, grouped, strict); - if (!jp) - return NULL; - if (decompiler(jp)) - str = js_GetPrinterOutput(jp); - else - str = NULL; - js_DestroyPrinter(jp); - return str; -} - -static const char native_code_str[] = "\t[native code]\n"; - -static JSBool -DecompileFunction(JSPrinter *jp) -{ - JSContext *cx = jp->sprinter.context; - - RootedFunction fun(cx, jp->fun); - JS_ASSERT(fun); - JS_ASSERT(!jp->script); - - /* - * If pretty, conform to ECMA-262 Edition 3, 15.3.4.2, by decompiling a - * FunctionDeclaration. Otherwise, check the JSFUN_LAMBDA flag and force - * an expression by parenthesizing. - */ - if (jp->pretty) { - js_printf(jp, "\t"); - } else { - if (!jp->grouped && fun->isLambda()) - js_puts(jp, "("); - } - - js_printf(jp, "%s ", js_function_str); - if (fun->atom() && !QuoteString(&jp->sprinter, fun->atom(), 0)) - return JS_FALSE; - js_puts(jp, "("); - - if (fun->isNative() || fun->isSelfHostedBuiltin()) { - js_printf(jp, ") {\n"); - jp->indent += 4; - js_printf(jp, native_code_str); - jp->indent -= 4; - js_printf(jp, "\t}"); - } else { - RootedScript script(cx, fun->nonLazyScript()); -#if JS_HAS_DESTRUCTURING - SprintStack ss(cx); -#endif - - /* Print the parameters. */ - jsbytecode *pc = script->main(); - jsbytecode *endpc = pc + script->length; - JSBool ok = JS_TRUE; - -#if JS_HAS_DESTRUCTURING - ss.printer = NULL; - jp->script = script; -#endif - - jsbytecode *deftable = NULL; - jsbytecode *defbegin = NULL; - int32_t deflen = 0; - uint16_t defstart = 0; - unsigned nformal = fun->nargs - fun->hasRest(); - - if (fun->hasDefaults()) { - // Since bug 781422, this code is completely wrong. If you ever have - // the unenviable task of reenabling the decompiler, you'll have to - // completely rewrite defaults decompiling. The previous code for - // this is available in version control, if you need inspiration. - MOZ_CRASH(); - } - - for (unsigned i = 0; i < fun->nargs; i++) { - if (i > 0) - js_puts(jp, ", "); - - bool isRest = fun->hasRest() && i == unsigned(fun->nargs) - 1; - if (isRest) - js_puts(jp, "..."); - JSAtom *param = GetArgOrVarAtom(jp, i); - -#if JS_HAS_DESTRUCTURING -#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, JS_FALSE) - - if (!param) { - ptrdiff_t todo; - const char *lval; - - LOCAL_ASSERT(*pc == JSOP_GETARG || *pc == JSOP_GETALIASEDVAR); - pc += js_CodeSpec[*pc].length; - LOCAL_ASSERT(*pc == JSOP_DUP); - if (!ss.printer) { - ok = InitSprintStack(cx, &ss, jp, StackDepth(script)); - if (!ok) - break; - } - pc = DecompileDestructuring(&ss, pc, endpc); - if (!pc) { - ok = JS_FALSE; - break; - } - LOCAL_ASSERT(*pc == JSOP_POP); - pc += JSOP_POP_LENGTH; - lval = PopStr(&ss, JSOP_NOP); - todo = jp->sprinter.put(lval); - if (todo < 0) { - ok = JS_FALSE; - break; - } - continue; - } - -#endif -#undef LOCAL_ASSERT - - if (fun->hasDefaults() && deflen && i >= defstart && !isRest) { -#define TABLE_OFF(off) GET_JUMP_OFFSET(&deftable[(off)*JUMP_OFFSET_LEN]) - jsbytecode *casestart = defbegin + TABLE_OFF(i - defstart); - jsbytecode *caseend = defbegin + ((i < nformal - 1) ? TABLE_OFF(i - defstart + 1) : deflen); -#undef TABLE_OFF - unsigned exprlength = caseend - casestart - js_CodeSpec[JSOP_POP].length; - if (!DecompileCode(jp, script, casestart, exprlength, 0, fun->hasRest())) - return JS_FALSE; - } else if (!QuoteString(&jp->sprinter, param, 0)) { - ok = JS_FALSE; - break; - } - } - -#if JS_HAS_DESTRUCTURING - jp->script = NULL; -#endif - if (!ok) - return JS_FALSE; - js_printf(jp, ") "); - if (!fun->isExprClosure()) { - js_printf(jp, "{\n"); - jp->indent += 4; - } - - ok = DecompileBody(jp, script, pc); - if (!ok) - return JS_FALSE; - - if (!fun->isExprClosure()) { - jp->indent -= 4; - js_printf(jp, "\t}"); - } - } - - if (!jp->pretty && !jp->grouped && fun->isLambda()) - js_puts(jp, ")"); - - return JS_TRUE; -} - static JSObject * GetBlockChainAtPC(JSContext *cx, JSScript *script, jsbytecode *pc) { @@ -6167,109 +1705,19 @@ js::DecompileArgument(JSContext *cx, int formalIndex, HandleValue v) return LossyTwoByteCharsToNewLatin1CharsZ(cx, linear->range()).c_str(); } -static char * -DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *pc) -{ - JS_ASSERT(script->code <= pc && pc < script->code + script->length); - - JSOp op = (JSOp) *pc; - - /* None of these stack-writing ops generates novel values. */ - JS_ASSERT(op != JSOP_CASE && op != JSOP_DUP && op != JSOP_DUP2); - - /* - * |this| could convert to a very long object initialiser, so cite it by - * its keyword name instead. - */ - if (op == JSOP_THIS) - return JS_strdup(cx, js_this_str); - - /* - * JSOP_BINDNAME is special: it generates a value, the base object of a - * reference. But if it is the generating op for a diagnostic produced by - * js_DecompileValueGenerator, the name being bound is irrelevant. Just - * fall back to the base object. - */ - if (op == JSOP_BINDNAME) - return FAILED_EXPRESSION_DECOMPILER; - - /* NAME ops are self-contained, others require left or right context. */ - const JSCodeSpec *cs = &js_CodeSpec[op]; - jsbytecode *begin = pc; - jsbytecode *end = pc + cs->length; - switch (JOF_MODE(cs->format)) { - case JOF_PROP: - case JOF_ELEM: - case 0: { - jssrcnote *sn = js_GetSrcNote(cx, script, pc); - if (!sn) - return FAILED_EXPRESSION_DECOMPILER; - switch (SN_TYPE(sn)) { - case SRC_PCBASE: - begin -= js_GetSrcNoteOffset(sn, 0); - break; - case SRC_PCDELTA: - end = begin + js_GetSrcNoteOffset(sn, 0); - begin += cs->length; - break; - default: - return FAILED_EXPRESSION_DECOMPILER; - } - break; - } - default:; - } - - /* - * Include the trailing SWAP when decompiling CALLPROP or CALLELEM ops, - * so that the result is the entire access rather than the lvalue. - */ - if (op == JSOP_CALLPROP || op == JSOP_CALLELEM) { - JS_ASSERT(*end == JSOP_SWAP); - end += JSOP_SWAP_LENGTH; - } - - ptrdiff_t len = end - begin; - if (len <= 0) - return FAILED_EXPRESSION_DECOMPILER; - - struct Guard { - jsbytecode **pcstack; - JSPrinter *printer; - Guard() : pcstack(), printer(NULL) {} - ~Guard() { - if (printer) - js_DestroyPrinter(printer); - js_free(pcstack); - } - } g; - - g.pcstack = js_pod_malloc(StackDepth(script)); - if (!g.pcstack) - return NULL; - - int pcdepth = ReconstructPCStack(cx, script, begin, g.pcstack); - if (pcdepth < 0) - return FAILED_EXPRESSION_DECOMPILER; - - g.printer = js_NewPrinter(cx, "js_DecompileValueGenerator", fun, 0, false, false, false); - if (!g.printer) - return NULL; - - g.printer->dvgfence = end; - g.printer->pcstack = g.pcstack; - if (!DecompileCode(g.printer, script, begin, (unsigned) len, (unsigned) pcdepth)) - return NULL; - - return JS_strdup(cx, g.printer->sprinter.string()); -} - unsigned js_ReconstructStackDepth(JSContext *cx, JSScript *script, jsbytecode *pc) { return ReconstructPCStack(cx, script, pc, NULL); } +#define LOCAL_ASSERT_CUSTOM(expr, BAD_EXIT) \ + JS_BEGIN_MACRO \ + JS_ASSERT(expr); \ + if (!(expr)) { BAD_EXIT; } \ + JS_END_MACRO +#define LOCAL_ASSERT_RV(expr, rv) \ + LOCAL_ASSERT_CUSTOM(expr, return (rv)) #define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, -1); static int @@ -6795,13 +2243,6 @@ js::GetPCCountScriptSummary(JSContext *cx, size_t index) return buf.finishString(); } -struct AutoDestroyPrinter -{ - JSPrinter *jp; - AutoDestroyPrinter(JSPrinter *jp) : jp(jp) {} - ~AutoDestroyPrinter() { js_DestroyPrinter(jp); } -}; - static bool GetPCCountJSON(JSContext *cx, const ScriptAndCounts &sac, StringBuffer &buf) { diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index 8c8f91ea4dd..79601241cd5 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -238,33 +238,6 @@ extern const char js_EscapeMap[]; extern JSString * js_QuoteString(JSContext *cx, JSString *str, jschar quote); -/* - * JSPrinter operations, for printf style message formatting. The return - * value from js_GetPrinterOutput() is the printer's cumulative output, in - * a GC'ed string. - * - * strict is true if the context in which the output will appear has - * already been marked as strict, thus indicating that nested - * functions need not be re-marked with a strict directive. It should - * be false in the outermost printer. - */ - -extern JSPrinter * -js_NewPrinter(JSContext *cx, const char *name, JSFunction *fun, - unsigned indent, JSBool pretty, JSBool grouped, JSBool strict); - -extern void -js_DestroyPrinter(JSPrinter *jp); - -extern JSString * -js_GetPrinterOutput(JSPrinter *jp); - -extern int -js_printf(JSPrinter *jp, const char *format, ...); - -extern JSBool -js_puts(JSPrinter *jp, const char *s); - #define GET_ATOM_FROM_BYTECODE(script, pc, pcoff, atom) \ JS_BEGIN_MACRO \ JS_ASSERT(js_CodeSpec[*(pc)].format & JOF_ATOM); \ @@ -289,24 +262,6 @@ StackDefs(JSScript *script, jsbytecode *pc); } /* namespace js */ -/* - * Decompilers, for script, function, and expression pretty-printing. - */ - -/* - * Some C++ compilers treat the language linkage (extern "C" vs. - * extern "C++") as part of function (and thus pointer-to-function) - * types. The use of this typedef (defined in "C") ensures that - * js_DecompileToString's definition (in "C++") gets matched up with - * this declaration. - */ -typedef JSBool (* JSDecompilerPtr)(JSPrinter *); - -extern JSString * -js_DecompileToString(JSContext *cx, const char *name, JSFunction *fun, - unsigned indent, JSBool pretty, JSBool grouped, JSBool strict, - JSDecompilerPtr decompiler); - /* * Given bytecode address pc in script's main program code, return the operand * stack depth just before (JSOp) *pc executes.