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