Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / js / src / jsopcode.cpp
blob1c58107abe8b926896f6b93323647a2b86bd5a09
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set sw=4 ts=8 et tw=99:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
17 * The Original Code is Mozilla Communicator client code, released
18 * March 31, 1998.
20 * The Initial Developer of the Original Code is
21 * Netscape Communications Corporation.
22 * Portions created by the Initial Developer are Copyright (C) 1998
23 * the Initial Developer. All Rights Reserved.
25 * Contributor(s):
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
42 * JS bytecode descriptors, disassemblers, and decompilers.
44 #ifdef HAVE_MEMORY_H
45 #include <memory.h>
46 #endif
47 #include <stdarg.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include "jstypes.h"
52 #include "jsstdint.h"
53 #include "jsarena.h"
54 #include "jsutil.h"
55 #include "jsprf.h"
56 #include "jsapi.h"
57 #include "jsarray.h"
58 #include "jsatom.h"
59 #include "jscntxt.h"
60 #include "jsversion.h"
61 #include "jsdbgapi.h"
62 #include "jsemit.h"
63 #include "jsfun.h"
64 #include "jsiter.h"
65 #include "jsnum.h"
66 #include "jsobj.h"
67 #include "jsopcode.h"
68 #include "jsregexp.h"
69 #include "jsscan.h"
70 #include "jsscope.h"
71 #include "jsscript.h"
72 #include "jsstr.h"
73 #include "jsstaticcheck.h"
74 #include "jstracer.h"
75 #include "jsvector.h"
77 #include "jsinterpinlines.h"
78 #include "jsobjinlines.h"
79 #include "jsscriptinlines.h"
80 #include "jscntxtinlines.h"
82 #include "jsautooplen.h"
84 using namespace js;
85 using namespace js::gc;
88 * Index limit must stay within 32 bits.
90 JS_STATIC_ASSERT(sizeof(uint32) * JS_BITS_PER_BYTE >= INDEX_LIMIT_LOG2 + 1);
92 /* Verify JSOP_XXX_LENGTH constant definitions. */
93 #define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
94 JS_STATIC_ASSERT(op##_LENGTH == length);
95 #include "jsopcode.tbl"
96 #undef OPDEF
98 static const char js_incop_strs[][3] = {"++", "--"};
99 static const char js_for_each_str[] = "for each";
101 const JSCodeSpec js_CodeSpec[] = {
102 #define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
103 {length,nuses,ndefs,prec,format},
104 #include "jsopcode.tbl"
105 #undef OPDEF
108 uintN js_NumCodeSpecs = JS_ARRAY_LENGTH(js_CodeSpec);
111 * Each element of the array is either a source literal associated with JS
112 * bytecode or null.
114 static const char *CodeToken[] = {
115 #define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
116 token,
117 #include "jsopcode.tbl"
118 #undef OPDEF
121 #if defined(DEBUG) || defined(JS_JIT_SPEW) || defined(JS_METHODJIT_SPEW)
123 * Array of JS bytecode names used by DEBUG-only js_Disassemble and by
124 * JIT debug spew.
126 const char *js_CodeName[] = {
127 #define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
128 name,
129 #include "jsopcode.tbl"
130 #undef OPDEF
132 #endif
134 /************************************************************************/
136 static ptrdiff_t
137 GetJumpOffset(jsbytecode *pc, jsbytecode *pc2)
139 uint32 type;
141 type = JOF_OPTYPE(*pc);
142 if (JOF_TYPE_IS_EXTENDED_JUMP(type))
143 return GET_JUMPX_OFFSET(pc2);
144 return GET_JUMP_OFFSET(pc2);
147 uintN
148 js_GetIndexFromBytecode(JSContext *cx, JSScript *script, jsbytecode *pc,
149 ptrdiff_t pcoff)
151 JSOp op;
152 uintN span, base;
154 op = js_GetOpcode(cx, script, pc);
155 JS_ASSERT(js_CodeSpec[op].length >= 1 + pcoff + UINT16_LEN);
158 * We need to detect index base prefix. It presents when resetbase
159 * follows the bytecode.
161 span = js_CodeSpec[op].length;
162 base = 0;
163 if (pc - script->code + span < script->length) {
164 if (pc[span] == JSOP_RESETBASE) {
165 base = GET_INDEXBASE(pc - JSOP_INDEXBASE_LENGTH);
166 } else if (pc[span] == JSOP_RESETBASE0) {
167 JS_ASSERT(JSOP_INDEXBASE1 <= pc[-1] || pc[-1] <= JSOP_INDEXBASE3);
168 base = (pc[-1] - JSOP_INDEXBASE1 + 1) << 16;
171 return base + GET_UINT16(pc + pcoff);
174 uintN
175 js_GetVariableBytecodeLength(jsbytecode *pc)
177 JSOp op;
178 uintN jmplen, ncases;
179 jsint low, high;
181 op = (JSOp) *pc;
182 JS_ASSERT(js_CodeSpec[op].length == -1);
183 switch (op) {
184 case JSOP_TABLESWITCHX:
185 jmplen = JUMPX_OFFSET_LEN;
186 goto do_table;
187 case JSOP_TABLESWITCH:
188 jmplen = JUMP_OFFSET_LEN;
189 do_table:
190 /* Structure: default-jump case-low case-high case1-jump ... */
191 pc += jmplen;
192 low = GET_JUMP_OFFSET(pc);
193 pc += JUMP_OFFSET_LEN;
194 high = GET_JUMP_OFFSET(pc);
195 ncases = (uintN)(high - low + 1);
196 return 1 + jmplen + INDEX_LEN + INDEX_LEN + ncases * jmplen;
198 case JSOP_LOOKUPSWITCHX:
199 jmplen = JUMPX_OFFSET_LEN;
200 goto do_lookup;
201 default:
202 JS_ASSERT(op == JSOP_LOOKUPSWITCH);
203 jmplen = JUMP_OFFSET_LEN;
204 do_lookup:
205 /* Structure: default-jump case-count (case1-value case1-jump) ... */
206 pc += jmplen;
207 ncases = GET_UINT16(pc);
208 return 1 + jmplen + INDEX_LEN + ncases * (INDEX_LEN + jmplen);
212 uintN
213 js_GetVariableStackUses(JSOp op, jsbytecode *pc)
215 JS_ASSERT(*pc == op || *pc == JSOP_TRAP);
216 JS_ASSERT(js_CodeSpec[op].nuses == -1);
217 switch (op) {
218 case JSOP_POPN:
219 return GET_UINT16(pc);
220 case JSOP_LEAVEBLOCK:
221 return GET_UINT16(pc);
222 case JSOP_LEAVEBLOCKEXPR:
223 return GET_UINT16(pc) + 1;
224 default:
225 /* stack: fun, this, [argc arguments] */
226 JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL ||
227 op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
228 return 2 + GET_ARGC(pc);
232 uintN
233 js_GetEnterBlockStackDefs(JSContext *cx, JSScript *script, jsbytecode *pc)
235 JSObject *obj;
237 JS_ASSERT(*pc == JSOP_ENTERBLOCK || *pc == JSOP_TRAP);
238 GET_OBJECT_FROM_BYTECODE(script, pc, 0, obj);
239 return OBJ_BLOCK_COUNT(cx, obj);
242 class AutoScriptUntrapper {
243 JSContext *cx;
244 JSScript *script;
245 jsbytecode *origPC;
246 jsbytecode *newPC;
248 public:
249 AutoScriptUntrapper(JSContext *cx, JSScript *script, jsbytecode **pc)
250 : cx(cx), script(script), origPC(*pc)
252 jsbytecode *newCode = js_UntrapScriptCode(cx, script);
253 if (newCode == script->code) {
254 // No change needed
255 newPC = origPC;
256 } else {
257 script->main += newCode - script->code;
258 *pc = newPC = origPC + (newCode - script->code);
259 script->code = newCode;
262 ~AutoScriptUntrapper()
264 ptrdiff_t delta = newPC - origPC;
265 if (delta) {
266 jsbytecode *oldCode = script->code - delta;
267 cx->free(script->code);
268 script->code = oldCode;
269 script->main -= delta;
274 #ifdef DEBUG
276 /* If pc != NULL, includes a prefix indicating whether the PC is at the current line. */
277 JS_FRIEND_API(JSBool)
278 js_DisassembleAtPC(JSContext *cx, JSScript *script, JSBool lines, FILE *fp, jsbytecode *pc)
280 jsbytecode *next, *end;
281 uintN len;
283 next = script->code;
284 end = next + script->length;
285 while (next < end) {
286 if (next == script->main)
287 fputs("main:\n", fp);
288 if (pc != NULL) {
289 if (pc == next)
290 fputs("--> ", fp);
291 else
292 fputs(" ", fp);
294 len = js_Disassemble1(cx, script, next,
295 next - script->code,
296 lines, fp);
297 if (!len)
298 return JS_FALSE;
299 next += len;
301 return JS_TRUE;
304 JS_FRIEND_API(JSBool)
305 js_Disassemble(JSContext *cx, JSScript *script, JSBool lines, FILE *fp)
307 return js_DisassembleAtPC(cx, script, lines, fp, NULL);
310 JS_FRIEND_API(JSBool)
311 js_DumpPC(JSContext *cx)
313 return js_DisassembleAtPC(cx, cx->fp()->script(), true, stdout, cx->regs->pc);
316 JSBool
317 js_DumpScript(JSContext *cx, JSScript *script)
319 return js_Disassemble(cx, script, true, stdout);
322 static bool
323 ToDisassemblySource(JSContext *cx, jsval v, JSAutoByteString *bytes)
325 if (!JSVAL_IS_PRIMITIVE(v)) {
326 JSObject *obj = JSVAL_TO_OBJECT(v);
327 Class *clasp = obj->getClass();
329 if (clasp == &js_BlockClass) {
330 char *source = JS_sprintf_append(NULL, "depth %d {", OBJ_BLOCK_DEPTH(cx, obj));
331 if (!source)
332 return false;
334 Shape::Range r = obj->lastProperty()->all();
335 while (!r.empty()) {
336 const Shape &shape = r.front();
337 JSAutoByteString bytes;
338 if (!js_AtomToPrintableString(cx, JSID_TO_ATOM(shape.id), &bytes))
339 return false;
341 r.popFront();
342 source = JS_sprintf_append(source, "%s: %d%s",
343 bytes.ptr(), shape.shortid,
344 !r.empty() ? ", " : "");
345 if (!source)
346 return false;
349 source = JS_sprintf_append(source, "}");
350 if (!source)
351 return false;
352 bytes->initBytes(source);
353 return true;
356 if (clasp == &js_FunctionClass) {
357 JSFunction *fun = GET_FUNCTION_PRIVATE(cx, obj);
358 JSString *str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT);
359 if (!str)
360 return false;
361 return bytes->encode(cx, str);
364 if (clasp == &js_RegExpClass) {
365 AutoValueRooter tvr(cx);
366 if (!js_regexp_toString(cx, obj, tvr.addr()))
367 return false;
368 return bytes->encode(cx, JSVAL_TO_STRING(Jsvalify(tvr.value())));
372 return !!js_ValueToPrintable(cx, Valueify(v), bytes, true);
375 JS_FRIEND_API(uintN)
376 js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc,
377 uintN loc, JSBool lines, FILE *fp)
379 JSOp op;
380 const JSCodeSpec *cs;
381 ptrdiff_t len, off, jmplen;
382 uint32 type;
383 JSAtom *atom;
384 uintN index;
385 JSObject *obj;
386 jsval v;
387 jsint i;
389 AutoScriptUntrapper untrapper(cx, script, &pc);
391 op = (JSOp)*pc;
392 if (op >= JSOP_LIMIT) {
393 char numBuf1[12], numBuf2[12];
394 JS_snprintf(numBuf1, sizeof numBuf1, "%d", op);
395 JS_snprintf(numBuf2, sizeof numBuf2, "%d", JSOP_LIMIT);
396 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
397 JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2);
398 return 0;
400 cs = &js_CodeSpec[op];
401 len = (ptrdiff_t) cs->length;
402 fprintf(fp, "%05u:", loc);
403 if (lines)
404 fprintf(fp, "%4u", JS_PCToLineNumber(cx, script, pc));
405 fprintf(fp, " %s", js_CodeName[op]);
406 type = JOF_TYPE(cs->format);
407 switch (type) {
408 case JOF_BYTE:
409 break;
411 case JOF_JUMP:
412 case JOF_JUMPX:
413 off = GetJumpOffset(pc, pc);
414 fprintf(fp, " %u (%d)", loc + (intN) off, (intN) off);
415 break;
417 case JOF_ATOM:
418 case JOF_OBJECT:
419 case JOF_REGEXP:
420 index = js_GetIndexFromBytecode(cx, script, pc, 0);
421 if (type == JOF_ATOM) {
422 if (op == JSOP_DOUBLE) {
423 v = Jsvalify(script->getConst(index));
424 } else {
425 JS_GET_SCRIPT_ATOM(script, pc, index, atom);
426 v = ATOM_TO_JSVAL(atom);
428 } else {
429 if (type == JOF_OBJECT)
430 obj = script->getObject(index);
431 else
432 obj = script->getRegExp(index);
433 v = OBJECT_TO_JSVAL(obj);
436 JSAutoByteString bytes;
437 if (!ToDisassemblySource(cx, v, &bytes))
438 return 0;
439 fprintf(fp, " %s", bytes.ptr());
441 break;
443 case JOF_GLOBAL:
444 atom = script->getGlobalAtom(GET_SLOTNO(pc));
445 v = ATOM_TO_JSVAL(atom);
447 JSAutoByteString bytes;
448 if (!ToDisassemblySource(cx, v, &bytes))
449 return 0;
450 fprintf(fp, " %s", bytes.ptr());
452 break;
454 case JOF_UINT16PAIR:
455 i = (jsint)GET_UINT16(pc);
456 fprintf(fp, " %d", i);
457 pc += UINT16_LEN;
458 /* FALL THROUGH */
460 case JOF_UINT16:
461 i = (jsint)GET_UINT16(pc);
462 goto print_int;
464 case JOF_TABLESWITCH:
465 case JOF_TABLESWITCHX:
467 jsbytecode *pc2;
468 jsint i, low, high;
470 jmplen = (type == JOF_TABLESWITCH) ? JUMP_OFFSET_LEN
471 : JUMPX_OFFSET_LEN;
472 pc2 = pc;
473 off = GetJumpOffset(pc, pc2);
474 pc2 += jmplen;
475 low = GET_JUMP_OFFSET(pc2);
476 pc2 += JUMP_OFFSET_LEN;
477 high = GET_JUMP_OFFSET(pc2);
478 pc2 += JUMP_OFFSET_LEN;
479 fprintf(fp, " defaultOffset %d low %d high %d", (intN) off, low, high);
480 for (i = low; i <= high; i++) {
481 off = GetJumpOffset(pc, pc2);
482 fprintf(fp, "\n\t%d: %d", i, (intN) off);
483 pc2 += jmplen;
485 len = 1 + pc2 - pc;
486 break;
489 case JOF_LOOKUPSWITCH:
490 case JOF_LOOKUPSWITCHX:
492 jsbytecode *pc2;
493 jsatomid npairs;
495 jmplen = (type == JOF_LOOKUPSWITCH) ? JUMP_OFFSET_LEN
496 : JUMPX_OFFSET_LEN;
497 pc2 = pc;
498 off = GetJumpOffset(pc, pc2);
499 pc2 += jmplen;
500 npairs = GET_UINT16(pc2);
501 pc2 += UINT16_LEN;
502 fprintf(fp, " offset %d npairs %u", (intN) off, (uintN) npairs);
503 while (npairs) {
504 uint16 constIndex = GET_INDEX(pc2);
505 pc2 += INDEX_LEN;
506 off = GetJumpOffset(pc, pc2);
507 pc2 += jmplen;
509 JSAutoByteString bytes;
510 if (!ToDisassemblySource(cx, Jsvalify(script->getConst(constIndex)), &bytes))
511 return 0;
512 fprintf(fp, "\n\t%s: %d", bytes.ptr(), (intN) off);
513 npairs--;
515 len = 1 + pc2 - pc;
516 break;
519 case JOF_QARG:
520 fprintf(fp, " %u", GET_ARGNO(pc));
521 break;
523 case JOF_LOCAL:
524 fprintf(fp, " %u", GET_SLOTNO(pc));
525 break;
527 case JOF_SLOTATOM:
528 case JOF_SLOTOBJECT: {
529 fprintf(fp, " %u", GET_SLOTNO(pc));
530 index = js_GetIndexFromBytecode(cx, script, pc, SLOTNO_LEN);
531 if (type == JOF_SLOTATOM) {
532 JS_GET_SCRIPT_ATOM(script, pc, index, atom);
533 v = ATOM_TO_JSVAL(atom);
534 } else {
535 obj = script->getObject(index);
536 v = OBJECT_TO_JSVAL(obj);
539 JSAutoByteString bytes;
540 if (!ToDisassemblySource(cx, v, &bytes))
541 return 0;
542 fprintf(fp, " %s", bytes.ptr());
543 break;
546 case JOF_UINT24:
547 JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY);
548 i = (jsint)GET_UINT24(pc);
549 goto print_int;
551 case JOF_UINT8:
552 i = pc[1];
553 goto print_int;
555 case JOF_INT8:
556 i = GET_INT8(pc);
557 goto print_int;
559 case JOF_INT32:
560 JS_ASSERT(op == JSOP_INT32);
561 i = GET_INT32(pc);
562 print_int:
563 fprintf(fp, " %d", i);
564 break;
566 default: {
567 char numBuf[12];
568 JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) cs->format);
569 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
570 JSMSG_UNKNOWN_FORMAT, numBuf);
571 return 0;
574 fputs("\n", fp);
575 return len;
578 #endif /* DEBUG */
580 /************************************************************************/
583 * Sprintf, but with unlimited and automatically allocated buffering.
585 typedef struct Sprinter {
586 JSContext *context; /* context executing the decompiler */
587 JSArenaPool *pool; /* string allocation pool */
588 char *base; /* base address of buffer in pool */
589 size_t size; /* size of buffer allocated at base */
590 ptrdiff_t offset; /* offset of next free char in buffer */
591 } Sprinter;
593 #define INIT_SPRINTER(cx, sp, ap, off) \
594 ((sp)->context = cx, (sp)->pool = ap, (sp)->base = NULL, (sp)->size = 0, \
595 (sp)->offset = off)
597 #define OFF2STR(sp,off) ((sp)->base + (off))
598 #define STR2OFF(sp,str) ((str) - (sp)->base)
599 #define RETRACT(sp,str) ((sp)->offset = STR2OFF(sp, str))
601 static JSBool
602 SprintEnsureBuffer(Sprinter *sp, size_t len)
604 ptrdiff_t nb;
605 char *base;
607 nb = (sp->offset + len + 1) - sp->size;
608 if (nb < 0)
609 return JS_TRUE;
610 base = sp->base;
611 if (!base) {
612 JS_ARENA_ALLOCATE_CAST(base, char *, sp->pool, nb);
613 } else {
614 JS_ARENA_GROW_CAST(base, char *, sp->pool, sp->size, nb);
616 if (!base) {
617 js_ReportOutOfScriptQuota(sp->context);
618 return JS_FALSE;
620 sp->base = base;
621 sp->size += nb;
622 return JS_TRUE;
625 static ptrdiff_t
626 SprintPut(Sprinter *sp, const char *s, size_t len)
628 ptrdiff_t offset = sp->size; /* save old size */
629 char *bp = sp->base; /* save old base */
631 /* Allocate space for s, including the '\0' at the end. */
632 if (!SprintEnsureBuffer(sp, len))
633 return -1;
635 if (sp->base != bp && /* buffer was realloc'ed */
636 s >= bp && s < bp + offset) { /* s was within the buffer */
637 s = sp->base + (s - bp); /* this is where it lives now */
640 /* Advance offset and copy s into sp's buffer. */
641 offset = sp->offset;
642 sp->offset += len;
643 bp = sp->base + offset;
644 memmove(bp, s, len);
645 bp[len] = 0;
646 return offset;
649 static ptrdiff_t
650 SprintCString(Sprinter *sp, const char *s)
652 return SprintPut(sp, s, strlen(s));
655 static ptrdiff_t
656 SprintString(Sprinter *sp, JSString *str)
658 size_t length = str->length();
659 const jschar *chars = str->getChars(sp->context);
660 if (!chars)
661 return -1;
663 size_t size = js_GetDeflatedStringLength(sp->context, chars, length);
664 if (size == (size_t)-1 || !SprintEnsureBuffer(sp, size))
665 return -1;
667 ptrdiff_t offset = sp->offset;
668 sp->offset += size;
669 js_DeflateStringToBuffer(sp->context, chars, length, sp->base + offset,
670 &size);
671 sp->base[sp->offset] = 0;
672 return offset;
676 static ptrdiff_t
677 Sprint(Sprinter *sp, const char *format, ...)
679 va_list ap;
680 char *bp;
681 ptrdiff_t offset;
683 va_start(ap, format);
684 bp = JS_vsmprintf(format, ap); /* XXX vsaprintf */
685 va_end(ap);
686 if (!bp) {
687 JS_ReportOutOfMemory(sp->context);
688 return -1;
690 offset = SprintCString(sp, bp);
691 js_free(bp);
692 return offset;
695 const char js_EscapeMap[] = {
696 '\b', 'b',
697 '\f', 'f',
698 '\n', 'n',
699 '\r', 'r',
700 '\t', 't',
701 '\v', 'v',
702 '"', '"',
703 '\'', '\'',
704 '\\', '\\',
705 '\0', '0'
708 #define DONT_ESCAPE 0x10000
710 static char *
711 QuoteString(Sprinter *sp, JSString *str, uint32 quote)
713 /* Sample off first for later return value pointer computation. */
714 JSBool dontEscape = (quote & DONT_ESCAPE) != 0;
715 jschar qc = (jschar) quote;
716 ptrdiff_t off = sp->offset;
717 if (qc && Sprint(sp, "%c", (char)qc) < 0)
718 return NULL;
720 const jschar *s = str->getChars(sp->context);
721 if (!s)
722 return NULL;
723 const jschar *z = s + str->length();
725 /* Loop control variables: z points at end of string sentinel. */
726 for (const jschar *t = s; t < z; s = ++t) {
727 /* Move t forward from s past un-quote-worthy characters. */
728 jschar c = *t;
729 while (JS_ISPRINT(c) && c != qc && c != '\\' && c != '\t' &&
730 !(c >> 8)) {
731 c = *++t;
732 if (t == z)
733 break;
735 ptrdiff_t len = t - s;
737 /* Allocate space for s, including the '\0' at the end. */
738 if (!SprintEnsureBuffer(sp, len))
739 return NULL;
741 /* Advance sp->offset and copy s into sp's buffer. */
742 char *bp = sp->base + sp->offset;
743 sp->offset += len;
744 while (--len >= 0)
745 *bp++ = (char) *s++;
746 *bp = '\0';
748 if (t == z)
749 break;
751 /* Use js_EscapeMap, \u, or \x only if necessary. */
752 bool ok;
753 const char *e;
754 if (!(c >> 8) && (e = strchr(js_EscapeMap, (int)c)) != NULL) {
755 ok = dontEscape
756 ? Sprint(sp, "%c", (char)c) >= 0
757 : Sprint(sp, "\\%c", e[1]) >= 0;
758 } else {
760 * Use \x only if the high byte is 0 and we're in a quoted string,
761 * because ECMA-262 allows only \u, not \x, in Unicode identifiers
762 * (see bug 621814).
764 ok = Sprint(sp, (qc && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c) >= 0;
766 if (!ok)
767 return NULL;
770 /* Sprint the closing quote and return the quoted string. */
771 if (qc && Sprint(sp, "%c", (char)qc) < 0)
772 return NULL;
775 * If we haven't Sprint'd anything yet, Sprint an empty string so that
776 * the OFF2STR below gives a valid result.
778 if (off == sp->offset && Sprint(sp, "") < 0)
779 return NULL;
780 return OFF2STR(sp, off);
783 JSString *
784 js_QuoteString(JSContext *cx, JSString *str, jschar quote)
786 void *mark;
787 Sprinter sprinter;
788 char *bytes;
789 JSString *escstr;
791 mark = JS_ARENA_MARK(&cx->tempPool);
792 INIT_SPRINTER(cx, &sprinter, &cx->tempPool, 0);
793 bytes = QuoteString(&sprinter, str, quote);
794 escstr = bytes ? JS_NewStringCopyZ(cx, bytes) : NULL;
795 JS_ARENA_RELEASE(&cx->tempPool, mark);
796 return escstr;
799 /************************************************************************/
801 struct JSPrinter {
802 Sprinter sprinter; /* base class state */
803 JSArenaPool pool; /* string allocation pool */
804 uintN indent; /* indentation in spaces */
805 bool pretty; /* pretty-print: indent, use newlines */
806 bool grouped; /* in parenthesized expression context */
807 bool strict; /* in code marked strict */
808 JSScript *script; /* script being printed */
809 jsbytecode *dvgfence; /* DecompileExpression fencepost */
810 jsbytecode **pcstack; /* DecompileExpression modeled stack */
811 JSFunction *fun; /* interpreted function */
812 jsuword *localNames; /* argument and variable names */
815 JSPrinter *
816 js_NewPrinter(JSContext *cx, const char *name, JSFunction *fun,
817 uintN indent, JSBool pretty, JSBool grouped, JSBool strict)
819 JSPrinter *jp;
821 jp = (JSPrinter *) cx->malloc(sizeof(JSPrinter));
822 if (!jp)
823 return NULL;
824 INIT_SPRINTER(cx, &jp->sprinter, &jp->pool, 0);
825 JS_InitArenaPool(&jp->pool, name, 256, 1, &cx->scriptStackQuota);
826 jp->indent = indent;
827 jp->pretty = !!pretty;
828 jp->grouped = !!grouped;
829 jp->strict = !!strict;
830 jp->script = NULL;
831 jp->dvgfence = NULL;
832 jp->pcstack = NULL;
833 jp->fun = fun;
834 jp->localNames = NULL;
835 if (fun && fun->isInterpreted() && fun->script()->bindings.hasLocalNames()) {
836 jp->localNames = fun->script()->bindings.getLocalNameArray(cx, &jp->pool);
837 if (!jp->localNames) {
838 js_DestroyPrinter(jp);
839 return NULL;
842 return jp;
845 void
846 js_DestroyPrinter(JSPrinter *jp)
848 JS_FinishArenaPool(&jp->pool);
849 jp->sprinter.context->free(jp);
852 JSString *
853 js_GetPrinterOutput(JSPrinter *jp)
855 JSContext *cx;
856 JSString *str;
858 cx = jp->sprinter.context;
859 if (!jp->sprinter.base)
860 return cx->runtime->emptyString;
861 str = JS_NewStringCopyZ(cx, jp->sprinter.base);
862 if (!str)
863 return NULL;
864 JS_FreeArenaPool(&jp->pool);
865 INIT_SPRINTER(cx, &jp->sprinter, &jp->pool, 0);
866 return str;
870 * NB: Indexed by SRC_DECL_* defines from jsemit.h.
872 static const char * const var_prefix[] = {"var ", "const ", "let "};
874 static const char *
875 VarPrefix(jssrcnote *sn)
877 if (sn && (SN_TYPE(sn) == SRC_DECL || SN_TYPE(sn) == SRC_GROUPASSIGN)) {
878 ptrdiff_t type = js_GetSrcNoteOffset(sn, 0);
879 if ((uintN)type <= SRC_DECL_LET)
880 return var_prefix[type];
882 return "";
886 js_printf(JSPrinter *jp, const char *format, ...)
888 va_list ap;
889 char *bp, *fp;
890 int cc;
892 if (*format == '\0')
893 return 0;
895 va_start(ap, format);
897 /* If pretty-printing, expand magic tab into a run of jp->indent spaces. */
898 if (*format == '\t') {
899 format++;
900 if (jp->pretty && Sprint(&jp->sprinter, "%*s", jp->indent, "") < 0) {
901 va_end(ap);
902 return -1;
906 /* Suppress newlines (must be once per format, at the end) if not pretty. */
907 fp = NULL;
908 if (!jp->pretty && format[cc = strlen(format) - 1] == '\n') {
909 fp = JS_strdup(jp->sprinter.context, format);
910 if (!fp) {
911 va_end(ap);
912 return -1;
914 fp[cc] = '\0';
915 format = fp;
918 /* Allocate temp space, convert format, and put. */
919 bp = JS_vsmprintf(format, ap); /* XXX vsaprintf */
920 if (fp) {
921 jp->sprinter.context->free(fp);
922 format = NULL;
924 if (!bp) {
925 JS_ReportOutOfMemory(jp->sprinter.context);
926 va_end(ap);
927 return -1;
930 cc = strlen(bp);
931 if (SprintPut(&jp->sprinter, bp, (size_t)cc) < 0)
932 cc = -1;
933 js_free(bp);
935 va_end(ap);
936 return cc;
939 JSBool
940 js_puts(JSPrinter *jp, const char *s)
942 return SprintCString(&jp->sprinter, s) >= 0;
945 /************************************************************************/
947 typedef struct SprintStack {
948 Sprinter sprinter; /* sprinter for postfix to infix buffering */
949 ptrdiff_t *offsets; /* stack of postfix string offsets */
950 jsbytecode *opcodes; /* parallel stack of JS opcodes */
951 uintN top; /* top of stack index */
952 uintN inArrayInit; /* array initialiser/comprehension level */
953 JSBool inGenExp; /* in generator expression */
954 JSPrinter *printer; /* permanent output goes here */
955 } SprintStack;
958 * Find the depth of the operand stack when the interpreter reaches the given
959 * pc in script. pcstack must have space for least script->depth elements. On
960 * return it will contain pointers to opcodes that populated the interpreter's
961 * current operand stack.
963 * This function cannot raise an exception or error. However, due to a risk of
964 * potential bugs when modeling the stack, the function returns -1 if it
965 * detects an inconsistency in the model. Such an inconsistency triggers an
966 * assert in a debug build.
968 static intN
969 ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc,
970 jsbytecode **pcstack);
972 #define FAILED_EXPRESSION_DECOMPILER ((char *) 1)
975 * Decompile a part of expression up to the given pc. The function returns
976 * NULL on out-of-memory, or the FAILED_EXPRESSION_DECOMPILER sentinel when
977 * the decompiler fails due to a bug and/or unimplemented feature, or the
978 * decompiled string on success.
980 static char *
981 DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun,
982 jsbytecode *pc);
985 * Get a stacked offset from ss->sprinter.base, or if the stacked value |off|
986 * is negative, fetch the generating pc from printer->pcstack[-2 - off] and
987 * decompile the code that generated the missing value. This is used when
988 * reporting errors, where the model stack will lack |pcdepth| non-negative
989 * offsets (see DecompileExpression and DecompileCode).
991 * If the stacked offset is -1, return 0 to index the NUL padding at the start
992 * of ss->sprinter.base. If this happens, it means there is a decompiler bug
993 * to fix, but it won't violate memory safety.
995 static ptrdiff_t
996 GetOff(SprintStack *ss, uintN i)
998 ptrdiff_t off;
999 jsbytecode *pc;
1000 char *bytes;
1002 off = ss->offsets[i];
1003 if (off >= 0)
1004 return off;
1006 JS_ASSERT(off <= -2);
1007 JS_ASSERT(ss->printer->pcstack);
1008 if (off <= -2 && ss->printer->pcstack) {
1009 pc = ss->printer->pcstack[-2 - off];
1010 bytes = DecompileExpression(ss->sprinter.context, ss->printer->script,
1011 ss->printer->fun, pc);
1012 if (!bytes)
1013 return 0;
1014 if (bytes != FAILED_EXPRESSION_DECOMPILER) {
1015 off = SprintCString(&ss->sprinter, bytes);
1016 if (off < 0)
1017 off = 0;
1018 ss->offsets[i] = off;
1019 ss->sprinter.context->free(bytes);
1020 return off;
1022 if (!ss->sprinter.base && SprintPut(&ss->sprinter, "", 0) >= 0) {
1023 memset(ss->sprinter.base, 0, ss->sprinter.offset);
1024 ss->offsets[i] = -1;
1027 return 0;
1030 static const char *
1031 GetStr(SprintStack *ss, uintN i)
1033 ptrdiff_t off;
1036 * Must call GetOff before using ss->sprinter.base, since it may be null
1037 * until bootstrapped by GetOff.
1039 off = GetOff(ss, i);
1040 return OFF2STR(&ss->sprinter, off);
1044 * Gap between stacked strings to allow for insertion of parens and commas
1045 * when auto-parenthesizing expressions and decompiling array initialisers.
1047 #define PAREN_SLOP (2 + 1)
1049 /* Fake opcodes (see jsopcode.h) must not overflow unsigned 8-bit space. */
1050 JS_STATIC_ASSERT(JSOP_FAKE_LIMIT <= 255);
1052 static void
1053 AddParenSlop(SprintStack *ss)
1055 memset(OFF2STR(&ss->sprinter, ss->sprinter.offset), 0, PAREN_SLOP);
1056 ss->sprinter.offset += PAREN_SLOP;
1059 static JSBool
1060 PushOff(SprintStack *ss, ptrdiff_t off, JSOp op)
1062 uintN top;
1064 if (!SprintEnsureBuffer(&ss->sprinter, PAREN_SLOP))
1065 return JS_FALSE;
1067 /* ss->top points to the next free slot; be paranoid about overflow. */
1068 top = ss->top;
1069 JS_ASSERT(top < StackDepth(ss->printer->script));
1070 if (top >= StackDepth(ss->printer->script)) {
1071 JS_ReportOutOfMemory(ss->sprinter.context);
1072 return JS_FALSE;
1075 /* The opcodes stack must contain real bytecodes that index js_CodeSpec. */
1076 ss->offsets[top] = off;
1077 ss->opcodes[top] = jsbytecode((op == JSOP_GETPROP2) ? JSOP_GETPROP
1078 : (op == JSOP_GETELEM2) ? JSOP_GETELEM
1079 : op);
1080 ss->top = ++top;
1081 AddParenSlop(ss);
1082 return JS_TRUE;
1085 static ptrdiff_t
1086 PopOffPrec(SprintStack *ss, uint8 prec)
1088 uintN top;
1089 const JSCodeSpec *topcs;
1090 ptrdiff_t off;
1092 /* ss->top points to the next free slot; be paranoid about underflow. */
1093 top = ss->top;
1094 JS_ASSERT(top != 0);
1095 if (top == 0)
1096 return 0;
1098 ss->top = --top;
1099 off = GetOff(ss, top);
1100 topcs = &js_CodeSpec[ss->opcodes[top]];
1101 if (topcs->prec != 0 && topcs->prec < prec) {
1102 ss->sprinter.offset = ss->offsets[top] = off - 2;
1103 off = Sprint(&ss->sprinter, "(%s)", OFF2STR(&ss->sprinter, off));
1104 } else {
1105 ss->sprinter.offset = off;
1107 return off;
1110 static const char *
1111 PopStrPrec(SprintStack *ss, uint8 prec)
1113 ptrdiff_t off;
1115 off = PopOffPrec(ss, prec);
1116 return OFF2STR(&ss->sprinter, off);
1119 static ptrdiff_t
1120 PopOff(SprintStack *ss, JSOp op)
1122 return PopOffPrec(ss, js_CodeSpec[op].prec);
1125 static const char *
1126 PopStr(SprintStack *ss, JSOp op)
1128 return PopStrPrec(ss, js_CodeSpec[op].prec);
1131 static inline bool
1132 IsInitializerOp(unsigned char op)
1134 return op == JSOP_NEWINIT || op == JSOP_NEWARRAY || op == JSOP_NEWOBJECT;
1137 typedef struct TableEntry {
1138 jsval key;
1139 ptrdiff_t offset;
1140 JSAtom *label;
1141 jsint order; /* source order for stable tableswitch sort */
1142 } TableEntry;
1144 static JSBool
1145 CompareOffsets(void *arg, const void *v1, const void *v2, int *result)
1147 ptrdiff_t offset_diff;
1148 const TableEntry *te1 = (const TableEntry *) v1,
1149 *te2 = (const TableEntry *) v2;
1151 offset_diff = te1->offset - te2->offset;
1152 *result = (offset_diff == 0 ? te1->order - te2->order
1153 : offset_diff < 0 ? -1
1154 : 1);
1155 return JS_TRUE;
1158 static ptrdiff_t
1159 SprintDoubleValue(Sprinter *sp, jsval v, JSOp *opp)
1161 jsdouble d;
1162 ptrdiff_t todo;
1163 char *s;
1165 JS_ASSERT(JSVAL_IS_DOUBLE(v));
1166 d = JSVAL_TO_DOUBLE(v);
1167 if (JSDOUBLE_IS_NEGZERO(d)) {
1168 todo = SprintCString(sp, "-0");
1169 *opp = JSOP_NEG;
1170 } else if (!JSDOUBLE_IS_FINITE(d)) {
1171 /* Don't use Infinity and NaN, they're mutable. */
1172 todo = SprintCString(sp,
1173 JSDOUBLE_IS_NaN(d)
1174 ? "0 / 0"
1175 : (d < 0)
1176 ? "1 / -0"
1177 : "1 / 0");
1178 *opp = JSOP_DIV;
1179 } else {
1180 ToCStringBuf cbuf;
1181 s = NumberToCString(sp->context, &cbuf, d);
1182 if (!s) {
1183 JS_ReportOutOfMemory(sp->context);
1184 return -1;
1186 JS_ASSERT(strcmp(s, js_Infinity_str) &&
1187 (*s != '-' ||
1188 strcmp(s + 1, js_Infinity_str)) &&
1189 strcmp(s, js_NaN_str));
1190 todo = Sprint(sp, s);
1192 return todo;
1195 static jsbytecode *
1196 Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop);
1198 static JSBool
1199 DecompileSwitch(SprintStack *ss, TableEntry *table, uintN tableLength,
1200 jsbytecode *pc, ptrdiff_t switchLength,
1201 ptrdiff_t defaultOffset, JSBool isCondSwitch)
1203 JSContext *cx;
1204 JSPrinter *jp;
1205 ptrdiff_t off, off2, diff, caseExprOff, todo;
1206 char *lval, *rval;
1207 uintN i;
1208 jsval key;
1209 JSString *str;
1211 cx = ss->sprinter.context;
1212 jp = ss->printer;
1214 /* JSOP_CONDSWITCH doesn't pop, unlike JSOP_{LOOKUP,TABLE}SWITCH. */
1215 off = isCondSwitch ? GetOff(ss, ss->top-1) : PopOff(ss, JSOP_NOP);
1216 lval = OFF2STR(&ss->sprinter, off);
1218 js_printf(jp, "\tswitch (%s) {\n", lval);
1220 if (tableLength) {
1221 diff = table[0].offset - defaultOffset;
1222 if (diff > 0) {
1223 jp->indent += 2;
1224 js_printf(jp, "\t%s:\n", js_default_str);
1225 jp->indent += 2;
1226 if (!Decompile(ss, pc + defaultOffset, diff, JSOP_NOP))
1227 return JS_FALSE;
1228 jp->indent -= 4;
1231 caseExprOff = isCondSwitch ? JSOP_CONDSWITCH_LENGTH : 0;
1233 for (i = 0; i < tableLength; i++) {
1234 off = table[i].offset;
1235 off2 = (i + 1 < tableLength) ? table[i + 1].offset : switchLength;
1237 key = table[i].key;
1238 if (isCondSwitch) {
1239 ptrdiff_t nextCaseExprOff;
1242 * key encodes the JSOP_CASE bytecode's offset from switchtop.
1243 * The next case expression follows immediately, unless we are
1244 * at the last case.
1246 nextCaseExprOff = (ptrdiff_t)JSVAL_TO_INT(key);
1247 nextCaseExprOff += js_CodeSpec[pc[nextCaseExprOff]].length;
1248 jp->indent += 2;
1249 if (!Decompile(ss, pc + caseExprOff,
1250 nextCaseExprOff - caseExprOff, JSOP_NOP)) {
1251 return JS_FALSE;
1253 caseExprOff = nextCaseExprOff;
1255 /* Balance the stack as if this JSOP_CASE matched. */
1256 --ss->top;
1257 } else {
1259 * key comes from an atom, not the decompiler, so we need to
1260 * quote it if it's a string literal. But if table[i].label
1261 * is non-null, key was constant-propagated and label is the
1262 * name of the const we should show as the case label. We set
1263 * key to undefined so this identifier is escaped, if required
1264 * by non-ASCII characters, but not quoted, by QuoteString.
1266 todo = -1;
1267 if (table[i].label) {
1268 str = ATOM_TO_STRING(table[i].label);
1269 key = JSVAL_VOID;
1270 } else if (JSVAL_IS_DOUBLE(key)) {
1271 JSOp junk;
1273 todo = SprintDoubleValue(&ss->sprinter, key, &junk);
1274 str = NULL;
1275 } else {
1276 str = js_ValueToString(cx, Valueify(key));
1277 if (!str)
1278 return JS_FALSE;
1280 if (todo >= 0) {
1281 rval = OFF2STR(&ss->sprinter, todo);
1282 } else {
1283 rval = QuoteString(&ss->sprinter, str, (jschar)
1284 (JSVAL_IS_STRING(key) ? '"' : 0));
1285 if (!rval)
1286 return JS_FALSE;
1288 RETRACT(&ss->sprinter, rval);
1289 jp->indent += 2;
1290 js_printf(jp, "\tcase %s:\n", rval);
1293 jp->indent += 2;
1294 if (off <= defaultOffset && defaultOffset < off2) {
1295 diff = defaultOffset - off;
1296 if (diff != 0) {
1297 if (!Decompile(ss, pc + off, diff, JSOP_NOP))
1298 return JS_FALSE;
1299 off = defaultOffset;
1301 jp->indent -= 2;
1302 js_printf(jp, "\t%s:\n", js_default_str);
1303 jp->indent += 2;
1305 if (!Decompile(ss, pc + off, off2 - off, JSOP_NOP))
1306 return JS_FALSE;
1307 jp->indent -= 4;
1309 /* Re-balance as if last JSOP_CASE or JSOP_DEFAULT mismatched. */
1310 if (isCondSwitch)
1311 ++ss->top;
1315 if (defaultOffset == switchLength) {
1316 jp->indent += 2;
1317 js_printf(jp, "\t%s:;\n", js_default_str);
1318 jp->indent -= 2;
1320 js_printf(jp, "\t}\n");
1322 /* By the end of a JSOP_CONDSWITCH, the discriminant has been popped. */
1323 if (isCondSwitch)
1324 --ss->top;
1325 return JS_TRUE;
1328 #define LOCAL_ASSERT_CUSTOM(expr, BAD_EXIT) \
1329 JS_BEGIN_MACRO \
1330 JS_ASSERT(expr); \
1331 if (!(expr)) { BAD_EXIT; } \
1332 JS_END_MACRO
1334 #define LOCAL_ASSERT_RV(expr, rv) \
1335 LOCAL_ASSERT_CUSTOM(expr, return (rv))
1337 static JSAtom *
1338 GetArgOrVarAtom(JSPrinter *jp, uintN slot)
1340 JSAtom *name;
1342 LOCAL_ASSERT_RV(jp->fun, NULL);
1343 LOCAL_ASSERT_RV(slot < jp->fun->script()->bindings.countLocalNames(), NULL);
1344 name = JS_LOCAL_NAME_TO_ATOM(jp->localNames[slot]);
1345 #if !JS_HAS_DESTRUCTURING
1346 LOCAL_ASSERT_RV(name, NULL);
1347 #endif
1348 return name;
1351 const char *
1352 GetLocal(SprintStack *ss, jsint i)
1354 #define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, "")
1356 ptrdiff_t off = ss->offsets[i];
1357 if (off >= 0)
1358 return OFF2STR(&ss->sprinter, off);
1361 * We must be called from js_DecompileValueGenerator (via Decompile) when
1362 * dereferencing a local that's undefined or null. Search script->objects
1363 * for the block containing this local by its stack index, i.
1365 * In case of destructuring's use of JSOP_GETLOCAL, however, there may be
1366 * no such local. This could mean no blocks (no script objects at all, or
1367 * none of the script's object literals are blocks), or the stack slot i is
1368 * not in a block. In either case, return GetStr(ss, i).
1370 JSScript *script = ss->printer->script;
1371 if (!JSScript::isValidOffset(script->objectsOffset))
1372 return GetStr(ss, i);
1374 for (jsatomid j = 0, n = script->objects()->length; j != n; j++) {
1375 JSObject *obj = script->getObject(j);
1376 if (obj->isBlock()) {
1377 jsint depth = OBJ_BLOCK_DEPTH(cx, obj);
1378 jsint count = OBJ_BLOCK_COUNT(cx, obj);
1380 if (jsuint(i - depth) < jsuint(count)) {
1381 jsint slot = i - depth;
1383 for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) {
1384 const Shape &shape = r.front();
1386 if (shape.shortid == slot) {
1387 LOCAL_ASSERT(JSID_IS_ATOM(shape.id));
1389 JSAtom *atom = JSID_TO_ATOM(shape.id);
1390 const char *rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
1391 if (!rval)
1392 return NULL;
1394 RETRACT(&ss->sprinter, rval);
1395 return rval;
1399 break;
1404 return GetStr(ss, i);
1406 #undef LOCAL_ASSERT
1409 static JSBool
1410 IsVarSlot(JSPrinter *jp, jsbytecode *pc, jsint *indexp)
1412 uintN slot;
1414 slot = GET_SLOTNO(pc);
1415 if (slot < jp->script->nfixed) {
1416 /* The slot refers to a variable with name stored in jp->localNames. */
1417 *indexp = jp->fun->nargs + slot;
1418 return JS_TRUE;
1421 /* We have a local which index is relative to the stack base. */
1422 slot -= jp->script->nfixed;
1423 JS_ASSERT(slot < StackDepth(jp->script));
1424 *indexp = slot;
1425 return JS_FALSE;
1428 #define LOAD_ATOM(PCOFF) \
1429 GET_ATOM_FROM_BYTECODE(jp->script, pc, PCOFF, atom)
1431 #if JS_HAS_DESTRUCTURING
1433 #define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, NULL)
1434 #define LOAD_OP_DATA(pc) (oplen = (cs = &js_CodeSpec[op=(JSOp)*pc])->length)
1436 static jsbytecode *
1437 DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc);
1439 static jsbytecode *
1440 DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
1441 JSBool *hole)
1443 JSContext *cx;
1444 JSPrinter *jp;
1445 JSOp op;
1446 const JSCodeSpec *cs;
1447 uintN oplen;
1448 jsint i;
1449 const char *lval, *xval;
1450 ptrdiff_t todo;
1451 JSAtom *atom;
1453 *hole = JS_FALSE;
1454 cx = ss->sprinter.context;
1455 jp = ss->printer;
1456 LOAD_OP_DATA(pc);
1458 switch (op) {
1459 case JSOP_POP:
1460 *hole = JS_TRUE;
1461 todo = SprintPut(&ss->sprinter, ", ", 2);
1462 break;
1464 case JSOP_DUP:
1465 pc = DecompileDestructuring(ss, pc, endpc);
1466 if (!pc)
1467 return NULL;
1468 if (pc == endpc)
1469 return pc;
1470 LOAD_OP_DATA(pc);
1471 lval = PopStr(ss, JSOP_NOP);
1472 todo = SprintCString(&ss->sprinter, lval);
1473 if (op == JSOP_POPN)
1474 return pc;
1475 LOCAL_ASSERT(*pc == JSOP_POP);
1476 break;
1478 case JSOP_SETARG:
1479 case JSOP_SETLOCAL:
1480 LOCAL_ASSERT(pc[oplen] == JSOP_POP || pc[oplen] == JSOP_POPN);
1481 /* FALL THROUGH */
1483 case JSOP_SETLOCALPOP:
1484 atom = NULL;
1485 lval = NULL;
1486 if (op == JSOP_SETARG) {
1487 atom = GetArgOrVarAtom(jp, GET_SLOTNO(pc));
1488 LOCAL_ASSERT(atom);
1489 } else if (IsVarSlot(jp, pc, &i)) {
1490 atom = GetArgOrVarAtom(jp, i);
1491 LOCAL_ASSERT(atom);
1492 } else {
1493 lval = GetLocal(ss, i);
1496 JSAutoByteString bytes;
1497 if (atom)
1498 lval = js_AtomToPrintableString(cx, atom, &bytes);
1499 LOCAL_ASSERT(lval);
1500 todo = SprintCString(&ss->sprinter, lval);
1502 if (op != JSOP_SETLOCALPOP) {
1503 pc += oplen;
1504 if (pc == endpc)
1505 return pc;
1506 LOAD_OP_DATA(pc);
1507 if (op == JSOP_POPN)
1508 return pc;
1509 LOCAL_ASSERT(op == JSOP_POP);
1511 break;
1513 default:
1515 * We may need to auto-parenthesize the left-most value decompiled
1516 * here, so add back PAREN_SLOP temporarily. Then decompile until the
1517 * opcode that would reduce the stack depth to (ss->top-1), which we
1518 * pass to Decompile encoded as -(ss->top-1) - 1 or just -ss->top for
1519 * the nb parameter.
1521 todo = ss->sprinter.offset;
1522 ss->sprinter.offset = todo + PAREN_SLOP;
1523 pc = Decompile(ss, pc, -((intN)ss->top), JSOP_NOP);
1524 if (!pc)
1525 return NULL;
1526 if (pc == endpc)
1527 return pc;
1528 LOAD_OP_DATA(pc);
1529 LOCAL_ASSERT(op == JSOP_ENUMELEM || op == JSOP_ENUMCONSTELEM);
1530 xval = PopStr(ss, JSOP_NOP);
1531 lval = PopStr(ss, JSOP_GETPROP);
1532 ss->sprinter.offset = todo;
1533 if (*lval == '\0') {
1534 /* lval is from JSOP_BINDNAME, so just print xval. */
1535 todo = SprintCString(&ss->sprinter, xval);
1536 } else if (*xval == '\0') {
1537 /* xval is from JSOP_SETCALL or JSOP_BINDXMLNAME, print lval. */
1538 todo = SprintCString(&ss->sprinter, lval);
1539 } else {
1540 todo = Sprint(&ss->sprinter,
1541 (JOF_OPMODE(ss->opcodes[ss->top+1]) == JOF_XMLNAME)
1542 ? "%s.%s"
1543 : "%s[%s]",
1544 lval, xval);
1546 break;
1549 if (todo < 0)
1550 return NULL;
1552 LOCAL_ASSERT(pc < endpc);
1553 pc += oplen;
1554 return pc;
1558 * Starting with a SRC_DESTRUCT-annotated JSOP_DUP, decompile a destructuring
1559 * left-hand side object or array initialiser, including nested destructuring
1560 * initialisers. On successful return, the decompilation will be pushed on ss
1561 * and the return value will point to the POP or GROUP bytecode following the
1562 * destructuring expression.
1564 * At any point, if pc is equal to endpc and would otherwise advance, we stop
1565 * immediately and return endpc.
1567 static jsbytecode *
1568 DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc)
1570 ptrdiff_t head;
1571 JSContext *cx;
1572 JSPrinter *jp;
1573 JSOp op, saveop;
1574 const JSCodeSpec *cs;
1575 uintN oplen;
1576 jsint i, lasti;
1577 jsdouble d;
1578 const char *lval;
1579 JSAtom *atom;
1580 jssrcnote *sn;
1581 JSBool hole;
1583 LOCAL_ASSERT(*pc == JSOP_DUP);
1584 pc += JSOP_DUP_LENGTH;
1587 * Set head so we can rewrite '[' to '{' as needed. Back up PAREN_SLOP
1588 * chars so the destructuring decompilation accumulates contiguously in
1589 * ss->sprinter starting with "[".
1591 head = SprintPut(&ss->sprinter, "[", 1);
1592 if (head < 0 || !PushOff(ss, head, JSOP_NOP))
1593 return NULL;
1594 ss->sprinter.offset -= PAREN_SLOP;
1595 LOCAL_ASSERT(head == ss->sprinter.offset - 1);
1596 LOCAL_ASSERT(*OFF2STR(&ss->sprinter, head) == '[');
1598 cx = ss->sprinter.context;
1599 jp = ss->printer;
1600 lasti = -1;
1602 while (pc < endpc) {
1603 #if JS_HAS_DESTRUCTURING_SHORTHAND
1604 ptrdiff_t nameoff = -1;
1605 #endif
1607 LOAD_OP_DATA(pc);
1608 saveop = op;
1610 switch (op) {
1611 case JSOP_POP:
1612 pc += oplen;
1613 goto out;
1615 /* Handle the optimized number-pushing opcodes. */
1616 case JSOP_ZERO: d = i = 0; goto do_getelem;
1617 case JSOP_ONE: d = i = 1; goto do_getelem;
1618 case JSOP_UINT16: d = i = GET_UINT16(pc); goto do_getelem;
1619 case JSOP_UINT24: d = i = GET_UINT24(pc); goto do_getelem;
1620 case JSOP_INT8: d = i = GET_INT8(pc); goto do_getelem;
1621 case JSOP_INT32: d = i = GET_INT32(pc); goto do_getelem;
1623 case JSOP_DOUBLE:
1624 GET_DOUBLE_FROM_BYTECODE(jp->script, pc, 0, d);
1625 LOCAL_ASSERT(JSDOUBLE_IS_FINITE(d) && !JSDOUBLE_IS_NEGZERO(d));
1626 i = (jsint)d;
1628 do_getelem:
1629 sn = js_GetSrcNote(jp->script, pc);
1630 pc += oplen;
1631 if (pc == endpc)
1632 return pc;
1633 LOAD_OP_DATA(pc);
1634 LOCAL_ASSERT(op == JSOP_GETELEM);
1636 /* Distinguish object from array by opcode or source note. */
1637 if (sn && SN_TYPE(sn) == SRC_INITPROP) {
1638 *OFF2STR(&ss->sprinter, head) = '{';
1639 if (Sprint(&ss->sprinter, "%g: ", d) < 0)
1640 return NULL;
1641 } else {
1642 /* Sanity check for the gnarly control flow above. */
1643 LOCAL_ASSERT(i == d);
1645 /* Fill in any holes (holes at the end don't matter). */
1646 while (++lasti < i) {
1647 if (SprintPut(&ss->sprinter, ", ", 2) < 0)
1648 return NULL;
1651 break;
1653 case JSOP_LENGTH:
1654 atom = cx->runtime->atomState.lengthAtom;
1655 goto do_destructure_atom;
1657 case JSOP_CALLPROP:
1658 case JSOP_GETPROP:
1659 LOAD_ATOM(0);
1660 do_destructure_atom:
1662 *OFF2STR(&ss->sprinter, head) = '{';
1663 #if JS_HAS_DESTRUCTURING_SHORTHAND
1664 nameoff = ss->sprinter.offset;
1665 #endif
1666 if (!QuoteString(&ss->sprinter, atom,
1667 js_IsIdentifier(atom) ? 0 : (jschar)'\'')) {
1668 return NULL;
1670 if (SprintPut(&ss->sprinter, ": ", 2) < 0)
1671 return NULL;
1672 break;
1675 default:
1676 LOCAL_ASSERT(0);
1679 pc += oplen;
1680 if (pc == endpc)
1681 return pc;
1684 * Decompile the left-hand side expression whose bytecode starts at pc
1685 * and continues for a bounded number of bytecodes or stack operations
1686 * (and which in any event stops before endpc).
1688 pc = DecompileDestructuringLHS(ss, pc, endpc, &hole);
1689 if (!pc)
1690 return NULL;
1692 #if JS_HAS_DESTRUCTURING_SHORTHAND
1693 if (nameoff >= 0) {
1694 ptrdiff_t offset, initlen;
1696 offset = ss->sprinter.offset;
1697 LOCAL_ASSERT(*OFF2STR(&ss->sprinter, offset) == '\0');
1698 initlen = offset - nameoff;
1699 LOCAL_ASSERT(initlen >= 4);
1701 /* Early check to rule out odd "name: lval" length. */
1702 if (((size_t)initlen & 1) == 0) {
1703 size_t namelen;
1704 const char *name;
1707 * Even "name: lval" string length: check for "x: x" and the
1708 * like, and apply the shorthand if we can.
1710 namelen = (size_t)(initlen - 2) >> 1;
1711 name = OFF2STR(&ss->sprinter, nameoff);
1712 if (!strncmp(name + namelen, ": ", 2) &&
1713 !strncmp(name, name + namelen + 2, namelen)) {
1714 offset -= namelen + 2;
1715 *OFF2STR(&ss->sprinter, offset) = '\0';
1716 ss->sprinter.offset = offset;
1720 #endif
1722 if (pc == endpc || *pc != JSOP_DUP)
1723 break;
1726 * We should stop if JSOP_DUP is either without notes or its note is
1727 * not SRC_CONTINUE. The former happens when JSOP_DUP duplicates the
1728 * last destructuring reference implementing an op= assignment like in
1729 * '([t] = z).y += x'. In the latter case the note is SRC_DESTRUCT and
1730 * means another destructuring initialiser abuts this one like in
1731 * '[a] = [b] = c'.
1733 sn = js_GetSrcNote(jp->script, pc);
1734 if (!sn)
1735 break;
1736 if (SN_TYPE(sn) != SRC_CONTINUE) {
1737 LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCT);
1738 break;
1741 if (!hole && SprintPut(&ss->sprinter, ", ", 2) < 0)
1742 return NULL;
1744 pc += JSOP_DUP_LENGTH;
1747 out:
1748 lval = OFF2STR(&ss->sprinter, head);
1749 if (SprintPut(&ss->sprinter, (*lval == '[') ? "]" : "}", 1) < 0)
1750 return NULL;
1751 return pc;
1754 static jsbytecode *
1755 DecompileGroupAssignment(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
1756 jssrcnote *sn, ptrdiff_t *todop)
1758 JSOp op;
1759 const JSCodeSpec *cs;
1760 uintN oplen, start, end, i;
1761 ptrdiff_t todo;
1762 JSBool hole;
1763 const char *rval;
1765 LOAD_OP_DATA(pc);
1766 LOCAL_ASSERT(op == JSOP_PUSH || op == JSOP_GETLOCAL);
1768 todo = Sprint(&ss->sprinter, "%s[", VarPrefix(sn));
1769 if (todo < 0 || !PushOff(ss, todo, JSOP_NOP))
1770 return NULL;
1771 ss->sprinter.offset -= PAREN_SLOP;
1773 for (;;) {
1774 pc += oplen;
1775 if (pc == endpc)
1776 return pc;
1777 pc = DecompileDestructuringLHS(ss, pc, endpc, &hole);
1778 if (!pc)
1779 return NULL;
1780 if (pc == endpc)
1781 return pc;
1782 LOAD_OP_DATA(pc);
1783 if (op != JSOP_PUSH && op != JSOP_GETLOCAL)
1784 break;
1785 if (!hole && SprintPut(&ss->sprinter, ", ", 2) < 0)
1786 return NULL;
1789 LOCAL_ASSERT(op == JSOP_POPN);
1790 if (SprintPut(&ss->sprinter, "] = [", 5) < 0)
1791 return NULL;
1793 end = ss->top - 1;
1794 start = end - GET_UINT16(pc);
1795 for (i = start; i < end; i++) {
1796 rval = GetStr(ss, i);
1797 if (Sprint(&ss->sprinter,
1798 (i == start) ? "%s" : ", %s",
1799 (i == end - 1 && *rval == '\0') ? ", " : rval) < 0) {
1800 return NULL;
1804 if (SprintPut(&ss->sprinter, "]", 1) < 0)
1805 return NULL;
1806 ss->sprinter.offset = ss->offsets[i];
1807 ss->top = start;
1808 *todop = todo;
1809 return pc;
1812 #undef LOCAL_ASSERT
1813 #undef LOAD_OP_DATA
1815 #endif /* JS_HAS_DESTRUCTURING */
1817 static JSBool
1818 InitSprintStack(JSContext *cx, SprintStack *ss, JSPrinter *jp, uintN depth)
1820 size_t offsetsz, opcodesz;
1821 void *space;
1823 INIT_SPRINTER(cx, &ss->sprinter, &cx->tempPool, PAREN_SLOP);
1825 /* Allocate the parallel (to avoid padding) offset and opcode stacks. */
1826 offsetsz = depth * sizeof(ptrdiff_t);
1827 opcodesz = depth * sizeof(jsbytecode);
1828 JS_ARENA_ALLOCATE(space, &cx->tempPool, offsetsz + opcodesz);
1829 if (!space) {
1830 js_ReportOutOfScriptQuota(cx);
1831 return JS_FALSE;
1833 ss->offsets = (ptrdiff_t *) space;
1834 ss->opcodes = (jsbytecode *) ((char *)space + offsetsz);
1836 ss->top = ss->inArrayInit = 0;
1837 ss->inGenExp = JS_FALSE;
1838 ss->printer = jp;
1839 return JS_TRUE;
1843 * If nb is non-negative, decompile nb bytecodes starting at pc. Otherwise
1844 * the decompiler starts at pc and continues until it reaches an opcode for
1845 * which decompiling would result in the stack depth equaling -(nb + 1).
1847 * The nextop parameter is either JSOP_NOP or the "next" opcode in order of
1848 * abstract interpretation (not necessarily physically next in a bytecode
1849 * vector). So nextop is JSOP_POP for the last operand in a comma expression,
1850 * or JSOP_AND for the right operand of &&.
1852 static jsbytecode *
1853 Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
1855 JSContext *cx;
1856 JSPrinter *jp, *jp2;
1857 jsbytecode *startpc, *endpc, *pc2, *done;
1858 ptrdiff_t tail, todo, len, oplen, cond, next;
1859 JSOp op, lastop, saveop;
1860 const JSCodeSpec *cs;
1861 jssrcnote *sn, *sn2;
1862 const char *lval, *rval, *xval, *fmt, *token;
1863 uintN nuses;
1864 jsint i, argc;
1865 char **argv;
1866 JSAtom *atom;
1867 JSObject *obj;
1868 JSFunction *fun;
1869 JSString *str;
1870 JSBool ok;
1871 #if JS_HAS_XML_SUPPORT
1872 JSBool foreach, inXML, quoteAttr;
1873 #else
1874 #define inXML JS_FALSE
1875 #endif
1876 jsval val;
1878 static const char exception_cookie[] = "/*EXCEPTION*/";
1879 static const char retsub_pc_cookie[] = "/*RETSUB_PC*/";
1880 static const char forelem_cookie[] = "/*FORELEM*/";
1881 static const char with_cookie[] = "/*WITH*/";
1882 static const char dot_format[] = "%s.%s";
1883 static const char index_format[] = "%s[%s]";
1884 static const char predot_format[] = "%s%s.%s";
1885 static const char postdot_format[] = "%s.%s%s";
1886 static const char preindex_format[] = "%s%s[%s]";
1887 static const char postindex_format[] = "%s[%s]%s";
1888 static const char ss_format[] = "%s%s";
1889 static const char sss_format[] = "%s%s%s";
1891 /* Argument and variables decompilation uses the following to share code. */
1892 JS_STATIC_ASSERT(ARGNO_LEN == SLOTNO_LEN);
1895 * Local macros
1897 #define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, NULL)
1898 #define DECOMPILE_CODE(pc,nb) if (!Decompile(ss, pc, nb, JSOP_NOP)) return NULL
1899 #define NEXT_OP(pc) (((pc) + (len) == endpc) ? nextop : pc[len])
1900 #define TOP_STR() GetStr(ss, ss->top - 1)
1901 #define POP_STR() PopStr(ss, op)
1902 #define POP_STR_PREC(prec) PopStrPrec(ss, prec)
1905 * Pop a condition expression for if/while. JSOP_IFEQ's precedence forces
1906 * extra parens around assignment, which avoids a strict-mode warning.
1908 #define POP_COND_STR() \
1909 PopStr(ss, (js_CodeSpec[ss->opcodes[ss->top - 1]].format & JOF_SET) \
1910 ? JSOP_IFEQ \
1911 : JSOP_NOP)
1913 #define ATOM_IS_IDENTIFIER(atom) js_IsIdentifier(atom)
1916 * Given an atom already fetched from jp->script's atom map, quote/escape its
1917 * string appropriately into rval, and select fmt from the quoted and unquoted
1918 * alternatives.
1920 #define GET_QUOTE_AND_FMT(qfmt, ufmt, rval) \
1921 JS_BEGIN_MACRO \
1922 jschar quote_; \
1923 if (!ATOM_IS_IDENTIFIER(atom)) { \
1924 quote_ = '\''; \
1925 fmt = qfmt; \
1926 } else { \
1927 quote_ = 0; \
1928 fmt = ufmt; \
1930 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), quote_); \
1931 if (!rval) \
1932 return NULL; \
1933 JS_END_MACRO
1935 #define LOAD_OBJECT(PCOFF) \
1936 GET_OBJECT_FROM_BYTECODE(jp->script, pc, PCOFF, obj)
1938 #define LOAD_FUNCTION(PCOFF) \
1939 GET_FUNCTION_FROM_BYTECODE(jp->script, pc, PCOFF, fun)
1941 #define LOAD_REGEXP(PCOFF) \
1942 GET_REGEXP_FROM_BYTECODE(jp->script, pc, PCOFF, obj)
1944 #define GET_SOURCE_NOTE_ATOM(sn, atom) \
1945 JS_BEGIN_MACRO \
1946 jsatomid atomIndex_ = (jsatomid) js_GetSrcNoteOffset((sn), 0); \
1948 LOCAL_ASSERT(atomIndex_ < jp->script->atomMap.length); \
1949 (atom) = jp->script->atomMap.vector[atomIndex_]; \
1950 JS_END_MACRO
1953 * Get atom from jp->script's atom map, quote/escape its string appropriately
1954 * into rval, and select fmt from the quoted and unquoted alternatives.
1956 #define GET_ATOM_QUOTE_AND_FMT(qfmt, ufmt, rval) \
1957 JS_BEGIN_MACRO \
1958 LOAD_ATOM(0); \
1959 GET_QUOTE_AND_FMT(qfmt, ufmt, rval); \
1960 JS_END_MACRO
1963 * Per spec, new x(y).z means (new x(y))).z. For example new (x(y).z) must
1964 * decompile with the constructor parenthesized, but new x.z should not. The
1965 * normal rules give x(y).z and x.z identical precedence: both are produced by
1966 * JSOP_GETPROP.
1968 * Therefore, we need to know in case JSOP_NEW whether the constructor
1969 * expression contains any unparenthesized function calls. So when building a
1970 * MemberExpression or CallExpression, we set ss->opcodes[n] to JSOP_CALL if
1971 * this is true. x(y).z gets JSOP_CALL, not JSOP_GETPROP.
1973 #define PROPAGATE_CALLNESS() \
1974 JS_BEGIN_MACRO \
1975 if (ss->opcodes[ss->top - 1] == JSOP_CALL || \
1976 ss->opcodes[ss->top - 1] == JSOP_EVAL || \
1977 ss->opcodes[ss->top - 1] == JSOP_FUNCALL || \
1978 ss->opcodes[ss->top - 1] == JSOP_FUNAPPLY) { \
1979 saveop = JSOP_CALL; \
1981 JS_END_MACRO
1983 cx = ss->sprinter.context;
1984 JS_CHECK_RECURSION(cx, return NULL);
1986 jp = ss->printer;
1987 startpc = pc;
1988 endpc = (nb < 0) ? jp->script->code + jp->script->length : pc + nb;
1989 tail = -1;
1990 todo = -2; /* NB: different from Sprint() error return. */
1991 saveop = JSOP_NOP;
1992 sn = NULL;
1993 rval = NULL;
1994 #if JS_HAS_XML_SUPPORT
1995 foreach = inXML = quoteAttr = JS_FALSE;
1996 #endif
1998 while (nb < 0 || pc < endpc) {
2000 * Move saveop to lastop so prefixed bytecodes can take special action
2001 * while sharing maximal code. Set op and saveop to the new bytecode,
2002 * use op in POP_STR to trigger automatic parenthesization, but push
2003 * saveop at the bottom of the loop if this op pushes. Thus op may be
2004 * set to nop or otherwise mutated to suppress auto-parens.
2006 lastop = saveop;
2007 op = (JSOp) *pc;
2008 cs = &js_CodeSpec[op];
2009 if (cs->format & JOF_INDEXBASE) {
2011 * The decompiler uses js_GetIndexFromBytecode to get atoms and
2012 * objects and ignores these suffix/prefix bytecodes, thus
2013 * simplifying code that must process JSOP_GETTER/JSOP_SETTER
2014 * prefixes.
2016 pc += cs->length;
2017 if (pc >= endpc)
2018 break;
2019 op = (JSOp) *pc;
2020 cs = &js_CodeSpec[op];
2022 saveop = op;
2023 len = oplen = cs->length;
2024 nuses = js_GetStackUses(cs, op, pc);
2027 * Here it is possible that nuses > ss->top when the op has a hidden
2028 * source note. But when nb < 0 we assume that the caller knows that
2029 * Decompile would never meet such opcodes.
2031 if (nb < 0) {
2032 LOCAL_ASSERT(ss->top >= nuses);
2033 uintN ndefs = js_GetStackDefs(cx, cs, op, jp->script, pc);
2034 if ((uintN) -(nb + 1) == ss->top - nuses + ndefs)
2035 return pc;
2039 * Save source literal associated with JS now before the following
2040 * rewrite changes op. See bug 380197.
2042 token = CodeToken[op];
2044 if (pc + oplen == jp->dvgfence) {
2045 JSStackFrame *fp;
2046 uint32 format, mode, type;
2049 * Rewrite non-get ops to their "get" format if the error is in
2050 * the bytecode at pc, so we don't decompile more than the error
2051 * expression.
2053 fp = js_GetScriptedCaller(cx, NULL);
2054 format = cs->format;
2055 if (((fp && pc == fp->pc(cx)) ||
2056 (pc == startpc && nuses != 0)) &&
2057 format & (JOF_SET|JOF_DEL|JOF_INCDEC|JOF_FOR|JOF_VARPROP)) {
2058 mode = JOF_MODE(format);
2059 if (mode == JOF_NAME) {
2061 * JOF_NAME does not imply JOF_ATOM, so we must check for
2062 * the QARG and QVAR format types, and translate those to
2063 * JSOP_GETARG or JSOP_GETLOCAL appropriately, instead of
2064 * to JSOP_NAME.
2066 type = JOF_TYPE(format);
2067 op = (type == JOF_QARG)
2068 ? JSOP_GETARG
2069 : (type == JOF_LOCAL)
2070 ? JSOP_GETLOCAL
2071 : JSOP_NAME;
2073 JS_ASSERT(js_CodeSpec[op].nuses >= 0);
2074 i = nuses - js_CodeSpec[op].nuses;
2075 while (--i >= 0)
2076 PopOff(ss, JSOP_NOP);
2077 } else {
2079 * We must replace the faulting pc's bytecode with a
2080 * corresponding JSOP_GET* code. For JSOP_SET{PROP,ELEM},
2081 * we must use the "2nd" form of JSOP_GET{PROP,ELEM}, to
2082 * throw away the assignment op's right-hand operand and
2083 * decompile it as if it were a GET of its left-hand
2084 * operand.
2086 if (mode == JOF_PROP) {
2087 op = (JSOp) ((format & JOF_SET)
2088 ? JSOP_GETPROP2
2089 : JSOP_GETPROP);
2090 } else if (mode == JOF_ELEM) {
2091 op = (JSOp) ((format & JOF_SET)
2092 ? JSOP_GETELEM2
2093 : JSOP_GETELEM);
2094 } else {
2096 * Unknown mode (including mode 0) means that op is
2097 * uncategorized for our purposes, so we must write
2098 * per-op special case code here.
2100 switch (op) {
2101 case JSOP_ENUMELEM:
2102 case JSOP_ENUMCONSTELEM:
2103 op = JSOP_GETELEM;
2104 break;
2105 case JSOP_GETTHISPROP:
2107 * NB: JSOP_GETTHISPROP can't fail due to |this|
2108 * being null or undefined at runtime (beware that
2109 * this may change for ES4). Therefore any error
2110 * resulting from this op must be due to the value
2111 * of the property accessed via |this|, so do not
2112 * rewrite op to JSOP_THIS.
2114 * The next two cases should not change op if
2115 * js_DecompileValueGenerator was called from the
2116 * the property getter. They should rewrite only
2117 * if the base object in the arg/var/local is null
2118 * or undefined. FIXME: bug 431569.
2120 break;
2121 case JSOP_GETARGPROP:
2122 op = JSOP_GETARG;
2123 break;
2124 case JSOP_GETLOCALPROP:
2125 op = JSOP_GETLOCAL;
2126 break;
2127 case JSOP_SETXMLNAME:
2128 op = JSOp(JSOP_GETELEM2);
2129 break;
2130 default:
2131 LOCAL_ASSERT(0);
2137 saveop = op;
2138 if (op >= JSOP_LIMIT) {
2139 if (op == JSOP_GETPROP2)
2140 saveop = JSOP_GETPROP;
2141 else if (op == JSOP_GETELEM2)
2142 saveop = JSOP_GETELEM;
2144 LOCAL_ASSERT(js_CodeSpec[saveop].length == oplen ||
2145 JOF_TYPE(format) == JOF_SLOTATOM);
2147 jp->dvgfence = NULL;
2150 if (token) {
2151 switch (nuses) {
2152 case 2:
2153 sn = js_GetSrcNote(jp->script, pc);
2154 if (sn && SN_TYPE(sn) == SRC_ASSIGNOP) {
2156 * Avoid over-parenthesizing y in x op= y based on its
2157 * expansion: x = x op y (replace y by z = w to see the
2158 * problem).
2160 op = (JSOp) pc[oplen];
2161 rval = POP_STR();
2162 lval = POP_STR();
2163 /* Print only the right operand of the assignment-op. */
2164 todo = SprintCString(&ss->sprinter, rval);
2165 op = saveop;
2166 } else if (!inXML) {
2167 rval = POP_STR_PREC(cs->prec + !!(cs->format & JOF_LEFTASSOC));
2168 lval = POP_STR_PREC(cs->prec + !(cs->format & JOF_LEFTASSOC));
2169 todo = Sprint(&ss->sprinter, "%s %s %s",
2170 lval, token, rval);
2171 } else {
2172 /* In XML, just concatenate the two operands. */
2173 LOCAL_ASSERT(op == JSOP_ADD);
2174 rval = POP_STR();
2175 lval = POP_STR();
2176 todo = Sprint(&ss->sprinter, ss_format, lval, rval);
2178 break;
2180 case 1:
2181 rval = POP_STR();
2182 todo = Sprint(&ss->sprinter, ss_format, token, rval);
2183 break;
2185 case 0:
2186 todo = SprintCString(&ss->sprinter, token);
2187 break;
2189 default:
2190 todo = -2;
2191 break;
2193 } else {
2194 switch (op) {
2195 case JSOP_NOP:
2197 * Check for a do-while loop, a for-loop with an empty
2198 * initializer part, a labeled statement, a function
2199 * definition, or try/finally.
2201 sn = js_GetSrcNote(jp->script, pc);
2202 todo = -2;
2203 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
2204 case SRC_WHILE:
2205 ++pc;
2206 tail = js_GetSrcNoteOffset(sn, 0) - 1;
2207 LOCAL_ASSERT(pc[tail] == JSOP_IFNE ||
2208 pc[tail] == JSOP_IFNEX);
2209 js_printf(jp, "\tdo {\n");
2210 jp->indent += 4;
2211 DECOMPILE_CODE(pc, tail);
2212 jp->indent -= 4;
2213 js_printf(jp, "\t} while (%s);\n", POP_COND_STR());
2214 pc += tail;
2215 len = js_CodeSpec[*pc].length;
2216 todo = -2;
2217 break;
2219 case SRC_FOR:
2220 rval = "";
2222 do_forloop:
2223 JS_ASSERT(SN_TYPE(sn) == SRC_FOR);
2225 /* Skip the JSOP_NOP or JSOP_POP bytecode. */
2226 pc += JSOP_NOP_LENGTH;
2228 /* Get the cond, next, and loop-closing tail offsets. */
2229 cond = js_GetSrcNoteOffset(sn, 0);
2230 next = js_GetSrcNoteOffset(sn, 1);
2231 tail = js_GetSrcNoteOffset(sn, 2);
2234 * If this loop has a condition, then pc points at a goto
2235 * targeting the condition.
2237 pc2 = pc;
2238 if (cond != tail) {
2239 LOCAL_ASSERT(*pc == JSOP_GOTO || *pc == JSOP_GOTOX);
2240 pc2 += (*pc == JSOP_GOTO) ? JSOP_GOTO_LENGTH : JSOP_GOTOX_LENGTH;
2242 LOCAL_ASSERT(tail + GetJumpOffset(pc+tail, pc+tail) == pc2 - pc);
2244 /* Print the keyword and the possibly empty init-part. */
2245 js_printf(jp, "\tfor (%s;", rval);
2247 if (cond != tail) {
2248 /* Decompile the loop condition. */
2249 DECOMPILE_CODE(pc + cond, tail - cond);
2250 js_printf(jp, " %s", POP_STR());
2253 /* Need a semicolon whether or not there was a cond. */
2254 js_puts(jp, ";");
2256 if (next != cond) {
2258 * Decompile the loop updater. It may end in a JSOP_POP
2259 * that we skip; or in a JSOP_POPN that we do not skip,
2260 * followed by a JSOP_NOP (skipped as if it's a POP).
2261 * We cope with the difference between these two cases
2262 * by checking for stack imbalance and popping if there
2263 * is an rval.
2265 uintN saveTop = ss->top;
2267 DECOMPILE_CODE(pc + next, cond - next - JSOP_POP_LENGTH);
2268 LOCAL_ASSERT(ss->top - saveTop <= 1U);
2269 rval = (ss->top == saveTop)
2270 ? ss->sprinter.base + ss->sprinter.offset
2271 : POP_STR();
2272 js_printf(jp, " %s", rval);
2275 /* Do the loop body. */
2276 js_printf(jp, ") {\n");
2277 jp->indent += 4;
2278 next -= pc2 - pc;
2279 DECOMPILE_CODE(pc2, next);
2280 jp->indent -= 4;
2281 js_printf(jp, "\t}\n");
2283 /* Set len so pc skips over the entire loop. */
2284 len = tail + js_CodeSpec[pc[tail]].length;
2285 break;
2287 case SRC_LABEL:
2288 GET_SOURCE_NOTE_ATOM(sn, atom);
2289 jp->indent -= 4;
2290 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
2291 if (!rval)
2292 return NULL;
2293 RETRACT(&ss->sprinter, rval);
2294 js_printf(jp, "\t%s:\n", rval);
2295 jp->indent += 4;
2296 break;
2298 case SRC_LABELBRACE:
2299 GET_SOURCE_NOTE_ATOM(sn, atom);
2300 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
2301 if (!rval)
2302 return NULL;
2303 RETRACT(&ss->sprinter, rval);
2304 js_printf(jp, "\t%s: {\n", rval);
2305 jp->indent += 4;
2306 break;
2308 case SRC_ENDBRACE:
2309 jp->indent -= 4;
2310 js_printf(jp, "\t}\n");
2311 break;
2313 case SRC_FUNCDEF:
2314 fun = jp->script->getFunction(js_GetSrcNoteOffset(sn, 0));
2315 do_function:
2316 js_puts(jp, "\n");
2317 jp2 = js_NewPrinter(cx, "nested_function", fun,
2318 jp->indent, jp->pretty, jp->grouped,
2319 jp->strict);
2320 if (!jp2)
2321 return NULL;
2322 ok = js_DecompileFunction(jp2);
2323 if (ok && jp2->sprinter.base)
2324 js_puts(jp, jp2->sprinter.base);
2325 js_DestroyPrinter(jp2);
2326 if (!ok)
2327 return NULL;
2328 js_puts(jp, "\n\n");
2329 break;
2331 case SRC_BRACE:
2332 js_printf(jp, "\t{\n");
2333 jp->indent += 4;
2334 len = js_GetSrcNoteOffset(sn, 0);
2335 DECOMPILE_CODE(pc + oplen, len - oplen);
2336 jp->indent -= 4;
2337 js_printf(jp, "\t}\n");
2338 break;
2340 default:;
2342 break;
2344 case JSOP_PUSH:
2345 #if JS_HAS_DESTRUCTURING
2346 sn = js_GetSrcNote(jp->script, pc);
2347 if (sn && SN_TYPE(sn) == SRC_GROUPASSIGN) {
2348 pc = DecompileGroupAssignment(ss, pc, endpc, sn, &todo);
2349 if (!pc)
2350 return NULL;
2351 LOCAL_ASSERT(*pc == JSOP_POPN);
2352 len = oplen = JSOP_POPN_LENGTH;
2353 goto end_groupassignment;
2355 #endif
2356 /* FALL THROUGH */
2358 case JSOP_BINDNAME:
2359 case JSOP_BINDGNAME:
2360 todo = Sprint(&ss->sprinter, "");
2361 break;
2363 case JSOP_TRY:
2364 js_printf(jp, "\ttry {\n");
2365 jp->indent += 4;
2366 todo = -2;
2367 break;
2369 case JSOP_FINALLY:
2370 jp->indent -= 4;
2371 js_printf(jp, "\t} finally {\n");
2372 jp->indent += 4;
2375 * We push push the pair of exception/restsub cookies to
2376 * simulate the effects [gosub] or control transfer during
2377 * exception capturing on the stack.
2379 todo = Sprint(&ss->sprinter, exception_cookie);
2380 if (todo < 0 || !PushOff(ss, todo, op))
2381 return NULL;
2382 todo = Sprint(&ss->sprinter, retsub_pc_cookie);
2383 break;
2385 case JSOP_RETSUB:
2386 rval = POP_STR();
2387 LOCAL_ASSERT(strcmp(rval, retsub_pc_cookie) == 0);
2388 lval = POP_STR();
2389 LOCAL_ASSERT(strcmp(lval, exception_cookie) == 0);
2390 todo = -2;
2391 break;
2393 case JSOP_GOSUB:
2394 case JSOP_GOSUBX:
2396 * JSOP_GOSUB and GOSUBX have no effect on the decompiler's
2397 * string stack because the next op in bytecode order finds
2398 * the stack balanced by a JSOP_RETSUB executed elsewhere.
2400 todo = -2;
2401 break;
2403 case JSOP_POPN:
2405 uintN newtop, oldtop;
2408 * The compiler models operand stack depth and fixes the stack
2409 * pointer on entry to a catch clause based on its depth model.
2410 * The decompiler must match the code generator's model, which
2411 * is why JSOP_FINALLY pushes a cookie that JSOP_RETSUB pops.
2413 oldtop = ss->top;
2414 newtop = oldtop - GET_UINT16(pc);
2415 LOCAL_ASSERT(newtop <= oldtop);
2416 todo = -2;
2418 sn = js_GetSrcNote(jp->script, pc);
2419 if (sn && SN_TYPE(sn) == SRC_HIDDEN)
2420 break;
2421 #if JS_HAS_DESTRUCTURING
2422 if (sn && SN_TYPE(sn) == SRC_GROUPASSIGN) {
2423 todo = Sprint(&ss->sprinter, "%s[] = [",
2424 VarPrefix(sn));
2425 if (todo < 0)
2426 return NULL;
2427 for (uintN i = newtop; i < oldtop; i++) {
2428 rval = OFF2STR(&ss->sprinter, ss->offsets[i]);
2429 if (Sprint(&ss->sprinter, ss_format,
2430 (i == newtop) ? "" : ", ",
2431 (i == oldtop - 1 && *rval == '\0')
2432 ? ", " : rval) < 0) {
2433 return NULL;
2436 if (SprintPut(&ss->sprinter, "]", 1) < 0)
2437 return NULL;
2440 * If this is an empty group assignment, we have no stack
2441 * budget into which we can push our result string. Adjust
2442 * ss->sprinter.offset so that our consumer can find the
2443 * empty group assignment decompilation.
2445 if (newtop == oldtop) {
2446 ss->sprinter.offset = todo;
2447 } else {
2449 * Kill newtop before the end_groupassignment: label by
2450 * retracting/popping early. Control will either jump
2451 * to do_forloop: or do_letheadbody: or else break from
2452 * our case JSOP_POPN: after the switch (*pc2) below.
2454 LOCAL_ASSERT(newtop < oldtop);
2455 ss->sprinter.offset = GetOff(ss, newtop);
2456 ss->top = newtop;
2459 end_groupassignment:
2460 LOCAL_ASSERT(*pc == JSOP_POPN);
2463 * Thread directly to the next opcode if we can, to handle
2464 * the special cases of a group assignment in the first or
2465 * last part of a for(;;) loop head, or in a let block or
2466 * expression head.
2468 * NB: todo at this point indexes space in ss->sprinter
2469 * that is liable to be overwritten. The code below knows
2470 * exactly how long rval lives, or else copies it down via
2471 * SprintCString.
2473 rval = OFF2STR(&ss->sprinter, todo);
2474 todo = -2;
2475 pc2 = pc + oplen;
2476 if (*pc2 == JSOP_NOP) {
2477 sn = js_GetSrcNote(jp->script, pc2);
2478 if (sn) {
2479 if (SN_TYPE(sn) == SRC_FOR) {
2480 op = JSOP_NOP;
2481 pc = pc2;
2482 goto do_forloop;
2485 if (SN_TYPE(sn) == SRC_DECL) {
2486 if (ss->top == StackDepth(jp->script)) {
2488 * This must be an empty destructuring
2489 * in the head of a let whose body block
2490 * is also empty.
2492 pc = pc2 + JSOP_NOP_LENGTH;
2493 len = js_GetSrcNoteOffset(sn, 0);
2494 LOCAL_ASSERT(pc[len] == JSOP_LEAVEBLOCK);
2495 js_printf(jp, "\tlet (%s) {\n", rval);
2496 js_printf(jp, "\t}\n");
2497 break;
2499 todo = SprintCString(&ss->sprinter, rval);
2500 if (todo < 0 || !PushOff(ss, todo, JSOP_NOP))
2501 return NULL;
2502 op = JSOP_POP;
2503 pc = pc2 + JSOP_NOP_LENGTH;
2504 goto do_letheadbody;
2506 } else {
2508 * An unnannotated NOP following a POPN must be the
2509 * third part of for(;;) loop head. If the POPN's
2510 * immediate operand is 0, then we may have no slot
2511 * on the sprint-stack in which to push our result
2512 * string. In this case the result can be recovered
2513 * at ss->sprinter.base + ss->sprinter.offset.
2515 if (GET_UINT16(pc) == 0)
2516 break;
2517 todo = SprintCString(&ss->sprinter, rval);
2518 saveop = JSOP_NOP;
2523 * If control flow reaches this point with todo still -2,
2524 * just print rval as an expression statement.
2526 if (todo == -2)
2527 js_printf(jp, "\t%s;\n", rval);
2528 break;
2530 #endif
2531 if (newtop < oldtop) {
2532 ss->sprinter.offset = GetOff(ss, newtop);
2533 ss->top = newtop;
2535 break;
2538 case JSOP_EXCEPTION:
2539 /* The catch decompiler handles this op itself. */
2540 LOCAL_ASSERT(JS_FALSE);
2541 break;
2543 case JSOP_POP:
2545 * By default, do not automatically parenthesize when popping
2546 * a stacked expression decompilation. We auto-parenthesize
2547 * only when JSOP_POP is annotated with SRC_PCDELTA, meaning
2548 * comma operator.
2550 op = JSOP_POPV;
2551 /* FALL THROUGH */
2553 case JSOP_POPV:
2554 sn = js_GetSrcNote(jp->script, pc);
2555 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
2556 case SRC_FOR:
2557 /* Force parens around 'in' expression at 'for' front. */
2558 if (ss->opcodes[ss->top-1] == JSOP_IN)
2559 op = JSOP_LSH;
2560 rval = POP_STR();
2561 todo = -2;
2562 goto do_forloop;
2564 case SRC_PCDELTA:
2565 /* Comma operator: use JSOP_POP for correct precedence. */
2566 op = JSOP_POP;
2568 /* Pop and save to avoid blowing stack depth budget. */
2569 lval = JS_strdup(cx, POP_STR());
2570 if (!lval)
2571 return NULL;
2574 * The offset tells distance to the end of the right-hand
2575 * operand of the comma operator.
2577 done = pc + len;
2578 pc += js_GetSrcNoteOffset(sn, 0);
2579 len = 0;
2581 if (!Decompile(ss, done, pc - done, JSOP_POP)) {
2582 cx->free((char *)lval);
2583 return NULL;
2586 /* Pop Decompile result and print comma expression. */
2587 rval = POP_STR();
2588 todo = Sprint(&ss->sprinter, "%s, %s", lval, rval);
2589 cx->free((char *)lval);
2590 break;
2592 case SRC_HIDDEN:
2593 /* Hide this pop, it's from a goto in a with or for/in. */
2594 todo = -2;
2595 break;
2597 case SRC_DECL:
2598 /* This pop is at the end of the let block/expr head. */
2599 pc += JSOP_POP_LENGTH;
2600 #if JS_HAS_DESTRUCTURING
2601 do_letheadbody:
2602 #endif
2603 len = js_GetSrcNoteOffset(sn, 0);
2604 if (pc[len] == JSOP_LEAVEBLOCK) {
2605 js_printf(jp, "\tlet (%s) {\n", POP_STR());
2606 jp->indent += 4;
2607 DECOMPILE_CODE(pc, len);
2608 jp->indent -= 4;
2609 js_printf(jp, "\t}\n");
2610 todo = -2;
2611 } else {
2612 LOCAL_ASSERT(pc[len] == JSOP_LEAVEBLOCKEXPR);
2614 lval = JS_strdup(cx, PopStr(ss, JSOP_NOP));
2615 if (!lval)
2616 return NULL;
2618 /* Set saveop to reflect what we will push. */
2619 saveop = JSOP_LEAVEBLOCKEXPR;
2620 if (!Decompile(ss, pc, len, saveop)) {
2621 cx->free((char *)lval);
2622 return NULL;
2624 rval = PopStr(ss, JSOP_SETNAME);
2625 todo = Sprint(&ss->sprinter,
2626 (*rval == '{')
2627 ? "let (%s) (%s)"
2628 : "let (%s) %s",
2629 lval, rval);
2630 cx->free((char *)lval);
2632 break;
2634 default:
2635 /* Turn off parens around a yield statement. */
2636 if (ss->opcodes[ss->top-1] == JSOP_YIELD)
2637 op = JSOP_NOP;
2639 rval = POP_STR();
2642 * Don't emit decompiler-pushed strings that are not
2643 * handled by other opcodes. They are pushed onto the
2644 * stack to help model the interpreter stack and should
2645 * not appear in the decompiler's output.
2647 if (*rval != '\0' && (rval[0] != '/' || rval[1] != '*')) {
2648 js_printf(jp,
2649 (*rval == '{' ||
2650 (strncmp(rval, js_function_str, 8) == 0 &&
2651 rval[8] == ' '))
2652 ? "\t(%s);\n"
2653 : "\t%s;\n",
2654 rval);
2655 } else {
2656 LOCAL_ASSERT(*rval == '\0' ||
2657 strcmp(rval, exception_cookie) == 0);
2659 todo = -2;
2660 break;
2662 sn = NULL;
2663 break;
2665 case JSOP_ENTERWITH:
2666 LOCAL_ASSERT(!js_GetSrcNote(jp->script, pc));
2667 rval = POP_STR();
2668 js_printf(jp, "\twith (%s) {\n", rval);
2669 jp->indent += 4;
2670 todo = Sprint(&ss->sprinter, with_cookie);
2671 break;
2673 case JSOP_LEAVEWITH:
2674 sn = js_GetSrcNote(jp->script, pc);
2675 todo = -2;
2676 if (sn && SN_TYPE(sn) == SRC_HIDDEN)
2677 break;
2678 rval = POP_STR();
2679 LOCAL_ASSERT(strcmp(rval, with_cookie) == 0);
2680 jp->indent -= 4;
2681 js_printf(jp, "\t}\n");
2682 break;
2684 case JSOP_ENTERBLOCK:
2686 JSAtom **atomv, *smallv[5];
2688 LOAD_OBJECT(0);
2689 argc = OBJ_BLOCK_COUNT(cx, obj);
2690 if ((size_t)argc <= JS_ARRAY_LENGTH(smallv)) {
2691 atomv = smallv;
2692 } else {
2693 atomv = (JSAtom **) cx->malloc(argc * sizeof(JSAtom *));
2694 if (!atomv)
2695 return NULL;
2698 MUST_FLOW_THROUGH("enterblock_out");
2699 #define LOCAL_ASSERT_OUT(expr) LOCAL_ASSERT_CUSTOM(expr, ok = JS_FALSE; \
2700 goto enterblock_out)
2701 for (Shape::Range r = obj->lastProperty()->all(); !r.empty(); r.popFront()) {
2702 const Shape &shape = r.front();
2704 if (!shape.hasShortID())
2705 continue;
2706 LOCAL_ASSERT_OUT(shape.shortid < argc);
2707 atomv[shape.shortid] = JSID_TO_ATOM(shape.id);
2709 ok = JS_TRUE;
2710 for (i = 0; i < argc; i++) {
2711 atom = atomv[i];
2712 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
2713 if (!rval ||
2714 !PushOff(ss, STR2OFF(&ss->sprinter, rval), op)) {
2715 ok = JS_FALSE;
2716 goto enterblock_out;
2720 sn = js_GetSrcNote(jp->script, pc);
2721 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
2722 #if JS_HAS_BLOCK_SCOPE
2723 case SRC_BRACE:
2724 js_printf(jp, "\t{\n");
2725 jp->indent += 4;
2726 len = js_GetSrcNoteOffset(sn, 0);
2727 ok = Decompile(ss, pc + oplen, len - oplen, JSOP_NOP)
2728 != NULL;
2729 if (!ok)
2730 goto enterblock_out;
2731 jp->indent -= 4;
2732 js_printf(jp, "\t}\n");
2733 break;
2734 #endif
2736 case SRC_CATCH:
2737 jp->indent -= 4;
2738 js_printf(jp, "\t} catch (");
2740 pc2 = pc;
2741 pc += oplen;
2742 LOCAL_ASSERT_OUT(*pc == JSOP_EXCEPTION);
2743 pc += JSOP_EXCEPTION_LENGTH;
2744 todo = Sprint(&ss->sprinter, exception_cookie);
2745 if (todo < 0 || !PushOff(ss, todo, JSOP_EXCEPTION)) {
2746 ok = JS_FALSE;
2747 goto enterblock_out;
2750 if (*pc == JSOP_DUP) {
2751 sn2 = js_GetSrcNote(jp->script, pc);
2752 if (!sn2 || SN_TYPE(sn2) != SRC_DESTRUCT) {
2754 * This is a dup to save the exception for later.
2755 * It is emitted only when the catch head contains
2756 * an exception guard.
2758 LOCAL_ASSERT_OUT(js_GetSrcNoteOffset(sn, 0) != 0);
2759 pc += JSOP_DUP_LENGTH;
2760 todo = Sprint(&ss->sprinter, exception_cookie);
2761 if (todo < 0 ||
2762 !PushOff(ss, todo, JSOP_EXCEPTION)) {
2763 ok = JS_FALSE;
2764 goto enterblock_out;
2769 #if JS_HAS_DESTRUCTURING
2770 if (*pc == JSOP_DUP) {
2771 pc = DecompileDestructuring(ss, pc, endpc);
2772 if (!pc) {
2773 ok = JS_FALSE;
2774 goto enterblock_out;
2776 LOCAL_ASSERT_OUT(*pc == JSOP_POP);
2777 pc += JSOP_POP_LENGTH;
2778 lval = PopStr(ss, JSOP_NOP);
2779 js_puts(jp, lval);
2780 } else {
2781 #endif
2782 LOCAL_ASSERT_OUT(*pc == JSOP_SETLOCALPOP);
2783 i = GET_SLOTNO(pc) - jp->script->nfixed;
2784 pc += JSOP_SETLOCALPOP_LENGTH;
2785 atom = atomv[i - OBJ_BLOCK_DEPTH(cx, obj)];
2786 str = ATOM_TO_STRING(atom);
2787 if (!QuoteString(&jp->sprinter, str, 0)) {
2788 ok = JS_FALSE;
2789 goto enterblock_out;
2791 #if JS_HAS_DESTRUCTURING
2793 #endif
2796 * Pop the exception_cookie (or its dup in the case of a
2797 * guarded catch head) off the stack now.
2799 rval = PopStr(ss, JSOP_NOP);
2800 LOCAL_ASSERT_OUT(strcmp(rval, exception_cookie) == 0);
2802 len = js_GetSrcNoteOffset(sn, 0);
2803 if (len) {
2804 len -= pc - pc2;
2805 LOCAL_ASSERT_OUT(len > 0);
2806 js_printf(jp, " if ");
2807 ok = Decompile(ss, pc, len, JSOP_NOP) != NULL;
2808 if (!ok)
2809 goto enterblock_out;
2810 js_printf(jp, "%s", POP_STR());
2811 pc += len;
2812 LOCAL_ASSERT_OUT(*pc == JSOP_IFEQ || *pc == JSOP_IFEQX);
2813 pc += js_CodeSpec[*pc].length;
2816 js_printf(jp, ") {\n");
2817 jp->indent += 4;
2818 len = 0;
2819 break;
2820 default:
2821 break;
2824 todo = -2;
2826 #undef LOCAL_ASSERT_OUT
2827 enterblock_out:
2828 if (atomv != smallv)
2829 cx->free(atomv);
2830 if (!ok)
2831 return NULL;
2833 break;
2835 case JSOP_LEAVEBLOCK:
2836 case JSOP_LEAVEBLOCKEXPR:
2838 uintN top, depth;
2840 sn = js_GetSrcNote(jp->script, pc);
2841 todo = -2;
2842 if (op == JSOP_LEAVEBLOCKEXPR) {
2843 LOCAL_ASSERT(SN_TYPE(sn) == SRC_PCBASE);
2844 rval = POP_STR();
2845 } else if (sn) {
2846 LOCAL_ASSERT(op == JSOP_LEAVEBLOCK);
2847 if (SN_TYPE(sn) == SRC_HIDDEN)
2848 break;
2851 * This JSOP_LEAVEBLOCK must be for a catch block. If sn's
2852 * offset does not equal the model stack depth, there must
2853 * be a copy of the exception value on the stack due to a
2854 * catch guard (see above, the JSOP_ENTERBLOCK + SRC_CATCH
2855 * case code).
2857 LOCAL_ASSERT(SN_TYPE(sn) == SRC_CATCH);
2858 if ((uintN)js_GetSrcNoteOffset(sn, 0) != ss->top) {
2859 LOCAL_ASSERT((uintN)js_GetSrcNoteOffset(sn, 0)
2860 == ss->top - 1);
2861 rval = POP_STR();
2862 LOCAL_ASSERT(strcmp(rval, exception_cookie) == 0);
2865 top = ss->top;
2866 depth = GET_UINT16(pc);
2867 LOCAL_ASSERT(top >= depth);
2868 top -= depth;
2869 ss->top = top;
2870 ss->sprinter.offset = GetOff(ss, top);
2871 if (op == JSOP_LEAVEBLOCKEXPR)
2872 todo = SprintCString(&ss->sprinter, rval);
2873 break;
2876 case JSOP_GETUPVAR_DBG:
2877 case JSOP_CALLUPVAR_DBG:
2878 case JSOP_GETFCSLOT:
2879 case JSOP_CALLFCSLOT:
2881 if (!jp->fun) {
2882 JS_ASSERT(jp->script->savedCallerFun);
2883 jp->fun = jp->script->getFunction(0);
2886 if (!jp->localNames) {
2887 JS_ASSERT(fun == jp->fun);
2888 jp->localNames =
2889 jp->fun->script()->bindings.getLocalNameArray(cx, &jp->pool);
2892 uintN index = GET_UINT16(pc);
2893 if (index < jp->fun->script()->bindings.countUpvars()) {
2894 index += jp->fun->script()->bindings.countArgsAndVars();
2895 } else {
2896 JSUpvarArray *uva;
2897 #ifdef DEBUG
2899 * We must be in an eval called from jp->fun, where
2900 * jp->script is the eval-compiled script.
2902 * However, it's possible that a js_Invoke already
2903 * pushed a frame trying to call Construct on an
2904 * object that's not a constructor, causing us to be
2905 * called with an intervening frame on the stack.
2907 JSStackFrame *fp = js_GetTopStackFrame(cx);
2908 if (fp) {
2909 while (!fp->isEvalFrame())
2910 fp = fp->prev();
2911 JS_ASSERT(fp->script() == jp->script);
2912 JS_ASSERT(fp->prev()->fun() == jp->fun);
2913 JS_ASSERT(jp->fun->isInterpreted());
2914 JS_ASSERT(jp->script != jp->fun->script());
2915 JS_ASSERT(JSScript::isValidOffset(jp->script->upvarsOffset));
2917 #endif
2918 uva = jp->script->upvars();
2919 index = uva->vector[index].slot();
2921 atom = GetArgOrVarAtom(jp, index);
2922 goto do_name;
2925 case JSOP_CALLLOCAL:
2926 case JSOP_GETLOCAL:
2927 if (IsVarSlot(jp, pc, &i)) {
2928 atom = GetArgOrVarAtom(jp, i);
2929 LOCAL_ASSERT(atom);
2930 goto do_name;
2932 LOCAL_ASSERT((uintN)i < ss->top);
2933 sn = js_GetSrcNote(jp->script, pc);
2935 #if JS_HAS_DESTRUCTURING
2936 if (sn && SN_TYPE(sn) == SRC_GROUPASSIGN) {
2938 * Distinguish a js_DecompileValueGenerator call that
2939 * targets op alone, from decompilation of a full group
2940 * assignment sequence, triggered by SRC_GROUPASSIGN
2941 * annotating the first JSOP_GETLOCAL in the sequence.
2943 if (endpc - pc > JSOP_GETLOCAL_LENGTH || pc > startpc) {
2944 pc = DecompileGroupAssignment(ss, pc, endpc, sn, &todo);
2945 if (!pc)
2946 return NULL;
2947 LOCAL_ASSERT(*pc == JSOP_POPN);
2948 len = oplen = JSOP_POPN_LENGTH;
2949 goto end_groupassignment;
2952 /* Null sn to prevent bogus VarPrefix'ing below. */
2953 sn = NULL;
2955 #endif
2957 rval = GetLocal(ss, i);
2958 todo = Sprint(&ss->sprinter, ss_format, VarPrefix(sn), rval);
2959 break;
2961 case JSOP_SETLOCAL:
2962 case JSOP_SETLOCALPOP:
2963 if (IsVarSlot(jp, pc, &i)) {
2964 atom = GetArgOrVarAtom(jp, i);
2965 LOCAL_ASSERT(atom);
2966 goto do_setname;
2968 lval = GetLocal(ss, i);
2969 rval = POP_STR();
2970 goto do_setlval;
2972 case JSOP_INCLOCAL:
2973 case JSOP_DECLOCAL:
2974 if (IsVarSlot(jp, pc, &i)) {
2975 atom = GetArgOrVarAtom(jp, i);
2976 LOCAL_ASSERT(atom);
2977 goto do_incatom;
2979 lval = GetLocal(ss, i);
2980 goto do_inclval;
2982 case JSOP_LOCALINC:
2983 case JSOP_LOCALDEC:
2984 if (IsVarSlot(jp, pc, &i)) {
2985 atom = GetArgOrVarAtom(jp, i);
2986 LOCAL_ASSERT(atom);
2987 goto do_atominc;
2989 lval = GetLocal(ss, i);
2990 goto do_lvalinc;
2992 case JSOP_RETRVAL:
2993 todo = -2;
2994 break;
2996 case JSOP_RETURN:
2997 LOCAL_ASSERT(jp->fun);
2998 fun = jp->fun;
2999 if (fun->flags & JSFUN_EXPR_CLOSURE) {
3000 /* Turn on parens around comma-expression here. */
3001 op = JSOP_SETNAME;
3002 rval = POP_STR();
3003 js_printf(jp, (*rval == '{') ? "(%s)%s" : ss_format,
3004 rval,
3005 ((fun->flags & JSFUN_LAMBDA) || !fun->atom)
3006 ? ""
3007 : ";");
3008 todo = -2;
3009 break;
3011 /* FALL THROUGH */
3013 case JSOP_SETRVAL:
3014 rval = POP_STR();
3015 if (*rval != '\0')
3016 js_printf(jp, "\t%s %s;\n", js_return_str, rval);
3017 else
3018 js_printf(jp, "\t%s;\n", js_return_str);
3019 todo = -2;
3020 break;
3022 #if JS_HAS_GENERATORS
3023 case JSOP_YIELD:
3024 #if JS_HAS_GENERATOR_EXPRS
3025 if (!ss->inGenExp || !(sn = js_GetSrcNote(jp->script, pc)))
3026 #endif
3028 /* Turn off most parens. */
3029 op = JSOP_SETNAME;
3030 rval = POP_STR();
3031 todo = (*rval != '\0')
3032 ? Sprint(&ss->sprinter,
3033 (strncmp(rval, js_yield_str, 5) == 0 &&
3034 (rval[5] == ' ' || rval[5] == '\0'))
3035 ? "%s (%s)"
3036 : "%s %s",
3037 js_yield_str, rval)
3038 : SprintCString(&ss->sprinter, js_yield_str);
3039 break;
3042 #if JS_HAS_GENERATOR_EXPRS
3043 LOCAL_ASSERT(SN_TYPE(sn) == SRC_HIDDEN);
3044 /* FALL THROUGH */
3045 #endif
3047 case JSOP_ARRAYPUSH:
3049 uintN pos, forpos;
3050 ptrdiff_t start;
3052 /* Turn off most parens. */
3053 op = JSOP_SETNAME;
3055 /* Pop the expression being pushed or yielded. */
3056 rval = POP_STR();
3059 * Skip the for loop head stacked by JSOP_FORLOCAL until we hit
3060 * a block local slot (note empty destructuring patterns result
3061 * in unit-count blocks).
3063 pos = ss->top;
3064 while (pos != 0) {
3065 op = (JSOp) ss->opcodes[--pos];
3066 if (op == JSOP_ENTERBLOCK)
3067 break;
3069 JS_ASSERT(op == JSOP_ENTERBLOCK);
3072 * Here, forpos must index the space before the left-most |for|
3073 * in the single string of accumulated |for| heads and optional
3074 * final |if (condition)|.
3076 forpos = pos + 1;
3077 LOCAL_ASSERT(forpos < ss->top);
3080 * Now move pos downward over the block's local slots. Even an
3081 * empty destructuring pattern has one (dummy) local.
3083 while (ss->opcodes[pos] == JSOP_ENTERBLOCK) {
3084 if (pos == 0)
3085 break;
3086 --pos;
3089 #if JS_HAS_GENERATOR_EXPRS
3090 if (saveop == JSOP_YIELD) {
3092 * Generator expression: decompile just rval followed by
3093 * the string starting at forpos. Leave the result string
3094 * in ss->offsets[0] so it can be recovered by our caller
3095 * (the JSOP_ANONFUNOBJ with SRC_GENEXP case). Bump the
3096 * top of stack to balance yield, which is an expression
3097 * (so has neutral stack balance).
3099 LOCAL_ASSERT(pos == 0);
3100 xval = OFF2STR(&ss->sprinter, ss->offsets[forpos]);
3101 ss->sprinter.offset = PAREN_SLOP;
3102 todo = Sprint(&ss->sprinter, ss_format, rval, xval);
3103 if (todo < 0)
3104 return NULL;
3105 ss->offsets[0] = todo;
3106 ++ss->top;
3107 return pc;
3109 #endif /* JS_HAS_GENERATOR_EXPRS */
3112 * Array comprehension: retract the sprinter to the beginning
3113 * of the array initialiser and decompile "[<rval> for ...]".
3115 JS_ASSERT(jp->script->nfixed + pos == GET_UINT16(pc));
3116 LOCAL_ASSERT(ss->opcodes[pos] == JSOP_NEWINIT);
3118 start = ss->offsets[pos];
3119 LOCAL_ASSERT(ss->sprinter.base[start] == '[' ||
3120 ss->sprinter.base[start] == '#');
3121 LOCAL_ASSERT(forpos < ss->top);
3122 xval = OFF2STR(&ss->sprinter, ss->offsets[forpos]);
3123 lval = OFF2STR(&ss->sprinter, start);
3124 RETRACT(&ss->sprinter, lval);
3126 todo = Sprint(&ss->sprinter, sss_format, lval, rval, xval);
3127 if (todo < 0)
3128 return NULL;
3129 ss->offsets[pos] = todo;
3130 todo = -2;
3131 break;
3133 #endif /* JS_HAS_GENERATORS */
3135 case JSOP_THROWING:
3136 todo = -2;
3137 break;
3139 case JSOP_THROW:
3140 sn = js_GetSrcNote(jp->script, pc);
3141 todo = -2;
3142 if (sn && SN_TYPE(sn) == SRC_HIDDEN)
3143 break;
3144 rval = POP_STR();
3145 js_printf(jp, "\t%s %s;\n", js_throw_str, rval);
3146 break;
3148 case JSOP_ITER:
3149 foreach = (pc[1] & (JSITER_FOREACH | JSITER_KEYVALUE)) ==
3150 JSITER_FOREACH;
3151 todo = -2;
3152 break;
3154 case JSOP_MOREITER:
3155 JS_NOT_REACHED("JSOP_MOREITER");
3156 break;
3158 case JSOP_ENDITER:
3159 sn = js_GetSrcNote(jp->script, pc);
3160 todo = -2;
3161 if (sn && SN_TYPE(sn) == SRC_HIDDEN)
3162 break;
3163 (void) PopOff(ss, op);
3164 break;
3166 case JSOP_GOTO:
3167 case JSOP_GOTOX:
3168 sn = js_GetSrcNote(jp->script, pc);
3169 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
3170 case SRC_FOR_IN:
3172 * The loop back-edge carries +1 stack balance, for the
3173 * flag processed by JSOP_IFNE. We do not decompile the
3174 * JSOP_IFNE, and instead push the left-hand side of 'in'
3175 * after the loop edge in this stack slot (the JSOP_FOR*
3176 * opcodes' decompilers do this pushing).
3178 cond = GetJumpOffset(pc, pc);
3179 next = js_GetSrcNoteOffset(sn, 0);
3180 tail = js_GetSrcNoteOffset(sn, 1);
3181 JS_ASSERT(pc[cond] == JSOP_MOREITER);
3182 DECOMPILE_CODE(pc + oplen, next - oplen);
3183 lval = POP_STR();
3184 LOCAL_ASSERT(ss->top >= 1);
3186 if (ss->inArrayInit || ss->inGenExp) {
3187 rval = POP_STR();
3188 if (ss->top >= 1 && ss->opcodes[ss->top - 1] == JSOP_FORLOCAL) {
3189 ss->sprinter.offset = ss->offsets[ss->top] - PAREN_SLOP;
3190 if (Sprint(&ss->sprinter, " %s (%s in %s)",
3191 foreach ? js_for_each_str : js_for_str,
3192 lval, rval) < 0) {
3193 return NULL;
3197 * Do not AddParentSlop here, as we will push the
3198 * top-most offset again, which will add paren slop
3199 * for us. We must push to balance the stack budget
3200 * when nesting for heads in a comprehension.
3202 todo = ss->offsets[ss->top - 1];
3203 } else {
3204 todo = Sprint(&ss->sprinter, " %s (%s in %s)",
3205 foreach ? js_for_each_str : js_for_str,
3206 lval, rval);
3208 if (todo < 0 || !PushOff(ss, todo, JSOP_FORLOCAL))
3209 return NULL;
3210 DECOMPILE_CODE(pc + next, cond - next);
3211 } else {
3213 * As above, rval or an extension of it must remain
3214 * stacked during loop body decompilation.
3216 rval = GetStr(ss, ss->top - 1);
3217 js_printf(jp, "\t%s (%s in %s) {\n",
3218 foreach ? js_for_each_str : js_for_str,
3219 lval, rval);
3220 jp->indent += 4;
3221 DECOMPILE_CODE(pc + next, cond - next);
3222 jp->indent -= 4;
3223 js_printf(jp, "\t}\n");
3226 pc += tail;
3227 LOCAL_ASSERT(*pc == JSOP_IFNE || *pc == JSOP_IFNEX);
3228 len = js_CodeSpec[*pc].length;
3229 break;
3231 case SRC_WHILE:
3232 cond = GetJumpOffset(pc, pc);
3233 tail = js_GetSrcNoteOffset(sn, 0);
3234 DECOMPILE_CODE(pc + cond, tail - cond);
3235 js_printf(jp, "\twhile (%s) {\n", POP_COND_STR());
3236 jp->indent += 4;
3237 DECOMPILE_CODE(pc + oplen, cond - oplen);
3238 jp->indent -= 4;
3239 js_printf(jp, "\t}\n");
3240 pc += tail;
3241 LOCAL_ASSERT(*pc == JSOP_IFNE || *pc == JSOP_IFNEX);
3242 len = js_CodeSpec[*pc].length;
3243 todo = -2;
3244 break;
3246 case SRC_CONT2LABEL:
3247 GET_SOURCE_NOTE_ATOM(sn, atom);
3248 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3249 if (!rval)
3250 return NULL;
3251 RETRACT(&ss->sprinter, rval);
3252 js_printf(jp, "\tcontinue %s;\n", rval);
3253 break;
3255 case SRC_CONTINUE:
3256 js_printf(jp, "\tcontinue;\n");
3257 break;
3259 case SRC_BREAK2LABEL:
3260 GET_SOURCE_NOTE_ATOM(sn, atom);
3261 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3262 if (!rval)
3263 return NULL;
3264 RETRACT(&ss->sprinter, rval);
3265 js_printf(jp, "\tbreak %s;\n", rval);
3266 break;
3268 case SRC_HIDDEN:
3269 break;
3271 default:
3272 js_printf(jp, "\tbreak;\n");
3273 break;
3275 todo = -2;
3276 break;
3278 case JSOP_IFEQ:
3279 case JSOP_IFEQX:
3281 JSBool elseif = JS_FALSE;
3283 if_again:
3284 len = GetJumpOffset(pc, pc);
3285 sn = js_GetSrcNote(jp->script, pc);
3287 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
3288 case SRC_IF:
3289 case SRC_IF_ELSE:
3290 rval = POP_COND_STR();
3291 if (ss->inArrayInit || ss->inGenExp) {
3292 LOCAL_ASSERT(SN_TYPE(sn) == SRC_IF);
3293 ss->sprinter.offset -= PAREN_SLOP;
3294 if (Sprint(&ss->sprinter, " if (%s)", rval) < 0)
3295 return NULL;
3296 AddParenSlop(ss);
3297 } else {
3298 js_printf(jp,
3299 elseif ? " if (%s) {\n" : "\tif (%s) {\n",
3300 rval);
3301 jp->indent += 4;
3304 if (SN_TYPE(sn) == SRC_IF) {
3305 DECOMPILE_CODE(pc + oplen, len - oplen);
3306 } else {
3307 LOCAL_ASSERT(!ss->inArrayInit && !ss->inGenExp);
3308 tail = js_GetSrcNoteOffset(sn, 0);
3309 DECOMPILE_CODE(pc + oplen, tail - oplen);
3310 jp->indent -= 4;
3311 pc += tail;
3312 LOCAL_ASSERT(*pc == JSOP_GOTO || *pc == JSOP_GOTOX);
3313 oplen = js_CodeSpec[*pc].length;
3314 len = GetJumpOffset(pc, pc);
3315 js_printf(jp, "\t} else");
3318 * If the second offset for sn is non-zero, it tells
3319 * the distance from the goto around the else, to the
3320 * ifeq for the if inside the else that forms an "if
3321 * else if" chain. Thus cond spans the condition of
3322 * the second if, so we simply decompile it and start
3323 * over at label if_again.
3325 cond = js_GetSrcNoteOffset(sn, 1);
3326 if (cond != 0) {
3327 cond -= tail;
3328 DECOMPILE_CODE(pc + oplen, cond - oplen);
3329 pc += cond;
3330 oplen = js_CodeSpec[*pc].length;
3331 elseif = JS_TRUE;
3332 goto if_again;
3335 js_printf(jp, " {\n");
3336 jp->indent += 4;
3337 DECOMPILE_CODE(pc + oplen, len - oplen);
3340 if (!ss->inArrayInit && !ss->inGenExp) {
3341 jp->indent -= 4;
3342 js_printf(jp, "\t}\n");
3344 todo = -2;
3345 break;
3347 case SRC_COND:
3348 xval = JS_strdup(cx, POP_STR());
3349 if (!xval)
3350 return NULL;
3351 len = js_GetSrcNoteOffset(sn, 0);
3352 DECOMPILE_CODE(pc + oplen, len - oplen);
3353 lval = JS_strdup(cx, POP_STR());
3354 if (!lval) {
3355 cx->free((void *)xval);
3356 return NULL;
3358 pc += len;
3359 LOCAL_ASSERT(*pc == JSOP_GOTO || *pc == JSOP_GOTOX);
3360 oplen = js_CodeSpec[*pc].length;
3361 len = GetJumpOffset(pc, pc);
3362 DECOMPILE_CODE(pc + oplen, len - oplen);
3363 rval = POP_STR();
3364 todo = Sprint(&ss->sprinter, "%s ? %s : %s",
3365 xval, lval, rval);
3366 cx->free((void *)xval);
3367 cx->free((void *)lval);
3368 break;
3370 default:
3371 break;
3373 break;
3376 case JSOP_IFNE:
3377 case JSOP_IFNEX:
3378 LOCAL_ASSERT(0);
3379 break;
3381 case JSOP_OR:
3382 case JSOP_ORX:
3383 xval = "||";
3385 do_logical_connective:
3386 /* Top of stack is the first clause in a disjunction (||). */
3387 lval = JS_strdup(cx, POP_STR());
3388 if (!lval)
3389 return NULL;
3390 done = pc + GetJumpOffset(pc, pc);
3391 pc += len;
3392 len = done - pc;
3393 if (!Decompile(ss, pc, len, op)) {
3394 cx->free((char *)lval);
3395 return NULL;
3397 rval = POP_STR();
3398 if (jp->pretty &&
3399 jp->indent + 4 + strlen(lval) + 4 + strlen(rval) > 75) {
3400 rval = JS_strdup(cx, rval);
3401 if (!rval) {
3402 tail = -1;
3403 } else {
3404 todo = Sprint(&ss->sprinter, "%s %s\n", lval, xval);
3405 tail = Sprint(&ss->sprinter, "%*s%s",
3406 jp->indent + 4, "", rval);
3407 cx->free((char *)rval);
3409 if (tail < 0)
3410 todo = -1;
3411 } else {
3412 todo = Sprint(&ss->sprinter, "%s %s %s", lval, xval, rval);
3414 cx->free((char *)lval);
3415 break;
3417 case JSOP_AND:
3418 case JSOP_ANDX:
3419 xval = "&&";
3420 goto do_logical_connective;
3422 case JSOP_FORARG:
3423 sn = NULL;
3424 i = GET_ARGNO(pc);
3425 goto do_forvarslot;
3427 case JSOP_FORLOCAL:
3428 sn = js_GetSrcNote(jp->script, pc);
3429 if (!IsVarSlot(jp, pc, &i)) {
3430 JS_ASSERT(op == JSOP_FORLOCAL);
3431 todo = Sprint(&ss->sprinter, ss_format, VarPrefix(sn), GetStr(ss, i));
3432 break;
3435 do_forvarslot:
3436 atom = GetArgOrVarAtom(jp, i);
3437 LOCAL_ASSERT(atom);
3438 todo = SprintCString(&ss->sprinter, VarPrefix(sn));
3439 if (todo < 0 || !QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0))
3440 return NULL;
3441 break;
3443 case JSOP_FORNAME:
3444 case JSOP_FORGNAME:
3445 LOAD_ATOM(0);
3447 sn = js_GetSrcNote(jp->script, pc);
3448 todo = SprintCString(&ss->sprinter, VarPrefix(sn));
3449 if (todo < 0 || !QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0))
3450 return NULL;
3451 break;
3453 case JSOP_FORPROP:
3454 xval = NULL;
3455 LOAD_ATOM(0);
3456 if (!ATOM_IS_IDENTIFIER(atom)) {
3457 xval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom),
3458 (jschar)'\'');
3459 if (!xval)
3460 return NULL;
3462 lval = POP_STR();
3463 if (xval) {
3464 JS_ASSERT(*lval);
3465 todo = Sprint(&ss->sprinter, index_format, lval, xval);
3466 } else {
3467 todo = Sprint(&ss->sprinter, ss_format, lval, *lval ? "." : "");
3468 if (todo < 0)
3469 return NULL;
3470 if (!QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0))
3471 return NULL;
3473 break;
3475 case JSOP_FORELEM:
3476 todo = SprintCString(&ss->sprinter, forelem_cookie);
3477 break;
3479 case JSOP_ENUMELEM:
3480 case JSOP_ENUMCONSTELEM:
3482 * The stack has the object under the (top) index expression.
3483 * The "rval" property id is underneath those two on the stack.
3484 * The for loop body net and gross lengths can now be adjusted
3485 * to account for the length of the indexing expression that
3486 * came after JSOP_FORELEM and before JSOP_ENUMELEM.
3488 atom = NULL;
3489 op = JSOP_NOP; /* turn off parens around xval */
3490 xval = POP_STR();
3491 op = JSOP_GETELEM; /* lval must have high precedence */
3492 lval = POP_STR();
3493 op = saveop;
3494 rval = POP_STR();
3495 LOCAL_ASSERT(strcmp(rval, forelem_cookie) == 0);
3496 if (*xval == '\0') {
3497 todo = SprintCString(&ss->sprinter, lval);
3498 } else {
3499 todo = Sprint(&ss->sprinter,
3500 (JOF_OPMODE(lastop) == JOF_XMLNAME)
3501 ? dot_format
3502 : index_format,
3503 lval, xval);
3505 break;
3507 case JSOP_GETTER:
3508 case JSOP_SETTER:
3509 todo = -2;
3510 break;
3512 case JSOP_DUP2:
3513 rval = GetStr(ss, ss->top-2);
3514 todo = SprintCString(&ss->sprinter, rval);
3515 if (todo < 0 || !PushOff(ss, todo,
3516 (JSOp) ss->opcodes[ss->top-2])) {
3517 return NULL;
3519 /* FALL THROUGH */
3521 case JSOP_DUP:
3522 #if JS_HAS_DESTRUCTURING
3523 sn = js_GetSrcNote(jp->script, pc);
3524 if (sn) {
3525 LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCT);
3526 pc = DecompileDestructuring(ss, pc, endpc);
3527 if (!pc)
3528 return NULL;
3529 len = 0;
3530 lval = POP_STR();
3531 op = saveop = JSOP_ENUMELEM;
3532 rval = POP_STR();
3534 if (strcmp(rval, forelem_cookie) == 0) {
3535 todo = Sprint(&ss->sprinter, ss_format,
3536 VarPrefix(sn), lval);
3538 // Skip POP so the SRC_FOR_IN code can pop for itself.
3539 if (*pc == JSOP_POP)
3540 len = JSOP_POP_LENGTH;
3541 } else {
3542 todo = Sprint(&ss->sprinter, "%s%s = %s",
3543 VarPrefix(sn), lval, rval);
3545 break;
3547 #endif
3549 rval = GetStr(ss, ss->top-1);
3550 saveop = (JSOp) ss->opcodes[ss->top-1];
3551 todo = SprintCString(&ss->sprinter, rval);
3552 break;
3554 case JSOP_SETARG:
3555 atom = GetArgOrVarAtom(jp, GET_ARGNO(pc));
3556 LOCAL_ASSERT(atom);
3557 goto do_setname;
3559 case JSOP_SETCONST:
3560 case JSOP_SETNAME:
3561 case JSOP_SETGNAME:
3562 LOAD_ATOM(0);
3564 do_setname:
3565 lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3566 if (!lval)
3567 return NULL;
3568 rval = POP_STR();
3569 if (op == JSOP_SETNAME || op == JSOP_SETGNAME)
3570 (void) PopOff(ss, op);
3572 do_setlval:
3573 sn = js_GetSrcNote(jp->script, pc - 1);
3574 if (sn && SN_TYPE(sn) == SRC_ASSIGNOP) {
3575 todo = Sprint(&ss->sprinter, "%s %s= %s",
3576 lval,
3577 (lastop == JSOP_GETTER)
3578 ? js_getter_str
3579 : (lastop == JSOP_SETTER)
3580 ? js_setter_str
3581 : CodeToken[lastop],
3582 rval);
3583 } else {
3584 sn = js_GetSrcNote(jp->script, pc);
3585 todo = Sprint(&ss->sprinter, "%s%s = %s",
3586 VarPrefix(sn), lval, rval);
3588 if (op == JSOP_SETLOCALPOP) {
3589 if (!PushOff(ss, todo, saveop))
3590 return NULL;
3591 rval = POP_STR();
3592 LOCAL_ASSERT(*rval != '\0');
3593 js_printf(jp, "\t%s;\n", rval);
3594 todo = -2;
3596 break;
3598 case JSOP_NEW:
3599 case JSOP_CALL:
3600 case JSOP_EVAL:
3601 case JSOP_FUNCALL:
3602 case JSOP_FUNAPPLY:
3603 argc = GET_ARGC(pc);
3604 argv = (char **)
3605 cx->malloc((size_t)(argc + 1) * sizeof *argv);
3606 if (!argv)
3607 return NULL;
3609 op = JSOP_SETNAME;
3610 ok = JS_TRUE;
3611 for (i = argc; i > 0; i--)
3612 argv[i] = JS_strdup(cx, POP_STR());
3614 /* Skip the JSOP_PUSHOBJ-created empty string. */
3615 LOCAL_ASSERT(ss->top >= 2);
3616 (void) PopOff(ss, op);
3619 * Special case: new (x(y)(z)) must be parenthesized like so.
3620 * Same for new (x(y).z) -- contrast with new x(y).z.
3621 * See PROPAGATE_CALLNESS.
3623 op = (JSOp) ss->opcodes[ss->top - 1];
3624 lval = PopStr(ss,
3625 (saveop == JSOP_NEW &&
3626 (op == JSOP_CALL ||
3627 op == JSOP_EVAL ||
3628 op == JSOP_FUNCALL ||
3629 op == JSOP_FUNAPPLY ||
3630 (js_CodeSpec[op].format & JOF_CALLOP)))
3631 ? JSOP_NAME
3632 : saveop);
3633 op = saveop;
3635 argv[0] = JS_strdup(cx, lval);
3636 if (!argv[0])
3637 ok = JS_FALSE;
3639 lval = "(", rval = ")";
3640 if (op == JSOP_NEW) {
3641 if (argc == 0)
3642 lval = rval = "";
3643 todo = Sprint(&ss->sprinter, "%s %s%s",
3644 js_new_str, argv[0], lval);
3645 } else {
3646 todo = Sprint(&ss->sprinter, ss_format,
3647 argv[0], lval);
3649 if (todo < 0)
3650 ok = JS_FALSE;
3652 for (i = 1; i <= argc; i++) {
3653 if (!argv[i] ||
3654 Sprint(&ss->sprinter, ss_format,
3655 argv[i], (i < argc) ? ", " : "") < 0) {
3656 ok = JS_FALSE;
3657 break;
3660 if (Sprint(&ss->sprinter, rval) < 0)
3661 ok = JS_FALSE;
3663 for (i = 0; i <= argc; i++)
3664 cx->free(argv[i]);
3665 cx->free(argv);
3666 if (!ok)
3667 return NULL;
3668 break;
3670 case JSOP_SETCALL:
3671 todo = Sprint(&ss->sprinter, "");
3672 break;
3674 case JSOP_DELNAME:
3675 LOAD_ATOM(0);
3676 lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3677 if (!lval)
3678 return NULL;
3679 RETRACT(&ss->sprinter, lval);
3680 do_delete_lval:
3681 todo = Sprint(&ss->sprinter, "%s %s", js_delete_str, lval);
3682 break;
3684 case JSOP_DELPROP:
3685 GET_ATOM_QUOTE_AND_FMT("%s %s[%s]", "%s %s.%s", rval);
3686 op = JSOP_GETPROP;
3687 lval = POP_STR();
3688 todo = Sprint(&ss->sprinter, fmt, js_delete_str, lval, rval);
3689 break;
3691 case JSOP_DELELEM:
3692 op = JSOP_NOP; /* turn off parens */
3693 xval = POP_STR();
3694 op = JSOP_GETPROP;
3695 lval = POP_STR();
3696 if (*xval == '\0')
3697 goto do_delete_lval;
3698 todo = Sprint(&ss->sprinter,
3699 (JOF_OPMODE(lastop) == JOF_XMLNAME)
3700 ? "%s %s.%s"
3701 : "%s %s[%s]",
3702 js_delete_str, lval, xval);
3703 break;
3705 #if JS_HAS_XML_SUPPORT
3706 case JSOP_DELDESC:
3707 xval = POP_STR();
3708 op = JSOP_GETPROP;
3709 lval = POP_STR();
3710 todo = Sprint(&ss->sprinter, "%s %s..%s",
3711 js_delete_str, lval, xval);
3712 break;
3713 #endif
3715 case JSOP_TYPEOFEXPR:
3716 case JSOP_TYPEOF:
3717 case JSOP_VOID:
3718 rval = POP_STR();
3719 todo = Sprint(&ss->sprinter, "%s %s",
3720 (op == JSOP_VOID) ? js_void_str : js_typeof_str,
3721 rval);
3722 break;
3724 case JSOP_INCARG:
3725 case JSOP_DECARG:
3726 atom = GetArgOrVarAtom(jp, GET_ARGNO(pc));
3727 LOCAL_ASSERT(atom);
3728 goto do_incatom;
3730 case JSOP_INCNAME:
3731 case JSOP_DECNAME:
3732 case JSOP_INCGNAME:
3733 case JSOP_DECGNAME:
3734 LOAD_ATOM(0);
3735 do_incatom:
3736 lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3737 if (!lval)
3738 return NULL;
3739 RETRACT(&ss->sprinter, lval);
3740 do_inclval:
3741 todo = Sprint(&ss->sprinter, ss_format,
3742 js_incop_strs[!(cs->format & JOF_INC)], lval);
3743 break;
3745 case JSOP_INCPROP:
3746 case JSOP_DECPROP:
3747 GET_ATOM_QUOTE_AND_FMT(preindex_format, predot_format, rval);
3750 * Force precedence below the numeric literal opcodes, so that
3751 * 42..foo or 10000..toString(16), e.g., decompile with parens
3752 * around the left-hand side of dot.
3754 op = JSOP_GETPROP;
3755 lval = POP_STR();
3756 todo = Sprint(&ss->sprinter, fmt,
3757 js_incop_strs[!(cs->format & JOF_INC)],
3758 lval, rval);
3759 break;
3761 case JSOP_INCELEM:
3762 case JSOP_DECELEM:
3763 op = JSOP_NOP; /* turn off parens */
3764 xval = POP_STR();
3765 op = JSOP_GETELEM;
3766 lval = POP_STR();
3767 if (*xval != '\0') {
3768 todo = Sprint(&ss->sprinter,
3769 (JOF_OPMODE(lastop) == JOF_XMLNAME)
3770 ? predot_format
3771 : preindex_format,
3772 js_incop_strs[!(cs->format & JOF_INC)],
3773 lval, xval);
3774 } else {
3775 todo = Sprint(&ss->sprinter, ss_format,
3776 js_incop_strs[!(cs->format & JOF_INC)], lval);
3778 break;
3780 case JSOP_ARGINC:
3781 case JSOP_ARGDEC:
3782 atom = GetArgOrVarAtom(jp, GET_ARGNO(pc));
3783 LOCAL_ASSERT(atom);
3784 goto do_atominc;
3786 case JSOP_NAMEINC:
3787 case JSOP_NAMEDEC:
3788 case JSOP_GNAMEINC:
3789 case JSOP_GNAMEDEC:
3790 LOAD_ATOM(0);
3791 do_atominc:
3792 lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3793 if (!lval)
3794 return NULL;
3795 RETRACT(&ss->sprinter, lval);
3796 do_lvalinc:
3797 todo = Sprint(&ss->sprinter, ss_format,
3798 lval, js_incop_strs[!(cs->format & JOF_INC)]);
3799 break;
3801 case JSOP_PROPINC:
3802 case JSOP_PROPDEC:
3803 GET_ATOM_QUOTE_AND_FMT(postindex_format, postdot_format, rval);
3806 * Force precedence below the numeric literal opcodes, so that
3807 * 42..foo or 10000..toString(16), e.g., decompile with parens
3808 * around the left-hand side of dot.
3810 op = JSOP_GETPROP;
3811 lval = POP_STR();
3812 todo = Sprint(&ss->sprinter, fmt, lval, rval,
3813 js_incop_strs[!(cs->format & JOF_INC)]);
3814 break;
3816 case JSOP_ELEMINC:
3817 case JSOP_ELEMDEC:
3818 op = JSOP_NOP; /* turn off parens */
3819 xval = POP_STR();
3820 op = JSOP_GETELEM;
3821 lval = POP_STR();
3822 if (*xval != '\0') {
3823 todo = Sprint(&ss->sprinter,
3824 (JOF_OPMODE(lastop) == JOF_XMLNAME)
3825 ? postdot_format
3826 : postindex_format,
3827 lval, xval,
3828 js_incop_strs[!(cs->format & JOF_INC)]);
3829 } else {
3830 todo = Sprint(&ss->sprinter, ss_format,
3831 lval, js_incop_strs[!(cs->format & JOF_INC)]);
3833 break;
3835 case JSOP_LENGTH:
3836 fmt = dot_format;
3837 rval = js_length_str;
3838 goto do_getprop_lval;
3840 case JSOP_GETPROP2:
3841 op = JSOP_GETPROP;
3842 (void) PopOff(ss, lastop);
3843 /* FALL THROUGH */
3845 case JSOP_CALLPROP:
3846 case JSOP_GETPROP:
3847 case JSOP_GETXPROP:
3848 LOAD_ATOM(0);
3850 do_getprop:
3851 GET_QUOTE_AND_FMT(index_format, dot_format, rval);
3852 do_getprop_lval:
3853 PROPAGATE_CALLNESS();
3854 lval = POP_STR();
3855 todo = Sprint(&ss->sprinter, fmt, lval, rval);
3856 break;
3858 case JSOP_GETTHISPROP:
3859 LOAD_ATOM(0);
3860 GET_QUOTE_AND_FMT(index_format, dot_format, rval);
3861 todo = Sprint(&ss->sprinter, fmt, js_this_str, rval);
3862 break;
3864 case JSOP_GETARGPROP:
3865 /* Get the name of the argument or variable. */
3866 i = GET_ARGNO(pc);
3868 do_getarg_prop:
3869 atom = GetArgOrVarAtom(ss->printer, i);
3870 LOCAL_ASSERT(atom);
3871 lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3872 if (!lval || !PushOff(ss, STR2OFF(&ss->sprinter, lval), op))
3873 return NULL;
3875 /* Get the name of the property. */
3876 LOAD_ATOM(ARGNO_LEN);
3877 goto do_getprop;
3879 case JSOP_GETLOCALPROP:
3880 if (IsVarSlot(jp, pc, &i))
3881 goto do_getarg_prop;
3882 LOCAL_ASSERT((uintN)i < ss->top);
3883 lval = GetLocal(ss, i);
3884 if (!lval)
3885 return NULL;
3886 todo = SprintCString(&ss->sprinter, lval);
3887 if (todo < 0 || !PushOff(ss, todo, op))
3888 return NULL;
3889 LOAD_ATOM(2);
3890 goto do_getprop;
3892 case JSOP_SETPROP:
3893 case JSOP_SETMETHOD:
3894 LOAD_ATOM(0);
3895 GET_QUOTE_AND_FMT("%s[%s] %s= %s", "%s.%s %s= %s", xval);
3896 rval = POP_STR();
3899 * Force precedence below the numeric literal opcodes, so that
3900 * 42..foo or 10000..toString(16), e.g., decompile with parens
3901 * around the left-hand side of dot.
3903 op = JSOP_GETPROP;
3904 lval = POP_STR();
3905 sn = js_GetSrcNote(jp->script, pc - 1);
3906 todo = Sprint(&ss->sprinter, fmt, lval, xval,
3907 (sn && SN_TYPE(sn) == SRC_ASSIGNOP)
3908 ? (lastop == JSOP_GETTER)
3909 ? js_getter_str
3910 : (lastop == JSOP_SETTER)
3911 ? js_setter_str
3912 : CodeToken[lastop]
3913 : "",
3914 rval);
3915 break;
3917 case JSOP_GETELEM2:
3918 op = JSOP_GETELEM;
3919 (void) PopOff(ss, lastop);
3920 /* FALL THROUGH */
3922 case JSOP_CALLELEM:
3923 case JSOP_GETELEM:
3924 op = JSOP_NOP; /* turn off parens */
3925 xval = POP_STR();
3926 op = saveop;
3927 PROPAGATE_CALLNESS();
3928 lval = POP_STR();
3929 if (*xval == '\0') {
3930 todo = Sprint(&ss->sprinter, "%s", lval);
3931 } else {
3932 todo = Sprint(&ss->sprinter,
3933 (JOF_OPMODE(lastop) == JOF_XMLNAME)
3934 ? dot_format
3935 : index_format,
3936 lval, xval);
3938 break;
3940 case JSOP_SETELEM:
3941 rval = POP_STR();
3942 op = JSOP_NOP; /* turn off parens */
3943 xval = POP_STR();
3944 cs = &js_CodeSpec[ss->opcodes[ss->top]];
3945 op = JSOP_GETELEM; /* lval must have high precedence */
3946 lval = POP_STR();
3947 op = saveop;
3948 if (*xval == '\0')
3949 goto do_setlval;
3950 sn = js_GetSrcNote(jp->script, pc - 1);
3951 todo = Sprint(&ss->sprinter,
3952 (JOF_MODE(cs->format) == JOF_XMLNAME)
3953 ? "%s.%s %s= %s"
3954 : "%s[%s] %s= %s",
3955 lval, xval,
3956 (sn && SN_TYPE(sn) == SRC_ASSIGNOP)
3957 ? (lastop == JSOP_GETTER)
3958 ? js_getter_str
3959 : (lastop == JSOP_SETTER)
3960 ? js_setter_str
3961 : CodeToken[lastop]
3962 : "",
3963 rval);
3964 break;
3966 case JSOP_ARGSUB:
3967 i = (jsint) GET_ARGNO(pc);
3968 todo = Sprint(&ss->sprinter, "%s[%d]",
3969 js_arguments_str, (int) i);
3970 break;
3972 case JSOP_ARGCNT:
3973 todo = Sprint(&ss->sprinter, dot_format,
3974 js_arguments_str, js_length_str);
3975 break;
3977 case JSOP_CALLARG:
3978 case JSOP_GETARG:
3979 i = GET_ARGNO(pc);
3980 atom = GetArgOrVarAtom(jp, i);
3981 #if JS_HAS_DESTRUCTURING
3982 if (!atom) {
3983 todo = Sprint(&ss->sprinter, "%s[%d]", js_arguments_str, i);
3984 break;
3986 #else
3987 LOCAL_ASSERT(atom);
3988 #endif
3989 goto do_name;
3991 case JSOP_CALLGLOBAL:
3992 case JSOP_GETGLOBAL:
3993 atom = jp->script->getGlobalAtom(GET_SLOTNO(pc));
3994 goto do_name;
3996 case JSOP_CALLNAME:
3997 case JSOP_NAME:
3998 case JSOP_GETGNAME:
3999 case JSOP_CALLGNAME:
4000 LOAD_ATOM(0);
4001 do_name:
4002 lval = "";
4003 #if JS_HAS_XML_SUPPORT
4004 do_qname:
4005 #endif
4006 sn = js_GetSrcNote(jp->script, pc);
4007 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom),
4008 inXML ? DONT_ESCAPE : 0);
4009 if (!rval)
4010 return NULL;
4011 RETRACT(&ss->sprinter, rval);
4012 todo = Sprint(&ss->sprinter, sss_format,
4013 VarPrefix(sn), lval, rval);
4014 break;
4016 case JSOP_UINT16:
4017 i = (jsint) GET_UINT16(pc);
4018 goto do_sprint_int;
4020 case JSOP_UINT24:
4021 i = (jsint) GET_UINT24(pc);
4022 goto do_sprint_int;
4024 case JSOP_INT8:
4025 i = GET_INT8(pc);
4026 goto do_sprint_int;
4028 case JSOP_INT32:
4029 i = GET_INT32(pc);
4030 do_sprint_int:
4031 todo = Sprint(&ss->sprinter, "%d", i);
4032 break;
4034 case JSOP_DOUBLE:
4036 double d;
4037 GET_DOUBLE_FROM_BYTECODE(jp->script, pc, 0, d);
4038 val = DOUBLE_TO_JSVAL(d);
4039 todo = SprintDoubleValue(&ss->sprinter, val, &saveop);
4040 break;
4043 case JSOP_STRING:
4044 LOAD_ATOM(0);
4045 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom),
4046 inXML ? DONT_ESCAPE : '"');
4047 if (!rval)
4048 return NULL;
4049 todo = STR2OFF(&ss->sprinter, rval);
4050 break;
4052 case JSOP_LAMBDA:
4053 case JSOP_LAMBDA_FC:
4054 case JSOP_LAMBDA_DBGFC:
4055 #if JS_HAS_GENERATOR_EXPRS
4056 sn = js_GetSrcNote(jp->script, pc);
4057 if (sn && SN_TYPE(sn) == SRC_GENEXP) {
4058 void *mark;
4059 jsuword *innerLocalNames, *outerLocalNames;
4060 JSScript *inner, *outer;
4061 SprintStack ss2;
4062 JSFunction *outerfun;
4064 LOAD_FUNCTION(0);
4067 * All allocation when decompiling is LIFO, using malloc
4068 * or, more commonly, arena-allocating from cx->tempPool.
4069 * Therefore after InitSprintStack succeeds, we must
4070 * release to mark before returning.
4072 mark = JS_ARENA_MARK(&cx->tempPool);
4073 if (fun->script()->bindings.hasLocalNames()) {
4074 innerLocalNames =
4075 fun->script()->bindings.getLocalNameArray(cx, &cx->tempPool);
4076 if (!innerLocalNames)
4077 return NULL;
4078 } else {
4079 innerLocalNames = NULL;
4081 inner = fun->script();
4082 if (!InitSprintStack(cx, &ss2, jp, StackDepth(inner))) {
4083 JS_ARENA_RELEASE(&cx->tempPool, mark);
4084 return NULL;
4086 ss2.inGenExp = JS_TRUE;
4089 * Recursively decompile this generator function as an
4090 * un-parenthesized generator expression. The ss->inGenExp
4091 * special case of JSOP_YIELD shares array comprehension
4092 * decompilation code that leaves the result as the single
4093 * string pushed on ss2.
4095 outer = jp->script;
4096 outerfun = jp->fun;
4097 outerLocalNames = jp->localNames;
4098 LOCAL_ASSERT(JS_UPTRDIFF(pc, outer->code) <= outer->length);
4099 jp->script = inner;
4100 jp->fun = fun;
4101 jp->localNames = innerLocalNames;
4104 * Decompile only the main bytecode, to avoid tripping over
4105 * new prolog ops that have stack effects.
4107 ok = Decompile(&ss2, inner->main,
4108 inner->length - (inner->main - inner->code),
4109 JSOP_NOP)
4110 != NULL;
4111 jp->script = outer;
4112 jp->fun = outerfun;
4113 jp->localNames = outerLocalNames;
4114 if (!ok) {
4115 JS_ARENA_RELEASE(&cx->tempPool, mark);
4116 return NULL;
4120 * Advance over this op and its global |this| push, and
4121 * arrange to advance over the call to this lambda.
4123 pc += len;
4124 if (*pc == JSOP_BLOCKCHAIN) {
4125 pc += JSOP_BLOCKCHAIN_LENGTH;
4126 } else {
4127 LOCAL_ASSERT(*pc == JSOP_NULLBLOCKCHAIN);
4128 pc += JSOP_NULLBLOCKCHAIN_LENGTH;
4130 LOCAL_ASSERT(*pc == JSOP_PUSH);
4131 pc += JSOP_PUSH_LENGTH;
4132 LOCAL_ASSERT(*pc == JSOP_CALL);
4133 LOCAL_ASSERT(GET_ARGC(pc) == 0);
4134 len = JSOP_CALL_LENGTH;
4137 * Arrange to parenthesize this genexp unless:
4139 * 1. It is the complete expression consumed by a control
4140 * flow bytecode such as JSOP_TABLESWITCH whose syntax
4141 * always parenthesizes the controlling expression.
4142 * 2. It is the sole argument to a function call.
4144 * But if this genexp runs up against endpc, parenthesize
4145 * regardless. (This can happen if we are called from
4146 * DecompileExpression or recursively from case
4147 * JSOP_{NOP,AND,OR}.)
4149 * There's no special case for |if (genexp)| because the
4150 * compiler optimizes that to |if (true)|.
4152 pc2 = pc + len;
4153 op = JSOp(*pc2);
4154 if (op == JSOP_TRACE || op == JSOP_NOP)
4155 pc2 += JSOP_NOP_LENGTH;
4156 LOCAL_ASSERT(pc2 < endpc ||
4157 endpc < outer->code + outer->length);
4158 LOCAL_ASSERT(ss2.top == 1);
4159 ss2.opcodes[0] = JSOP_POP;
4160 if (pc2 == endpc) {
4161 op = JSOP_SETNAME;
4162 } else {
4163 op = (JSOp) *pc2;
4164 op = ((js_CodeSpec[op].format & JOF_PARENHEAD) ||
4165 ((js_CodeSpec[op].format & JOF_INVOKE) && GET_ARGC(pc2) == 1))
4166 ? JSOP_POP
4167 : JSOP_SETNAME;
4170 * Stack this result as if it's a name and not an
4171 * anonymous function, so it doesn't get decompiled as
4172 * a generator function in a getter or setter context.
4173 * The precedence level is the same for JSOP_NAME and
4174 * JSOP_LAMBDA.
4176 LOCAL_ASSERT(js_CodeSpec[JSOP_NAME].prec ==
4177 js_CodeSpec[saveop].prec);
4178 saveop = JSOP_NAME;
4182 * Alas, we have to malloc a copy of the result left on
4183 * the top of ss2 because both ss and ss2 arena-allocate
4184 * from cx's tempPool.
4186 rval = JS_strdup(cx, PopStr(&ss2, op));
4187 JS_ARENA_RELEASE(&cx->tempPool, mark);
4188 if (!rval)
4189 return NULL;
4190 todo = SprintCString(&ss->sprinter, rval);
4191 cx->free((void *)rval);
4192 break;
4194 #endif /* JS_HAS_GENERATOR_EXPRS */
4195 /* FALL THROUGH */
4197 LOAD_FUNCTION(0);
4200 * Always parenthesize expression closures. We can't force
4201 * saveop to a low-precedence op to arrange for auto-magic
4202 * parenthesization without confusing getter/setter code
4203 * that checks for JSOP_LAMBDA.
4205 bool grouped = !(fun->flags & JSFUN_EXPR_CLOSURE);
4206 bool strict = jp->script->strictModeCode;
4207 str = js_DecompileToString(cx, "lambda", fun, 0,
4208 false, grouped, strict,
4209 js_DecompileFunction);
4210 if (!str)
4211 return NULL;
4213 sprint_string:
4214 todo = SprintString(&ss->sprinter, str);
4215 break;
4217 case JSOP_CALLEE:
4218 JS_ASSERT(jp->fun && jp->fun->atom);
4219 todo = SprintString(&ss->sprinter, ATOM_TO_STRING(jp->fun->atom));
4220 break;
4222 case JSOP_OBJECT:
4223 LOAD_OBJECT(0);
4224 str = js_ValueToSource(cx, ObjectValue(*obj));
4225 if (!str)
4226 return NULL;
4227 goto sprint_string;
4229 case JSOP_REGEXP:
4230 GET_REGEXP_FROM_BYTECODE(jp->script, pc, 0, obj);
4231 if (!js_regexp_toString(cx, obj, Valueify(&val)))
4232 return NULL;
4233 str = JSVAL_TO_STRING(val);
4234 goto sprint_string;
4236 case JSOP_TABLESWITCH:
4237 case JSOP_TABLESWITCHX:
4239 ptrdiff_t jmplen, off, off2;
4240 jsint j, n, low, high;
4241 TableEntry *table, *tmp;
4243 sn = js_GetSrcNote(jp->script, pc);
4244 LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH);
4245 len = js_GetSrcNoteOffset(sn, 0);
4246 jmplen = (op == JSOP_TABLESWITCH) ? JUMP_OFFSET_LEN
4247 : JUMPX_OFFSET_LEN;
4248 pc2 = pc;
4249 off = GetJumpOffset(pc, pc2);
4250 pc2 += jmplen;
4251 low = GET_JUMP_OFFSET(pc2);
4252 pc2 += JUMP_OFFSET_LEN;
4253 high = GET_JUMP_OFFSET(pc2);
4254 pc2 += JUMP_OFFSET_LEN;
4256 n = high - low + 1;
4257 if (n == 0) {
4258 table = NULL;
4259 j = 0;
4260 ok = JS_TRUE;
4261 } else {
4262 table = (TableEntry *)
4263 cx->malloc((size_t)n * sizeof *table);
4264 if (!table)
4265 return NULL;
4266 for (i = j = 0; i < n; i++) {
4267 table[j].label = NULL;
4268 off2 = GetJumpOffset(pc, pc2);
4269 if (off2) {
4270 sn = js_GetSrcNote(jp->script, pc2);
4271 if (sn) {
4272 LOCAL_ASSERT(SN_TYPE(sn) == SRC_LABEL);
4273 GET_SOURCE_NOTE_ATOM(sn, table[j].label);
4275 table[j].key = INT_TO_JSVAL(low + i);
4276 table[j].offset = off2;
4277 table[j].order = j;
4278 j++;
4280 pc2 += jmplen;
4282 tmp = (TableEntry *)
4283 cx->malloc((size_t)j * sizeof *table);
4284 if (tmp) {
4285 VOUCH_DOES_NOT_REQUIRE_STACK();
4286 ok = js_MergeSort(table, (size_t)j, sizeof(TableEntry),
4287 CompareOffsets, NULL, tmp,
4288 JS_SORTING_GENERIC);
4289 cx->free(tmp);
4290 } else {
4291 ok = JS_FALSE;
4295 if (ok) {
4296 ok = DecompileSwitch(ss, table, (uintN)j, pc, len, off,
4297 JS_FALSE);
4299 cx->free(table);
4300 if (!ok)
4301 return NULL;
4302 todo = -2;
4303 break;
4306 case JSOP_LOOKUPSWITCH:
4307 case JSOP_LOOKUPSWITCHX:
4309 ptrdiff_t jmplen, off, off2;
4310 jsatomid npairs, k;
4311 TableEntry *table;
4313 sn = js_GetSrcNote(jp->script, pc);
4314 LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH);
4315 len = js_GetSrcNoteOffset(sn, 0);
4316 jmplen = (op == JSOP_LOOKUPSWITCH) ? JUMP_OFFSET_LEN
4317 : JUMPX_OFFSET_LEN;
4318 pc2 = pc;
4319 off = GetJumpOffset(pc, pc2);
4320 pc2 += jmplen;
4321 npairs = GET_UINT16(pc2);
4322 pc2 += UINT16_LEN;
4324 table = (TableEntry *)
4325 cx->malloc((size_t)npairs * sizeof *table);
4326 if (!table)
4327 return NULL;
4328 for (k = 0; k < npairs; k++) {
4329 sn = js_GetSrcNote(jp->script, pc2);
4330 if (sn) {
4331 LOCAL_ASSERT(SN_TYPE(sn) == SRC_LABEL);
4332 GET_SOURCE_NOTE_ATOM(sn, table[k].label);
4333 } else {
4334 table[k].label = NULL;
4336 uint16 constIndex = GET_INDEX(pc2);
4337 pc2 += INDEX_LEN;
4338 off2 = GetJumpOffset(pc, pc2);
4339 pc2 += jmplen;
4340 table[k].key = Jsvalify(jp->script->getConst(constIndex));
4341 table[k].offset = off2;
4344 ok = DecompileSwitch(ss, table, (uintN)npairs, pc, len, off,
4345 JS_FALSE);
4346 cx->free(table);
4347 if (!ok)
4348 return NULL;
4349 todo = -2;
4350 break;
4353 case JSOP_CONDSWITCH:
4355 ptrdiff_t off, off2, caseOff;
4356 jsint ncases;
4357 TableEntry *table;
4359 sn = js_GetSrcNote(jp->script, pc);
4360 LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH);
4361 len = js_GetSrcNoteOffset(sn, 0);
4362 off = js_GetSrcNoteOffset(sn, 1);
4365 * Count the cases using offsets from switch to first case,
4366 * and case to case, stored in srcnote immediates.
4368 pc2 = pc;
4369 off2 = off;
4370 for (ncases = 0; off2 != 0; ncases++) {
4371 pc2 += off2;
4372 LOCAL_ASSERT(*pc2 == JSOP_CASE || *pc2 == JSOP_DEFAULT ||
4373 *pc2 == JSOP_CASEX || *pc2 == JSOP_DEFAULTX);
4374 if (*pc2 == JSOP_DEFAULT || *pc2 == JSOP_DEFAULTX) {
4375 /* End of cases, but count default as a case. */
4376 off2 = 0;
4377 } else {
4378 sn = js_GetSrcNote(jp->script, pc2);
4379 LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_PCDELTA);
4380 off2 = js_GetSrcNoteOffset(sn, 0);
4385 * Allocate table and rescan the cases using their srcnotes,
4386 * stashing each case's delta from switch top in table[i].key,
4387 * and the distance to its statements in table[i].offset.
4389 table = (TableEntry *)
4390 cx->malloc((size_t)ncases * sizeof *table);
4391 if (!table)
4392 return NULL;
4393 pc2 = pc;
4394 off2 = off;
4395 for (i = 0; i < ncases; i++) {
4396 pc2 += off2;
4397 LOCAL_ASSERT(*pc2 == JSOP_CASE || *pc2 == JSOP_DEFAULT ||
4398 *pc2 == JSOP_CASEX || *pc2 == JSOP_DEFAULTX);
4399 caseOff = pc2 - pc;
4400 table[i].key = INT_TO_JSVAL((jsint) caseOff);
4401 table[i].offset = caseOff + GetJumpOffset(pc2, pc2);
4402 if (*pc2 == JSOP_CASE || *pc2 == JSOP_CASEX) {
4403 sn = js_GetSrcNote(jp->script, pc2);
4404 LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_PCDELTA);
4405 off2 = js_GetSrcNoteOffset(sn, 0);
4410 * Find offset of default code by fetching the default offset
4411 * from the end of table. JSOP_CONDSWITCH always has a default
4412 * case at the end.
4414 off = JSVAL_TO_INT(table[ncases-1].key);
4415 pc2 = pc + off;
4416 off += GetJumpOffset(pc2, pc2);
4418 ok = DecompileSwitch(ss, table, (uintN)ncases, pc, len, off,
4419 JS_TRUE);
4420 cx->free(table);
4421 if (!ok)
4422 return NULL;
4423 todo = -2;
4424 break;
4427 case JSOP_CASE:
4428 case JSOP_CASEX:
4430 lval = POP_STR();
4431 if (!lval)
4432 return NULL;
4433 js_printf(jp, "\tcase %s:\n", lval);
4434 todo = -2;
4435 break;
4438 case JSOP_DEFFUN:
4439 case JSOP_DEFFUN_FC:
4440 case JSOP_DEFFUN_DBGFC:
4441 LOAD_FUNCTION(0);
4442 todo = -2;
4443 goto do_function;
4444 break;
4446 case JSOP_TRAP:
4447 saveop = op = JS_GetTrapOpcode(cx, jp->script, pc);
4448 *pc = op;
4449 cs = &js_CodeSpec[op];
4450 len = cs->length;
4451 DECOMPILE_CODE(pc, len);
4452 *pc = JSOP_TRAP;
4453 todo = -2;
4454 break;
4456 case JSOP_HOLE:
4457 todo = SprintPut(&ss->sprinter, "", 0);
4458 break;
4460 case JSOP_NEWINIT:
4462 i = pc[1];
4463 LOCAL_ASSERT(i == JSProto_Array || i == JSProto_Object);
4465 todo = ss->sprinter.offset;
4466 #if JS_HAS_SHARP_VARS
4467 op = (JSOp)pc[len];
4468 if (op == JSOP_SHARPINIT)
4469 op = (JSOp)pc[len += JSOP_SHARPINIT_LENGTH];
4470 if (op == JSOP_DEFSHARP) {
4471 pc += len;
4472 cs = &js_CodeSpec[op];
4473 len = cs->length;
4474 if (Sprint(&ss->sprinter, "#%u=",
4475 (unsigned) (jsint) GET_UINT16(pc + UINT16_LEN))
4476 < 0) {
4477 return NULL;
4480 #endif /* JS_HAS_SHARP_VARS */
4481 if (i == JSProto_Array) {
4482 ++ss->inArrayInit;
4483 if (SprintCString(&ss->sprinter, "[") < 0)
4484 return NULL;
4485 } else {
4486 if (SprintCString(&ss->sprinter, "{") < 0)
4487 return NULL;
4489 break;
4492 case JSOP_NEWARRAY:
4494 todo = ss->sprinter.offset;
4495 ++ss->inArrayInit;
4496 if (SprintCString(&ss->sprinter, "[") < 0)
4497 return NULL;
4498 break;
4501 case JSOP_NEWOBJECT:
4503 todo = ss->sprinter.offset;
4504 if (SprintCString(&ss->sprinter, "{") < 0)
4505 return NULL;
4506 break;
4509 case JSOP_ENDINIT:
4511 JSBool inArray;
4513 op = JSOP_NOP; /* turn off parens */
4514 rval = POP_STR();
4515 sn = js_GetSrcNote(jp->script, pc);
4517 /* Skip any #n= prefix to find the opening bracket. */
4518 for (xval = rval; *xval != '[' && *xval != '{'; xval++)
4519 continue;
4520 inArray = (*xval == '[');
4521 if (inArray)
4522 --ss->inArrayInit;
4523 todo = Sprint(&ss->sprinter, "%s%s%c",
4524 rval,
4525 (sn && SN_TYPE(sn) == SRC_CONTINUE) ? ", " : "",
4526 inArray ? ']' : '}');
4527 break;
4531 JSBool isFirst;
4532 const char *maybeComma;
4534 case JSOP_INITELEM:
4535 isFirst = IsInitializerOp(ss->opcodes[ss->top - 3]);
4537 /* Turn off most parens. */
4538 op = JSOP_SETNAME;
4539 rval = POP_STR();
4541 /* Turn off all parens for xval and lval, which we control. */
4542 op = JSOP_NOP;
4543 xval = POP_STR();
4544 lval = POP_STR();
4545 sn = js_GetSrcNote(jp->script, pc);
4547 if (sn && SN_TYPE(sn) == SRC_INITPROP) {
4548 atom = NULL;
4549 goto do_initprop;
4551 maybeComma = isFirst ? "" : ", ";
4552 todo = Sprint(&ss->sprinter, sss_format,
4553 lval,
4554 maybeComma,
4555 rval);
4556 break;
4558 case JSOP_INITPROP:
4559 case JSOP_INITMETHOD:
4560 LOAD_ATOM(0);
4561 xval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom),
4562 jschar(ATOM_IS_IDENTIFIER(atom) ? 0 : '\''));
4563 if (!xval)
4564 return NULL;
4565 isFirst = IsInitializerOp(ss->opcodes[ss->top - 2]);
4566 rval = POP_STR();
4567 lval = POP_STR();
4568 /* fall through */
4570 do_initprop:
4571 maybeComma = isFirst ? "" : ", ";
4572 if (lastop == JSOP_GETTER || lastop == JSOP_SETTER) {
4573 const char *end = rval + strlen(rval);
4575 if (*rval == '(')
4576 ++rval, --end;
4577 LOCAL_ASSERT(strncmp(rval, js_function_str, 8) == 0);
4578 LOCAL_ASSERT(rval[8] == ' ');
4579 rval += 8 + 1;
4580 LOCAL_ASSERT(*end ? *end == ')' : end[-1] == '}');
4581 todo = Sprint(&ss->sprinter, "%s%s%s %s%s%.*s",
4582 lval,
4583 maybeComma,
4584 (lastop == JSOP_GETTER)
4585 ? js_get_str : js_set_str,
4586 xval,
4587 (rval[0] != '(') ? " " : "",
4588 end - rval, rval);
4589 } else {
4590 todo = Sprint(&ss->sprinter, "%s%s%s: %s",
4591 lval, maybeComma, xval, rval);
4593 break;
4596 #if JS_HAS_SHARP_VARS
4597 case JSOP_DEFSHARP:
4598 i = (jsint) GET_UINT16(pc + UINT16_LEN);
4599 rval = POP_STR();
4600 todo = Sprint(&ss->sprinter, "#%u=%s", (unsigned) i, rval);
4601 break;
4603 case JSOP_USESHARP:
4604 i = (jsint) GET_UINT16(pc + UINT16_LEN);
4605 todo = Sprint(&ss->sprinter, "#%u#", (unsigned) i);
4606 break;
4607 #endif /* JS_HAS_SHARP_VARS */
4609 case JSOP_DEBUGGER:
4610 js_printf(jp, "\tdebugger;\n");
4611 todo = -2;
4612 break;
4614 #if JS_HAS_XML_SUPPORT
4615 case JSOP_STARTXML:
4616 case JSOP_STARTXMLEXPR:
4617 inXML = op == JSOP_STARTXML;
4618 todo = -2;
4619 break;
4621 case JSOP_DEFXMLNS:
4622 rval = POP_STR();
4623 js_printf(jp, "\t%s %s %s = %s;\n",
4624 js_default_str, js_xml_str, js_namespace_str, rval);
4625 todo = -2;
4626 break;
4628 case JSOP_ANYNAME:
4629 if (pc[JSOP_ANYNAME_LENGTH] == JSOP_TOATTRNAME) {
4630 len += JSOP_TOATTRNAME_LENGTH;
4631 todo = SprintPut(&ss->sprinter, "@*", 2);
4632 } else {
4633 todo = SprintPut(&ss->sprinter, "*", 1);
4635 break;
4637 case JSOP_QNAMEPART:
4638 LOAD_ATOM(0);
4639 if (pc[JSOP_QNAMEPART_LENGTH] == JSOP_TOATTRNAME) {
4640 saveop = JSOP_TOATTRNAME;
4641 len += JSOP_TOATTRNAME_LENGTH;
4642 lval = "@";
4643 goto do_qname;
4645 goto do_name;
4647 case JSOP_QNAMECONST:
4648 LOAD_ATOM(0);
4649 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
4650 if (!rval)
4651 return NULL;
4652 RETRACT(&ss->sprinter, rval);
4653 lval = POP_STR();
4654 todo = Sprint(&ss->sprinter, "%s::%s", lval, rval);
4655 break;
4657 case JSOP_QNAME:
4658 rval = POP_STR();
4659 lval = POP_STR();
4660 todo = Sprint(&ss->sprinter, "%s::[%s]", lval, rval);
4661 break;
4663 case JSOP_TOATTRNAME:
4664 op = JSOP_NOP; /* turn off parens */
4665 rval = POP_STR();
4666 todo = Sprint(&ss->sprinter, "@[%s]", rval);
4667 break;
4669 case JSOP_TOATTRVAL:
4670 todo = -2;
4671 break;
4673 case JSOP_ADDATTRNAME:
4674 rval = POP_STR();
4675 lval = POP_STR();
4676 todo = Sprint(&ss->sprinter, "%s %s", lval, rval);
4677 /* This gets reset by all XML tag expressions. */
4678 quoteAttr = JS_TRUE;
4679 break;
4681 case JSOP_ADDATTRVAL:
4682 rval = POP_STR();
4683 lval = POP_STR();
4684 if (quoteAttr)
4685 todo = Sprint(&ss->sprinter, "%s=\"%s\"", lval, rval);
4686 else
4687 todo = Sprint(&ss->sprinter, "%s=%s", lval, rval);
4688 break;
4690 case JSOP_BINDXMLNAME:
4691 /* Leave the name stacked and push a dummy string. */
4692 todo = Sprint(&ss->sprinter, "");
4693 break;
4695 case JSOP_SETXMLNAME:
4696 /* Pop the r.h.s., the dummy string, and the name. */
4697 rval = POP_STR();
4698 (void) PopOff(ss, op);
4699 lval = POP_STR();
4700 goto do_setlval;
4702 case JSOP_XMLELTEXPR:
4703 case JSOP_XMLTAGEXPR:
4704 todo = Sprint(&ss->sprinter, "{%s}", POP_STR());
4705 inXML = JS_TRUE;
4706 /* If we're an attribute value, we shouldn't quote this. */
4707 quoteAttr = JS_FALSE;
4708 break;
4710 case JSOP_TOXMLLIST:
4711 op = JSOP_NOP; /* turn off parens */
4712 todo = Sprint(&ss->sprinter, "<>%s</>", POP_STR());
4713 inXML = JS_FALSE;
4714 break;
4716 case JSOP_TOXML:
4717 case JSOP_CALLXMLNAME:
4718 case JSOP_XMLNAME:
4719 case JSOP_FILTER:
4720 /* These ops indicate the end of XML expressions. */
4721 inXML = JS_FALSE;
4722 todo = -2;
4723 break;
4725 case JSOP_ENDFILTER:
4726 rval = POP_STR();
4727 PROPAGATE_CALLNESS();
4728 lval = POP_STR();
4729 todo = Sprint(&ss->sprinter, "%s.(%s)", lval, rval);
4730 break;
4732 case JSOP_DESCENDANTS:
4733 rval = POP_STR();
4734 PROPAGATE_CALLNESS();
4735 lval = POP_STR();
4736 todo = Sprint(&ss->sprinter, "%s..%s", lval, rval);
4737 break;
4739 case JSOP_XMLCDATA:
4740 LOAD_ATOM(0);
4741 todo = SprintPut(&ss->sprinter, "<![CDATA[", 9);
4742 if (!QuoteString(&ss->sprinter, ATOM_TO_STRING(atom),
4743 DONT_ESCAPE))
4744 return NULL;
4745 SprintPut(&ss->sprinter, "]]>", 3);
4746 break;
4748 case JSOP_XMLCOMMENT:
4749 LOAD_ATOM(0);
4750 todo = SprintPut(&ss->sprinter, "<!--", 4);
4751 if (!QuoteString(&ss->sprinter, ATOM_TO_STRING(atom),
4752 DONT_ESCAPE))
4753 return NULL;
4754 SprintPut(&ss->sprinter, "-->", 3);
4755 break;
4757 case JSOP_XMLPI:
4758 LOAD_ATOM(0);
4759 rval = JS_strdup(cx, POP_STR());
4760 if (!rval)
4761 return NULL;
4762 todo = SprintPut(&ss->sprinter, "<?", 2);
4763 ok = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0) &&
4764 (*rval == '\0' ||
4765 (SprintPut(&ss->sprinter, " ", 1) >= 0 &&
4766 SprintCString(&ss->sprinter, rval)));
4767 cx->free((char *)rval);
4768 if (!ok)
4769 return NULL;
4770 SprintPut(&ss->sprinter, "?>", 2);
4771 break;
4773 case JSOP_GETFUNNS:
4774 todo = SprintPut(&ss->sprinter, js_function_str, 8);
4775 break;
4776 #endif /* JS_HAS_XML_SUPPORT */
4778 default:
4779 todo = -2;
4780 break;
4784 if (todo < 0) {
4785 /* -2 means "don't push", -1 means reported error. */
4786 if (todo == -1)
4787 return NULL;
4788 } else {
4789 if (!PushOff(ss, todo, saveop))
4790 return NULL;
4793 if (cs->format & JOF_CALLOP) {
4794 todo = Sprint(&ss->sprinter, "");
4795 if (todo < 0 || !PushOff(ss, todo, saveop))
4796 return NULL;
4799 pc += len;
4803 * Undefine local macros.
4805 #undef inXML
4806 #undef DECOMPILE_CODE
4807 #undef NEXT_OP
4808 #undef TOP_STR
4809 #undef POP_STR
4810 #undef POP_STR_PREC
4811 #undef LOCAL_ASSERT
4812 #undef ATOM_IS_IDENTIFIER
4813 #undef GET_QUOTE_AND_FMT
4814 #undef GET_ATOM_QUOTE_AND_FMT
4816 return pc;
4819 static JSBool
4820 DecompileCode(JSPrinter *jp, JSScript *script, jsbytecode *pc, uintN len,
4821 uintN pcdepth)
4823 uintN depth, i;
4824 SprintStack ss;
4825 JSContext *cx;
4826 void *mark;
4827 JSBool ok;
4828 JSScript *oldscript;
4829 char *last;
4831 depth = StackDepth(script);
4832 JS_ASSERT(pcdepth <= depth);
4833 cx = jp->sprinter.context;
4835 AutoScriptUntrapper untrapper(cx, script, &pc);
4837 /* Initialize a sprinter for use with the offset stack. */
4838 mark = JS_ARENA_MARK(&cx->tempPool);
4839 ok = InitSprintStack(cx, &ss, jp, depth);
4840 if (!ok)
4841 goto out;
4844 * If we are called from js_DecompileValueGenerator with a portion of
4845 * script's bytecode that starts with a non-zero model stack depth given
4846 * by pcdepth, attempt to initialize the missing string offsets in ss to
4847 * |spindex| negative indexes from fp->sp for the activation fp in which
4848 * the error arose.
4850 * See js_DecompileValueGenerator for how its |spindex| parameter is used,
4851 * and see also GetOff, which makes use of the ss.offsets[i] < -1 that are
4852 * potentially stored below.
4854 ss.top = pcdepth;
4855 if (pcdepth != 0) {
4856 for (i = 0; i < pcdepth; i++) {
4857 ss.offsets[i] = -2 - (ptrdiff_t)i;
4858 ss.opcodes[i] = *jp->pcstack[i];
4862 /* Call recursive subroutine to do the hard work. */
4863 oldscript = jp->script;
4864 jp->script = script;
4865 ok = Decompile(&ss, pc, len, JSOP_NOP) != NULL;
4866 jp->script = oldscript;
4868 /* If the given code didn't empty the stack, do it now. */
4869 if (ok && ss.top) {
4870 do {
4871 last = OFF2STR(&ss.sprinter, PopOff(&ss, JSOP_POP));
4872 } while (ss.top > pcdepth);
4873 js_printf(jp, "%s", last);
4876 out:
4877 /* Free all temporary stuff allocated under this call. */
4878 JS_ARENA_RELEASE(&cx->tempPool, mark);
4879 return ok;
4883 * Decompile a function body, expression closure expression, or complete
4884 * script. Start at |pc|; go to the end of |script|. Include a directive
4885 * prologue, if appropriate.
4887 static JSBool
4888 DecompileBody(JSPrinter *jp, JSScript *script, jsbytecode *pc)
4890 /* Print a strict mode code directive, if needed. */
4891 if (script->strictModeCode && !jp->strict) {
4892 if (jp->fun && (jp->fun->flags & JSFUN_EXPR_CLOSURE)) {
4894 * We have no syntax for strict function expressions;
4895 * at least give a hint.
4897 js_printf(jp, "\t/* use strict */ \n");
4898 } else {
4899 js_printf(jp, "\t\"use strict\";\n");
4901 jp->strict = true;
4904 jsbytecode *end = script->code + script->length;
4905 return DecompileCode(jp, script, pc, end - pc, 0);
4908 JSBool
4909 js_DecompileScript(JSPrinter *jp, JSScript *script)
4911 return DecompileBody(jp, script, script->code);
4914 JSString *
4915 js_DecompileToString(JSContext *cx, const char *name, JSFunction *fun,
4916 uintN indent, JSBool pretty, JSBool grouped, JSBool strict,
4917 JSDecompilerPtr decompiler)
4919 JSPrinter *jp;
4920 JSString *str;
4922 jp = js_NewPrinter(cx, name, fun, indent, pretty, grouped, strict);
4923 if (!jp)
4924 return NULL;
4925 if (decompiler(jp))
4926 str = js_GetPrinterOutput(jp);
4927 else
4928 str = NULL;
4929 js_DestroyPrinter(jp);
4930 return str;
4933 static const char native_code_str[] = "\t[native code]\n";
4935 JSBool
4936 js_DecompileFunctionBody(JSPrinter *jp)
4938 JSScript *script;
4940 JS_ASSERT(jp->fun);
4941 JS_ASSERT(!jp->script);
4942 if (!FUN_INTERPRETED(jp->fun)) {
4943 js_printf(jp, native_code_str);
4944 return JS_TRUE;
4947 script = jp->fun->u.i.script;
4948 return DecompileBody(jp, script, script->code);
4951 JSBool
4952 js_DecompileFunction(JSPrinter *jp)
4954 JSFunction *fun;
4955 uintN i;
4956 JSAtom *param;
4957 jsbytecode *pc, *endpc;
4958 JSBool ok;
4960 fun = jp->fun;
4961 JS_ASSERT(fun);
4962 JS_ASSERT(!jp->script);
4965 * If pretty, conform to ECMA-262 Edition 3, 15.3.4.2, by decompiling a
4966 * FunctionDeclaration. Otherwise, check the JSFUN_LAMBDA flag and force
4967 * an expression by parenthesizing.
4969 if (jp->pretty) {
4970 js_printf(jp, "\t");
4971 } else {
4972 if (!jp->grouped && (fun->flags & JSFUN_LAMBDA))
4973 js_puts(jp, "(");
4976 js_printf(jp, "%s ", js_function_str);
4977 if (fun->atom && !QuoteString(&jp->sprinter, ATOM_TO_STRING(fun->atom), 0))
4978 return JS_FALSE;
4979 js_puts(jp, "(");
4981 if (!FUN_INTERPRETED(fun)) {
4982 js_printf(jp, ") {\n");
4983 jp->indent += 4;
4984 js_printf(jp, native_code_str);
4985 jp->indent -= 4;
4986 js_printf(jp, "\t}");
4987 } else {
4988 JSScript *script = fun->u.i.script;
4989 #if JS_HAS_DESTRUCTURING
4990 SprintStack ss;
4991 void *mark;
4992 #endif
4994 /* Print the parameters. */
4995 pc = script->main;
4996 AutoScriptUntrapper untrapper(jp->sprinter.context, script, &pc);
4997 endpc = pc + script->length;
4998 ok = JS_TRUE;
5000 #if JS_HAS_DESTRUCTURING
5001 ss.printer = NULL;
5002 jp->script = script;
5003 mark = JS_ARENA_MARK(&jp->sprinter.context->tempPool);
5004 #endif
5006 for (i = 0; i < fun->nargs; i++) {
5007 if (i > 0)
5008 js_puts(jp, ", ");
5010 param = GetArgOrVarAtom(jp, i);
5012 #if JS_HAS_DESTRUCTURING
5013 #define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, JS_FALSE)
5015 if (!param) {
5016 ptrdiff_t todo;
5017 const char *lval;
5019 LOCAL_ASSERT(*pc == JSOP_GETARG);
5020 pc += JSOP_GETARG_LENGTH;
5021 LOCAL_ASSERT(*pc == JSOP_DUP);
5022 if (!ss.printer) {
5023 ok = InitSprintStack(jp->sprinter.context, &ss, jp, StackDepth(script));
5024 if (!ok)
5025 break;
5027 pc = DecompileDestructuring(&ss, pc, endpc);
5028 if (!pc) {
5029 ok = JS_FALSE;
5030 break;
5032 LOCAL_ASSERT(*pc == JSOP_POP);
5033 pc += JSOP_POP_LENGTH;
5034 lval = PopStr(&ss, JSOP_NOP);
5035 todo = SprintCString(&jp->sprinter, lval);
5036 if (todo < 0) {
5037 ok = JS_FALSE;
5038 break;
5040 continue;
5043 #undef LOCAL_ASSERT
5044 #endif
5046 if (!QuoteString(&jp->sprinter, ATOM_TO_STRING(param), 0)) {
5047 ok = JS_FALSE;
5048 break;
5052 #if JS_HAS_DESTRUCTURING
5053 jp->script = NULL;
5054 JS_ARENA_RELEASE(&jp->sprinter.context->tempPool, mark);
5055 #endif
5056 if (!ok)
5057 return JS_FALSE;
5058 js_printf(jp, ") ");
5059 if (!(fun->flags & JSFUN_EXPR_CLOSURE)) {
5060 js_printf(jp, "{\n");
5061 jp->indent += 4;
5064 ok = DecompileBody(jp, script, pc);
5065 if (!ok)
5066 return JS_FALSE;
5068 if (!(fun->flags & JSFUN_EXPR_CLOSURE)) {
5069 jp->indent -= 4;
5070 js_printf(jp, "\t}");
5074 if (!jp->pretty && !jp->grouped && (fun->flags & JSFUN_LAMBDA))
5075 js_puts(jp, ")");
5077 return JS_TRUE;
5080 char *
5081 js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v_in,
5082 JSString *fallback)
5084 JSStackFrame *fp;
5085 JSScript *script;
5086 jsbytecode *pc;
5088 Value v = Valueify(v_in);
5090 JS_ASSERT(spindex < 0 ||
5091 spindex == JSDVG_IGNORE_STACK ||
5092 spindex == JSDVG_SEARCH_STACK);
5094 LeaveTrace(cx);
5096 if (!cx->regs || !cx->regs->fp || !cx->regs->fp->isScriptFrame())
5097 goto do_fallback;
5099 fp = cx->regs->fp;
5100 script = fp->script();
5101 pc = fp->hasImacropc() ? fp->imacropc() : cx->regs->pc;
5102 JS_ASSERT(script->code <= pc && pc < script->code + script->length);
5104 if (pc < script->main)
5105 goto do_fallback;
5107 if (spindex != JSDVG_IGNORE_STACK) {
5108 jsbytecode **pcstack;
5111 * Prepare computing pcstack containing pointers to opcodes that
5112 * populated interpreter's stack with its current content.
5114 pcstack = (jsbytecode **)
5115 cx->malloc(StackDepth(script) * sizeof *pcstack);
5116 if (!pcstack)
5117 return NULL;
5118 intN pcdepth = ReconstructPCStack(cx, script, pc, pcstack);
5119 if (pcdepth < 0)
5120 goto release_pcstack;
5122 if (spindex != JSDVG_SEARCH_STACK) {
5123 JS_ASSERT(spindex < 0);
5124 pcdepth += spindex;
5125 if (pcdepth < 0)
5126 goto release_pcstack;
5127 pc = pcstack[pcdepth];
5128 } else {
5130 * We search from fp->sp to base to find the most recently
5131 * calculated value matching v under assumption that it is
5132 * it that caused exception, see bug 328664.
5134 Value *stackBase = fp->base();
5135 Value *sp = cx->regs->sp;
5136 do {
5137 if (sp == stackBase) {
5138 pcdepth = -1;
5139 goto release_pcstack;
5141 } while (*--sp != v);
5144 * The value may have come from beyond stackBase + pcdepth, meaning
5145 * that it came from a temporary slot pushed by the interpreter or
5146 * arguments pushed for an Invoke call. Only update pc if beneath
5147 * stackBase + pcdepth. If above, the value may or may not be
5148 * produced by the current pc. Since it takes a fairly contrived
5149 * combination of calls to produce a situation where this is not
5150 * what we want, we just use the current pc.
5152 if (sp < stackBase + pcdepth)
5153 pc = pcstack[sp - stackBase];
5156 release_pcstack:
5157 cx->free(pcstack);
5158 if (pcdepth < 0)
5159 goto do_fallback;
5163 jsbytecode* basepc = cx->regs->pc;
5164 jsbytecode* savedImacropc = fp->maybeImacropc();
5165 if (savedImacropc) {
5166 JS_ASSERT(cx->hasfp());
5167 cx->regs->pc = savedImacropc;
5168 fp->clearImacropc();
5172 * FIXME: bug 489843. Stack reconstruction may have returned a pc
5173 * value *inside* an imacro; this would confuse the decompiler.
5175 char *name;
5176 if (savedImacropc && size_t(pc - script->code) >= script->length)
5177 name = FAILED_EXPRESSION_DECOMPILER;
5178 else
5179 name = DecompileExpression(cx, script, fp->maybeFun(), pc);
5181 if (savedImacropc) {
5182 JS_ASSERT(cx->hasfp());
5183 cx->regs->pc = basepc;
5184 fp->setImacropc(savedImacropc);
5187 if (name != FAILED_EXPRESSION_DECOMPILER)
5188 return name;
5191 do_fallback:
5192 if (!fallback) {
5193 fallback = js_ValueToSource(cx, v);
5194 if (!fallback)
5195 return NULL;
5197 size_t length = fallback->length();
5198 const jschar *chars = fallback->getChars(cx);
5199 if (!chars)
5200 return NULL;
5201 return js_DeflateString(cx, chars, length);
5204 static char *
5205 DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun,
5206 jsbytecode *pc)
5208 JSOp op;
5209 const JSCodeSpec *cs;
5210 jsbytecode *begin, *end;
5211 jssrcnote *sn;
5212 ptrdiff_t len;
5213 jsbytecode **pcstack;
5214 intN pcdepth;
5215 JSPrinter *jp;
5216 char *name;
5218 JS_ASSERT(script->code <= pc && pc < script->code + script->length);
5220 pcstack = NULL;
5221 AutoScriptUntrapper untrapper(cx, script, &pc);
5222 op = (JSOp) *pc;
5224 /* None of these stack-writing ops generates novel values. */
5225 JS_ASSERT(op != JSOP_CASE && op != JSOP_CASEX &&
5226 op != JSOP_DUP && op != JSOP_DUP2);
5228 /* JSOP_PUSH is used to generate undefined for group assignment holes. */
5229 if (op == JSOP_PUSH) {
5230 name = JS_strdup(cx, js_undefined_str);
5231 goto out;
5235 * |this| could convert to a very long object initialiser, so cite it by
5236 * its keyword name instead.
5238 if (op == JSOP_THIS) {
5239 name = JS_strdup(cx, js_this_str);
5240 goto out;
5244 * JSOP_BINDNAME is special: it generates a value, the base object of a
5245 * reference. But if it is the generating op for a diagnostic produced by
5246 * js_DecompileValueGenerator, the name being bound is irrelevant. Just
5247 * fall back to the base object.
5249 if (op == JSOP_BINDNAME) {
5250 name = FAILED_EXPRESSION_DECOMPILER;
5251 goto out;
5254 /* NAME ops are self-contained, others require left or right context. */
5255 cs = &js_CodeSpec[op];
5256 begin = pc;
5257 end = pc + cs->length;
5258 switch (JOF_MODE(cs->format)) {
5259 case JOF_PROP:
5260 case JOF_ELEM:
5261 case JOF_XMLNAME:
5262 case 0:
5263 sn = js_GetSrcNote(script, pc);
5264 if (!sn) {
5265 name = FAILED_EXPRESSION_DECOMPILER;
5266 goto out;
5268 switch (SN_TYPE(sn)) {
5269 case SRC_PCBASE:
5270 begin -= js_GetSrcNoteOffset(sn, 0);
5271 break;
5272 case SRC_PCDELTA:
5273 end = begin + js_GetSrcNoteOffset(sn, 0);
5274 begin += cs->length;
5275 break;
5276 default:
5277 name = FAILED_EXPRESSION_DECOMPILER;
5278 goto out;
5280 break;
5281 default:;
5283 len = end - begin;
5284 if (len <= 0) {
5285 name = FAILED_EXPRESSION_DECOMPILER;
5286 goto out;
5289 pcstack = (jsbytecode **)
5290 cx->malloc(StackDepth(script) * sizeof *pcstack);
5291 if (!pcstack) {
5292 name = NULL;
5293 goto out;
5296 MUST_FLOW_THROUGH("out");
5297 pcdepth = ReconstructPCStack(cx, script, begin, pcstack);
5298 if (pcdepth < 0) {
5299 name = FAILED_EXPRESSION_DECOMPILER;
5300 goto out;
5303 name = NULL;
5304 jp = js_NewPrinter(cx, "js_DecompileValueGenerator", fun, 0,
5305 false, false, false);
5306 if (jp) {
5307 jp->dvgfence = end;
5308 jp->pcstack = pcstack;
5309 if (DecompileCode(jp, script, begin, (uintN) len, (uintN) pcdepth)) {
5310 name = (jp->sprinter.base) ? jp->sprinter.base : (char *) "";
5311 name = JS_strdup(cx, name);
5313 js_DestroyPrinter(jp);
5316 out:
5317 cx->free(pcstack);
5318 return name;
5321 uintN
5322 js_ReconstructStackDepth(JSContext *cx, JSScript *script, jsbytecode *pc)
5324 return ReconstructPCStack(cx, script, pc, NULL);
5327 #define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, -1);
5329 static intN
5330 SimulateOp(JSContext *cx, JSScript *script, JSOp op, const JSCodeSpec *cs,
5331 jsbytecode *pc, jsbytecode **pcstack, uintN &pcdepth)
5333 uintN nuses = js_GetStackUses(cs, op, pc);
5334 uintN ndefs = js_GetStackDefs(cx, cs, op, script, pc);
5335 LOCAL_ASSERT(pcdepth >= nuses);
5336 pcdepth -= nuses;
5337 LOCAL_ASSERT(pcdepth + ndefs <= StackDepth(script));
5340 * Fill the slots that the opcode defines withs its pc unless it just
5341 * reshuffles the stack. In the latter case we want to preserve the
5342 * opcode that generated the original value.
5344 switch (op) {
5345 default:
5346 if (pcstack) {
5347 for (uintN i = 0; i != ndefs; ++i)
5348 pcstack[pcdepth + i] = pc;
5350 break;
5352 case JSOP_CASE:
5353 case JSOP_CASEX:
5354 /* Keep the switch value. */
5355 JS_ASSERT(ndefs == 1);
5356 break;
5358 case JSOP_DUP:
5359 JS_ASSERT(ndefs == 2);
5360 if (pcstack)
5361 pcstack[pcdepth + 1] = pcstack[pcdepth];
5362 break;
5364 case JSOP_DUP2:
5365 JS_ASSERT(ndefs == 4);
5366 if (pcstack) {
5367 pcstack[pcdepth + 2] = pcstack[pcdepth];
5368 pcstack[pcdepth + 3] = pcstack[pcdepth + 1];
5370 break;
5372 case JSOP_SWAP:
5373 JS_ASSERT(ndefs == 2);
5374 if (pcstack) {
5375 jsbytecode *tmp = pcstack[pcdepth + 1];
5376 pcstack[pcdepth + 1] = pcstack[pcdepth];
5377 pcstack[pcdepth] = tmp;
5379 break;
5381 pcdepth += ndefs;
5382 return pcdepth;
5385 #ifdef JS_TRACER
5387 #undef LOCAL_ASSERT
5388 #define LOCAL_ASSERT(expr) LOCAL_ASSERT_CUSTOM(expr, goto failure);
5390 static intN
5391 SimulateImacroCFG(JSContext *cx, JSScript *script,
5392 uintN pcdepth, jsbytecode *pc, jsbytecode *target,
5393 jsbytecode **pcstack)
5395 size_t nbytes = 0;
5396 jsbytecode** tmp_pcstack = NULL;
5397 if (pcstack) {
5398 nbytes = StackDepth(script) * sizeof *pcstack;
5399 tmp_pcstack = (jsbytecode **) cx->malloc(nbytes);
5400 if (!tmp_pcstack)
5401 return -1;
5402 memcpy(tmp_pcstack, pcstack, nbytes);
5405 ptrdiff_t oplen;
5406 for (; pc < target; pc += oplen) {
5407 JSOp op = js_GetOpcode(cx, script, pc);
5408 const JSCodeSpec *cs = &js_CodeSpec[op];
5409 oplen = cs->length;
5410 if (oplen < 0)
5411 oplen = js_GetVariableBytecodeLength(pc);
5413 if (SimulateOp(cx, script, op, cs, pc, tmp_pcstack, pcdepth) < 0)
5414 goto failure;
5416 uint32 type = cs->format & JOF_TYPEMASK;
5417 if (type == JOF_JUMP || type == JOF_JUMPX) {
5418 ptrdiff_t jmpoff = (type == JOF_JUMP) ? GET_JUMP_OFFSET(pc)
5419 : GET_JUMPX_OFFSET(pc);
5420 LOCAL_ASSERT(jmpoff >= 0);
5421 intN tmp_pcdepth = SimulateImacroCFG(cx, script, pcdepth, pc + jmpoff,
5422 target, tmp_pcstack);
5423 if (tmp_pcdepth >= 0) {
5424 pcdepth = uintN(tmp_pcdepth);
5425 goto success;
5428 if (op == JSOP_GOTO || op == JSOP_GOTOX)
5429 goto failure;
5433 if (pc > target)
5434 goto failure;
5436 LOCAL_ASSERT(pc == target);
5438 success:
5439 if (tmp_pcstack) {
5440 memcpy(pcstack, tmp_pcstack, nbytes);
5441 cx->free(tmp_pcstack);
5443 return pcdepth;
5445 failure:
5446 if (tmp_pcstack)
5447 cx->free(tmp_pcstack);
5448 return -1;
5451 #undef LOCAL_ASSERT
5452 #define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, -1);
5454 static intN
5455 ReconstructImacroPCStack(JSContext *cx, JSScript *script,
5456 jsbytecode *imacstart, jsbytecode *target,
5457 jsbytecode **pcstack)
5460 * Begin with a recursive call back to ReconstructPCStack to pick up
5461 * the state-of-the-world at the *start* of the imacro.
5463 JSStackFrame *fp = js_GetScriptedCaller(cx, NULL);
5464 JS_ASSERT(fp->hasImacropc());
5465 intN pcdepth = ReconstructPCStack(cx, script, fp->imacropc(), pcstack);
5466 if (pcdepth < 0)
5467 return pcdepth;
5468 return SimulateImacroCFG(cx, script, pcdepth, imacstart, target, pcstack);
5471 extern jsbytecode* js_GetImacroStart(jsbytecode* pc);
5472 #endif
5474 static intN
5475 ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *target,
5476 jsbytecode **pcstack)
5479 * Walk forward from script->main and compute the stack depth and stack of
5480 * operand-generating opcode PCs in pcstack.
5482 * FIXME: Code to compute oplen copied from js_Disassemble1 and reduced.
5483 * FIXME: Optimize to use last empty-stack sequence point.
5485 #ifdef JS_TRACER
5486 jsbytecode *imacstart = js_GetImacroStart(target);
5488 if (imacstart)
5489 return ReconstructImacroPCStack(cx, script, imacstart, target, pcstack);
5490 #endif
5492 LOCAL_ASSERT(script->main <= target && target < script->code + script->length);
5493 jsbytecode *pc = script->main;
5494 uintN pcdepth = 0;
5495 ptrdiff_t oplen;
5496 for (; pc < target; pc += oplen) {
5497 JSOp op = js_GetOpcode(cx, script, pc);
5498 const JSCodeSpec *cs = &js_CodeSpec[op];
5499 oplen = cs->length;
5500 if (oplen < 0)
5501 oplen = js_GetVariableBytecodeLength(pc);
5504 * A (C ? T : E) expression requires skipping either T (if target is in
5505 * E) or both T and E (if target is after the whole expression) before
5506 * adjusting pcdepth based on the JSOP_IFEQ or JSOP_IFEQX at pc that
5507 * tests condition C. We know that the stack depth can't change from
5508 * what it was with C on top of stack.
5510 jssrcnote *sn = js_GetSrcNote(script, pc);
5511 if (sn && SN_TYPE(sn) == SRC_COND) {
5512 ptrdiff_t jmpoff = js_GetSrcNoteOffset(sn, 0);
5513 if (pc + jmpoff < target) {
5514 pc += jmpoff;
5515 op = js_GetOpcode(cx, script, pc);
5516 JS_ASSERT(op == JSOP_GOTO || op == JSOP_GOTOX);
5517 cs = &js_CodeSpec[op];
5518 oplen = cs->length;
5519 JS_ASSERT(oplen > 0);
5520 ptrdiff_t jmplen = GetJumpOffset(pc, pc);
5521 if (pc + jmplen < target) {
5522 oplen = (uintN) jmplen;
5523 continue;
5527 * Ok, target lies in E. Manually pop C off the model stack,
5528 * since we have moved beyond the IFEQ now.
5530 LOCAL_ASSERT(pcdepth != 0);
5531 --pcdepth;
5535 /* Ignore early-exit code, which is annotated SRC_HIDDEN. */
5536 if (sn && SN_TYPE(sn) == SRC_HIDDEN)
5537 continue;
5539 if (SimulateOp(cx, script, op, cs, pc, pcstack, pcdepth) < 0)
5540 return -1;
5543 LOCAL_ASSERT(pc == target);
5544 return pcdepth;
5546 #undef LOCAL_ASSERT
5549 #undef LOCAL_ASSERT_RV