/js/lib/Socket.IO-node/support/expresso/deps/jscoverage/js/jsopcode.cpp
C++ | 2044 lines | 1761 code | 132 blank | 151 comment | 168 complexity | 37f3a9e7670e3289ef5fc12c8b095fde MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
- /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: set sw=4 ts=8 et tw=99:
- *
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Communicator client code, released
- * March 31, 1998.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either of the GNU General Public License Version 2 or later (the "GPL"),
- * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
- /*
- * JS bytecode descriptors, disassemblers, and decompilers.
- */
- #include "jsstddef.h"
- #ifdef HAVE_MEMORY_H
- #include <memory.h>
- #endif
- #include <stdarg.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include "jstypes.h"
- #include "jsarena.h" /* Added by JSIFY */
- #include "jsutil.h" /* Added by JSIFY */
- #include "jsdtoa.h"
- #include "jsprf.h"
- #include "jsapi.h"
- #include "jsarray.h"
- #include "jsatom.h"
- #include "jscntxt.h"
- #include "jsversion.h"
- #include "jsdbgapi.h"
- #include "jsemit.h"
- #include "jsfun.h"
- #include "jsiter.h"
- #include "jsnum.h"
- #include "jsobj.h"
- #include "jsopcode.h"
- #include "jsregexp.h"
- #include "jsscan.h"
- #include "jsscope.h"
- #include "jsscript.h"
- #include "jsstr.h"
- #include "jsstaticcheck.h"
- #include "jstracer.h"
- #include "jsautooplen.h"
- /* Verify JSOP_XXX_LENGTH constant definitions. */
- #define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
- JS_STATIC_ASSERT(op##_LENGTH == length);
- #include "jsopcode.tbl"
- #undef OPDEF
- static const char js_incop_strs[][3] = {"++", "--"};
- static const char js_for_each_str[] = "for each";
- const JSCodeSpec js_CodeSpec[] = {
- #define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
- {length,nuses,ndefs,prec,format},
- #include "jsopcode.tbl"
- #undef OPDEF
- };
- uintN js_NumCodeSpecs = JS_ARRAY_LENGTH(js_CodeSpec);
- /*
- * Each element of the array is either a source literal associated with JS
- * bytecode or null.
- */
- static const char *CodeToken[] = {
- #define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
- token,
- #include "jsopcode.tbl"
- #undef OPDEF
- };
- #if defined(DEBUG) || defined(JS_JIT_SPEW)
- /*
- * Array of JS bytecode names used by DEBUG-only js_Disassemble and by
- * JIT debug spew.
- */
- const char *js_CodeName[] = {
- #define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
- name,
- #include "jsopcode.tbl"
- #undef OPDEF
- };
- #endif
- /************************************************************************/
- static ptrdiff_t
- GetJumpOffset(jsbytecode *pc, jsbytecode *pc2)
- {
- uint32 type;
- type = JOF_OPTYPE(*pc);
- if (JOF_TYPE_IS_EXTENDED_JUMP(type))
- return GET_JUMPX_OFFSET(pc2);
- return GET_JUMP_OFFSET(pc2);
- }
- uintN
- js_GetIndexFromBytecode(JSContext *cx, JSScript *script, jsbytecode *pc,
- ptrdiff_t pcoff)
- {
- JSOp op;
- uintN span, base;
- op = (JSOp)*pc;
- if (op == JSOP_TRAP)
- op = JS_GetTrapOpcode(cx, script, pc);
- JS_ASSERT(js_CodeSpec[op].length >= 1 + pcoff + UINT16_LEN);
- /*
- * We need to detect index base prefix. It presents when resetbase
- * follows the bytecode.
- */
- span = js_CodeSpec[op].length;
- base = 0;
- if (pc - script->code + span < script->length) {
- if (pc[span] == JSOP_RESETBASE) {
- base = GET_INDEXBASE(pc - JSOP_INDEXBASE_LENGTH);
- } else if (pc[span] == JSOP_RESETBASE0) {
- JS_ASSERT(JSOP_INDEXBASE1 <= pc[-1] || pc[-1] <= JSOP_INDEXBASE3);
- base = (pc[-1] - JSOP_INDEXBASE1 + 1) << 16;
- }
- }
- return base + GET_UINT16(pc + pcoff);
- }
- uintN
- js_GetVariableBytecodeLength(jsbytecode *pc)
- {
- JSOp op;
- uintN jmplen, ncases;
- jsint low, high;
- op = (JSOp) *pc;
- JS_ASSERT(js_CodeSpec[op].length == -1);
- switch (op) {
- case JSOP_TABLESWITCHX:
- jmplen = JUMPX_OFFSET_LEN;
- goto do_table;
- case JSOP_TABLESWITCH:
- jmplen = JUMP_OFFSET_LEN;
- do_table:
- /* Structure: default-jump case-low case-high case1-jump ... */
- pc += jmplen;
- low = GET_JUMP_OFFSET(pc);
- pc += JUMP_OFFSET_LEN;
- high = GET_JUMP_OFFSET(pc);
- ncases = (uintN)(high - low + 1);
- return 1 + jmplen + INDEX_LEN + INDEX_LEN + ncases * jmplen;
- case JSOP_LOOKUPSWITCHX:
- jmplen = JUMPX_OFFSET_LEN;
- goto do_lookup;
- default:
- JS_ASSERT(op == JSOP_LOOKUPSWITCH);
- jmplen = JUMP_OFFSET_LEN;
- do_lookup:
- /* Structure: default-jump case-count (case1-value case1-jump) ... */
- pc += jmplen;
- ncases = GET_UINT16(pc);
- return 1 + jmplen + INDEX_LEN + ncases * (INDEX_LEN + jmplen);
- }
- }
- uintN
- js_GetVariableStackUseLength(JSOp op, jsbytecode *pc)
- {
- JS_ASSERT(*pc == op || *pc == JSOP_TRAP);
- JS_ASSERT(js_CodeSpec[op].nuses == -1);
- switch (op) {
- case JSOP_POPN:
- return GET_UINT16(pc);
- case JSOP_LEAVEBLOCK:
- return GET_UINT16(pc);
- case JSOP_LEAVEBLOCKEXPR:
- return GET_UINT16(pc) + 1;
- case JSOP_NEWARRAY:
- return GET_UINT24(pc);
- default:
- /* stack: fun, this, [argc arguments] */
- JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL ||
- op == JSOP_EVAL || op == JSOP_SETCALL ||
- op == JSOP_APPLY);
- return 2 + GET_ARGC(pc);
- }
- }
- #ifdef DEBUG
- JS_FRIEND_API(JSBool)
- js_Disassemble(JSContext *cx, JSScript *script, JSBool lines, FILE *fp)
- {
- jsbytecode *pc, *end;
- uintN len;
- pc = script->code;
- end = pc + script->length;
- while (pc < end) {
- if (pc == script->main)
- fputs("main:\n", fp);
- len = js_Disassemble1(cx, script, pc,
- PTRDIFF(pc, script->code, jsbytecode),
- lines, fp);
- if (!len)
- return JS_FALSE;
- pc += len;
- }
- return JS_TRUE;
- }
- const char *
- ToDisassemblySource(JSContext *cx, jsval v)
- {
- JSObject *obj;
- JSScopeProperty *sprop;
- char *source;
- const char *bytes;
- JSString *str;
- if (!JSVAL_IS_PRIMITIVE(v)) {
- obj = JSVAL_TO_OBJECT(v);
- if (OBJ_GET_CLASS(cx, obj) == &js_BlockClass) {
- source = JS_sprintf_append(NULL, "depth %d {",
- OBJ_BLOCK_DEPTH(cx, obj));
- for (sprop = OBJ_SCOPE(obj)->lastProp; sprop;
- sprop = sprop->parent) {
- bytes = js_AtomToPrintableString(cx, JSID_TO_ATOM(sprop->id));
- if (!bytes)
- return NULL;
- source = JS_sprintf_append(source, "%s: %d%s",
- bytes, sprop->shortid,
- sprop->parent ? ", " : "");
- }
- source = JS_sprintf_append(source, "}");
- if (!source)
- return NULL;
- str = JS_NewString(cx, source, strlen(source));
- if (!str)
- return NULL;
- return js_GetStringBytes(cx, str);
- }
- }
- return js_ValueToPrintableSource(cx, v);
- }
- JS_FRIEND_API(uintN)
- js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc,
- uintN loc, JSBool lines, FILE *fp)
- {
- JSOp op;
- const JSCodeSpec *cs;
- ptrdiff_t len, off, jmplen;
- uint32 type;
- JSAtom *atom;
- uintN index;
- JSObject *obj;
- jsval v;
- const char *bytes;
- jsint i;
- op = (JSOp)*pc;
- if (op >= JSOP_LIMIT) {
- char numBuf1[12], numBuf2[12];
- JS_snprintf(numBuf1, sizeof numBuf1, "%d", op);
- JS_snprintf(numBuf2, sizeof numBuf2, "%d", JSOP_LIMIT);
- JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
- JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2);
- return 0;
- }
- cs = &js_CodeSpec[op];
- len = (ptrdiff_t) cs->length;
- fprintf(fp, "%05u:", loc);
- if (lines)
- fprintf(fp, "%4u", JS_PCToLineNumber(cx, script, pc));
- fprintf(fp, " %s", js_CodeName[op]);
- type = JOF_TYPE(cs->format);
- switch (type) {
- case JOF_BYTE:
- if (op == JSOP_TRAP) {
- op = JS_GetTrapOpcode(cx, script, pc);
- len = (ptrdiff_t) js_CodeSpec[op].length;
- }
- break;
- case JOF_JUMP:
- case JOF_JUMPX:
- off = GetJumpOffset(pc, pc);
- fprintf(fp, " %u (%d)", loc + (intN) off, (intN) off);
- break;
- case JOF_ATOM:
- case JOF_OBJECT:
- case JOF_REGEXP:
- index = js_GetIndexFromBytecode(cx, script, pc, 0);
- if (type == JOF_ATOM) {
- JS_GET_SCRIPT_ATOM(script, index, atom);
- v = ATOM_KEY(atom);
- } else {
- if (type == JOF_OBJECT)
- JS_GET_SCRIPT_OBJECT(script, index, obj);
- else
- JS_GET_SCRIPT_REGEXP(script, index, obj);
- v = OBJECT_TO_JSVAL(obj);
- }
- bytes = ToDisassemblySource(cx, v);
- if (!bytes)
- return 0;
- fprintf(fp, " %s", bytes);
- break;
- case JOF_UINT16:
- i = (jsint)GET_UINT16(pc);
- goto print_int;
- case JOF_TABLESWITCH:
- case JOF_TABLESWITCHX:
- {
- jsbytecode *pc2;
- jsint i, low, high;
- jmplen = (type == JOF_TABLESWITCH) ? JUMP_OFFSET_LEN
- : JUMPX_OFFSET_LEN;
- pc2 = pc;
- off = GetJumpOffset(pc, pc2);
- pc2 += jmplen;
- low = GET_JUMP_OFFSET(pc2);
- pc2 += JUMP_OFFSET_LEN;
- high = GET_JUMP_OFFSET(pc2);
- pc2 += JUMP_OFFSET_LEN;
- fprintf(fp, " defaultOffset %d low %d high %d", (intN) off, low, high);
- for (i = low; i <= high; i++) {
- off = GetJumpOffset(pc, pc2);
- fprintf(fp, "\n\t%d: %d", i, (intN) off);
- pc2 += jmplen;
- }
- len = 1 + pc2 - pc;
- break;
- }
- case JOF_LOOKUPSWITCH:
- case JOF_LOOKUPSWITCHX:
- {
- jsbytecode *pc2;
- jsatomid npairs;
- jmplen = (type == JOF_LOOKUPSWITCH) ? JUMP_OFFSET_LEN
- : JUMPX_OFFSET_LEN;
- pc2 = pc;
- off = GetJumpOffset(pc, pc2);
- pc2 += jmplen;
- npairs = GET_UINT16(pc2);
- pc2 += UINT16_LEN;
- fprintf(fp, " offset %d npairs %u", (intN) off, (uintN) npairs);
- while (npairs) {
- JS_GET_SCRIPT_ATOM(script, GET_INDEX(pc2), atom);
- pc2 += INDEX_LEN;
- off = GetJumpOffset(pc, pc2);
- pc2 += jmplen;
- bytes = ToDisassemblySource(cx, ATOM_KEY(atom));
- if (!bytes)
- return 0;
- fprintf(fp, "\n\t%s: %d", bytes, (intN) off);
- npairs--;
- }
- len = 1 + pc2 - pc;
- break;
- }
- case JOF_QARG:
- fprintf(fp, " %u", GET_ARGNO(pc));
- break;
- case JOF_LOCAL:
- fprintf(fp, " %u", GET_SLOTNO(pc));
- break;
- case JOF_SLOTATOM:
- case JOF_SLOTOBJECT:
- fprintf(fp, " %u", GET_SLOTNO(pc));
- index = js_GetIndexFromBytecode(cx, script, pc, SLOTNO_LEN);
- if (type == JOF_SLOTATOM) {
- JS_GET_SCRIPT_ATOM(script, index, atom);
- v = ATOM_KEY(atom);
- } else {
- JS_GET_SCRIPT_OBJECT(script, index, obj);
- v = OBJECT_TO_JSVAL(obj);
- }
- bytes = ToDisassemblySource(cx, v);
- if (!bytes)
- return 0;
- fprintf(fp, " %s", bytes);
- break;
- case JOF_UINT24:
- JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY);
- i = (jsint)GET_UINT24(pc);
- goto print_int;
- case JOF_UINT8:
- i = pc[1];
- goto print_int;
- case JOF_INT8:
- i = GET_INT8(pc);
- goto print_int;
- case JOF_INT32:
- JS_ASSERT(op == JSOP_INT32);
- i = GET_INT32(pc);
- print_int:
- fprintf(fp, " %d", i);
- break;
- default: {
- char numBuf[12];
- JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) cs->format);
- JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
- JSMSG_UNKNOWN_FORMAT, numBuf);
- return 0;
- }
- }
- fputs("\n", fp);
- return len;
- }
- #endif /* DEBUG */
- /************************************************************************/
- /*
- * Sprintf, but with unlimited and automatically allocated buffering.
- */
- typedef struct Sprinter {
- JSContext *context; /* context executing the decompiler */
- JSArenaPool *pool; /* string allocation pool */
- char *base; /* base address of buffer in pool */
- size_t size; /* size of buffer allocated at base */
- ptrdiff_t offset; /* offset of next free char in buffer */
- } Sprinter;
- #define INIT_SPRINTER(cx, sp, ap, off) \
- ((sp)->context = cx, (sp)->pool = ap, (sp)->base = NULL, (sp)->size = 0, \
- (sp)->offset = off)
- #define OFF2STR(sp,off) ((sp)->base + (off))
- #define STR2OFF(sp,str) ((str) - (sp)->base)
- #define RETRACT(sp,str) ((sp)->offset = STR2OFF(sp, str))
- static JSBool
- SprintEnsureBuffer(Sprinter *sp, size_t len)
- {
- ptrdiff_t nb;
- char *base;
- nb = (sp->offset + len + 1) - sp->size;
- if (nb < 0)
- return JS_TRUE;
- base = sp->base;
- if (!base) {
- JS_ARENA_ALLOCATE_CAST(base, char *, sp->pool, nb);
- } else {
- JS_ARENA_GROW_CAST(base, char *, sp->pool, sp->size, nb);
- }
- if (!base) {
- js_ReportOutOfScriptQuota(sp->context);
- return JS_FALSE;
- }
- sp->base = base;
- sp->size += nb;
- return JS_TRUE;
- }
- static ptrdiff_t
- SprintPut(Sprinter *sp, const char *s, size_t len)
- {
- ptrdiff_t offset;
- char *bp;
- /* Allocate space for s, including the '\0' at the end. */
- if (!SprintEnsureBuffer(sp, len))
- return -1;
- /* Advance offset and copy s into sp's buffer. */
- offset = sp->offset;
- sp->offset += len;
- bp = sp->base + offset;
- memmove(bp, s, len);
- bp[len] = 0;
- return offset;
- }
- static ptrdiff_t
- SprintCString(Sprinter *sp, const char *s)
- {
- return SprintPut(sp, s, strlen(s));
- }
- static ptrdiff_t
- SprintString(Sprinter *sp, JSString *str)
- {
- jschar *chars;
- size_t length, size;
- ptrdiff_t offset;
- JSSTRING_CHARS_AND_LENGTH(str, chars, length);
- if (length == 0)
- return sp->offset;
- size = js_GetDeflatedStringLength(sp->context, chars, length);
- if (size == (size_t)-1 || !SprintEnsureBuffer(sp, size))
- return -1;
- offset = sp->offset;
- sp->offset += size;
- js_DeflateStringToBuffer(sp->context, chars, length, sp->base + offset,
- &size);
- sp->base[sp->offset] = 0;
- return offset;
- }
- static ptrdiff_t
- Sprint(Sprinter *sp, const char *format, ...)
- {
- va_list ap;
- char *bp;
- ptrdiff_t offset;
- va_start(ap, format);
- bp = JS_vsmprintf(format, ap); /* XXX vsaprintf */
- va_end(ap);
- if (!bp) {
- JS_ReportOutOfMemory(sp->context);
- return -1;
- }
- offset = SprintCString(sp, bp);
- free(bp);
- return offset;
- }
- const char js_EscapeMap[] = {
- '\b', 'b',
- '\f', 'f',
- '\n', 'n',
- '\r', 'r',
- '\t', 't',
- '\v', 'v',
- '"', '"',
- '\'', '\'',
- '\\', '\\',
- '\0', '0'
- };
- #define DONT_ESCAPE 0x10000
- static char *
- QuoteString(Sprinter *sp, JSString *str, uint32 quote)
- {
- JSBool dontEscape, ok;
- jschar qc, c;
- ptrdiff_t off, len;
- const jschar *s, *t, *z;
- const char *e;
- char *bp;
- /* Sample off first for later return value pointer computation. */
- dontEscape = (quote & DONT_ESCAPE) != 0;
- qc = (jschar) quote;
- off = sp->offset;
- if (qc && Sprint(sp, "%c", (char)qc) < 0)
- return NULL;
- /* Loop control variables: z points at end of string sentinel. */
- JSSTRING_CHARS_AND_END(str, s, z);
- for (t = s; t < z; s = ++t) {
- /* Move t forward from s past un-quote-worthy characters. */
- c = *t;
- while (JS_ISPRINT(c) && c != qc && c != '\\' && c != '\t' &&
- !(c >> 8)) {
- c = *++t;
- if (t == z)
- break;
- }
- len = PTRDIFF(t, s, jschar);
- /* Allocate space for s, including the '\0' at the end. */
- if (!SprintEnsureBuffer(sp, len))
- return NULL;
- /* Advance sp->offset and copy s into sp's buffer. */
- bp = sp->base + sp->offset;
- sp->offset += len;
- while (--len >= 0)
- *bp++ = (char) *s++;
- *bp = '\0';
- if (t == z)
- break;
- /* Use js_EscapeMap, \u, or \x only if necessary. */
- if (!(c >> 8) && (e = strchr(js_EscapeMap, (int)c)) != NULL) {
- ok = dontEscape
- ? Sprint(sp, "%c", (char)c) >= 0
- : Sprint(sp, "\\%c", e[1]) >= 0;
- } else {
- ok = Sprint(sp, (c >> 8) ? "\\u%04X" : "\\x%02X", c) >= 0;
- }
- if (!ok)
- return NULL;
- }
- /* Sprint the closing quote and return the quoted string. */
- if (qc && Sprint(sp, "%c", (char)qc) < 0)
- return NULL;
- /*
- * If we haven't Sprint'd anything yet, Sprint an empty string so that
- * the OFF2STR below gives a valid result.
- */
- if (off == sp->offset && Sprint(sp, "") < 0)
- return NULL;
- return OFF2STR(sp, off);
- }
- JSString *
- js_QuoteString(JSContext *cx, JSString *str, jschar quote)
- {
- void *mark;
- Sprinter sprinter;
- char *bytes;
- JSString *escstr;
- mark = JS_ARENA_MARK(&cx->tempPool);
- INIT_SPRINTER(cx, &sprinter, &cx->tempPool, 0);
- bytes = QuoteString(&sprinter, str, quote);
- escstr = bytes ? JS_NewStringCopyZ(cx, bytes) : NULL;
- JS_ARENA_RELEASE(&cx->tempPool, mark);
- return escstr;
- }
- /************************************************************************/
- struct JSPrinter {
- Sprinter sprinter; /* base class state */
- JSArenaPool pool; /* string allocation pool */
- uintN indent; /* indentation in spaces */
- JSPackedBool pretty; /* pretty-print: indent, use newlines */
- JSPackedBool grouped; /* in parenthesized expression context */
- JSScript *script; /* script being printed */
- jsbytecode *dvgfence; /* DecompileExpression fencepost */
- jsbytecode **pcstack; /* DecompileExpression modeled stack */
- JSFunction *fun; /* interpreted function */
- jsuword *localNames; /* argument and variable names */
- };
- /*
- * Hack another flag, a la JS_DONT_PRETTY_PRINT, into uintN indent parameters
- * to functions such as js_DecompileFunction and js_NewPrinter. This time, as
- * opposed to JS_DONT_PRETTY_PRINT back in the dark ages, we can assume that a
- * uintN is at least 32 bits.
- */
- #define JS_IN_GROUP_CONTEXT 0x10000
- JSPrinter *
- JS_NEW_PRINTER(JSContext *cx, const char *name, JSFunction *fun,
- uintN indent, JSBool pretty)
- {
- JSPrinter *jp;
- jp = (JSPrinter *) JS_malloc(cx, sizeof(JSPrinter));
- if (!jp)
- return NULL;
- INIT_SPRINTER(cx, &jp->sprinter, &jp->pool, 0);
- JS_INIT_ARENA_POOL(&jp->pool, name, 256, 1, &cx->scriptStackQuota);
- jp->indent = indent & ~JS_IN_GROUP_CONTEXT;
- jp->pretty = pretty;
- jp->grouped = (indent & JS_IN_GROUP_CONTEXT) != 0;
- jp->script = NULL;
- jp->dvgfence = NULL;
- jp->pcstack = NULL;
- jp->fun = fun;
- jp->localNames = NULL;
- if (fun && FUN_INTERPRETED(fun) && JS_GET_LOCAL_NAME_COUNT(fun)) {
- jp->localNames = js_GetLocalNameArray(cx, fun, &jp->pool);
- if (!jp->localNames) {
- js_DestroyPrinter(jp);
- return NULL;
- }
- }
- return jp;
- }
- void
- js_DestroyPrinter(JSPrinter *jp)
- {
- JS_FinishArenaPool(&jp->pool);
- JS_free(jp->sprinter.context, jp);
- }
- JSString *
- js_GetPrinterOutput(JSPrinter *jp)
- {
- JSContext *cx;
- JSString *str;
- cx = jp->sprinter.context;
- if (!jp->sprinter.base)
- return cx->runtime->emptyString;
- str = JS_NewStringCopyZ(cx, jp->sprinter.base);
- if (!str)
- return NULL;
- JS_FreeArenaPool(&jp->pool);
- INIT_SPRINTER(cx, &jp->sprinter, &jp->pool, 0);
- return str;
- }
- /*
- * NB: Indexed by SRC_DECL_* defines from jsemit.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 ((uintN)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)
- 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)
- 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(jp->sprinter.context, fp);
- format = NULL;
- }
- if (!bp) {
- JS_ReportOutOfMemory(jp->sprinter.context);
- return -1;
- }
- cc = strlen(bp);
- if (SprintPut(&jp->sprinter, bp, (size_t)cc) < 0)
- cc = -1;
- free(bp);
- va_end(ap);
- return cc;
- }
- JSBool
- js_puts(JSPrinter *jp, const char *s)
- {
- return SprintCString(&jp->sprinter, s) >= 0;
- }
- /************************************************************************/
- typedef 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 */
- uintN top; /* top of stack index */
- uintN inArrayInit; /* array initialiser/comprehension level */
- JSBool inGenExp; /* in generator expression */
- JSPrinter *printer; /* permanent output goes here */
- } SprintStack;
- /*
- * Find the depth of the operand stack when the interpreter reaches the given
- * pc in script. pcstack must have space for least script->depth elements. On
- * return it will contain pointers to opcodes that populated the interpreter's
- * current operand stack.
- *
- * This function cannot raise an exception or error. However, due to a risk of
- * potential bugs when modeling the stack, the function returns -1 if it
- * detects an inconsistency in the model. Such an inconsistency triggers an
- * assert in a debug build.
- */
- static intN
- ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc,
- jsbytecode **pcstack);
- #define FAILED_EXPRESSION_DECOMPILER ((char *) 1)
- /*
- * Decompile a part of expression up to the given pc. The function returns
- * NULL on out-of-memory, or the FAILED_EXPRESSION_DECOMPILER sentinel when
- * the decompiler fails due to a bug and/or unimplemented feature, or the
- * decompiled string on success.
- */
- static char *
- DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun,
- jsbytecode *pc);
- /*
- * Get a stacked offset from ss->sprinter.base, or if the stacked value |off|
- * is negative, 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, uintN i)
- {
- ptrdiff_t off;
- jsbytecode *pc;
- char *bytes;
- off = ss->offsets[i];
- if (off >= 0)
- return off;
- JS_ASSERT(off <= -2);
- JS_ASSERT(ss->printer->pcstack);
- if (off < -2 && ss->printer->pcstack) {
- pc = ss->printer->pcstack[-2 - off];
- bytes = DecompileExpression(ss->sprinter.context, ss->printer->script,
- ss->printer->fun, pc);
- if (!bytes)
- return 0;
- if (bytes != FAILED_EXPRESSION_DECOMPILER) {
- off = SprintCString(&ss->sprinter, bytes);
- if (off < 0)
- off = 0;
- ss->offsets[i] = off;
- JS_free(ss->sprinter.context, bytes);
- return off;
- }
- if (!ss->sprinter.base && SprintPut(&ss->sprinter, "", 0) >= 0) {
- memset(ss->sprinter.base, 0, ss->sprinter.offset);
- ss->offsets[i] = -1;
- }
- }
- return 0;
- }
- static const char *
- GetStr(SprintStack *ss, uintN i)
- {
- ptrdiff_t off;
- /*
- * Must call GetOff before using ss->sprinter.base, since it may be null
- * until bootstrapped by GetOff.
- */
- off = GetOff(ss, i);
- return OFF2STR(&ss->sprinter, off);
- }
- /*
- * Gap between stacked strings to allow for insertion of parens and commas
- * when auto-parenthesizing expressions and decompiling array initialisers
- * (see the JSOP_NEWARRAY case in Decompile).
- */
- #define PAREN_SLOP (2 + 1)
- /*
- * These pseudo-ops help js_DecompileValueGenerator decompile JSOP_SETNAME,
- * JSOP_SETPROP, and JSOP_SETELEM, respectively. They are never stored in
- * bytecode, so they don't preempt valid opcodes.
- */
- #define JSOP_GETPROP2 256
- #define JSOP_GETELEM2 257
- static void
- AddParenSlop(SprintStack *ss)
- {
- memset(OFF2STR(&ss->sprinter, ss->sprinter.offset), 0, PAREN_SLOP);
- ss->sprinter.offset += PAREN_SLOP;
- }
- static JSBool
- PushOff(SprintStack *ss, ptrdiff_t off, JSOp op)
- {
- uintN top;
- if (!SprintEnsureBuffer(&ss->sprinter, PAREN_SLOP))
- return JS_FALSE;
- /* 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)) {
- JS_ReportOutOfMemory(ss->sprinter.context);
- return JS_FALSE;
- }
- /* The opcodes stack must contain real bytecodes that index js_CodeSpec. */
- ss->offsets[top] = off;
- ss->opcodes[top] = (op == JSOP_GETPROP2) ? JSOP_GETPROP
- : (op == JSOP_GETELEM2) ? JSOP_GETELEM
- : (jsbytecode) op;
- ss->top = ++top;
- AddParenSlop(ss);
- return JS_TRUE;
- }
- static ptrdiff_t
- PopOffPrec(SprintStack *ss, uint8 prec)
- {
- uintN top;
- const JSCodeSpec *topcs;
- ptrdiff_t off;
- /* 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);
- topcs = &js_CodeSpec[ss->opcodes[top]];
- if (topcs->prec != 0 && topcs->prec < prec) {
- ss->sprinter.offset = ss->offsets[top] = off - 2;
- off = Sprint(&ss->sprinter, "(%s)", OFF2STR(&ss->sprinter, off));
- } else {
- ss->sprinter.offset = off;
- }
- return off;
- }
- static const char *
- PopStrPrec(SprintStack *ss, uint8 prec)
- {
- ptrdiff_t off;
- off = PopOffPrec(ss, prec);
- return OFF2STR(&ss->sprinter, off);
- }
- static ptrdiff_t
- PopOff(SprintStack *ss, JSOp op)
- {
- return PopOffPrec(ss, js_CodeSpec[op].prec);
- }
- static const char *
- PopStr(SprintStack *ss, JSOp op)
- {
- return PopStrPrec(ss, js_CodeSpec[op].prec);
- }
- typedef struct TableEntry {
- jsval key;
- ptrdiff_t offset;
- JSAtom *label;
- jsint order; /* source order for stable tableswitch sort */
- } TableEntry;
- static JSBool
- CompareOffsets(void *arg, const void *v1, const void *v2, int *result)
- {
- ptrdiff_t offset_diff;
- const TableEntry *te1 = (const TableEntry *) v1,
- *te2 = (const TableEntry *) v2;
- offset_diff = te1->offset - te2->offset;
- *result = (offset_diff == 0 ? te1->order - te2->order
- : offset_diff < 0 ? -1
- : 1);
- return JS_TRUE;
- }
- static ptrdiff_t
- SprintDoubleValue(Sprinter *sp, jsval v, JSOp *opp)
- {
- jsdouble d;
- ptrdiff_t todo;
- char *s, buf[DTOSTR_STANDARD_BUFFER_SIZE];
- JS_ASSERT(JSVAL_IS_DOUBLE(v));
- d = *JSVAL_TO_DOUBLE(v);
- if (JSDOUBLE_IS_NEGZERO(d)) {
- todo = SprintCString(sp, "-0");
- *opp = JSOP_NEG;
- } else if (!JSDOUBLE_IS_FINITE(d)) {
- /* Don't use Infinity and NaN, they're mutable. */
- todo = SprintCString(sp,
- JSDOUBLE_IS_NaN(d)
- ? "0 / 0"
- : (d < 0)
- ? "1 / -0"
- : "1 / 0");
- *opp = JSOP_DIV;
- } else {
- s = JS_dtostr(buf, sizeof buf, DTOSTR_STANDARD, 0, d);
- if (!s) {
- JS_ReportOutOfMemory(sp->context);
- return -1;
- }
- JS_ASSERT(strcmp(s, js_Infinity_str) &&
- (*s != '-' ||
- strcmp(s + 1, js_Infinity_str)) &&
- strcmp(s, js_NaN_str));
- todo = Sprint(sp, s);
- }
- return todo;
- }
- static jsbytecode *
- Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop);
- static JSBool
- DecompileSwitch(SprintStack *ss, TableEntry *table, uintN tableLength,
- jsbytecode *pc, ptrdiff_t switchLength,
- ptrdiff_t defaultOffset, JSBool isCondSwitch)
- {
- JSContext *cx;
- JSPrinter *jp;
- ptrdiff_t off, off2, diff, caseExprOff, todo;
- char *lval, *rval;
- uintN i;
- jsval key;
- JSString *str;
- cx = ss->sprinter.context;
- jp = ss->printer;
- /* JSOP_CONDSWITCH doesn't pop, unlike JSOP_{LOOKUP,TABLE}SWITCH. */
- off = isCondSwitch ? GetOff(ss, ss->top-1) : PopOff(ss, JSOP_NOP);
- lval = OFF2STR(&ss->sprinter, off);
- js_printf(jp, "\tswitch (%s) {\n", lval);
- 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, JSOP_NOP))
- 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, JSOP_NOP)) {
- 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 = ATOM_TO_STRING(table[i].label);
- key = JSVAL_VOID;
- } else if (JSVAL_IS_DOUBLE(key)) {
- JSOp junk;
- todo = SprintDoubleValue(&ss->sprinter, key, &junk);
- str = NULL;
- } else {
- str = js_ValueToString(cx, key);
- if (!str)
- return JS_FALSE;
- }
- if (todo >= 0) {
- rval = OFF2STR(&ss->sprinter, todo);
- } else {
- rval = QuoteString(&ss->sprinter, str, (jschar)
- (JSVAL_IS_STRING(key) ? '"' : 0));
- if (!rval)
- return JS_FALSE;
- }
- RETRACT(&ss->sprinter, 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, JSOP_NOP))
- 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, JSOP_NOP))
- 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, uintN slot)
- {
- JSAtom *name;
- LOCAL_ASSERT_RV(jp->fun, NULL);
- LOCAL_ASSERT_RV(slot < (uintN) JS_GET_LOCAL_NAME_COUNT(jp->fun), NULL);
- name = JS_LOCAL_NAME_TO_ATOM(jp->localNames[slot]);
- #if !JS_HAS_DESTRUCTURING
- LOCAL_ASSERT_RV(name, NULL);
- #endif
- return name;
- }
- const char *
- GetLocal(SprintStack *ss, jsint i)
- {
- ptrdiff_t off;
- JSContext *cx;
- JSScript *script;
- jsatomid j, n;
- JSAtom *atom;
- JSObject *obj;
- jsint depth, count;
- JSScopeProperty *sprop;
- const char *rval;
- #define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, "")
- off = ss->offsets[i];
- if (off >= 0)
- return OFF2STR(&ss->sprinter, 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.
- */
- cx = ss->sprinter.context;
- script = ss->printer->script;
- LOCAL_ASSERT(script->objectsOffset != 0);
- for (j = 0, n = JS_SCRIPT_OBJECTS(script)->length; ; j++) {
- LOCAL_ASSERT(j < n);
- JS_GET_SCRIPT_OBJECT(script, j, obj);
- if (OBJ_GET_CLASS(cx, obj) == &js_BlockClass) {
- depth = OBJ_BLOCK_DEPTH(cx, obj);
- count = OBJ_BLOCK_COUNT(cx, obj);
- if ((jsuint)(i - depth) < (jsuint)count)
- break;
- }
- }
- i -= depth;
- for (sprop = OBJ_SCOPE(obj)->lastProp; sprop; sprop = sprop->parent) {
- if (sprop->shortid == i)
- break;
- }
- LOCAL_ASSERT(sprop && JSID_IS_ATOM(sprop->id));
- atom = JSID_TO_ATOM(sprop->id);
- rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
- if (!rval)
- return NULL;
- RETRACT(&ss->sprinter, rval);
- return rval;
- #undef LOCAL_ASSERT
- }
- static JSBool
- IsVarSlot(JSPrinter *jp, jsbytecode *pc, jsint *indexp)
- {
- uintN slot;
- slot = GET_SLOTNO(pc);
- if (slot < jp->script->nfixed) {
- /* The slot refers to a variable with name stored in jp->localNames. */
- *indexp = jp->fun->nargs + slot;
- return JS_TRUE;
- }
- /* We have a local which index is relative to the stack base. */
- slot -= jp->script->nfixed;
- JS_ASSERT(slot < StackDepth(jp->script));
- *indexp = slot;
- return JS_FALSE;
- }
- #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);
- static jsbytecode *
- DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
- JSBool *hole)
- {
- JSContext *cx;
- JSPrinter *jp;
- JSOp op;
- const JSCodeSpec *cs;
- uintN oplen;
- jsint i;
- const char *lval, *xval;
- ptrdiff_t todo;
- JSAtom *atom;
- *hole = JS_FALSE;
- cx = ss->sprinter.context;
- jp = ss->printer;
- LOAD_OP_DATA(pc);
- switch (op) {
- case JSOP_POP:
- *hole = JS_TRUE;
- todo = SprintPut(&ss->sprinter, ", ", 2);
- break;
- case JSOP_DUP:
- pc = DecompileDestructuring(ss, pc, endpc);
- if (!pc)
- return NULL;
- if (pc == endpc)
- return pc;
- LOAD_OP_DATA(pc);
- lval = PopStr(ss, JSOP_NOP);
- todo = SprintCString(&ss->sprinter, lval);
- if (op == JSOP_POPN)
- return pc;
- LOCAL_ASSERT(*pc == JSOP_POP);
- break;
- case JSOP_SETARG:
- case JSOP_SETGVAR:
- case JSOP_SETLOCAL:
- LOCAL_ASSERT(pc[oplen] == JSOP_POP || pc[oplen] == JSOP_POPN);
- /* FALL THROUGH */
- case JSOP_SETLOCALPOP:
- atom = NULL;
- lval = NULL;
- if (op == JSOP_SETARG) {
- atom = GetArgOrVarAtom(jp, GET_SLOTNO(pc));
- LOCAL_ASSERT(atom);
- } else if (op == JSOP_SETGVAR) {
- GET_ATOM_FROM_BYTECODE(jp->script, pc, 0, atom);
- } else if (IsVarSlot(jp, pc, &i)) {
- atom = GetArgOrVarAtom(jp, i);
- LOCAL_ASSERT(atom);
- } else {
- lval = GetLocal(ss, i);
- }
- if (atom)
- lval = js_AtomToPrintableString(cx, atom);
- LOCAL_ASSERT(lval);
- todo = SprintCString(&ss->sprinter, lval);
- if (op != JSOP_SETLOCALPOP) {
- pc += oplen;
- if (pc == endpc)
- return pc;
- LOAD_OP_DATA(pc);
- if (op == JSOP_POPN)
- return pc;
- LOCAL_ASSERT(op == JSOP_POP);
- }
- break;
- default:
- /*
- * 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.
- */
- todo = ss->sprinter.offset;
- ss->sprinter.offset = todo + PAREN_SLOP;
- pc = Decompile(ss, pc, -((intN)ss->top), JSOP_NOP);
- 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.offset = todo;
- if (*lval == '\0') {
- /* lval is from JSOP_BINDNAME, so just print xval. */
- todo = SprintCString(&ss->sprinter, xval);
- } else if (*xval == '\0') {
- /* xval is from JSOP_SETCALL or JSOP_BINDXMLNAME, print lval. */
- todo = SprintCString(&ss->sprinter, lval);
- } else {
- todo = Sprint(&ss->sprinter,
- (JOF_OPMODE(ss->opcodes[ss->top+1]) == JOF_XMLNAME)
- ? "%s.%s"
- : "%s[%s]",
- lval, xval);
- }
- break;
- }
- if (todo < 0)
- return NULL;
- LOCAL_ASSERT(pc < endpc);
- pc += oplen;
- return pc;
- }
- /*
- * Starting with a SRC_DESTRUCT-annotated JSOP_DUP, decompile a destructuring
- * left-hand side object or array initialiser, including nested destructuring
- * initialisers. On successful return, the decompilation will be pushed on ss
- * and the return value will point to the POP or GROUP bytecode following the
- * destructuring expression.
- *
- * At any point, if pc is equal to endpc and would otherwise advance, we stop
- * immediately and return endpc.
- */
- static jsbytecode *
- DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc)
- {
- ptrdiff_t head;
- JSContext *cx;
- JSPrinter *jp;
- JSOp op, saveop;
- const JSCodeSpec *cs;
- uintN oplen;
- jsint i, lasti;
- jsdouble d;
- const char *lval;
- JSAtom *atom;
- jssrcnote *sn;
- JSString *str;
- JSBool hole;
- LOCAL_ASSERT(*pc == JSOP_DUP);
- pc += JSOP_DUP_LENGTH;
- /*
- * 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 "[".
- */
- head = SprintPut(&ss->sprinter, "[", 1);
- if (head < 0 || !PushOff(ss, head, JSOP_NOP))
- return NULL;
- ss->sprinter.offset -= PAREN_SLOP;
- LOCAL_ASSERT(head == ss->sprinter.offset - 1);
- LOCAL_ASSERT(*OFF2STR(&ss->sprinter, head) == '[');
- cx = ss->sprinter.context;
- jp = ss->printer;
- lasti = -1;
- while (pc < endpc) {
- #if JS_HAS_DESTRUCTURING_SHORTHAND
- ptrdiff_t nameoff = -1;
- #endif
- LOAD_OP_DATA(pc);
- saveop = op;
- switch (op) {
- case JSOP_POP:
- 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:
- GET_ATOM_FROM_BYTECODE(jp->script, pc, 0, atom);
- d = *ATOM_TO_DOUBLE(atom);
- LOCAL_ASSERT(JSDOUBLE_IS_FINITE(d) && !JSDOUBLE_IS_NEGZERO(d));
- i = (jsint)d;
- do_getelem:
- sn = js_GetSrcNote(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) {
- *OFF2STR(&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 (SprintPut(&ss->sprinter, ", ", 2) < 0)
- return NULL;
- }
- }
- break;
- case JSOP_LENGTH:
- atom = cx->runtime->atomState.lengthAtom;
- goto do_destructure_atom;
- case JSOP_CALLPROP:
- case JSOP_GETPROP:
- GET_ATOM_FROM_BYTECODE(jp->script, pc, 0, atom);
- do_destructure_atom:
- *OFF2STR(&ss->sprinter, head) = '{';
- str = ATOM_TO_STRING(atom);
- #if JS_HAS_DESTRUCTURING_SHORTHAND
- nameoff = ss->sprinter.offset;
- #endif
- if (!QuoteString(&ss->sprinter, str,
- js_IsIdentifier(str) ? 0 : (jschar)'\'')) {
- return NULL;
- }
- if (SprintPut(&ss->sprinter, ": ", 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).
- */
- pc = DecompileDestructuringLHS(ss, pc, endpc, &hole);
- if (!pc)
- return NULL;
- #if JS_HAS_DESTRUCTURING_SHORTHAND
- if (nameoff >= 0) {
- ptrdiff_t offset, initlen;
- offset = ss->sprinter.offset;
- LOCAL_ASSERT(*OFF2STR(&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 = OFF2STR(&ss->sprinter, nameoff);
- if (!strncmp(name + namelen, ": ", 2) &&
- !strncmp(name, name + namelen + 2, namelen)) {
- offset -= namelen + 2;
- *OFF2STR(&ss->sprinter, offset) = '\0';
- ss->sprinter.offset = offset;
- }
- }
- }
- #endif
- if (pc == endpc || *pc != JSOP_DUP)
- break;
- /*
- * Check for SRC_DESTRUCT on this JSOP_DUP, which would mean another
- * destructuring initialiser abuts this one, and we should stop. This
- * happens with source of the form '[a] = [b] = c'.
- */
- sn = js_GetSrcNote(jp->script, pc);
- if (sn && SN_TYPE(sn) == SRC_DESTRUCT)
- break;
- if (!hole && SprintPut(&ss->sprinter, ", ", 2) < 0)
- return NULL;
- pc += JSOP_DUP_LENGTH;
- }
- out:
- lval = OFF2STR(&ss->sprinter, head);
- if (SprintPut(&ss->sprinter, (*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;
- uintN oplen, start, end, i;
- ptrdiff_t todo;
- JSBool hole;
- const char *rval;
- LOAD_OP_DATA(pc);
- LOCAL_ASSERT(op == JSOP_PUSH || op == JSOP_GETLOCAL);
- todo = Sprint(&ss->sprinter, "%s[", VarPrefix(sn));
- if (todo < 0 || !PushOff(ss, todo, JSOP_NOP))
- return NULL;
- ss->sprinter.offset -= 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_PUSH && op != JSOP_GETLOCAL)
- break;
- if (!hole && SprintPut(&ss->sprinter, ", ", 2) < 0)
- return NULL;
- }
- LOCAL_ASSERT(op == JSOP_POPN);
- if (SprintPut(&ss->sprinter, "] = [", 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 (SprintPut(&ss->sprinter, "]", 1) < 0)
- return NULL;
- ss->sprinter.offset = ss->offsets[i];
- ss->top = start;
- *todop = todo;
- return pc;
- }
- #undef LOCAL_ASSERT
- #undef LOAD_OP_DATA
- #endif /* JS_HAS_DESTRUCTURING */
- static JSBool
- InitSprintStack(JSContext *cx, SprintStack *ss, JSPrinter *jp, uintN depth)
- {
- size_t offsetsz, opcodesz;
- void *space;
- INIT_SPRINTER(cx, &ss->sprinter, &cx->tempPool, PAREN_SLOP);
- /* Allocate the parallel (to avoid padding) offset and opcode stacks. */
- offsetsz = depth * sizeof(ptrdiff_t);
- opcodesz = depth * sizeof(jsbytecode);
- JS_ARENA_ALLOCATE(space, &cx->tempPool, offsetsz + opcodesz);
- if (!space) {
- js_ReportOutOfScriptQuota(cx);
- return JS_FALSE;
- }
- ss->offsets = (ptrdiff_t *) space;
- ss->opcodes = (jsbytecode *) ((char *)space + offsetsz);
- 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).
- *
- * The nextop parameter is either JSOP_NOP or the "next" opcode in order of
- * abstract interpretation (not necessarily physically next in a bytecode
- * vector). So nextop is JSOP_POP for the last operand in a comma expression,
- * or JSOP_AND for the right operand of &&.
- */
- static jsbytecode *
- Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
- {
- JSContext *cx;
- JSPrinter *jp, *jp2;
- jsbytecode *startpc, *endpc, *pc2, *done;
- 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;
- jsint i, argc;
- char **argv;
- JSAtom *atom;
- JSObject *obj;
- JSFunction *fun;
- JSString *str;
- JSBool ok;
- #if JS_HAS_XML_SUPPORT
- JSBool foreach, inXML, quoteAttr;
- #else
- #define inXML JS_FALSE
- #endif
- jsval val;
- static const char exception_cookie[] = "/*EXCEPTION*/";
- static const char retsub_pc_cookie[] = "/*RETSUB_PC*/";
- static const char iter_cookie[] = "/*ITER*/";
- static const char forelem_cookie[] = "/*FORELEM*/";
- static const char with_cookie[] = "/*WITH*/";
- static const char dot_format[] = "%s.%s";
- 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(pc,nb) if (!Decompile(ss, pc, nb, JSOP_NOP)) return NULL
- #define NEXT_OP(pc) (((pc) + (len) == endpc) ? nextop : pc[len])
- #define TOP_STR() GetStr(ss, ss->top - 1)
- #define POP_STR() PopStr(ss, op)
- #define POP_STR_PREC(prec) PopStrPrec(ss, prec)
- /*
- * Pop a condition expression for if/for/while. JSOP_IFEQ's precedence forces
- * extra parens around assignment, which avoids a strict-mode warning.
- */
- #define POP_COND_STR() \
- PopStr(ss, (js_CodeSpec[ss->opcodes[ss->top - 1]].format & JOF_SET) \
- ? JSOP_IFEQ \
- : JSOP_NOP)
- /*
- * Callers know that ATOM_IS_STRING(atom), and we leave it to the optimizer to
- * common ATOM_TO_STRING(atom) here and near the call sites.
- */
- #define ATOM_IS_IDENTIFIER(atom) js_IsIdentifier(ATOM_TO_STRING(atom))
- #define ATOM_IS_KEYWORD(atom) \
- (js_CheckKeyword(JSSTRING_CHARS(ATOM_TO_STRING(atom)), \
- JSSTRING_LENGTH(ATOM_TO_STRING(atom))) != TOK_EOF)
- /*
- * 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 (!ATOM_IS_IDENTIFIER(atom)) { \
- quote_ = '\''; \
- fmt = qfmt; \
- } else { \
- quote_ = 0; \
- fmt = ufmt; \
- } \
- rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), quote_); \
- if (!rval) \
- return NULL; \
- JS_END_MACRO
- #define LOAD_ATOM(PCOFF) \
- GET_ATOM_FROM_BYTECODE(jp->script, pc, PCOFF, atom)
- #define LOAD_OBJECT(PCOFF) \
- GET_OBJECT_FROM_BYTECODE(jp->script, pc, PCOFF, obj)
- #define LOAD_FUNCTION(PCOFF) \
- GET_FUNCTION_FROM_BYTECODE(jp->script, pc, PCOFF, fun)
- #define LOAD_REGEXP(PCOFF) \
- GET_REGEXP_FROM_BYTECODE(jp->script, pc, PCOFF, obj)
- #define GET_SOURCE_NOTE_ATOM(sn, atom) \
- JS_BEGIN_MACRO \
- jsatomid atomIndex_ = (jsatomid) js_GetSrcNoteOffset((sn), 0); \
- \
- LOCAL_ASSERT(atomIndex_ < jp->script->atomMap.length); \
- (atom) = jp->script->atomMap.vector[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) \
- saveop = JSOP_CALL; \
- JS_END_MACRO
- cx = ss->sprinter.context;
- 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;
- #if JS_HAS_XML_SUPPORT
- foreach = inXML = quoteAttr = JS_FALSE;
- #endif
- 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];
- if (cs->format & JOF_INDEXBASE) {
- /*
- * The decompiler uses js_GetIndexFromBytecode to get atoms and
- * objects and ignores these suffix/prefix bytecodes, thus
- * simplifying code that must process JSOP_GETTER/JSOP_SETTER
- * prefixes.
- */
- pc += cs->length;
- if (pc >= endpc)
- break;
- op = (JSOp) *pc;
- cs = &js_CodeSpec[op];
- }
- saveop = op;
- len = oplen = cs->length;
- if (nb < 0 && -(nb + 1) == (intN)ss->top - cs->nuses + cs->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) {
- JSStackFrame *fp;
- uint32 format, mode, type;
- /*
- * Rewrite non-get ops to their "get" format if the error is in
- * the bytecode at pc, so we don't decompile more than the error
- * expression.
- */
- for (fp = cx->fp; fp && !fp->script; fp = fp->down)
- continue;
- format = cs->format;
- if (((fp && fp->regs && pc == fp->regs->pc) ||
- (pc == startpc && cs->nuses != 0)) &&
- format & (JOF_SET|JOF_DEL|JOF_INCDEC|JOF_FOR|JOF_VARPROP)) {
- 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.
- */
- type = JOF_TYPE(format);
- op = (type == JOF_QARG)
- ? JSOP_GETARG
- : (type == JOF_LOCAL)
- ? JSOP_GETLOCAL
- : JSOP_NAME;
- i = cs->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;
- #if JS_HAS_LVALUE_RETURN
- case JSOP_SETCALL:
- op = JSOP_CALL;
- break;
- #endif
- case JSOP_GETTHISPROP:
- /*
- * NB: JSOP_GETTHISPROP can't fail due to |this|
- * being null or undefined at runtime (beware that
- * this may change for ES4). Therefore any error
- * resulting from this op must be due to the value
- * of the property accessed via |this|, so do not
- * rewrite op to JSOP_THIS.
- *
- * The next two cases should not change op if
- * js_DecompileValueGenerator was called from the
- * the property getter. They should rewrite only
- * if the base object in the arg/var/local is null
- * or undefined. FIXME: bug 431569.
- */
- break;
- case JSOP_GETARGPROP:
- op = JSOP_GETARG;
- break;
- case JSOP_GETLOCALPROP:
- op = JSOP_GETLOCAL;
- break;
- default:
- LOCAL_ASSERT(0);
- }
- }
- }
- }
- saveop = op;
- if (op >= JSOP_LIMIT) {
- switch (op) {
- case JSOP_GETPROP2:
- saveop = JSOP_GETPROP;
- break;
- case JSOP_GETELEM2:
- saveop = JSOP_GETELEM;
- break;
- default:;
- }
- }
- LOCAL_ASSERT(js_CodeSpec[saveop].length == oplen ||
- JOF_TYPE(format) == JOF_SLOTATOM);
- jp->dvgfence = NULL;
- }
- if (token) {
- switch (cs->nuses) {
- case 2:
- sn = js_GetSrcNote(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 = POP_STR();
- lval = POP_STR();
- /* Print only the right operand of the assignment-op. */
- todo = SprintCString(&ss->sprinter, rval);
- op = saveop;
- } else if (!inXML) {
- rval = POP_STR_PREC(cs->prec + !!(cs->format & JOF_LEFTASSOC));
- lval = POP_STR_PREC(cs->prec + !(cs->format & JOF_LEFTASSOC));
- todo = Sprint(&ss->sprinter, "%s %s %s",
- lval, token, rval);
- } else {
- /* In XML, just concatenate the two operands. */
- LOCAL_ASSERT(op == JSOP_ADD);
-