1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set sw=4 ts=8 et tw=78:
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 #include "jsstddef.h"
45 #ifdef HAVE_MEMORY_H
46 #include <memory.h>
47 #endif
48 #include <stdarg.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include "jstypes.h"
53 #include "jsarena.h" /* Added by JSIFY */
54 #include "jsutil.h" /* Added by JSIFY */
55 #include "jsdtoa.h"
56 #include "jsprf.h"
57 #include "jsapi.h"
58 #include "jsarray.h"
59 #include "jsatom.h"
60 #include "jscntxt.h"
61 #include "jsconfig.h"
62 #include "jsdbgapi.h"
63 #include "jsemit.h"
64 #include "jsfun.h"
65 #include "jslock.h"
66 #include "jsobj.h"
67 #include "jsopcode.h"
68 #include "jsregexp.h"
69 #include "jsscan.h"
70 #include "jsscope.h"
71 #include "jsscript.h"
72 #include "jsstr.h"
73 
74 #if JS_HAS_DESTRUCTURING
75 # include "jsnum.h"
76 #endif
77 
78 static const char js_incop_strs[][3] = {"++", "--"};
79 
80 /* Pollute the namespace locally for MSVC Win16, but not for WatCom.  */
81 #ifdef __WINDOWS_386__
82     #ifdef FAR
83         #undef FAR
84     #endif
85 #else  /* !__WINDOWS_386__ */
86 #ifndef FAR
87 #define FAR
88 #endif
89 #endif /* !__WINDOWS_386__ */
90 
91 const JSCodeSpec FAR js_CodeSpec[] = {
92 #define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
93     {name,token,length,nuses,ndefs,prec,format},
94 #include "jsopcode.tbl"
95 #undef OPDEF
96 };
97 
98 uintN js_NumCodeSpecs = sizeof (js_CodeSpec) / sizeof js_CodeSpec[0];
99 
100 /************************************************************************/
101 
102 static ptrdiff_t
GetJumpOffset(jsbytecode * pc,jsbytecode * pc2)103 GetJumpOffset(jsbytecode *pc, jsbytecode *pc2)
104 {
105     uint32 type;
106 
107     type = (js_CodeSpec[*pc].format & JOF_TYPEMASK);
108     if (JOF_TYPE_IS_EXTENDED_JUMP(type))
109         return GET_JUMPX_OFFSET(pc2);
110     return GET_JUMP_OFFSET(pc2);
111 }
112 
113 #ifdef DEBUG
114 
115 JS_FRIEND_API(JSBool)
js_Disassemble(JSContext * cx,JSScript * script,JSBool lines,FILE * fp)116 js_Disassemble(JSContext *cx, JSScript *script, JSBool lines, FILE *fp)
117 {
118     jsbytecode *pc, *end;
119     uintN len;
120 
121     pc = script->code;
122     end = pc + script->length;
123     while (pc < end) {
124         if (pc == script->main)
125             fputs("main:\n", fp);
126         len = js_Disassemble1(cx, script, pc,
127                               PTRDIFF(pc, script->code, jsbytecode),
128                               lines, fp);
129         if (!len)
130             return JS_FALSE;
131         pc += len;
132     }
133     return JS_TRUE;
134 }
135 
136 const char *
ToDisassemblySource(JSContext * cx,jsval v)137 ToDisassemblySource(JSContext *cx, jsval v)
138 {
139     JSObject *obj;
140     JSScopeProperty *sprop;
141     char *source;
142     const char *bytes;
143     JSString *str;
144 
145     if (!JSVAL_IS_PRIMITIVE(v)) {
146         obj = JSVAL_TO_OBJECT(v);
147         if (OBJ_GET_CLASS(cx, obj) == &js_BlockClass) {
148             source = JS_sprintf_append(NULL, "depth %d {",
149                                        OBJ_BLOCK_DEPTH(cx, obj));
150             for (sprop = OBJ_SCOPE(obj)->lastProp; sprop;
151                  sprop = sprop->parent) {
152                 bytes = js_AtomToPrintableString(cx, JSID_TO_ATOM(sprop->id));
153                 if (!bytes)
154                     return NULL;
155                 source = JS_sprintf_append(source, "%s: %d%s",
156                                            bytes, sprop->shortid,
157                                            sprop->parent ? ", " : "");
158             }
159             source = JS_sprintf_append(source, "}");
160             if (!source)
161                 return NULL;
162             str = JS_NewString(cx, source, strlen(source));
163             if (!str)
164                 return NULL;
165             return JS_GetStringBytes(str);
166         }
167     }
168     return js_ValueToPrintableSource(cx, v);
169 }
170 
171 JS_FRIEND_API(uintN)
js_Disassemble1(JSContext * cx,JSScript * script,jsbytecode * pc,uintN loc,JSBool lines,FILE * fp)172 js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc, uintN loc,
173                 JSBool lines, FILE *fp)
174 {
175     JSOp op;
176     const JSCodeSpec *cs;
177     ptrdiff_t len, off, jmplen;
178     uint32 type;
179     JSAtom *atom;
180     const char *bytes;
181 
182     op = (JSOp)*pc;
183     if (op >= JSOP_LIMIT) {
184         char numBuf1[12], numBuf2[12];
185         JS_snprintf(numBuf1, sizeof numBuf1, "%d", op);
186         JS_snprintf(numBuf2, sizeof numBuf2, "%d", JSOP_LIMIT);
187         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
188                              JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2);
189         return 0;
190     }
191     cs = &js_CodeSpec[op];
192     len = (ptrdiff_t) cs->length;
193     fprintf(fp, "%05u:", loc);
194     if (lines)
195         fprintf(fp, "%4u", JS_PCToLineNumber(cx, script, pc));
196     fprintf(fp, "  %s", cs->name);
197     type = cs->format & JOF_TYPEMASK;
198     switch (type) {
199       case JOF_BYTE:
200         if (op == JSOP_TRAP) {
201             op = JS_GetTrapOpcode(cx, script, pc);
202             if (op == JSOP_LIMIT)
203                 return 0;
204             len = (ptrdiff_t) js_CodeSpec[op].length;
205         }
206         break;
207 
208       case JOF_JUMP:
209       case JOF_JUMPX:
210         off = GetJumpOffset(pc, pc);
211         fprintf(fp, " %u (%d)", loc + off, off);
212         break;
213 
214       case JOF_CONST:
215         atom = GET_ATOM(cx, script, pc);
216         bytes = ToDisassemblySource(cx, ATOM_KEY(atom));
217         if (!bytes)
218             return 0;
219         fprintf(fp, " %s", bytes);
220         break;
221 
222       case JOF_UINT16:
223       case JOF_LOCAL:
224         fprintf(fp, " %u", GET_UINT16(pc));
225         break;
226 
227       case JOF_TABLESWITCH:
228       case JOF_TABLESWITCHX:
229       {
230         jsbytecode *pc2;
231         jsint i, low, high;
232 
233         jmplen = (type == JOF_TABLESWITCH) ? JUMP_OFFSET_LEN
234                                            : JUMPX_OFFSET_LEN;
235         pc2 = pc;
236         off = GetJumpOffset(pc, pc2);
237         pc2 += jmplen;
238         low = GET_JUMP_OFFSET(pc2);
239         pc2 += JUMP_OFFSET_LEN;
240         high = GET_JUMP_OFFSET(pc2);
241         pc2 += JUMP_OFFSET_LEN;
242         fprintf(fp, " defaultOffset %d low %d high %d", off, low, high);
243         for (i = low; i <= high; i++) {
244             off = GetJumpOffset(pc, pc2);
245             fprintf(fp, "\n\t%d: %d", i, off);
246             pc2 += jmplen;
247         }
248         len = 1 + pc2 - pc;
249         break;
250       }
251 
252       case JOF_LOOKUPSWITCH:
253       case JOF_LOOKUPSWITCHX:
254       {
255         jsbytecode *pc2;
256         jsatomid npairs;
257 
258         jmplen = (type == JOF_LOOKUPSWITCH) ? JUMP_OFFSET_LEN
259                                             : JUMPX_OFFSET_LEN;
260         pc2 = pc;
261         off = GetJumpOffset(pc, pc2);
262         pc2 += jmplen;
263         npairs = GET_ATOM_INDEX(pc2);
264         pc2 += ATOM_INDEX_LEN;
265         fprintf(fp, " offset %d npairs %u", off, (uintN) npairs);
266         while (npairs) {
267             atom = GET_ATOM(cx, script, pc2);
268             pc2 += ATOM_INDEX_LEN;
269             off = GetJumpOffset(pc, pc2);
270             pc2 += jmplen;
271 
272             bytes = ToDisassemblySource(cx, ATOM_KEY(atom));
273             if (!bytes)
274                 return 0;
275             fprintf(fp, "\n\t%s: %d", bytes, off);
276             npairs--;
277         }
278         len = 1 + pc2 - pc;
279         break;
280       }
281 
282       case JOF_QARG:
283         fprintf(fp, " %u", GET_ARGNO(pc));
284         break;
285 
286       case JOF_QVAR:
287         fprintf(fp, " %u", GET_VARNO(pc));
288         break;
289 
290       case JOF_INDEXCONST:
291         fprintf(fp, " %u", GET_VARNO(pc));
292         pc += VARNO_LEN;
293         atom = GET_ATOM(cx, script, pc);
294         bytes = ToDisassemblySource(cx, ATOM_KEY(atom));
295         if (!bytes)
296             return 0;
297         fprintf(fp, " %s", bytes);
298         break;
299 
300       case JOF_UINT24:
301         if (op == JSOP_FINDNAME) {
302             /* Special case to avoid a JOF_FINDNAME just for this op. */
303             atom = js_GetAtom(cx, &script->atomMap, GET_UINT24(pc));
304             bytes = ToDisassemblySource(cx, ATOM_KEY(atom));
305             if (!bytes)
306                 return 0;
307             fprintf(fp, " %s", bytes);
308             break;
309         }
310 
311         JS_ASSERT(op == JSOP_UINT24 || op == JSOP_LITERAL);
312         fprintf(fp, " %u", GET_UINT24(pc));
313         break;
314 
315       case JOF_LITOPX:
316         atom = js_GetAtom(cx, &script->atomMap, GET_LITERAL_INDEX(pc));
317         bytes = ToDisassemblySource(cx, ATOM_KEY(atom));
318         if (!bytes)
319             return 0;
320 
321         /*
322          * Bytecode: JSOP_LITOPX <uint24> op [<varno> if JSOP_DEFLOCALFUN].
323          * Advance pc to point at op.
324          */
325         pc += 1 + LITERAL_INDEX_LEN;
326         op = *pc;
327         cs = &js_CodeSpec[op];
328         fprintf(fp, " %s op %s", bytes, cs->name);
329         if ((cs->format & JOF_TYPEMASK) == JOF_INDEXCONST)
330             fprintf(fp, " %u", GET_VARNO(pc));
331 
332         /*
333          * Set len to advance pc to skip op and any other immediates (namely,
334          * <varno> if JSOP_DEFLOCALFUN).
335          */
336         JS_ASSERT(cs->length > ATOM_INDEX_LEN);
337         len = cs->length - ATOM_INDEX_LEN;
338         break;
339 
340       default: {
341         char numBuf[12];
342         JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) cs->format);
343         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
344                              JSMSG_UNKNOWN_FORMAT, numBuf);
345         return 0;
346       }
347     }
348     fputs("\n", fp);
349     return len;
350 }
351 
352 #endif /* DEBUG */
353 
354 /************************************************************************/
355 
356 /*
357  * Sprintf, but with unlimited and automatically allocated buffering.
358  */
359 typedef struct Sprinter {
360     JSContext       *context;       /* context executing the decompiler */
361     JSArenaPool     *pool;          /* string allocation pool */
362     char            *base;          /* base address of buffer in pool */
363     size_t          size;           /* size of buffer allocated at base */
364     ptrdiff_t       offset;         /* offset of next free char in buffer */
365 } Sprinter;
366 
367 #define INIT_SPRINTER(cx, sp, ap, off) \
368     ((sp)->context = cx, (sp)->pool = ap, (sp)->base = NULL, (sp)->size = 0,  \
369      (sp)->offset = off)
370 
371 #define OFF2STR(sp,off) ((sp)->base + (off))
372 #define STR2OFF(sp,str) ((str) - (sp)->base)
373 #define RETRACT(sp,str) ((sp)->offset = STR2OFF(sp, str))
374 
375 static JSBool
SprintAlloc(Sprinter * sp,size_t nb)376 SprintAlloc(Sprinter *sp, size_t nb)
377 {
378     char *base;
379 
380     base = sp->base;
381     if (!base) {
382         JS_ARENA_ALLOCATE_CAST(base, char *, sp->pool, nb);
383     } else {
384         JS_ARENA_GROW_CAST(base, char *, sp->pool, sp->size, nb);
385     }
386     if (!base) {
387         JS_ReportOutOfMemory(sp->context);
388         return JS_FALSE;
389     }
390     sp->base = base;
391     sp->size += nb;
392     return JS_TRUE;
393 }
394 
395 static ptrdiff_t
SprintPut(Sprinter * sp,const char * s,size_t len)396 SprintPut(Sprinter *sp, const char *s, size_t len)
397 {
398     ptrdiff_t nb, offset;
399     char *bp;
400 
401     /* Allocate space for s, including the '\0' at the end. */
402     nb = (sp->offset + len + 1) - sp->size;
403     if (nb > 0 && !SprintAlloc(sp, nb))
404         return -1;
405 
406     /* Advance offset and copy s into sp's buffer. */
407     offset = sp->offset;
408     sp->offset += len;
409     bp = sp->base + offset;
410     memmove(bp, s, len);
411     bp[len] = 0;
412     return offset;
413 }
414 
415 static ptrdiff_t
SprintCString(Sprinter * sp,const char * s)416 SprintCString(Sprinter *sp, const char *s)
417 {
418     return SprintPut(sp, s, strlen(s));
419 }
420 
421 static ptrdiff_t
Sprint(Sprinter * sp,const char * format,...)422 Sprint(Sprinter *sp, const char *format, ...)
423 {
424     va_list ap;
425     char *bp;
426     ptrdiff_t offset;
427 
428     va_start(ap, format);
429     bp = JS_vsmprintf(format, ap);      /* XXX vsaprintf */
430     va_end(ap);
431     if (!bp) {
432         JS_ReportOutOfMemory(sp->context);
433         return -1;
434     }
435     offset = SprintCString(sp, bp);
436     free(bp);
437     return offset;
438 }
439 
440 const jschar js_EscapeMap[] = {
441     '\b', 'b',
442     '\f', 'f',
443     '\n', 'n',
444     '\r', 'r',
445     '\t', 't',
446     '\v', 'v',
447     '"',  '"',
448     '\'', '\'',
449     '\\', '\\',
450     0
451 };
452 
453 #define DONT_ESCAPE     0x10000
454 
455 static char *
QuoteString(Sprinter * sp,JSString * str,uint32 quote)456 QuoteString(Sprinter *sp, JSString *str, uint32 quote)
457 {
458     JSBool dontEscape, ok;
459     jschar qc, c;
460     ptrdiff_t off, len, nb;
461     const jschar *s, *t, *u, *z;
462     char *bp;
463 
464     /* Sample off first for later return value pointer computation. */
465     dontEscape = (quote & DONT_ESCAPE) != 0;
466     qc = (jschar) quote;
467     off = sp->offset;
468     if (qc && Sprint(sp, "%c", (char)qc) < 0)
469         return NULL;
470 
471     /* Loop control variables: z points at end of string sentinel. */
472     s = JSSTRING_CHARS(str);
473     z = s + JSSTRING_LENGTH(str);
474     for (t = s; t < z; s = ++t) {
475         /* Move t forward from s past un-quote-worthy characters. */
476         c = *t;
477         while (JS_ISPRINT(c) && c != qc && c != '\\' && !(c >> 8)) {
478             c = *++t;
479             if (t == z)
480                 break;
481         }
482         len = PTRDIFF(t, s, jschar);
483 
484         /* Allocate space for s, including the '\0' at the end. */
485         nb = (sp->offset + len + 1) - sp->size;
486         if (nb > 0 && !SprintAlloc(sp, nb))
487             return NULL;
488 
489         /* Advance sp->offset and copy s into sp's buffer. */
490         bp = sp->base + sp->offset;
491         sp->offset += len;
492         while (--len >= 0)
493             *bp++ = (char) *s++;
494         *bp = '\0';
495 
496         if (t == z)
497             break;
498 
499         /* Use js_EscapeMap, \u, or \x only if necessary. */
500         if ((u = js_strchr(js_EscapeMap, c)) != NULL) {
501             ok = dontEscape
502                  ? Sprint(sp, "%c", (char)c) >= 0
503                  : Sprint(sp, "\\%c", (char)u[1]) >= 0;
504         } else {
505 #ifdef JS_C_STRINGS_ARE_UTF8
506             /* If this is a surrogate pair, make sure to print the pair. */
507             if (c >= 0xD800 && c <= 0xDBFF) {
508                 jschar buffer[3];
509                 buffer[0] = c;
510                 buffer[1] = *++t;
511                 buffer[2] = 0;
512                 if (t == z) {
513                     char numbuf[10];
514                     JS_snprintf(numbuf, sizeof numbuf, "0x%x", c);
515                     JS_ReportErrorFlagsAndNumber(sp->context, JSREPORT_ERROR,
516                                                  js_GetErrorMessage, NULL,
517                                                  JSMSG_BAD_SURROGATE_CHAR,
518                                                  numbuf);
519                     ok = JS_FALSE;
520                     break;
521                 }
522                 ok = Sprint(sp, "%hs", buffer) >= 0;
523             } else {
524                 /* Print as UTF-8 string. */
525                 ok = Sprint(sp, "%hc", c) >= 0;
526             }
527 #else
528             /* Use \uXXXX or \xXX  if the string can't be displayed as UTF-8. */
529             ok = Sprint(sp, (c >> 8) ? "\\u%04X" : "\\x%02X", c) >= 0;
530 #endif
531         }
532         if (!ok)
533             return NULL;
534     }
535 
536     /* Sprint the closing quote and return the quoted string. */
537     if (qc && Sprint(sp, "%c", (char)qc) < 0)
538         return NULL;
539 
540     /*
541      * If we haven't Sprint'd anything yet, Sprint an empty string so that
542      * the OFF2STR below gives a valid result.
543      */
544     if (off == sp->offset && Sprint(sp, "") < 0)
545         return NULL;
546     return OFF2STR(sp, off);
547 }
548 
549 JSString *
js_QuoteString(JSContext * cx,JSString * str,jschar quote)550 js_QuoteString(JSContext *cx, JSString *str, jschar quote)
551 {
552     void *mark;
553     Sprinter sprinter;
554     char *bytes;
555     JSString *escstr;
556 
557     mark = JS_ARENA_MARK(&cx->tempPool);
558     INIT_SPRINTER(cx, &sprinter, &cx->tempPool, 0);
559     bytes = QuoteString(&sprinter, str, quote);
560     escstr = bytes ? JS_NewStringCopyZ(cx, bytes) : NULL;
561     JS_ARENA_RELEASE(&cx->tempPool, mark);
562     return escstr;
563 }
564 
565 /************************************************************************/
566 
567 #if JS_HAS_BLOCK_SCOPE
568 typedef enum JSBraceState {
569     ALWAYS_BRACE,
570     MAYBE_BRACE,
571     DONT_BRACE
572 } JSBraceState;
573 #endif
574 
575 struct JSPrinter {
576     Sprinter        sprinter;       /* base class state */
577     JSArenaPool     pool;           /* string allocation pool */
578     uintN           indent;         /* indentation in spaces */
579     JSPackedBool    pretty;         /* pretty-print: indent, use newlines */
580     JSPackedBool    grouped;        /* in parenthesized expression context */
581     JSScript        *script;        /* script being printed */
582     jsbytecode      *dvgfence;      /* js_DecompileValueGenerator fencepost */
583     JSScope         *scope;         /* script function scope */
584 #if JS_HAS_BLOCK_SCOPE
585     JSBraceState    braceState;     /* remove braces around let declaration */
586     ptrdiff_t       spaceOffset;    /* -1 or offset of space before maybe-{ */
587 #endif
588 };
589 
590 /*
591  * Hack another flag, a la JS_DONT_PRETTY_PRINT, into uintN indent parameters
592  * to functions such as js_DecompileFunction and js_NewPrinter.  This time, as
593  * opposed to JS_DONT_PRETTY_PRINT back in the dark ages, we can assume that a
594  * uintN is at least 32 bits.
595  */
596 #define JS_IN_GROUP_CONTEXT 0x10000
597 
598 JSPrinter *
js_NewPrinter(JSContext * cx,const char * name,uintN indent,JSBool pretty)599 js_NewPrinter(JSContext *cx, const char *name, uintN indent, JSBool pretty)
600 {
601     JSPrinter *jp;
602 
603     jp = (JSPrinter *) JS_malloc(cx, sizeof(JSPrinter));
604     if (!jp)
605         return NULL;
606     INIT_SPRINTER(cx, &jp->sprinter, &jp->pool, 0);
607     JS_InitArenaPool(&jp->pool, name, 256, 1);
608     jp->indent = indent & ~JS_IN_GROUP_CONTEXT;
609     jp->pretty = pretty;
610     jp->grouped = (indent & JS_IN_GROUP_CONTEXT) != 0;
611     jp->script = NULL;
612     jp->dvgfence = NULL;
613     jp->scope = NULL;
614 #if JS_HAS_BLOCK_SCOPE
615     jp->braceState = ALWAYS_BRACE;
616     jp->spaceOffset = -1;
617 #endif
618     return jp;
619 }
620 
621 void
js_DestroyPrinter(JSPrinter * jp)622 js_DestroyPrinter(JSPrinter *jp)
623 {
624     JS_FinishArenaPool(&jp->pool);
625     JS_free(jp->sprinter.context, jp);
626 }
627 
628 JSString *
js_GetPrinterOutput(JSPrinter * jp)629 js_GetPrinterOutput(JSPrinter *jp)
630 {
631     JSContext *cx;
632     JSString *str;
633 
634     cx = jp->sprinter.context;
635     if (!jp->sprinter.base)
636         return cx->runtime->emptyString;
637     str = JS_NewStringCopyZ(cx, jp->sprinter.base);
638     if (!str)
639         return NULL;
640     JS_FreeArenaPool(&jp->pool);
641     INIT_SPRINTER(cx, &jp->sprinter, &jp->pool, 0);
642     return str;
643 }
644 
645 #if !JS_HAS_BLOCK_SCOPE
646 # define SET_MAYBE_BRACE(jp)    jp
647 # define CLEAR_MAYBE_BRACE(jp)  jp
648 #else
649 # define SET_MAYBE_BRACE(jp)    ((jp)->braceState = MAYBE_BRACE, (jp))
650 # define CLEAR_MAYBE_BRACE(jp)  ((jp)->braceState = ALWAYS_BRACE, (jp))
651 
652 static void
SetDontBrace(JSPrinter * jp)653 SetDontBrace(JSPrinter *jp)
654 {
655     ptrdiff_t offset;
656     const char *bp;
657 
658     /* When not pretty-printing, newline after brace is chopped. */
659     JS_ASSERT(jp->spaceOffset < 0);
660     offset = jp->sprinter.offset - (jp->pretty ? 3 : 2);
661 
662     /* The shortest case is "if (x) {". */
663     JS_ASSERT(offset >= 6);
664     bp = jp->sprinter.base;
665     if (bp[offset+0] == ' ' && bp[offset+1] == '{') {
666         JS_ASSERT(!jp->pretty || bp[offset+2] == '\n');
667         jp->spaceOffset = offset;
668         jp->braceState = DONT_BRACE;
669     }
670 }
671 #endif
672 
673 int
js_printf(JSPrinter * jp,const char * format,...)674 js_printf(JSPrinter *jp, const char *format, ...)
675 {
676     va_list ap;
677     char *bp, *fp;
678     int cc;
679 
680     if (*format == '\0')
681         return 0;
682 
683     va_start(ap, format);
684 
685     /* If pretty-printing, expand magic tab into a run of jp->indent spaces. */
686     if (*format == '\t') {
687         format++;
688 
689 #if JS_HAS_BLOCK_SCOPE
690         if (*format == '}' && jp->braceState != ALWAYS_BRACE) {
691             JSBraceState braceState;
692 
693             braceState = jp->braceState;
694             jp->braceState = ALWAYS_BRACE;
695             if (braceState == DONT_BRACE) {
696                 ptrdiff_t offset, delta, from;
697 
698                 JS_ASSERT(format[1] == '\n' || format[1] == ' ');
699                 offset = jp->spaceOffset;
700                 JS_ASSERT(offset >= 6);
701 
702                 /* Replace " {\n" at the end of jp->sprinter with "\n". */
703                 bp = jp->sprinter.base;
704                 if (bp[offset+0] == ' ' && bp[offset+1] == '{') {
705                     delta = 2;
706                     if (jp->pretty) {
707                         /* If pretty, we don't have to worry about 'else'. */
708                         JS_ASSERT(bp[offset+2] == '\n');
709                     } else if (bp[offset-1] != ')') {
710                         /* Must keep ' ' to avoid 'dolet' or 'elselet'. */
711                         ++offset;
712                         delta = 1;
713                     }
714 
715                     from = offset + delta;
716                     memmove(bp + offset, bp + from, jp->sprinter.offset - from);
717                     jp->sprinter.offset -= delta;
718                     jp->spaceOffset = -1;
719 
720                     format += 2;
721                     if (*format == '\0')
722                         return 0;
723                 }
724             }
725         }
726 #endif
727 
728         if (jp->pretty && Sprint(&jp->sprinter, "%*s", jp->indent, "") < 0)
729             return -1;
730     }
731 
732     /* Suppress newlines (must be once per format, at the end) if not pretty. */
733     fp = NULL;
734     if (!jp->pretty && format[cc = strlen(format) - 1] == '\n') {
735         fp = JS_strdup(jp->sprinter.context, format);
736         if (!fp)
737             return -1;
738         fp[cc] = '\0';
739         format = fp;
740     }
741 
742     /* Allocate temp space, convert format, and put. */
743     bp = JS_vsmprintf(format, ap);      /* XXX vsaprintf */
744     if (fp) {
745         JS_free(jp->sprinter.context, fp);
746         format = NULL;
747     }
748     if (!bp) {
749         JS_ReportOutOfMemory(jp->sprinter.context);
750         return -1;
751     }
752 
753     cc = strlen(bp);
754     if (SprintPut(&jp->sprinter, bp, (size_t)cc) < 0)
755         cc = -1;
756     free(bp);
757 
758     va_end(ap);
759     return cc;
760 }
761 
762 JSBool
js_puts(JSPrinter * jp,const char * s)763 js_puts(JSPrinter *jp, const char *s)
764 {
765     return SprintCString(&jp->sprinter, s) >= 0;
766 }
767 
768 /************************************************************************/
769 
770 typedef struct SprintStack {
771     Sprinter    sprinter;       /* sprinter for postfix to infix buffering */
772     ptrdiff_t   *offsets;       /* stack of postfix string offsets */
773     jsbytecode  *opcodes;       /* parallel stack of JS opcodes */
774     uintN       top;            /* top of stack index */
775     uintN       inArrayInit;    /* array initialiser/comprehension level */
776     JSPrinter   *printer;       /* permanent output goes here */
777 } SprintStack;
778 
779 /*
780  * Get a stacked offset from ss->sprinter.base, or if the stacked value |off|
781  * is negative, lazily fetch the generating pc at |spindex = 1 + off| and try
782  * to decompile the code that generated the missing value.  This is used when
783  * reporting errors, where the model stack will lack |pcdepth| non-negative
784  * offsets (see js_DecompileValueGenerator and js_DecompileCode).
785  *
786  * If the stacked offset is -1, return 0 to index the NUL padding at the start
787  * of ss->sprinter.base.  If this happens, it means there is a decompiler bug
788  * to fix, but it won't violate memory safety.
789  */
790 static ptrdiff_t
GetOff(SprintStack * ss,uintN i)791 GetOff(SprintStack *ss, uintN i)
792 {
793     ptrdiff_t off;
794     JSString *str;
795 
796     off = ss->offsets[i];
797     if (off < 0) {
798 #if defined DEBUG_brendan || defined DEBUG_mrbkap || defined DEBUG_crowder
799         JS_ASSERT(off < -1);
800 #endif
801         if (++off == 0) {
802             if (!ss->sprinter.base && SprintPut(&ss->sprinter, "", 0) >= 0)
803                 memset(ss->sprinter.base, 0, ss->sprinter.offset);
804             return 0;
805         }
806 
807         str = js_DecompileValueGenerator(ss->sprinter.context, off,
808                                          JSVAL_NULL, NULL);
809         if (!str)
810             return 0;
811         off = SprintCString(&ss->sprinter, JS_GetStringBytes(str));
812         if (off < 0)
813             off = 0;
814         ss->offsets[i] = off;
815     }
816     return off;
817 }
818 
819 static const char *
GetStr(SprintStack * ss,uintN i)820 GetStr(SprintStack *ss, uintN i)
821 {
822     ptrdiff_t off;
823 
824     /*
825      * Must call GetOff before using ss->sprinter.base, since it may be null
826      * until bootstrapped by GetOff.
827      */
828     off = GetOff(ss, i);
829     return OFF2STR(&ss->sprinter, off);
830 }
831 
832 /* Gap between stacked strings to allow for insertion of parens and commas. */
833 #define PAREN_SLOP      (2 + 1)
834 
835 /*
836  * These pseudo-ops help js_DecompileValueGenerator decompile JSOP_SETNAME,
837  * JSOP_SETPROP, and JSOP_SETELEM, respectively.  They are never stored in
838  * bytecode, so they don't preempt valid opcodes.
839  */
840 #define JSOP_GETPROP2   256
841 #define JSOP_GETELEM2   257
842 
843 static JSBool
PushOff(SprintStack * ss,ptrdiff_t off,JSOp op)844 PushOff(SprintStack *ss, ptrdiff_t off, JSOp op)
845 {
846     uintN top;
847 
848     if (!SprintAlloc(&ss->sprinter, PAREN_SLOP))
849         return JS_FALSE;
850 
851     /* ss->top points to the next free slot; be paranoid about overflow. */
852     top = ss->top;
853     JS_ASSERT(top < ss->printer->script->depth);
854     if (top >= ss->printer->script->depth) {
855         JS_ReportOutOfMemory(ss->sprinter.context);
856         return JS_FALSE;
857     }
858 
859     /* The opcodes stack must contain real bytecodes that index js_CodeSpec. */
860     ss->offsets[top] = off;
861     ss->opcodes[top] = (op == JSOP_GETPROP2) ? JSOP_GETPROP
862                      : (op == JSOP_GETELEM2) ? JSOP_GETELEM
863                      : (jsbytecode) op;
864     ss->top = ++top;
865     memset(OFF2STR(&ss->sprinter, ss->sprinter.offset), 0, PAREN_SLOP);
866     ss->sprinter.offset += PAREN_SLOP;
867     return JS_TRUE;
868 }
869 
870 static ptrdiff_t
PopOff(SprintStack * ss,JSOp op)871 PopOff(SprintStack *ss, JSOp op)
872 {
873     uintN top;
874     const JSCodeSpec *cs, *topcs;
875     ptrdiff_t off;
876 
877     /* ss->top points to the next free slot; be paranoid about underflow. */
878     top = ss->top;
879     JS_ASSERT(top != 0);
880     if (top == 0)
881         return 0;
882 
883     ss->top = --top;
884     off = GetOff(ss, top);
885     topcs = &js_CodeSpec[ss->opcodes[top]];
886     cs = &js_CodeSpec[op];
887     if (topcs->prec != 0 && topcs->prec < cs->prec) {
888         ss->sprinter.offset = ss->offsets[top] = off - 2;
889         off = Sprint(&ss->sprinter, "(%s)", OFF2STR(&ss->sprinter, off));
890     } else {
891         ss->sprinter.offset = off;
892     }
893     return off;
894 }
895 
896 static const char *
PopStr(SprintStack * ss,JSOp op)897 PopStr(SprintStack *ss, JSOp op)
898 {
899     ptrdiff_t off;
900 
901     off = PopOff(ss, op);
902     return OFF2STR(&ss->sprinter, off);
903 }
904 
905 typedef struct TableEntry {
906     jsval       key;
907     ptrdiff_t   offset;
908     JSAtom      *label;
909     jsint       order;          /* source order for stable tableswitch sort */
910 } TableEntry;
911 
912 static JSBool
CompareOffsets(void * arg,const void * v1,const void * v2,int * result)913 CompareOffsets(void *arg, const void *v1, const void *v2, int *result)
914 {
915     ptrdiff_t offset_diff;
916     const TableEntry *te1 = (const TableEntry *) v1,
917                      *te2 = (const TableEntry *) v2;
918 
919     offset_diff = te1->offset - te2->offset;
920     *result = (offset_diff == 0 ? te1->order - te2->order
921                : offset_diff < 0 ? -1
922                : 1);
923     return JS_TRUE;
924 }
925 
926 static jsbytecode *
927 Decompile(SprintStack *ss, jsbytecode *pc, intN nb);
928 
929 static JSBool
DecompileSwitch(SprintStack * ss,TableEntry * table,uintN tableLength,jsbytecode * pc,ptrdiff_t switchLength,ptrdiff_t defaultOffset,JSBool isCondSwitch)930 DecompileSwitch(SprintStack *ss, TableEntry *table, uintN tableLength,
931                 jsbytecode *pc, ptrdiff_t switchLength,
932                 ptrdiff_t defaultOffset, JSBool isCondSwitch)
933 {
934     JSContext *cx;
935     JSPrinter *jp;
936     ptrdiff_t off, off2, diff, caseExprOff;
937     char *lval, *rval;
938     uintN i;
939     jsval key;
940     JSString *str;
941 
942     cx = ss->sprinter.context;
943     jp = ss->printer;
944 
945     /* JSOP_CONDSWITCH doesn't pop, unlike JSOP_{LOOKUP,TABLE}SWITCH. */
946     off = isCondSwitch ? GetOff(ss, ss->top-1) : PopOff(ss, JSOP_NOP);
947     lval = OFF2STR(&ss->sprinter, off);
948 
949     js_printf(CLEAR_MAYBE_BRACE(jp), "\tswitch (%s) {\n", lval);
950 
951     if (tableLength) {
952         diff = table[0].offset - defaultOffset;
953         if (diff > 0) {
954             jp->indent += 2;
955             js_printf(jp, "\t%s:\n", js_default_str);
956             jp->indent += 2;
957             if (!Decompile(ss, pc + defaultOffset, diff))
958                 return JS_FALSE;
959             jp->indent -= 4;
960         }
961 
962         caseExprOff = isCondSwitch ? JSOP_CONDSWITCH_LENGTH : 0;
963 
964         for (i = 0; i < tableLength; i++) {
965             off = table[i].offset;
966             off2 = (i + 1 < tableLength) ? table[i + 1].offset : switchLength;
967 
968             key = table[i].key;
969             if (isCondSwitch) {
970                 ptrdiff_t nextCaseExprOff;
971 
972                 /*
973                  * key encodes the JSOP_CASE bytecode's offset from switchtop.
974                  * The next case expression follows immediately, unless we are
975                  * at the last case.
976                  */
977                 nextCaseExprOff = (ptrdiff_t)JSVAL_TO_INT(key);
978                 nextCaseExprOff += js_CodeSpec[pc[nextCaseExprOff]].length;
979                 jp->indent += 2;
980                 if (!Decompile(ss, pc + caseExprOff,
981                                nextCaseExprOff - caseExprOff)) {
982                     return JS_FALSE;
983                 }
984                 caseExprOff = nextCaseExprOff;
985 
986                 /* Balance the stack as if this JSOP_CASE matched. */
987                 --ss->top;
988             } else {
989                 /*
990                  * key comes from an atom, not the decompiler, so we need to
991                  * quote it if it's a string literal.  But if table[i].label
992                  * is non-null, key was constant-propagated and label is the
993                  * name of the const we should show as the case label.  We set
994                  * key to undefined so this identifier is escaped, if required
995                  * by non-ASCII characters, but not quoted, by QuoteString.
996                  */
997                 if (table[i].label) {
998                     str = ATOM_TO_STRING(table[i].label);
999                     key = JSVAL_VOID;
1000                 } else {
1001                     str = js_ValueToString(cx, key);
1002                     if (!str)
1003                         return JS_FALSE;
1004                 }
1005                 rval = QuoteString(&ss->sprinter, str,
1006                                    (jschar)(JSVAL_IS_STRING(key) ? '"' : 0));
1007                 if (!rval)
1008                     return JS_FALSE;
1009                 RETRACT(&ss->sprinter, rval);
1010                 jp->indent += 2;
1011                 js_printf(jp, "\tcase %s:\n", rval);
1012             }
1013 
1014             jp->indent += 2;
1015             if (off <= defaultOffset && defaultOffset < off2) {
1016                 diff = defaultOffset - off;
1017                 if (diff != 0) {
1018                     if (!Decompile(ss, pc + off, diff))
1019                         return JS_FALSE;
1020                     off = defaultOffset;
1021                 }
1022                 jp->indent -= 2;
1023                 js_printf(jp, "\t%s:\n", js_default_str);
1024                 jp->indent += 2;
1025             }
1026             if (!Decompile(ss, pc + off, off2 - off))
1027                 return JS_FALSE;
1028             jp->indent -= 4;
1029 
1030             /* Re-balance as if last JSOP_CASE or JSOP_DEFAULT mismatched. */
1031             if (isCondSwitch)
1032                 ++ss->top;
1033         }
1034     }
1035 
1036     if (defaultOffset == switchLength) {
1037         jp->indent += 2;
1038         js_printf(jp, "\t%s:;\n", js_default_str);
1039         jp->indent -= 2;
1040     }
1041     js_printf(jp, "\t}\n");
1042 
1043     /* By the end of a JSOP_CONDSWITCH, the discriminant has been popped. */
1044     if (isCondSwitch)
1045         --ss->top;
1046     return JS_TRUE;
1047 }
1048 
1049 static JSAtom *
GetSlotAtom(JSPrinter * jp,JSPropertyOp getter,uintN slot)1050 GetSlotAtom(JSPrinter *jp, JSPropertyOp getter, uintN slot)
1051 {
1052     JSScope *scope;
1053     JSScopeProperty *sprop;
1054     JSObject *obj, *proto;
1055 
1056     scope = jp->scope;
1057     while (scope) {
1058         for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) {
1059             if (sprop->getter != getter)
1060                 continue;
1061             JS_ASSERT(sprop->flags & SPROP_HAS_SHORTID);
1062             JS_ASSERT(JSID_IS_ATOM(sprop->id));
1063             if ((uintN) sprop->shortid == slot)
1064                 return JSID_TO_ATOM(sprop->id);
1065         }
1066         obj = scope->object;
1067         if (!obj)
1068             break;
1069         proto = OBJ_GET_PROTO(jp->sprinter.context, obj);
1070         if (!proto)
1071             break;
1072         scope = OBJ_SCOPE(proto);
1073     }
1074     return NULL;
1075 }
1076 
1077 /*
1078  * NB: Indexed by SRC_DECL_* defines from jsemit.h.
1079  */
1080 static const char * const var_prefix[] = {"var ", "const ", "let "};
1081 
1082 static const char *
VarPrefix(jssrcnote * sn)1083 VarPrefix(jssrcnote *sn)
1084 {
1085     if (sn && (SN_TYPE(sn) == SRC_DECL || SN_TYPE(sn) == SRC_GROUPASSIGN)) {
1086         ptrdiff_t type = js_GetSrcNoteOffset(sn, 0);
1087         if ((uintN)type <= SRC_DECL_LET)
1088             return var_prefix[type];
1089     }
1090     return "";
1091 }
1092 #define LOCAL_ASSERT_RV(expr, rv)                                             \
1093     JS_BEGIN_MACRO                                                            \
1094         JS_ASSERT(expr);                                                      \
1095         if (!(expr)) return (rv);                                             \
1096     JS_END_MACRO
1097 
1098 const char *
GetLocal(SprintStack * ss,jsint i)1099 GetLocal(SprintStack *ss, jsint i)
1100 {
1101     ptrdiff_t off;
1102     JSContext *cx;
1103     JSScript *script;
1104     jsatomid j, n;
1105     JSAtom *atom;
1106     JSObject *obj;
1107     jsint depth, count;
1108     JSScopeProperty *sprop;
1109     const char *rval;
1110 
1111 #define LOCAL_ASSERT(expr)      LOCAL_ASSERT_RV(expr, "")
1112 
1113     off = ss->offsets[i];
1114     if (off >= 0)
1115         return OFF2STR(&ss->sprinter, off);
1116 
1117     /*
1118      * We must be called from js_DecompileValueGenerator (via Decompile) when
1119      * dereferencing a local that's undefined or null.  Search script->atomMap
1120      * for the block containing this local by its stack index, i.
1121      */
1122     cx = ss->sprinter.context;
1123     script = ss->printer->script;
1124     for (j = 0, n = script->atomMap.length; j < n; j++) {
1125         atom = script->atomMap.vector[j];
1126         if (ATOM_IS_OBJECT(atom)) {
1127             obj = ATOM_TO_OBJECT(atom);
1128             if (OBJ_GET_CLASS(cx, obj) == &js_BlockClass) {
1129                 depth = OBJ_BLOCK_DEPTH(cx, obj);
1130                 count = OBJ_BLOCK_COUNT(cx, obj);
1131                 if ((jsuint)(i - depth) < (jsuint)count)
1132                     break;
1133             }
1134         }
1135     }
1136 
1137     LOCAL_ASSERT(j < n);
1138     i -= depth;
1139     for (sprop = OBJ_SCOPE(obj)->lastProp; sprop; sprop = sprop->parent) {
1140         if (sprop->shortid == i)
1141             break;
1142     }
1143 
1144     LOCAL_ASSERT(sprop && JSID_IS_ATOM(sprop->id));
1145     atom = JSID_TO_ATOM(sprop->id);
1146     rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
1147     if (!rval)
1148         return NULL;
1149     RETRACT(&ss->sprinter, rval);
1150     return rval;
1151 
1152 #undef LOCAL_ASSERT
1153 }
1154 
1155 #if JS_HAS_DESTRUCTURING
1156 
1157 #define LOCAL_ASSERT(expr)  LOCAL_ASSERT_RV(expr, NULL)
1158 #define LOAD_OP_DATA(pc)    (oplen = (cs = &js_CodeSpec[op = *pc])->length)
1159 
1160 static jsbytecode *
1161 DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc);
1162 
1163 static jsbytecode *
DecompileDestructuringLHS(SprintStack * ss,jsbytecode * pc,jsbytecode * endpc,JSBool * hole)1164 DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
1165                           JSBool *hole)
1166 {
1167     JSContext *cx;
1168     JSPrinter *jp;
1169     JSOp op;
1170     const JSCodeSpec *cs;
1171     uintN oplen, i;
1172     const char *lval, *xval;
1173     ptrdiff_t todo;
1174     JSAtom *atom;
1175 
1176     *hole = JS_FALSE;
1177     cx = ss->sprinter.context;
1178     jp = ss->printer;
1179     LOAD_OP_DATA(pc);
1180 
1181     switch (op) {
1182       case JSOP_POP:
1183         *hole = JS_TRUE;
1184         todo = SprintPut(&ss->sprinter, ", ", 2);
1185         break;
1186 
1187       case JSOP_DUP:
1188         pc = DecompileDestructuring(ss, pc, endpc);
1189         if (!pc)
1190             return NULL;
1191         if (pc == endpc)
1192             return pc;
1193         LOAD_OP_DATA(pc);
1194         lval = PopStr(ss, JSOP_NOP);
1195         todo = SprintCString(&ss->sprinter, lval);
1196         if (op == JSOP_SETSP)
1197             return pc;
1198         LOCAL_ASSERT(*pc == JSOP_POP);
1199         break;
1200 
1201       case JSOP_SETARG:
1202       case JSOP_SETVAR:
1203       case JSOP_SETGVAR:
1204       case JSOP_SETLOCAL:
1205         LOCAL_ASSERT(pc[oplen] == JSOP_POP || pc[oplen] == JSOP_SETSP);
1206         /* FALL THROUGH */
1207 
1208       case JSOP_SETLOCALPOP:
1209         i = GET_UINT16(pc);
1210         atom = NULL;
1211         lval = NULL;
1212         if (op == JSOP_SETARG)
1213             atom = GetSlotAtom(jp, js_GetArgument, i);
1214         else if (op == JSOP_SETVAR)
1215             atom = GetSlotAtom(jp, js_GetLocalVariable, i);
1216         else if (op == JSOP_SETGVAR)
1217             atom = GET_ATOM(cx, jp->script, pc);
1218         else
1219             lval = GetLocal(ss, i);
1220         if (atom)
1221             lval = js_AtomToPrintableString(cx, atom);
1222         LOCAL_ASSERT(lval);
1223         todo = SprintCString(&ss->sprinter, lval);
1224         if (op != JSOP_SETLOCALPOP) {
1225             pc += oplen;
1226             if (pc == endpc)
1227                 return pc;
1228             LOAD_OP_DATA(pc);
1229             if (op == JSOP_SETSP)
1230                 return pc;
1231             LOCAL_ASSERT(op == JSOP_POP);
1232         }
1233         break;
1234 
1235       default:
1236         /*
1237          * We may need to auto-parenthesize the left-most value decompiled
1238          * here, so add back PAREN_SLOP temporarily.  Then decompile until the
1239          * opcode that would reduce the stack depth to (ss->top-1), which we
1240          * pass to Decompile encoded as -(ss->top-1) - 1 or just -ss->top for
1241          * the nb parameter.
1242          */
1243         todo = ss->sprinter.offset;
1244         ss->sprinter.offset = todo + PAREN_SLOP;
1245         pc = Decompile(ss, pc, -ss->top);
1246         if (!pc)
1247             return NULL;
1248         if (pc == endpc)
1249             return pc;
1250         LOAD_OP_DATA(pc);
1251         LOCAL_ASSERT(op == JSOP_ENUMELEM || op == JSOP_ENUMCONSTELEM);
1252         xval = PopStr(ss, JSOP_NOP);
1253         lval = PopStr(ss, JSOP_GETPROP);
1254         ss->sprinter.offset = todo;
1255         if (*lval == '\0') {
1256             /* lval is from JSOP_BINDNAME, so just print xval. */
1257             todo = SprintCString(&ss->sprinter, xval);
1258         } else if (*xval == '\0') {
1259             /* xval is from JSOP_SETCALL or JSOP_BINDXMLNAME, print lval. */
1260             todo = SprintCString(&ss->sprinter, lval);
1261         } else {
1262             todo = Sprint(&ss->sprinter,
1263                           (js_CodeSpec[ss->opcodes[ss->top+1]].format
1264                            & JOF_XMLNAME)
1265                           ? "%s.%s"
1266                           : "%s[%s]",
1267                           lval, xval);
1268         }
1269         break;
1270     }
1271 
1272     if (todo < 0)
1273         return NULL;
1274 
1275     LOCAL_ASSERT(pc < endpc);
1276     pc += oplen;
1277     return pc;
1278 }
1279 
1280 /*
1281  * Starting with a SRC_DESTRUCT-annotated JSOP_DUP, decompile a destructuring
1282  * left-hand side object or array initialiser, including nested destructuring
1283  * initialisers.  On successful return, the decompilation will be pushed on ss
1284  * and the return value will point to the POP or GROUP bytecode following the
1285  * destructuring expression.
1286  *
1287  * At any point, if pc is equal to endpc and would otherwise advance, we stop
1288  * immediately and return endpc.
1289  */
1290 static jsbytecode *
DecompileDestructuring(SprintStack * ss,jsbytecode * pc,jsbytecode * endpc)1291 DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc)
1292 {
1293     ptrdiff_t head, todo;
1294     JSContext *cx;
1295     JSPrinter *jp;
1296     JSOp op, saveop;
1297     const JSCodeSpec *cs;
1298     uintN oplen;
1299     jsint i, lasti;
1300     jsdouble d;
1301     const char *lval;
1302     jsbytecode *pc2;
1303     jsatomid atomIndex;
1304     JSAtom *atom;
1305     jssrcnote *sn;
1306     JSString *str;
1307     JSBool hole;
1308 
1309     LOCAL_ASSERT(*pc == JSOP_DUP);
1310     pc += JSOP_DUP_LENGTH;
1311 
1312     /*
1313      * Set head so we can rewrite '[' to '{' as needed.  Back up PAREN_SLOP
1314      * chars so the destructuring decompilation accumulates contiguously in
1315      * ss->sprinter starting with "[".
1316      */
1317     head = SprintPut(&ss->sprinter, "[", 1);
1318     if (head < 0 || !PushOff(ss, head, JSOP_NOP))
1319         return NULL;
1320     ss->sprinter.offset -= PAREN_SLOP;
1321     LOCAL_ASSERT(head == ss->sprinter.offset - 1);
1322     LOCAL_ASSERT(*OFF2STR(&ss->sprinter, head) == '[');
1323 
1324     cx = ss->sprinter.context;
1325     jp = ss->printer;
1326     lasti = -1;
1327 
1328     while (pc < endpc) {
1329         LOAD_OP_DATA(pc);
1330         saveop = op;
1331 
1332         switch (op) {
1333           case JSOP_POP:
1334             pc += oplen;
1335             goto out;
1336 
1337           /* Handle the optimized number-pushing opcodes. */
1338           case JSOP_ZERO:   d = i = 0; goto do_getelem;
1339           case JSOP_ONE:    d = i = 1; goto do_getelem;
1340           case JSOP_UINT16: d = i = GET_UINT16(pc); goto do_getelem;
1341           case JSOP_UINT24: d = i = GET_UINT24(pc); goto do_getelem;
1342 
1343           /* Handle the extended literal form of JSOP_NUMBER. */
1344           case JSOP_LITOPX:
1345             atomIndex = GET_LITERAL_INDEX(pc);
1346             pc2 = pc + 1 + LITERAL_INDEX_LEN;
1347             op = *pc2;
1348             LOCAL_ASSERT(op == JSOP_NUMBER);
1349             goto do_getatom;
1350 
1351           case JSOP_NUMBER:
1352             atomIndex = GET_ATOM_INDEX(pc);
1353 
1354           do_getatom:
1355             atom = js_GetAtom(cx, &jp->script->atomMap, atomIndex);
1356             d = *ATOM_TO_DOUBLE(atom);
1357             LOCAL_ASSERT(JSDOUBLE_IS_FINITE(d) && !JSDOUBLE_IS_NEGZERO(d));
1358             i = (jsint)d;
1359 
1360           do_getelem:
1361             sn = js_GetSrcNote(jp->script, pc);
1362             pc += oplen;
1363             if (pc == endpc)
1364                 return pc;
1365             LOAD_OP_DATA(pc);
1366             LOCAL_ASSERT(op == JSOP_GETELEM);
1367 
1368             /* Distinguish object from array by opcode or source note. */
1369             if (saveop == JSOP_LITERAL ||
1370                 (sn && SN_TYPE(sn) == SRC_INITPROP)) {
1371                 *OFF2STR(&ss->sprinter, head) = '{';
1372                 if (Sprint(&ss->sprinter, "%g: ", d) < 0)
1373                     return NULL;
1374             } else {
1375                 /* Sanity check for the gnarly control flow above. */
1376                 LOCAL_ASSERT(i == d);
1377 
1378                 /* Fill in any holes (holes at the end don't matter). */
1379                 while (++lasti < i) {
1380                     if (SprintPut(&ss->sprinter, ", ", 2) < 0)
1381                         return NULL;
1382                 }
1383             }
1384             break;
1385 
1386           case JSOP_LITERAL:
1387             atomIndex = GET_LITERAL_INDEX(pc);
1388             goto do_getatom;
1389 
1390           case JSOP_GETPROP:
1391             *OFF2STR(&ss->sprinter, head) = '{';
1392             atom = GET_ATOM(cx, jp->script, pc);
1393             str = ATOM_TO_STRING(atom);
1394             if (!QuoteString(&ss->sprinter, str,
1395                              js_IsIdentifier(str) ? 0 : (jschar)'\'')) {
1396                 return NULL;
1397             }
1398             if (SprintPut(&ss->sprinter, ": ", 2) < 0)
1399                 return NULL;
1400             break;
1401 
1402           default:
1403             LOCAL_ASSERT(0);
1404         }
1405 
1406         pc += oplen;
1407         if (pc == endpc)
1408             return pc;
1409 
1410         /*
1411          * Decompile the left-hand side expression whose bytecode starts at pc
1412          * and continues for a bounded number of bytecodes or stack operations
1413          * (and which in any event stops before endpc).
1414          */
1415         pc = DecompileDestructuringLHS(ss, pc, endpc, &hole);
1416         if (!pc)
1417             return NULL;
1418         if (pc == endpc || *pc != JSOP_DUP)
1419             break;
1420 
1421         /*
1422          * Check for SRC_DESTRUCT on this JSOP_DUP, which would mean another
1423          * destructuring initialiser abuts this one, and we should stop.  This
1424          * happens with source of the form '[a] = [b] = c'.
1425          */
1426         sn = js_GetSrcNote(jp->script, pc);
1427         if (sn && SN_TYPE(sn) == SRC_DESTRUCT)
1428             break;
1429 
1430         if (!hole && SprintPut(&ss->sprinter, ", ", 2) < 0)
1431             return NULL;
1432 
1433         pc += JSOP_DUP_LENGTH;
1434     }
1435 
1436 out:
1437     lval = OFF2STR(&ss->sprinter, head);
1438     todo = SprintPut(&ss->sprinter, (*lval == '[') ? "]" : "}", 1);
1439     if (todo < 0)
1440         return NULL;
1441     return pc;
1442 }
1443 
1444 static jsbytecode *
DecompileGroupAssignment(SprintStack * ss,jsbytecode * pc,jsbytecode * endpc,jssrcnote * sn,ptrdiff_t * todop)1445 DecompileGroupAssignment(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
1446                          jssrcnote *sn, ptrdiff_t *todop)
1447 {
1448     JSOp op;
1449     const JSCodeSpec *cs;
1450     uintN oplen, start, end, i;
1451     ptrdiff_t todo;
1452     JSBool hole;
1453     const char *rval;
1454 
1455     LOAD_OP_DATA(pc);
1456     LOCAL_ASSERT(op == JSOP_PUSH || op == JSOP_GETLOCAL);
1457 
1458     todo = Sprint(&ss->sprinter, "%s[", VarPrefix(sn));
1459     if (todo < 0 || !PushOff(ss, todo, JSOP_NOP))
1460         return NULL;
1461     ss->sprinter.offset -= PAREN_SLOP;
1462 
1463     for (;;) {
1464         pc += oplen;
1465         if (pc == endpc)
1466             return pc;
1467         pc = DecompileDestructuringLHS(ss, pc, endpc, &hole);
1468         if (!pc)
1469             return NULL;
1470         if (pc == endpc)
1471             return pc;
1472         LOAD_OP_DATA(pc);
1473         if (op != JSOP_PUSH && op != JSOP_GETLOCAL)
1474             break;
1475         if (!hole && SprintPut(&ss->sprinter, ", ", 2) < 0)
1476             return NULL;
1477     }
1478 
1479     LOCAL_ASSERT(op == JSOP_SETSP);
1480     if (SprintPut(&ss->sprinter, "] = [", 5) < 0)
1481         return NULL;
1482 
1483     start = GET_UINT16(pc);
1484     end = ss->top - 1;
1485     for (i = start; i < end; i++) {
1486         rval = GetStr(ss, i);
1487         if (Sprint(&ss->sprinter, "%s%s",
1488                    (i == start) ? "" : ", ",
1489                    (i == end - 1 && *rval == '\0') ? ", " : rval) < 0) {
1490             return NULL;
1491         }
1492     }
1493 
1494     if (SprintPut(&ss->sprinter, "]", 1) < 0)
1495         return NULL;
1496     ss->sprinter.offset = ss->offsets[i];
1497     ss->top = start;
1498     *todop = todo;
1499     return pc;
1500 }
1501 
1502 #undef LOCAL_ASSERT
1503 #undef LOAD_OP_DATA
1504 
1505 #endif /* JS_HAS_DESTRUCTURING */
1506 
1507 /*
1508  * If nb is non-negative, decompile nb bytecodes starting at pc.  Otherwise
1509  * the decompiler starts at pc and continues until it reaches an opcode for
1510  * which decompiling would result in the stack depth equaling -(nb + 1).
1511  */
1512 static jsbytecode *
Decompile(SprintStack * ss,jsbytecode * pc,intN nb)1513 Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
1514 {
1515     JSContext *cx;
1516     JSPrinter *jp, *jp2;
1517     jsbytecode *startpc, *endpc, *pc2, *done, *forelem_tail, *forelem_done;
1518     ptrdiff_t tail, todo, len, oplen, cond, next;
1519     JSOp op, lastop, saveop;
1520     const JSCodeSpec *cs;
1521     jssrcnote *sn, *sn2;
1522     const char *lval, *rval, *xval, *fmt;
1523     jsint i, argc;
1524     char **argv;
1525     jsatomid atomIndex;
1526     JSAtom *atom;
1527     JSObject *obj;
1528     JSFunction *fun;
1529     JSString *str;
1530     JSBool ok;
1531 #if JS_HAS_XML_SUPPORT
1532     JSBool foreach, inXML, quoteAttr;
1533 #else
1534 #define inXML JS_FALSE
1535 #endif
1536     jsval val;
1537     int stackDummy;
1538 
1539     static const char exception_cookie[] = "/*EXCEPTION*/";
1540     static const char retsub_pc_cookie[] = "/*RETSUB_PC*/";
1541     static const char forelem_cookie[]   = "/*FORELEM*/";
1542     static const char with_cookie[]      = "/*WITH*/";
1543     static const char dot_format[]       = "%s.%s";
1544     static const char index_format[]     = "%s[%s]";
1545     static const char predot_format[]    = "%s%s.%s";
1546     static const char postdot_format[]   = "%s.%s%s";
1547     static const char preindex_format[]  = "%s%s[%s]";
1548     static const char postindex_format[] = "%s[%s]%s";
1549     static const char ss_format[]        = "%s%s";
1550 
1551 /*
1552  * Local macros
1553  */
1554 #define DECOMPILE_CODE(pc,nb)   if (!Decompile(ss, pc, nb)) return NULL
1555 #define POP_STR()               PopStr(ss, op)
1556 #define LOCAL_ASSERT(expr)      LOCAL_ASSERT_RV(expr, JS_FALSE)
1557 
1558 /*
1559  * Callers know that ATOM_IS_STRING(atom), and we leave it to the optimizer to
1560  * common ATOM_TO_STRING(atom) here and near the call sites.
1561  */
1562 #define ATOM_IS_IDENTIFIER(atom) js_IsIdentifier(ATOM_TO_STRING(atom))
1563 #define ATOM_IS_KEYWORD(atom)                                                 \
1564             (js_CheckKeyword(JSSTRING_CHARS(ATOM_TO_STRING(atom)),            \
1565                              JSSTRING_LENGTH(ATOM_TO_STRING(atom))) != TOK_EOF)
1566 
1567 /*
1568  * Given an atom already fetched from jp->script's atom map, quote/escape its
1569  * string appropriately into rval, and select fmt from the quoted and unquoted
1570  * alternatives.
1571  */
1572 #define GET_QUOTE_AND_FMT(qfmt, ufmt, rval)                                   \
1573     JS_BEGIN_MACRO                                                            \
1574         jschar quote_;                                                        \
1575         if (!ATOM_IS_IDENTIFIER(atom)) {                                      \
1576             quote_ = '\'';                                                    \
1577             fmt = qfmt;                                                       \
1578         } else {                                                              \
1579             quote_ = 0;                                                       \
1580             fmt = ufmt;                                                       \
1581         }                                                                     \
1582         rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), quote_);      \
1583         if (!rval)                                                            \
1584             return NULL;                                                      \
1585     JS_END_MACRO
1586 
1587 /*
1588  * Get atom from jp->script's atom map, quote/escape its string appropriately
1589  * into rval, and select fmt from the quoted and unquoted alternatives.
1590  */
1591 #define GET_ATOM_QUOTE_AND_FMT(qfmt, ufmt, rval)                              \
1592     JS_BEGIN_MACRO                                                            \
1593         atom = GET_ATOM(cx, jp->script, pc);                                  \
1594         GET_QUOTE_AND_FMT(qfmt, ufmt, rval);                                  \
1595     JS_END_MACRO
1596 
1597     cx = ss->sprinter.context;
1598     if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {
1599         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED);
1600         return NULL;
1601     }
1602 
1603     jp = ss->printer;
1604     startpc = pc;
1605     endpc = (nb < 0) ? jp->script->code + jp->script->length : pc + nb;
1606     forelem_tail = forelem_done = NULL;
1607     tail = -1;
1608     todo = -2;                  /* NB: different from Sprint() error return. */
1609     saveop = JSOP_NOP;
1610     sn = NULL;
1611     rval = NULL;
1612 #if JS_HAS_XML_SUPPORT
1613     foreach = inXML = quoteAttr = JS_FALSE;
1614 #endif
1615 
1616     while (nb < 0 || pc < endpc) {
1617         /*
1618          * Move saveop to lastop so prefixed bytecodes can take special action
1619          * while sharing maximal code.  Set op and saveop to the new bytecode,
1620          * use op in POP_STR to trigger automatic parenthesization, but push
1621          * saveop at the bottom of the loop if this op pushes.  Thus op may be
1622          * set to nop or otherwise mutated to suppress auto-parens.
1623          */
1624         lastop = saveop;
1625         op = saveop = (JSOp) *pc;
1626         cs = &js_CodeSpec[saveop];
1627         len = oplen = cs->length;
1628 
1629         if (nb < 0 && -(nb + 1) == (intN)ss->top - cs->nuses + cs->ndefs)
1630             return pc;
1631 
1632         if (pc + oplen == jp->dvgfence) {
1633             JSStackFrame *fp;
1634             uint32 format, mode, type;
1635 
1636             /*
1637              * Rewrite non-get ops to their "get" format if the error is in
1638              * the bytecode at pc, so we don't decompile more than the error
1639              * expression.
1640              */
1641             for (fp = cx->fp; fp && !fp->script; fp = fp->down)
1642                 continue;
1643             format = cs->format;
1644             if (((fp && pc == fp->pc) ||
1645                  (pc == startpc && cs->nuses != 0)) &&
1646                 format & (JOF_SET|JOF_DEL|JOF_INCDEC|JOF_IMPORT|JOF_FOR)) {
1647                 mode = (format & JOF_MODEMASK);
1648                 if (mode == JOF_NAME) {
1649                     /*
1650                      * JOF_NAME does not imply JOF_CONST, so we must check for
1651                      * the QARG and QVAR format types, and translate those to
1652                      * JSOP_GETARG or JSOP_GETVAR appropriately, instead of to
1653                      * JSOP_NAME.
1654                      */
1655                     type = format & JOF_TYPEMASK;
1656                     op = (type == JOF_QARG)
1657                          ? JSOP_GETARG
1658                          : (type == JOF_QVAR)
1659                          ? JSOP_GETVAR
1660                          : (type == JOF_LOCAL)
1661                          ? JSOP_GETLOCAL
1662                          : JSOP_NAME;
1663 
1664                     i = cs->nuses - js_CodeSpec[op].nuses;
1665                     while (--i >= 0)
1666                         PopOff(ss, JSOP_NOP);
1667                 } else {
1668                     /*
1669                      * We must replace the faulting pc's bytecode with a
1670                      * corresponding JSOP_GET* code.  For JSOP_SET{PROP,ELEM},
1671                      * we must use the "2nd" form of JSOP_GET{PROP,ELEM}, to
1672                      * throw away the assignment op's right-hand operand and
1673                      * decompile it as if it were a GET of its left-hand
1674                      * operand.
1675                      */
1676                     if (mode == JOF_PROP) {
1677                         op = (format & JOF_SET) ? JSOP_GETPROP2 : JSOP_GETPROP;
1678                     } else if (mode == JOF_ELEM) {
1679                         op = (format & JOF_SET) ? JSOP_GETELEM2 : JSOP_GETELEM;
1680                     } else {
1681                         /*
1682                          * Zero mode means precisely that op is uncategorized
1683                          * for our purposes, so we must write per-op special
1684                          * case code here.
1685                          */
1686                         switch (op) {
1687                           case JSOP_ENUMELEM:
1688                           case JSOP_ENUMCONSTELEM:
1689                             op = JSOP_GETELEM;
1690                             break;
1691 #if JS_HAS_LVALUE_RETURN
1692                           case JSOP_SETCALL:
1693                             op = JSOP_CALL;
1694                             break;
1695 #endif
1696                           default:
1697                             LOCAL_ASSERT(0);
1698                         }
1699                     }
1700                 }
1701             }
1702 
1703             saveop = op;
1704             if (op >= JSOP_LIMIT) {
1705                 switch (op) {
1706                   case JSOP_GETPROP2:
1707                     saveop = JSOP_GETPROP;
1708                     break;
1709                   case JSOP_GETELEM2:
1710                     saveop = JSOP_GETELEM;
1711                     break;
1712                   default:;
1713                 }
1714             }
1715             LOCAL_ASSERT(js_CodeSpec[saveop].length == oplen);
1716 
1717             jp->dvgfence = NULL;
1718         }
1719 
1720         if (cs->token) {
1721             switch (cs->nuses) {
1722               case 2:
1723                 sn = js_GetSrcNote(jp->script, pc);
1724                 if (sn && SN_TYPE(sn) == SRC_ASSIGNOP) {
1725                     /*
1726                      * Avoid over-parenthesizing y in x op= y based on its
1727                      * expansion: x = x op y (replace y by z = w to see the
1728                      * problem).
1729                      */
1730                     op = pc[oplen];
1731                     LOCAL_ASSERT(op != saveop);
1732                 }
1733                 rval = POP_STR();
1734                 lval = POP_STR();
1735                 if (op != saveop) {
1736                     /* Print only the right operand of the assignment-op. */
1737                     todo = SprintCString(&ss->sprinter, rval);
1738                     op = saveop;
1739                 } else if (!inXML) {
1740                     todo = Sprint(&ss->sprinter, "%s %s %s",
1741                                   lval, cs->token, rval);
1742                 } else {
1743                     /* In XML, just concatenate the two operands. */
1744                     LOCAL_ASSERT(op == JSOP_ADD);
1745                     todo = Sprint(&ss->sprinter, ss_format, lval, rval);
1746                 }
1747                 break;
1748 
1749               case 1:
1750                 rval = POP_STR();
1751                 todo = Sprint(&ss->sprinter, ss_format, cs->token, rval);
1752                 break;
1753 
1754               case 0:
1755                 todo = SprintCString(&ss->sprinter, cs->token);
1756                 break;
1757 
1758               default:
1759                 todo = -2;
1760                 break;
1761             }
1762         } else {
1763             switch (op) {
1764 #define BEGIN_LITOPX_CASE(OP)                                                 \
1765               case OP:                                                        \
1766                 atomIndex = GET_ATOM_INDEX(pc);                               \
1767               do_##OP:                                                        \
1768                 atom = js_GetAtom(cx, &jp->script->atomMap, atomIndex);
1769 
1770 #define END_LITOPX_CASE                                                       \
1771                 break;
1772 
1773               case JSOP_NOP:
1774                 /*
1775                  * Check for a do-while loop, a for-loop with an empty
1776                  * initializer part, a labeled statement, a function
1777                  * definition, or try/finally.
1778                  */
1779                 sn = js_GetSrcNote(jp->script, pc);
1780                 todo = -2;
1781                 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
1782                   case SRC_WHILE:
1783                     js_printf(SET_MAYBE_BRACE(jp), "\tdo {\n");
1784                     jp->indent += 4;
1785                     break;
1786 
1787                   case SRC_FOR:
1788                     rval = "";
1789 
1790                   do_forloop:
1791                     /* Skip the JSOP_NOP or JSOP_POP bytecode. */
1792                     pc++;
1793 
1794                     /* Get the cond, next, and loop-closing tail offsets. */
1795                     cond = js_GetSrcNoteOffset(sn, 0);
1796                     next = js_GetSrcNoteOffset(sn, 1);
1797                     tail = js_GetSrcNoteOffset(sn, 2);
1798                     LOCAL_ASSERT(tail + GetJumpOffset(pc+tail, pc+tail) == 0);
1799 
1800                     /* Print the keyword and the possibly empty init-part. */
1801                     js_printf(jp, "\tfor (%s;", rval);
1802 
1803                     if (pc[cond] == JSOP_IFEQ || pc[cond] == JSOP_IFEQX) {
1804                         /* Decompile the loop condition. */
1805                         DECOMPILE_CODE(pc, cond);
1806                         js_printf(jp, " %s", POP_STR());
1807                     }
1808 
1809                     /* Need a semicolon whether or not there was a cond. */
1810                     js_puts(jp, ";");
1811 
1812                     if (pc[next] != JSOP_GOTO && pc[next] != JSOP_GOTOX) {
1813                         /* Decompile the loop updater. */
1814                         DECOMPILE_CODE(pc + next, tail - next - 1);
1815                         js_printf(jp, " %s", POP_STR());
1816                     }
1817 
1818                     /* Do the loop body. */
1819                     js_printf(SET_MAYBE_BRACE(jp), ") {\n");
1820                     jp->indent += 4;
1821                     oplen = (cond) ? js_CodeSpec[pc[cond]].length : 0;
1822                     DECOMPILE_CODE(pc + cond + oplen, next - cond - oplen);
1823                     jp->indent -= 4;
1824                     js_printf(jp, "\t}\n");
1825 
1826                     /* Set len so pc skips over the entire loop. */
1827                     len = tail + js_CodeSpec[pc[tail]].length;
1828                     break;
1829 
1830                   case SRC_LABEL:
1831                     atom = js_GetAtom(cx, &jp->script->atomMap,
1832                                       (jsatomid) js_GetSrcNoteOffset(sn, 0));
1833                     jp->indent -= 4;
1834                     rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
1835                     if (!rval)
1836                         return NULL;
1837                     RETRACT(&ss->sprinter, rval);
1838                     js_printf(CLEAR_MAYBE_BRACE(jp), "\t%s:\n", rval);
1839                     jp->indent += 4;
1840                     break;
1841 
1842                   case SRC_LABELBRACE:
1843                     atom = js_GetAtom(cx, &jp->script->atomMap,
1844                                       (jsatomid) js_GetSrcNoteOffset(sn, 0));
1845                     rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
1846                     if (!rval)
1847                         return NULL;
1848                     RETRACT(&ss->sprinter, rval);
1849                     js_printf(CLEAR_MAYBE_BRACE(jp), "\t%s: {\n", rval);
1850                     jp->indent += 4;
1851                     break;
1852 
1853                   case SRC_ENDBRACE:
1854                     jp->indent -= 4;
1855                     js_printf(jp, "\t}\n");
1856                     break;
1857 
1858                   case SRC_FUNCDEF:
1859                     atom = js_GetAtom(cx, &jp->script->atomMap,
1860                                       (jsatomid) js_GetSrcNoteOffset(sn, 0));
1861                     LOCAL_ASSERT(ATOM_IS_OBJECT(atom));
1862                   do_function:
1863                     obj = ATOM_TO_OBJECT(atom);
1864                     fun = (JSFunction *) JS_GetPrivate(cx, obj);
1865                     jp2 = js_NewPrinter(cx, JS_GetFunctionName(fun),
1866                                         jp->indent, jp->pretty);
1867                     if (!jp2)
1868                         return NULL;
1869                     jp2->scope = jp->scope;
1870                     js_puts(jp2, "\n");
1871                     ok = js_DecompileFunction(jp2, fun);
1872                     if (ok) {
1873                         js_puts(jp2, "\n");
1874                         str = js_GetPrinterOutput(jp2);
1875                         if (str)
1876                             js_printf(jp, "%s\n", JS_GetStringBytes(str));
1877                         else
1878                             ok = JS_FALSE;
1879                     }
1880                     js_DestroyPrinter(jp2);
1881                     if (!ok)
1882                         return NULL;
1883 
1884                     break;
1885 
1886                   case SRC_BRACE:
1887                     js_printf(CLEAR_MAYBE_BRACE(jp), "\t{\n");
1888                     jp->indent += 4;
1889                     len = js_GetSrcNoteOffset(sn, 0);
1890                     DECOMPILE_CODE(pc + oplen, len - oplen);
1891                     jp->indent -= 4;
1892                     js_printf(jp, "\t}\n");
1893                     break;
1894 
1895                   default:;
1896                 }
1897                 break;
1898 
1899               case JSOP_GROUP:
1900                 cs = &js_CodeSpec[lastop];
1901                 if ((cs->prec != 0 &&
1902                      cs->prec == js_CodeSpec[pc[JSOP_GROUP_LENGTH]].prec) ||
1903                     pc[JSOP_GROUP_LENGTH] == JSOP_PUSHOBJ ||
1904                     pc[JSOP_GROUP_LENGTH] == JSOP_DUP) {
1905                     /*
1906                      * Force parens if this JSOP_GROUP forced re-association
1907                      * against precedence, or if this is a call or constructor
1908                      * expression, or if it is destructured (JSOP_DUP).
1909                      *
1910                      * This is necessary to handle the operator new grammar,
1911                      * by which new x(y).z means (new x(y))).z.  For example
1912                      * new (x(y).z) must decompile with the constructor
1913                      * parenthesized, but normal precedence has JSOP_GETPROP
1914                      * (for the final .z) higher than JSOP_NEW.  In general,
1915                      * if the call or constructor expression is parenthesized,
1916                      * we preserve parens.
1917                      */
1918                     op = JSOP_NAME;
1919                     rval = POP_STR();
1920                     todo = SprintCString(&ss->sprinter, rval);
1921                 } else {
1922                     /*
1923                      * Don't explicitly parenthesize -- just fix the top
1924                      * opcode so that the auto-parens magic in PopOff can do
1925                      * its thing.
1926                      */
1927                     LOCAL_ASSERT(ss->top != 0);
1928                     ss->opcodes[ss->top-1] = saveop = lastop;
1929                     todo = -2;
1930                 }
1931                 break;
1932 
1933               case JSOP_STARTITER:
1934                 todo = -2;
1935                 break;
1936 
1937               case JSOP_PUSH:
1938 #if JS_HAS_DESTRUCTURING
1939                 sn = js_GetSrcNote(jp->script, pc);
1940                 if (sn && SN_TYPE(sn) == SRC_GROUPASSIGN) {
1941                     pc = DecompileGroupAssignment(ss, pc, endpc, sn, &todo);
1942                     if (!pc)
1943                         return NULL;
1944                     LOCAL_ASSERT(*pc == JSOP_SETSP);
1945                     len = oplen = JSOP_SETSP_LENGTH;
1946                     goto end_groupassignment;
1947                 }
1948 #endif
1949                 /* FALL THROUGH */
1950 
1951               case JSOP_PUSHOBJ:
1952               case JSOP_BINDNAME:
1953               do_JSOP_BINDNAME:
1954                 todo = Sprint(&ss->sprinter, "");
1955                 break;
1956 
1957               case JSOP_TRY:
1958                 js_printf(CLEAR_MAYBE_BRACE(jp), "\ttry {\n");
1959                 jp->indent += 4;
1960                 todo = -2;
1961                 break;
1962 
1963               case JSOP_FINALLY:
1964                 jp->indent -= 4;
1965                 js_printf(CLEAR_MAYBE_BRACE(jp), "\t} finally {\n");
1966                 jp->indent += 4;
1967 
1968                 /*
1969                  * We must push an empty string placeholder for gosub's return
1970                  * address, popped by JSOP_RETSUB and counted by script->depth
1971                  * but not by ss->top (see JSOP_SETSP, below).
1972                  */
1973                 todo = Sprint(&ss->sprinter, exception_cookie);
1974                 if (todo < 0 || !PushOff(ss, todo, op))
1975                     return NULL;
1976                 todo = Sprint(&ss->sprinter, retsub_pc_cookie);
1977                 break;
1978 
1979               case JSOP_RETSUB:
1980                 rval = POP_STR();
1981                 LOCAL_ASSERT(strcmp(rval, retsub_pc_cookie) == 0);
1982                 lval = POP_STR();
1983                 LOCAL_ASSERT(strcmp(lval, exception_cookie) == 0);
1984                 todo = -2;
1985                 break;
1986 
1987               case JSOP_SWAP:
1988                 /*
1989                  * We don't generate this opcode currently, and previously we
1990                  * did not need to decompile it.  If old, serialized bytecode
1991                  * uses it still, we should fall through and set todo = -2.
1992                  */
1993                 /* FALL THROUGH */
1994 
1995               case JSOP_GOSUB:
1996               case JSOP_GOSUBX:
1997                 /*
1998                  * JSOP_GOSUB and GOSUBX have no effect on the decompiler's
1999                  * string stack because the next op in bytecode order finds
2000                  * the stack balanced by a JSOP_RETSUB executed elsewhere.
2001                  */
2002                 todo = -2;
2003                 break;
2004 
2005               case JSOP_SETSP:
2006               {
2007                 uintN newtop, oldtop, i;
2008 
2009                 /*
2010                  * The compiler models operand stack depth and fixes the stack
2011                  * pointer on entry to a catch clause based on its depth model.
2012                  * The decompiler must match the code generator's model, which
2013                  * is why JSOP_FINALLY pushes a cookie that JSOP_RETSUB pops.
2014                  */
2015                 newtop = (uintN) GET_UINT16(pc);
2016                 oldtop = ss->top;
2017                 LOCAL_ASSERT(newtop <= oldtop);
2018                 todo = -2;
2019 
2020 #if JS_HAS_DESTRUCTURING
2021                 sn = js_GetSrcNote(jp->script, pc);
2022                 if (sn && SN_TYPE(sn) == SRC_GROUPASSIGN) {
2023                     todo = Sprint(&ss->sprinter, "%s[] = [",
2024                                   VarPrefix(sn));
2025                     if (todo < 0)
2026                         return NULL;
2027                     for (i = newtop; i < oldtop; i++) {
2028                         rval = OFF2STR(&ss->sprinter, ss->offsets[i]);
2029                         if (Sprint(&ss->sprinter, ss_format,
2030                                    (i == newtop) ? "" : ", ",
2031                                    (i == oldtop - 1 && *rval == '\0')
2032                                    ? ", " : rval) < 0) {
2033                             return NULL;
2034                         }
2035                     }
2036                     if (SprintPut(&ss->sprinter, "]", 1) < 0)
2037                         return NULL;
2038 
2039                     /*
2040                      * Kill newtop before the end_groupassignment: label by
2041                      * retracting/popping early.  Control will either jump to
2042                      * do_forloop: or do_letheadbody: or else break from our
2043                      * case JSOP_SETSP: after the switch (*pc2) below.
2044                      */
2045                     if (newtop < oldtop) {
2046                         ss->sprinter.offset = GetOff(ss, newtop);
2047                         ss->top = newtop;
2048                     }
2049 
2050                   end_groupassignment:
2051                     /*
2052                      * Thread directly to the next opcode if we can, to handle
2053                      * the special cases of a group assignment in the first or
2054                      * last part of a for(;;) loop head, or in a let block or
2055                      * expression head.
2056                      *
2057                      * NB: todo at this point indexes space in ss->sprinter
2058                      * that is liable to be overwritten.  The code below knows
2059                      * exactly how long rval lives, or else copies it down via
2060                      * SprintCString.
2061                      */
2062                     rval = OFF2STR(&ss->sprinter, todo);
2063                     todo = -2;
2064                     pc2 = pc + oplen;
2065                     switch (*pc2) {
2066                       case JSOP_NOP:
2067                         /* First part of for(;;) or let block/expr head. */
2068                         sn = js_GetSrcNote(jp->script, pc2);
2069                         if (sn) {
2070                             if (SN_TYPE(sn) == SRC_FOR) {
2071                                 pc = pc2;
2072                                 goto do_forloop;
2073                             }
2074                             if (SN_TYPE(sn) == SRC_DECL) {
2075                                 if (ss->top == jp->script->depth) {
2076                                     /*
2077                                      * This must be an empty destructuring
2078                                      * in the head of a let whose body block
2079                                      * is also empty.
2080                                      */
2081                                     pc = pc2 + 1;
2082                                     len = js_GetSrcNoteOffset(sn, 0);
2083                                     LOCAL_ASSERT(pc[len] == JSOP_LEAVEBLOCK);
2084                                     js_printf(jp, "\tlet (%s) {\n", rval);
2085                                     js_printf(jp, "\t}\n");
2086                                     goto end_setsp;
2087                                 }
2088                                 todo = SprintCString(&ss->sprinter, rval);
2089                                 if (todo < 0 || !PushOff(ss, todo, JSOP_NOP))
2090                                     return NULL;
2091                                 op = JSOP_POP;
2092                                 pc = pc2 + 1;
2093                                 goto do_letheadbody;
2094                             }
2095                         }
2096                         break;
2097 
2098                       case JSOP_GOTO:
2099                       case JSOP_GOTOX:
2100                         /* Third part of for(;;) loop head. */
2101                         cond = GetJumpOffset(pc2, pc2);
2102                         sn = js_GetSrcNote(jp->script, pc2 + cond - 1);
2103                         if (sn && SN_TYPE(sn) == SRC_FOR) {
2104                             todo = SprintCString(&ss->sprinter, rval);
2105                             saveop = JSOP_NOP;
2106                         }
2107                         break;
2108                     }
2109 
2110                     /*
2111                      * If control flow reaches this point with todo still -2,
2112                      * just print rval as an expression statement.
2113                      */
2114                     if (todo == -2)
2115                         js_printf(jp, "\t%s;\n", rval);
2116                   end_setsp:
2117                     break;
2118                 }
2119 #endif
2120                 if (newtop < oldtop) {
2121                     ss->sprinter.offset = GetOff(ss, newtop);
2122                     ss->top = newtop;
2123                 }
2124                 break;
2125               }
2126 
2127               case JSOP_EXCEPTION:
2128                 /* The catch decompiler handles this op itself. */
2129                 LOCAL_ASSERT(JS_FALSE);
2130                 break;
2131 
2132               case JSOP_POP:
2133                 /*
2134                  * By default, do not automatically parenthesize when popping
2135                  * a stacked expression decompilation.  We auto-parenthesize
2136                  * only when JSOP_POP is annotated with SRC_PCDELTA, meaning
2137                  * comma operator.
2138                  */
2139                 op = JSOP_POPV;
2140                 /* FALL THROUGH */
2141 
2142               case JSOP_POPV:
2143                 sn = js_GetSrcNote(jp->script, pc);
2144                 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
2145                   case SRC_FOR:
2146                     /* Force parens around 'in' expression at 'for' front. */
2147                     if (ss->opcodes[ss->top-1] == JSOP_IN)
2148                         op = JSOP_LSH;
2149                     rval = POP_STR();
2150                     todo = -2;
2151                     goto do_forloop;
2152 
2153                   case SRC_PCDELTA:
2154                     /* Comma operator: use JSOP_POP for correct precedence. */
2155                     op = JSOP_POP;
2156 
2157                     /* Pop and save to avoid blowing stack depth budget. */
2158                     lval = JS_strdup(cx, POP_STR());
2159                     if (!lval)
2160                         return NULL;
2161 
2162                     /*
2163                      * The offset tells distance to the end of the right-hand
2164                      * operand of the comma operator.
2165                      */
2166                     done = pc + len;
2167                     pc += js_GetSrcNoteOffset(sn, 0);
2168                     len = 0;
2169 
2170                     if (!Decompile(ss, done, pc - done)) {
2171                         JS_free(cx, (char *)lval);
2172                         return NULL;
2173                     }
2174 
2175                     /* Pop Decompile result and print comma expression. */
2176                     rval = POP_STR();
2177                     todo = Sprint(&ss->sprinter, "%s, %s", lval, rval);
2178                     JS_free(cx, (char *)lval);
2179                     break;
2180 
2181                   case SRC_HIDDEN:
2182                     /* Hide this pop, it's from a goto in a with or for/in. */
2183                     todo = -2;
2184                     break;
2185 
2186                   case SRC_DECL:
2187                     /* This pop is at the end of the let block/expr head. */
2188                     pc += JSOP_POP_LENGTH;
2189 #if JS_HAS_DESTRUCTURING
2190                   do_letheadbody:
2191 #endif
2192                     len = js_GetSrcNoteOffset(sn, 0);
2193                     if (pc[len] == JSOP_LEAVEBLOCK) {
2194                         js_printf(CLEAR_MAYBE_BRACE(jp), "\tlet (%s) {\n",
2195                                   POP_STR());
2196                         jp->indent += 4;
2197                         DECOMPILE_CODE(pc, len);
2198                         jp->indent -= 4;
2199                         js_printf(jp, "\t}\n");
2200                         todo = -2;
2201                     } else {
2202                         LOCAL_ASSERT(pc[len] == JSOP_LEAVEBLOCKEXPR);
2203 
2204                         lval = JS_strdup(cx, POP_STR());
2205                         if (!lval)
2206                             return NULL;
2207 
2208                         if (!Decompile(ss, pc, len)) {
2209                             JS_free(cx, (char *)lval);
2210                             return NULL;
2211                         }
2212                         rval = POP_STR();
2213                         todo = Sprint(&ss->sprinter,
2214                                       (*rval == '{')
2215                                       ? "let (%s) (%s)"
2216                                       : "let (%s) %s",
2217                                       lval, rval);
2218                         JS_free(cx, (char *)lval);
2219                     }
2220                     break;
2221 
2222                   default:
2223                     /* Turn off parens around a yield statement. */
2224                     if (ss->opcodes[ss->top-1] == JSOP_YIELD)
2225                         op = JSOP_NOP;
2226 
2227                     rval = POP_STR();
2228                     if (*rval != '\0') {
2229 #if JS_HAS_BLOCK_SCOPE
2230                         /*
2231                          * If a let declaration is the only child of a control
2232                          * structure that does not require braces, it must not
2233                          * be braced.  If it were braced explicitly, it would
2234                          * be bracketed by JSOP_ENTERBLOCK/JSOP_LEAVEBLOCK.
2235                          */
2236                         if (jp->braceState == MAYBE_BRACE &&
2237                             pc + JSOP_POP_LENGTH == endpc &&
2238                             !strncmp(rval, var_prefix[SRC_DECL_LET], 4) &&
2239                             rval[4] != '(') {
2240                             SetDontBrace(jp);
2241                         }
2242 #endif
2243                         js_printf(jp,
2244                                   (*rval == '{' ||
2245                                    (strncmp(rval, js_function_str, 8) == 0 &&
2246                                     rval[8] == ' '))
2247                                   ? "\t(%s);\n"
2248                                   : "\t%s;\n",
2249                                   rval);
2250                     }
2251                     todo = -2;
2252                     break;
2253                 }
2254                 break;
2255 
2256               case JSOP_POP2:
2257               case JSOP_ENDITER:
2258                 sn = js_GetSrcNote(jp->script, pc);
2259                 todo = -2;
2260                 if (sn && SN_TYPE(sn) == SRC_HIDDEN)
2261                     break;
2262                 (void) PopOff(ss, op);
2263                 if (op == JSOP_POP2)
2264                     (void) PopOff(ss, op);
2265                 break;
2266 
2267               case JSOP_ENTERWITH:
2268                 LOCAL_ASSERT(!js_GetSrcNote(jp->script, pc));
2269                 rval = POP_STR();
2270                 js_printf(SET_MAYBE_BRACE(jp), "\twith (%s) {\n", rval);
2271                 jp->indent += 4;
2272                 todo = Sprint(&ss->sprinter, with_cookie);
2273                 break;
2274 
2275               case JSOP_LEAVEWITH:
2276                 sn = js_GetSrcNote(jp->script, pc);
2277                 todo = -2;
2278                 if (sn && SN_TYPE(sn) == SRC_HIDDEN)
2279                     break;
2280                 rval = POP_STR();
2281                 LOCAL_ASSERT(strcmp(rval, with_cookie) == 0);
2282                 jp->indent -= 4;
2283                 js_printf(jp, "\t}\n");
2284                 break;
2285 
2286               BEGIN_LITOPX_CASE(JSOP_ENTERBLOCK)
2287               {
2288                 JSAtom **atomv, *smallv[5];
2289                 JSScopeProperty *sprop;
2290 
2291                 obj = ATOM_TO_OBJECT(atom);
2292                 argc = OBJ_BLOCK_COUNT(cx, obj);
2293                 if ((size_t)argc <= sizeof smallv / sizeof smallv[0]) {
2294                     atomv = smallv;
2295                 } else {
2296                     atomv = (JSAtom **) JS_malloc(cx, argc * sizeof(JSAtom *));
2297                     if (!atomv)
2298                         return NULL;
2299                 }
2300 
2301                 /* From here on, control must flow through enterblock_out. */
2302                 for (sprop = OBJ_SCOPE(obj)->lastProp; sprop;
2303                      sprop = sprop->parent) {
2304                     if (!(sprop->flags & SPROP_HAS_SHORTID))
2305                         continue;
2306                     LOCAL_ASSERT(sprop->shortid < argc);
2307                     atomv[sprop->shortid] = JSID_TO_ATOM(sprop->id);
2308                 }
2309                 ok = JS_TRUE;
2310                 for (i = 0; i < argc; i++) {
2311                     atom = atomv[i];
2312                     rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
2313                     if (!rval ||
2314                         !PushOff(ss, STR2OFF(&ss->sprinter, rval), op)) {
2315                         ok = JS_FALSE;
2316                         goto enterblock_out;
2317                     }
2318                 }
2319 
2320                 sn = js_GetSrcNote(jp->script, pc);
2321                 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
2322 #if JS_HAS_BLOCK_SCOPE
2323                   case SRC_BRACE:
2324                     js_printf(CLEAR_MAYBE_BRACE(jp), "\t{\n");
2325                     jp->indent += 4;
2326                     len = js_GetSrcNoteOffset(sn, 0);
2327                     ok = Decompile(ss, pc + oplen, len - oplen) != NULL;
2328                     if (!ok)
2329                         goto enterblock_out;
2330                     jp->indent -= 4;
2331                     js_printf(jp, "\t}\n");
2332                     break;
2333 #endif
2334 
2335                   case SRC_CATCH:
2336                     jp->indent -= 4;
2337                     js_printf(CLEAR_MAYBE_BRACE(jp), "\t} catch (");
2338 
2339                     pc2 = pc;
2340                     pc += oplen;
2341                     LOCAL_ASSERT(*pc == JSOP_EXCEPTION);
2342                     pc += JSOP_EXCEPTION_LENGTH;
2343                     if (*pc == JSOP_DUP) {
2344                         sn2 = js_GetSrcNote(jp->script, pc);
2345                         if (sn2 && SN_TYPE(sn2) == SRC_HIDDEN) {
2346                             /*
2347                              * This is a hidden dup to save the exception for
2348                              * later. It must exist only when the catch has
2349                              * an exception guard.
2350                              */
2351                             LOCAL_ASSERT(js_GetSrcNoteOffset(sn, 0) != 0);
2352                             pc += JSOP_DUP_LENGTH;
2353                         }
2354                     }
2355 #if JS_HAS_DESTRUCTURING
2356                     if (*pc == JSOP_DUP) {
2357                         pc = DecompileDestructuring(ss, pc, endpc);
2358                         if (!pc) {
2359                             ok = JS_FALSE;
2360                             goto enterblock_out;
2361                         }
2362                         LOCAL_ASSERT(*pc == JSOP_POP);
2363                         pc += JSOP_POP_LENGTH;
2364                         lval = PopStr(ss, JSOP_NOP);
2365                         js_puts(jp, lval);
2366                     } else {
2367 #endif
2368                         LOCAL_ASSERT(*pc == JSOP_SETLOCALPOP);
2369                         i = GET_UINT16(pc);
2370                         pc += JSOP_SETLOCALPOP_LENGTH;
2371                         atom = atomv[i - OBJ_BLOCK_DEPTH(cx, obj)];
2372                         str = ATOM_TO_STRING(atom);
2373                         if (!QuoteString(&jp->sprinter, str, 0)) {
2374                             ok = JS_FALSE;
2375                             goto enterblock_out;
2376                         }
2377 #if JS_HAS_DESTRUCTURING
2378                     }
2379 #endif
2380 
2381                     len = js_GetSrcNoteOffset(sn, 0);
2382                     if (len) {
2383                         len -= PTRDIFF(pc, pc2, jsbytecode);
2384                         LOCAL_ASSERT(len > 0);
2385                         js_printf(jp, " if ");
2386                         ok = Decompile(ss, pc, len) != NULL;
2387                         if (!ok)
2388                             goto enterblock_out;
2389                         js_printf(jp, "%s", POP_STR());
2390                         pc += len;
2391                         LOCAL_ASSERT(*pc == JSOP_IFEQ || *pc == JSOP_IFEQX);
2392                         pc += js_CodeSpec[*pc].length;
2393                     }
2394 
2395                     js_printf(jp, ") {\n");
2396                     jp->indent += 4;
2397                     len = 0;
2398                     break;
2399                 }
2400 
2401                 todo = -2;
2402 
2403               enterblock_out:
2404                 if (atomv != smallv)
2405                     JS_free(cx, atomv);
2406                 if (!ok)
2407                     return NULL;
2408               }
2409               END_LITOPX_CASE
2410 
2411               case JSOP_LEAVEBLOCK:
2412               case JSOP_LEAVEBLOCKEXPR:
2413               {
2414                 uintN top, depth;
2415 
2416                 sn = js_GetSrcNote(jp->script, pc);
2417                 todo = -2;
2418                 if (op == JSOP_LEAVEBLOCKEXPR) {
2419                     LOCAL_ASSERT(SN_TYPE(sn) == SRC_PCBASE);
2420                     rval = POP_STR();
2421                 } else if (sn) {
2422                     LOCAL_ASSERT(op == JSOP_LEAVEBLOCK);
2423                     if (SN_TYPE(sn) == SRC_HIDDEN)
2424                         break;
2425                     LOCAL_ASSERT(SN_TYPE(sn) == SRC_CATCH);
2426                     LOCAL_ASSERT((uintN)js_GetSrcNoteOffset(sn, 0) == ss->top);
2427                 }
2428                 top = ss->top;
2429                 depth = GET_UINT16(pc);
2430                 LOCAL_ASSERT(top >= depth);
2431                 top -= depth;
2432                 ss->top = top;
2433                 ss->sprinter.offset = GetOff(ss, top);
2434                 if (op == JSOP_LEAVEBLOCKEXPR)
2435                     todo = SprintCString(&ss->sprinter, rval);
2436                 break;
2437               }
2438 
2439               case JSOP_GETLOCAL:
2440                 i = GET_UINT16(pc);
2441                 sn = js_GetSrcNote(jp->script, pc);
2442                 LOCAL_ASSERT((uintN)i < ss->top);
2443                 rval = GetLocal(ss, i);
2444 
2445 #if JS_HAS_DESTRUCTURING
2446                 if (sn && SN_TYPE(sn) == SRC_GROUPASSIGN) {
2447                     pc = DecompileGroupAssignment(ss, pc, endpc, sn, &todo);
2448                     if (!pc)
2449                         return NULL;
2450                     LOCAL_ASSERT(*pc == JSOP_SETSP);
2451                     len = oplen = JSOP_SETSP_LENGTH;
2452                     goto end_groupassignment;
2453                 }
2454 #endif
2455 
2456                 todo = Sprint(&ss->sprinter, ss_format, VarPrefix(sn), rval);
2457                 break;
2458 
2459               case JSOP_SETLOCAL:
2460               case JSOP_SETLOCALPOP:
2461                 i = GET_UINT16(pc);
2462                 lval = GetStr(ss, i);
2463                 rval = POP_STR();
2464                 goto do_setlval;
2465 
2466               case JSOP_INCLOCAL:
2467               case JSOP_DECLOCAL:
2468                 i = GET_UINT16(pc);
2469                 lval = GetLocal(ss, i);
2470                 goto do_inclval;
2471 
2472               case JSOP_LOCALINC:
2473               case JSOP_LOCALDEC:
2474                 i = GET_UINT16(pc);
2475                 lval = GetLocal(ss, i);
2476                 goto do_lvalinc;
2477 
2478               case JSOP_FORLOCAL:
2479                 i = GET_UINT16(pc);
2480                 lval = GetStr(ss, i);
2481                 atom = NULL;
2482                 goto do_forlvalinloop;
2483 
2484               case JSOP_RETRVAL:
2485                 todo = -2;
2486                 break;
2487 
2488               case JSOP_SETRVAL:
2489               case JSOP_RETURN:
2490                 rval = POP_STR();
2491                 if (*rval != '\0')
2492                     js_printf(jp, "\t%s %s;\n", js_return_str, rval);
2493                 else
2494                     js_printf(jp, "\t%s;\n", js_return_str);
2495                 todo = -2;
2496                 break;
2497 
2498 #if JS_HAS_GENERATORS
2499               case JSOP_YIELD:
2500                 op = JSOP_SETNAME;      /* turn off most parens */
2501                 rval = POP_STR();
2502                 todo = (*rval != '\0')
2503                        ? Sprint(&ss->sprinter,
2504                                 (strncmp(rval, js_yield_str, 5) == 0 &&
2505                                  (rval[5] == ' ' || rval[5] == '\0'))
2506                                 ? "%s (%s)"
2507                                 : "%s %s",
2508                                 js_yield_str, rval)
2509                        : SprintCString(&ss->sprinter, js_yield_str);
2510                 break;
2511 
2512               case JSOP_ARRAYPUSH:
2513               {
2514                 uintN pos, blockpos, startpos;
2515                 ptrdiff_t start;
2516 
2517                 rval = POP_STR();
2518                 pos = ss->top;
2519                 while ((op = ss->opcodes[--pos]) != JSOP_ENTERBLOCK &&
2520                        op != JSOP_NEWINIT) {
2521                     LOCAL_ASSERT(pos != 0);
2522                 }
2523                 blockpos = pos;
2524                 while (ss->opcodes[pos] == JSOP_ENTERBLOCK) {
2525                     if (pos == 0)
2526                         break;
2527                     --pos;
2528                 }
2529                 LOCAL_ASSERT(ss->opcodes[pos] == JSOP_NEWINIT);
2530                 startpos = pos;
2531                 start = ss->offsets[pos];
2532                 LOCAL_ASSERT(ss->sprinter.base[start] == '[' ||
2533                              ss->sprinter.base[start] == '#');
2534                 pos = blockpos;
2535                 while (ss->opcodes[++pos] == JSOP_STARTITER)
2536                     LOCAL_ASSERT(pos < ss->top);
2537                 LOCAL_ASSERT(pos < ss->top);
2538                 xval = OFF2STR(&ss->sprinter, ss->offsets[pos]);
2539                 lval = OFF2STR(&ss->sprinter, start);
2540                 RETRACT(&ss->sprinter, lval);
2541                 todo = Sprint(&ss->sprinter, "%s%s%.*s",
2542                               lval, rval, rval - xval, xval);
2543                 if (todo < 0)
2544                     return NULL;
2545                 ss->offsets[startpos] = todo;
2546                 todo = -2;
2547                 break;
2548               }
2549 #endif
2550 
2551               case JSOP_THROWING:
2552                 todo = -2;
2553                 break;
2554 
2555               case JSOP_THROW:
2556                 sn = js_GetSrcNote(jp->script, pc);
2557                 todo = -2;
2558                 if (sn && SN_TYPE(sn) == SRC_HIDDEN)
2559                     break;
2560                 rval = POP_STR();
2561                 js_printf(jp, "\t%s %s;\n", cs->name, rval);
2562                 break;
2563 
2564               case JSOP_GOTO:
2565               case JSOP_GOTOX:
2566                 sn = js_GetSrcNote(jp->script, pc);
2567                 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
2568                   case SRC_CONT2LABEL:
2569                     atom = js_GetAtom(cx, &jp->script->atomMap,
2570                                       (jsatomid) js_GetSrcNoteOffset(sn, 0));
2571                     rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
2572                     if (!rval)
2573                         return NULL;
2574                     RETRACT(&ss->sprinter, rval);
2575                     js_printf(jp, "\tcontinue %s;\n", rval);
2576                     break;
2577                   case SRC_CONTINUE:
2578                     js_printf(jp, "\tcontinue;\n");
2579                     break;
2580                   case SRC_BREAK2LABEL:
2581                     atom = js_GetAtom(cx, &jp->script->atomMap,
2582                                       (jsatomid) js_GetSrcNoteOffset(sn, 0));
2583                     rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
2584                     if (!rval)
2585                         return NULL;
2586                     RETRACT(&ss->sprinter, rval);
2587                     js_printf(jp, "\tbreak %s;\n", rval);
2588                     break;
2589                   case SRC_HIDDEN:
2590                     break;
2591                   default:
2592                     js_printf(jp, "\tbreak;\n");
2593                     break;
2594                 }
2595                 todo = -2;
2596                 break;
2597 
2598               case JSOP_IFEQ:
2599               case JSOP_IFEQX:
2600               {
2601                 JSBool elseif = JS_FALSE;
2602 
2603               if_again:
2604                 len = GetJumpOffset(pc, pc);
2605                 sn = js_GetSrcNote(jp->script, pc);
2606 
2607                 switch (sn ? SN_TYPE(sn) : SRC_NULL) {
2608                   case SRC_IF:
2609                   case SRC_IF_ELSE:
2610                     op = JSOP_NOP;              /* turn off parens */
2611                     rval = POP_STR();
2612                     if (ss->inArrayInit) {
2613                         LOCAL_ASSERT(SN_TYPE(sn) == SRC_IF);
2614                         if (Sprint(&ss->sprinter, " if (%s)", rval) < 0)
2615                             return NULL;
2616                     } else {
2617                         js_printf(SET_MAYBE_BRACE(jp),
2618                                   elseif ? " if (%s) {\n" : "\tif (%s) {\n",
2619                                   rval);
2620                         jp->indent += 4;
2621                     }
2622 
2623                     if (SN_TYPE(sn) == SRC_IF) {
2624                         DECOMPILE_CODE(pc + oplen, len - oplen);
2625                     } else {
2626                         LOCAL_ASSERT(!ss->inArrayInit);
2627                         tail = js_GetSrcNoteOffset(sn, 0);
2628                         DECOMPILE_CODE(pc + oplen, tail - oplen);
2629                         jp->indent -= 4;
2630                         pc += tail;
2631                         LOCAL_ASSERT(*pc == JSOP_GOTO || *pc == JSOP_GOTOX);
2632                         oplen = js_CodeSpec[*pc].length;
2633                         len = GetJumpOffset(pc, pc);
2634                         js_printf(jp, "\t} else");
2635 
2636                         /*
2637                          * If the second offset for sn is non-zero, it tells
2638                          * the distance from the goto around the else, to the
2639                          * ifeq for the if inside the else that forms an "if
2640                          * else if" chain.  Thus cond spans the condition of
2641                          * the second if, so we simply decompile it and start
2642                          * over at label if_again.
2643                          */
2644                         cond = js_GetSrcNoteOffset(sn, 1);
2645                         if (cond != 0) {
2646                             DECOMPILE_CODE(pc + oplen, cond - oplen);
2647                             pc += cond;
2648                             elseif = JS_TRUE;
2649                             goto if_again;
2650                         }
2651 
2652                         js_printf(SET_MAYBE_BRACE(jp), " {\n");
2653                         jp->indent += 4;
2654                         DECOMPILE_CODE(pc + oplen, len - oplen);
2655                     }
2656 
2657                     if (!ss->inArrayInit) {
2658                         jp->indent -= 4;
2659                         js_printf(jp, "\t}\n");
2660                     }
2661                     todo = -2;
2662                     break;
2663 
2664                   case SRC_WHILE:
2665                     rval = POP_STR();
2666                     js_printf(SET_MAYBE_BRACE(jp), "\twhile (%s) {\n", rval);
2667                     jp->indent += 4;
2668                     tail = js_GetSrcNoteOffset(sn, 0);
2669                     DECOMPILE_CODE(pc + oplen, tail - oplen);
2670                     jp->indent -= 4;
2671                     js_printf(jp, "\t}\n");
2672                     todo = -2;
2673                     break;
2674 
2675                   case SRC_COND:
2676                     xval = JS_strdup(cx, POP_STR());
2677                     if (!xval)
2678                         return NULL;
2679                     len = js_GetSrcNoteOffset(sn, 0);
2680                     DECOMPILE_CODE(pc + oplen, len - oplen);
2681                     lval = JS_strdup(cx, POP_STR());
2682                     if (!lval) {
2683                         JS_free(cx, (void *)xval);
2684                         return NULL;
2685                     }
2686                     pc += len;
2687                     LOCAL_ASSERT(*pc == JSOP_GOTO || *pc == JSOP_GOTOX);
2688                     oplen = js_CodeSpec[*pc].length;
2689                     len = GetJumpOffset(pc, pc);
2690                     DECOMPILE_CODE(pc + oplen, len - oplen);
2691                     rval = POP_STR();
2692                     todo = Sprint(&ss->sprinter, "%s ? %s : %s",
2693                                   xval, lval, rval);
2694                     JS_free(cx, (void *)xval);
2695                     JS_free(cx, (void *)lval);
2696                     break;
2697 
2698                   default:
2699                     break;
2700                 }
2701                 break;
2702               }
2703 
2704               case JSOP_IFNE:
2705               case JSOP_IFNEX:
2706                 /* Currently, this must be a do-while loop's upward branch. */
2707                 jp->indent -= 4;
2708                 js_printf(jp, "\t} while (%s);\n", POP_STR());
2709                 todo = -2;
2710                 break;
2711 
2712               case JSOP_OR:
2713               case JSOP_ORX:
2714                 xval = "||";
2715 
2716               do_logical_connective:
2717                 /* Top of stack is the first clause in a disjunction (||). */
2718                 lval = JS_strdup(cx, POP_STR());
2719                 if (!lval)
2720                     return NULL;
2721                 done = pc + GetJumpOffset(pc, pc);
2722                 pc += len;
2723                 len = PTRDIFF(done, pc, jsbytecode);
2724                 DECOMPILE_CODE(pc, len);
2725                 rval = POP_STR();
2726                 if (jp->pretty &&
2727                     jp->indent + 4 + strlen(lval) + 4 + strlen(rval) > 75) {
2728                     rval = JS_strdup(cx, rval);
2729                     if (!rval) {
2730                         tail = -1;
2731                     } else {
2732                         todo = Sprint(&ss->sprinter, "%s %s\n", lval, xval);
2733                         tail = Sprint(&ss->sprinter, "%*s%s",
2734                                       jp->indent + 4, "", rval);
2735                         JS_free(cx, (char *)rval);
2736                     }
2737                     if (tail < 0)
2738                         todo = -1;
2739                 } else {
2740                     todo = Sprint(&ss->sprinter, "%s %s %s", lval, xval, rval);
2741                 }
2742                 JS_free(cx, (char *)lval);
2743                 break;
2744 
2745               case JSOP_AND:
2746               case JSOP_ANDX:
2747                 xval = "&&";
2748                 goto do_logical_connective;
2749 
2750               case JSOP_FORARG:
2751                 atom = GetSlotAtom(jp, js_GetArgument, GET_ARGNO(pc));
2752                 LOCAL_ASSERT(atom);
2753                 goto do_fornameinloop;
2754 
2755               case JSOP_FORVAR:
2756                 atom = GetSlotAtom(jp, js_GetLocalVariable, GET_VARNO(pc));
2757                 LOCAL_ASSERT(atom);
2758                 goto do_fornameinloop;
2759 
2760               case JSOP_FORNAME:
2761                 atom = GET_ATOM(cx, jp->script, pc);
2762 
2763               do_fornameinloop:
2764                 lval = "";
2765               do_forlvalinloop:
2766                 sn = js_GetSrcNote(jp->script, pc);
2767                 xval = NULL;
2768                 goto do_forinloop;
2769 
2770               case JSOP_FORPROP:
2771                 xval = NULL;
2772                 atom = GET_ATOM(cx, jp->script, pc);
2773                 if (!ATOM_IS_IDENTIFIER(atom)) {
2774                     xval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom),
2775                                        (jschar)'\'');
2776                     if (!xval)
2777                         return NULL;
2778                     atom = NULL;
2779                 }
2780                 lval = POP_STR();
2781                 sn = NULL;
2782 
2783               do_forinloop:
2784                 pc += oplen;
2785                 LOCAL_ASSERT(*pc == JSOP_IFEQ || *pc == JSOP_IFEQX);
2786                 oplen = js_CodeSpec[*pc].length;
2787                 len = GetJumpOffset(pc, pc);
2788                 sn2 = js_GetSrcNote(jp->script, pc);
2789                 tail = js_GetSrcNoteOffset(sn2, 0);
2790 
2791               do_forinhead:
2792                 if (!atom && xval) {
2793                     /*
2794                      * If xval is not a dummy empty string, we have to strdup
2795                      * it to save it from being clobbered by the first Sprint
2796                      * below.  Standard dumb decompiler operating procedure!
2797                      */
2798                     if (*xval == '\0') {
2799                         xval = NULL;
2800                     } else {
2801                         xval = JS_strdup(cx, xval);
2802                         if (!xval)
2803                             return NULL;
2804                     }
2805                 }
2806 
2807 #if JS_HAS_XML_SUPPORT
2808                 if (foreach) {
2809                     foreach = JS_FALSE;
2810                     todo = Sprint(&ss->sprinter, "for %s (%s%s",
2811                                   js_each_str, VarPrefix(sn), lval);
2812                 } else
2813 #endif
2814                 {
2815                     todo = Sprint(&ss->sprinter, "for (%s%s",
2816                                   VarPrefix(sn), lval);
2817                 }
2818                 if (atom) {
2819                     if (*lval && SprintPut(&ss->sprinter, ".", 1) < 0)
2820                         return NULL;
2821                     xval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
2822                     if (!xval)
2823                         return NULL;
2824                 } else if (xval) {
2825                     LOCAL_ASSERT(*xval != '\0');
2826                     ok = (Sprint(&ss->sprinter,
2827                                  (js_CodeSpec[lastop].format & JOF_XMLNAME)
2828                                  ? ".%s"
2829                                  : "[%s]",
2830                                  xval)
2831                           >= 0);
2832                     JS_free(cx, (char *)xval);
2833                     if (!ok)
2834                         return NULL;
2835                 }
2836                 if (todo < 0)
2837                     return NULL;
2838 
2839                 lval = OFF2STR(&ss->sprinter, todo);
2840                 rval = GetStr(ss, ss->top-1);
2841                 RETRACT(&ss->sprinter, rval);
2842                 if (ss->inArrayInit) {
2843                     todo = Sprint(&ss->sprinter, " %s in %s)", lval, rval);
2844                     if (todo < 0)
2845                         return NULL;
2846                     ss->offsets[ss->top-1] = todo;
2847                     ss->sprinter.offset += PAREN_SLOP;
2848                     DECOMPILE_CODE(pc + oplen, tail - oplen);
2849                 } else {
2850                     js_printf(SET_MAYBE_BRACE(jp), "\t%s in %s) {\n",
2851                               lval, rval);
2852                     jp->indent += 4;
2853                     DECOMPILE_CODE(pc + oplen, tail - oplen);
2854                     jp->indent -= 4;
2855                     js_printf(jp, "\t}\n");
2856                 }
2857                 todo = -2;
2858                 break;
2859 
2860               case JSOP_FORELEM:
2861                 pc++;
2862                 LOCAL_ASSERT(*pc == JSOP_IFEQ || *pc == JSOP_IFEQX);
2863                 len = js_CodeSpec[*pc].length;
2864 
2865                 /*
2866                  * Arrange for the JSOP_ENUMELEM case to set tail for use by
2867                  * do_forinhead: code that uses on it to find the loop-closing
2868                  * jump (whatever its format, normal or extended), in order to
2869                  * bound the recursively decompiled loop body.
2870                  */
2871                 sn = js_GetSrcNote(jp->script, pc);
2872                 LOCAL_ASSERT(!forelem_tail);
2873                 forelem_tail = pc + js_GetSrcNoteOffset(sn, 0);
2874 
2875                 /*
2876                  * This gets a little wacky.  Only the length of the for loop
2877                  * body PLUS the element-indexing expression is known here, so
2878                  * we pass the after-loop pc to the JSOP_ENUMELEM case, which
2879                  * is immediately below, to decompile that helper bytecode via
2880                  * the 'forelem_done' local.
2881                  *
2882                  * Since a for..in loop can't nest in the head of another for
2883                  * loop, we can use forelem_{tail,done} singletons to remember
2884                  * state from JSOP_FORELEM to JSOP_ENUMELEM, thence (via goto)
2885                  * to label do_forinhead.
2886                  */
2887                 LOCAL_ASSERT(!forelem_done);
2888                 forelem_done = pc + GetJumpOffset(pc, pc);
2889 
2890                 /* Our net stack balance after forelem;ifeq is +1. */
2891                 todo = SprintCString(&ss->sprinter, forelem_cookie);
2892                 break;
2893 
2894               case JSOP_ENUMELEM:
2895               case JSOP_ENUMCONSTELEM:
2896                 /*
2897                  * The stack has the object under the (top) index expression.
2898                  * The "rval" property id is underneath those two on the stack.
2899                  * The for loop body net and gross lengths can now be adjusted
2900                  * to account for the length of the indexing expression that
2901                  * came after JSOP_FORELEM and before JSOP_ENUMELEM.
2902                  */
2903                 atom = NULL;
2904                 xval = POP_STR();
2905                 op = JSOP_GETELEM;      /* lval must have high precedence */
2906                 lval = POP_STR();
2907                 op = saveop;
2908                 rval = POP_STR();
2909                 LOCAL_ASSERT(strcmp(rval, forelem_cookie) == 0);
2910                 LOCAL_ASSERT(forelem_tail > pc);
2911                 tail = forelem_tail - pc;
2912                 forelem_tail = NULL;
2913                 LOCAL_ASSERT(forelem_done > pc);
2914                 len = forelem_done - pc;
2915                 forelem_done = NULL;
2916                 goto do_forinhead;
2917 
2918 #if JS_HAS_GETTER_SETTER
2919               case JSOP_GETTER:
2920               case JSOP_SETTER:
2921                 todo = -2;
2922                 break;
2923 #endif
2924 
2925               case JSOP_DUP2:
2926                 rval = GetStr(ss, ss->top-2);
2927                 todo = SprintCString(&ss->sprinter, rval);
2928                 if (todo < 0 || !PushOff(ss, todo, ss->opcodes[ss->top-2]))
2929                     return NULL;
2930                 /* FALL THROUGH */
2931 
2932               case JSOP_DUP:
2933 #if JS_HAS_DESTRUCTURING
2934                 sn = js_GetSrcNote(jp->script, pc);
2935                 if (sn) {
2936                     LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCT);
2937                     pc = DecompileDestructuring(ss, pc, endpc);
2938                     if (!pc)
2939                         return NULL;
2940                     len = 0;
2941                     lval = POP_STR();
2942                     op = saveop = JSOP_ENUMELEM;
2943                     rval = POP_STR();
2944 
2945                     if (strcmp(rval, forelem_cookie) == 0) {
2946                         LOCAL_ASSERT(forelem_tail > pc);
2947                         tail = forelem_tail - pc;
2948                         forelem_tail = NULL;
2949                         LOCAL_ASSERT(forelem_done > pc);
2950                         len = forelem_done - pc;
2951                         forelem_done = NULL;
2952                         xval = NULL;
2953                         atom = NULL;
2954 
2955                         /*
2956                          * Null sn if this is a 'for (var [k, v] = i in o)'
2957                          * loop, because 'var [k, v = i;' has already been
2958                          * hoisted.
2959                          */
2960                         if (js_GetSrcNoteOffset(sn, 0) == SRC_DECL_VAR)
2961                             sn = NULL;
2962                         goto do_forinhead;
2963                     }
2964 
2965                     todo = Sprint(&ss->sprinter, "%s%s = %s",
2966                                   VarPrefix(sn), lval, rval);
2967                     break;
2968                 }
2969 #endif
2970 
2971                 rval = GetStr(ss, ss->top-1);
2972                 saveop = ss->opcodes[ss->top-1];
2973                 todo = SprintCString(&ss->sprinter, rval);
2974                 break;
2975 
2976               case JSOP_SETARG:
2977                 atom = GetSlotAtom(jp, js_GetArgument, GET_ARGNO(pc));
2978                 LOCAL_ASSERT(atom);
2979                 goto do_setname;
2980 
2981               case JSOP_SETVAR:
2982                 atom = GetSlotAtom(jp, js_GetLocalVariable, GET_VARNO(pc));
2983                 LOCAL_ASSERT(atom);
2984                 goto do_setname;
2985 
2986               case JSOP_SETCONST:
2987               case JSOP_SETNAME:
2988               case JSOP_SETGVAR:
2989                 atomIndex = GET_ATOM_INDEX(pc);
2990 
2991               do_JSOP_SETCONST:
2992                 atom = js_GetAtom(cx, &jp->script->atomMap, atomIndex);
2993 
2994               do_setname:
2995                 lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
2996                 if (!lval)
2997                     return NULL;
2998                 rval = POP_STR();
2999                 if (op == JSOP_SETNAME)
3000                     (void) PopOff(ss, op);
3001 
3002               do_setlval:
3003                 sn = js_GetSrcNote(jp->script, pc - 1);
3004                 if (sn && SN_TYPE(sn) == SRC_ASSIGNOP) {
3005                     todo = Sprint(&ss->sprinter, "%s %s= %s",
3006                                   lval,
3007                                   (lastop == JSOP_GETTER)
3008                                   ? js_getter_str
3009                                   : (lastop == JSOP_SETTER)
3010                                   ? js_setter_str
3011                                   : js_CodeSpec[lastop].token,
3012                                   rval);
3013                 } else {
3014                     sn = js_GetSrcNote(jp->script, pc);
3015                     todo = Sprint(&ss->sprinter, "%s%s = %s",
3016                                   VarPrefix(sn), lval, rval);
3017                 }
3018                 if (op == JSOP_SETLOCALPOP) {
3019                     if (!PushOff(ss, todo, saveop))
3020                         return NULL;
3021                     rval = POP_STR();
3022                     LOCAL_ASSERT(*rval != '\0');
3023                     js_printf(jp, "\t%s;\n", rval);
3024                     todo = -2;
3025                 }
3026                 break;
3027 
3028               case JSOP_NEW:
3029               case JSOP_CALL:
3030               case JSOP_EVAL:
3031 #if JS_HAS_LVALUE_RETURN
3032               case JSOP_SETCALL:
3033 #endif
3034                 op = JSOP_SETNAME;      /* turn off most parens */
3035                 argc = GET_ARGC(pc);
3036                 argv = (char **)
3037                     JS_malloc(cx, (size_t)(argc + 1) * sizeof *argv);
3038                 if (!argv)
3039                     return NULL;
3040 
3041                 ok = JS_TRUE;
3042                 for (i = argc; i > 0; i--) {
3043                     argv[i] = JS_strdup(cx, POP_STR());
3044                     if (!argv[i]) {
3045                         ok = JS_FALSE;
3046                         break;
3047                     }
3048                 }
3049 
3050                 /* Skip the JSOP_PUSHOBJ-created empty string. */
3051                 LOCAL_ASSERT(ss->top >= 2);
3052                 (void) PopOff(ss, op);
3053 
3054                 op = saveop;
3055                 argv[0] = JS_strdup(cx, POP_STR());
3056                 if (!argv[i])
3057                     ok = JS_FALSE;
3058 
3059                 lval = "(", rval = ")";
3060                 if (op == JSOP_NEW) {
3061                     if (argc == 0)
3062                         lval = rval = "";
3063                     todo = Sprint(&ss->sprinter, "%s %s%s",
3064                                   js_new_str, argv[0], lval);
3065                 } else {
3066                     todo = Sprint(&ss->sprinter, ss_format,
3067                                   argv[0], lval);
3068                 }
3069                 if (todo < 0)
3070                     ok = JS_FALSE;
3071 
3072                 for (i = 1; i <= argc; i++) {
3073                     if (!argv[i] ||
3074                         Sprint(&ss->sprinter, ss_format,
3075                                argv[i], (i < argc) ? ", " : "") < 0) {
3076                         ok = JS_FALSE;
3077                         break;
3078                     }
3079                 }
3080                 if (Sprint(&ss->sprinter, rval) < 0)
3081                     ok = JS_FALSE;
3082 
3083                 for (i = 0; i <= argc; i++) {
3084                     if (argv[i])
3085                         JS_free(cx, argv[i]);
3086                 }
3087                 JS_free(cx, argv);
3088                 if (!ok)
3089                     return NULL;
3090 #if JS_HAS_LVALUE_RETURN
3091                 if (op == JSOP_SETCALL) {
3092                     if (!PushOff(ss, todo, op))
3093                         return NULL;
3094                     todo = Sprint(&ss->sprinter, "");
3095                 }
3096 #endif
3097                 break;
3098 
3099               case JSOP_DELNAME:
3100                 atom = GET_ATOM(cx, jp->script, pc);
3101                 lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3102                 if (!lval)
3103                     return NULL;
3104                 RETRACT(&ss->sprinter, lval);
3105               do_delete_lval:
3106                 todo = Sprint(&ss->sprinter, "%s %s", js_delete_str, lval);
3107                 break;
3108 
3109               case JSOP_DELPROP:
3110                 GET_ATOM_QUOTE_AND_FMT("%s %s[%s]", "%s %s.%s", rval);
3111                 lval = POP_STR();
3112                 todo = Sprint(&ss->sprinter, fmt, js_delete_str, lval, rval);
3113                 break;
3114 
3115               case JSOP_DELELEM:
3116                 op = JSOP_NOP;          /* turn off parens */
3117                 xval = POP_STR();
3118                 op = saveop;
3119                 lval = POP_STR();
3120                 if (*xval == '\0')
3121                     goto do_delete_lval;
3122                 todo = Sprint(&ss->sprinter,
3123                               (js_CodeSpec[lastop].format & JOF_XMLNAME)
3124                               ? "%s %s.%s"
3125                               : "%s %s[%s]",
3126                               js_delete_str, lval, xval);
3127                 break;
3128 
3129 #if JS_HAS_XML_SUPPORT
3130               case JSOP_DELDESC:
3131                 xval = POP_STR();
3132                 lval = POP_STR();
3133                 todo = Sprint(&ss->sprinter, "%s %s..%s",
3134                               js_delete_str, lval, xval);
3135                 break;
3136 #endif
3137 
3138               case JSOP_TYPEOFEXPR:
3139               case JSOP_TYPEOF:
3140               case JSOP_VOID:
3141                 rval = POP_STR();
3142                 todo = Sprint(&ss->sprinter, "%s %s", cs->name, rval);
3143                 break;
3144 
3145               case JSOP_INCARG:
3146               case JSOP_DECARG:
3147                 atom = GetSlotAtom(jp, js_GetArgument, GET_ARGNO(pc));
3148                 LOCAL_ASSERT(atom);
3149                 goto do_incatom;
3150 
3151               case JSOP_INCVAR:
3152               case JSOP_DECVAR:
3153                 atom = GetSlotAtom(jp, js_GetLocalVariable, GET_VARNO(pc));
3154                 LOCAL_ASSERT(atom);
3155                 goto do_incatom;
3156 
3157               case JSOP_INCNAME:
3158               case JSOP_DECNAME:
3159               case JSOP_INCGVAR:
3160               case JSOP_DECGVAR:
3161                 atom = GET_ATOM(cx, jp->script, pc);
3162               do_incatom:
3163                 lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3164                 if (!lval)
3165                     return NULL;
3166                 RETRACT(&ss->sprinter, lval);
3167               do_inclval:
3168                 todo = Sprint(&ss->sprinter, ss_format,
3169                               js_incop_strs[!(cs->format & JOF_INC)], lval);
3170                 break;
3171 
3172               case JSOP_INCPROP:
3173               case JSOP_DECPROP:
3174                 GET_ATOM_QUOTE_AND_FMT(preindex_format, predot_format, rval);
3175 
3176                 /*
3177                  * Force precedence below the numeric literal opcodes, so that
3178                  * 42..foo or 10000..toString(16), e.g., decompile with parens
3179                  * around the left-hand side of dot.
3180                  */
3181                 op = JSOP_GETPROP;
3182                 lval = POP_STR();
3183                 todo = Sprint(&ss->sprinter, fmt,
3184                               js_incop_strs[!(cs->format & JOF_INC)],
3185                               lval, rval);
3186                 break;
3187 
3188               case JSOP_INCELEM:
3189               case JSOP_DECELEM:
3190                 op = JSOP_NOP;          /* turn off parens */
3191                 xval = POP_STR();
3192                 op = JSOP_GETELEM;
3193                 lval = POP_STR();
3194                 if (*xval != '\0') {
3195                     todo = Sprint(&ss->sprinter,
3196                                   (js_CodeSpec[lastop].format & JOF_XMLNAME)
3197                                   ? predot_format
3198                                   : preindex_format,
3199                                   js_incop_strs[!(cs->format & JOF_INC)],
3200                                   lval, xval);
3201                 } else {
3202                     todo = Sprint(&ss->sprinter, ss_format,
3203                                   js_incop_strs[!(cs->format & JOF_INC)], lval);
3204                 }
3205                 break;
3206 
3207               case JSOP_ARGINC:
3208               case JSOP_ARGDEC:
3209                 atom = GetSlotAtom(jp, js_GetArgument, GET_ARGNO(pc));
3210                 LOCAL_ASSERT(atom);
3211                 goto do_atominc;
3212 
3213               case JSOP_VARINC:
3214               case JSOP_VARDEC:
3215                 atom = GetSlotAtom(jp, js_GetLocalVariable, GET_VARNO(pc));
3216                 LOCAL_ASSERT(atom);
3217                 goto do_atominc;
3218 
3219               case JSOP_NAMEINC:
3220               case JSOP_NAMEDEC:
3221               case JSOP_GVARINC:
3222               case JSOP_GVARDEC:
3223                 atom = GET_ATOM(cx, jp->script, pc);
3224               do_atominc:
3225                 lval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3226                 if (!lval)
3227                     return NULL;
3228                 RETRACT(&ss->sprinter, lval);
3229               do_lvalinc:
3230                 todo = Sprint(&ss->sprinter, ss_format,
3231                               lval, js_incop_strs[!(cs->format & JOF_INC)]);
3232                 break;
3233 
3234               case JSOP_PROPINC:
3235               case JSOP_PROPDEC:
3236                 GET_ATOM_QUOTE_AND_FMT(postindex_format, postdot_format, rval);
3237 
3238                 /*
3239                  * Force precedence below the numeric literal opcodes, so that
3240                  * 42..foo or 10000..toString(16), e.g., decompile with parens
3241                  * around the left-hand side of dot.
3242                  */
3243                 op = JSOP_GETPROP;
3244                 lval = POP_STR();
3245                 todo = Sprint(&ss->sprinter, fmt, lval, rval,
3246                               js_incop_strs[!(cs->format & JOF_INC)]);
3247                 break;
3248 
3249               case JSOP_ELEMINC:
3250               case JSOP_ELEMDEC:
3251                 op = JSOP_NOP;          /* turn off parens */
3252                 xval = POP_STR();
3253                 op = JSOP_GETELEM;
3254                 lval = POP_STR();
3255                 if (*xval != '\0') {
3256                     todo = Sprint(&ss->sprinter,
3257                                   (js_CodeSpec[lastop].format & JOF_XMLNAME)
3258                                   ? postdot_format
3259                                   : postindex_format,
3260                                   lval, xval,
3261                                   js_incop_strs[!(cs->format & JOF_INC)]);
3262                 } else {
3263                     todo = Sprint(&ss->sprinter, ss_format,
3264                                   lval, js_incop_strs[!(cs->format & JOF_INC)]);
3265                 }
3266                 break;
3267 
3268               case JSOP_GETPROP2:
3269                 op = JSOP_GETPROP;
3270                 (void) PopOff(ss, lastop);
3271                 /* FALL THROUGH */
3272 
3273               case JSOP_GETPROP:
3274               case JSOP_GETXPROP:
3275                 atom = GET_ATOM(cx, jp->script, pc);
3276 
3277               do_getprop:
3278                 GET_QUOTE_AND_FMT(index_format, dot_format, rval);
3279 
3280               do_getprop_lval:
3281                 lval = POP_STR();
3282                 todo = Sprint(&ss->sprinter, fmt, lval, rval);
3283                 break;
3284 
3285 #if JS_HAS_XML_SUPPORT
3286               BEGIN_LITOPX_CASE(JSOP_GETMETHOD)
3287                 sn = js_GetSrcNote(jp->script, pc);
3288                 if (sn && SN_TYPE(sn) == SRC_PCBASE)
3289                     goto do_getprop;
3290                 GET_QUOTE_AND_FMT("%s.function::[%s]", "%s.function::%s", rval);
3291                 goto do_getprop_lval;
3292 
3293               BEGIN_LITOPX_CASE(JSOP_SETMETHOD)
3294                 sn = js_GetSrcNote(jp->script, pc);
3295                 if (sn && SN_TYPE(sn) == SRC_PCBASE)
3296                     goto do_setprop;
3297                 GET_QUOTE_AND_FMT("%s.function::[%s] %s= %s",
3298                                   "%s.function::%s %s= %s",
3299                                   xval);
3300                 goto do_setprop_rval;
3301 #endif
3302 
3303               case JSOP_SETPROP:
3304                 atom = GET_ATOM(cx, jp->script, pc);
3305 
3306               do_setprop:
3307                 GET_QUOTE_AND_FMT("%s[%s] %s= %s", "%s.%s %s= %s", xval);
3308 
3309               do_setprop_rval:
3310                 rval = POP_STR();
3311 
3312                 /*
3313                  * Force precedence below the numeric literal opcodes, so that
3314                  * 42..foo or 10000..toString(16), e.g., decompile with parens
3315                  * around the left-hand side of dot.
3316                  */
3317                 op = JSOP_GETPROP;
3318                 lval = POP_STR();
3319                 sn = js_GetSrcNote(jp->script, pc - 1);
3320                 todo = Sprint(&ss->sprinter, fmt, lval, xval,
3321                               (sn && SN_TYPE(sn) == SRC_ASSIGNOP)
3322                               ? (lastop == JSOP_GETTER)
3323                                 ? js_getter_str
3324                                 : (lastop == JSOP_SETTER)
3325                                 ? js_setter_str
3326                                 : js_CodeSpec[lastop].token
3327                               : "",
3328                               rval);
3329                 break;
3330 
3331               case JSOP_GETELEM2:
3332                 op = JSOP_GETELEM;
3333                 (void) PopOff(ss, lastop);
3334                 /* FALL THROUGH */
3335 
3336               case JSOP_GETELEM:
3337               case JSOP_GETXELEM:
3338                 op = JSOP_NOP;          /* turn off parens */
3339                 xval = POP_STR();
3340                 op = saveop;
3341                 lval = POP_STR();
3342                 if (*xval == '\0') {
3343                     todo = Sprint(&ss->sprinter, "%s", lval);
3344                 } else {
3345                     todo = Sprint(&ss->sprinter,
3346                                   (js_CodeSpec[lastop].format & JOF_XMLNAME)
3347                                   ? dot_format
3348                                   : index_format,
3349                                   lval, xval);
3350                 }
3351                 break;
3352 
3353               case JSOP_SETELEM:
3354                 rval = POP_STR();
3355                 op = JSOP_NOP;          /* turn off parens */
3356                 xval = POP_STR();
3357                 cs = &js_CodeSpec[ss->opcodes[ss->top]];
3358                 op = JSOP_GETELEM;      /* lval must have high precedence */
3359                 lval = POP_STR();
3360                 op = saveop;
3361                 if (*xval == '\0')
3362                     goto do_setlval;
3363                 sn = js_GetSrcNote(jp->script, pc - 1);
3364                 todo = Sprint(&ss->sprinter,
3365                               (cs->format & JOF_XMLNAME)
3366                               ? "%s.%s %s= %s"
3367                               : "%s[%s] %s= %s",
3368                               lval, xval,
3369                               (sn && SN_TYPE(sn) == SRC_ASSIGNOP)
3370                               ? (lastop == JSOP_GETTER)
3371                                 ? js_getter_str
3372                                 : (lastop == JSOP_SETTER)
3373                                 ? js_setter_str
3374                                 : js_CodeSpec[lastop].token
3375                               : "",
3376                               rval);
3377                 break;
3378 
3379               case JSOP_ARGSUB:
3380                 i = (jsint) GET_ATOM_INDEX(pc);
3381                 todo = Sprint(&ss->sprinter, "%s[%d]",
3382                               js_arguments_str, (int) i);
3383                 break;
3384 
3385               case JSOP_ARGCNT:
3386                 todo = Sprint(&ss->sprinter, dot_format,
3387                               js_arguments_str, js_length_str);
3388                 break;
3389 
3390               case JSOP_GETARG:
3391                 i = GET_ARGNO(pc);
3392                 atom = GetSlotAtom(jp, js_GetArgument, i);
3393 #if JS_HAS_DESTRUCTURING
3394                 if (!atom) {
3395                     todo = Sprint(&ss->sprinter, "%s[%d]", js_arguments_str, i);
3396                     break;
3397                 }
3398 #else
3399                 LOCAL_ASSERT(atom);
3400 #endif
3401                 goto do_name;
3402 
3403               case JSOP_GETVAR:
3404                 atom = GetSlotAtom(jp, js_GetLocalVariable, GET_VARNO(pc));
3405                 LOCAL_ASSERT(atom);
3406                 goto do_name;
3407 
3408               case JSOP_NAME:
3409               case JSOP_GETGVAR:
3410                 atom = GET_ATOM(cx, jp->script, pc);
3411               do_name:
3412                 lval = "";
3413               do_qname:
3414                 sn = js_GetSrcNote(jp->script, pc);
3415                 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3416                 if (!rval)
3417                     return NULL;
3418                 RETRACT(&ss->sprinter, rval);
3419                 todo = Sprint(&ss->sprinter, "%s%s%s",
3420                               VarPrefix(sn), lval, rval);
3421                 break;
3422 
3423               case JSOP_UINT16:
3424                 i = (jsint) GET_ATOM_INDEX(pc);
3425                 goto do_sprint_int;
3426 
3427               case JSOP_UINT24:
3428                 i = (jsint) GET_UINT24(pc);
3429               do_sprint_int:
3430                 todo = Sprint(&ss->sprinter, "%u", (unsigned) i);
3431                 break;
3432 
3433               case JSOP_LITERAL:
3434                 atomIndex = GET_LITERAL_INDEX(pc);
3435                 goto do_JSOP_STRING;
3436 
3437               case JSOP_FINDNAME:
3438                 atomIndex = GET_LITERAL_INDEX(pc);
3439                 todo = Sprint(&ss->sprinter, "");
3440                 if (todo < 0 || !PushOff(ss, todo, op))
3441                     return NULL;
3442                 atom = js_GetAtom(cx, &jp->script->atomMap, atomIndex);
3443                 goto do_name;
3444 
3445               case JSOP_LITOPX:
3446                 atomIndex = GET_LITERAL_INDEX(pc);
3447                 pc2 = pc + 1 + LITERAL_INDEX_LEN;
3448                 op = saveop = *pc2;
3449                 pc += len - (1 + ATOM_INDEX_LEN);
3450                 cs = &js_CodeSpec[op];
3451                 len = cs->length;
3452                 switch (op) {
3453                   case JSOP_ANONFUNOBJ:   goto do_JSOP_ANONFUNOBJ;
3454                   case JSOP_BINDNAME:     goto do_JSOP_BINDNAME;
3455                   case JSOP_CLOSURE:      goto do_JSOP_CLOSURE;
3456 #if JS_HAS_EXPORT_IMPORT
3457                   case JSOP_EXPORTNAME:   goto do_JSOP_EXPORTNAME;
3458 #endif
3459 #if JS_HAS_XML_SUPPORT
3460                   case JSOP_GETMETHOD:    goto do_JSOP_GETMETHOD;
3461                   case JSOP_SETMETHOD:    goto do_JSOP_SETMETHOD;
3462 #endif
3463                   case JSOP_NAMEDFUNOBJ:  goto do_JSOP_NAMEDFUNOBJ;
3464                   case JSOP_NUMBER:       goto do_JSOP_NUMBER;
3465                   case JSOP_OBJECT:       goto do_JSOP_OBJECT;
3466 #if JS_HAS_XML_SUPPORT
3467                   case JSOP_QNAMECONST:   goto do_JSOP_QNAMECONST;
3468                   case JSOP_QNAMEPART:    goto do_JSOP_QNAMEPART;
3469 #endif
3470                   case JSOP_REGEXP:       goto do_JSOP_REGEXP;
3471                   case JSOP_SETCONST:     goto do_JSOP_SETCONST;
3472                   case JSOP_STRING:       goto do_JSOP_STRING;
3473 #if JS_HAS_XML_SUPPORT
3474                   case JSOP_XMLCDATA:     goto do_JSOP_XMLCDATA;
3475                   case JSOP_XMLCOMMENT:   goto do_JSOP_XMLCOMMENT;
3476                   case JSOP_XMLOBJECT:    goto do_JSOP_XMLOBJECT;
3477                   case JSOP_XMLPI:        goto do_JSOP_XMLPI;
3478 #endif
3479                   case JSOP_ENTERBLOCK:   goto do_JSOP_ENTERBLOCK;
3480                   default:                LOCAL_ASSERT(0);
3481                 }
3482                 /* NOTREACHED */
3483                 break;
3484 
3485               BEGIN_LITOPX_CASE(JSOP_NUMBER)
3486                 val = ATOM_KEY(atom);
3487                 if (JSVAL_IS_INT(val)) {
3488                     long ival = (long)JSVAL_TO_INT(val);
3489                     todo = Sprint(&ss->sprinter, "%ld", ival);
3490                 } else {
3491                     char buf[DTOSTR_STANDARD_BUFFER_SIZE];
3492                     char *numStr = JS_dtostr(buf, sizeof buf, DTOSTR_STANDARD,
3493                                              0, *JSVAL_TO_DOUBLE(val));
3494                     if (!numStr) {
3495                         JS_ReportOutOfMemory(cx);
3496                         return NULL;
3497                     }
3498                     todo = Sprint(&ss->sprinter, numStr);
3499                 }
3500               END_LITOPX_CASE
3501 
3502               BEGIN_LITOPX_CASE(JSOP_STRING)
3503                 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom),
3504                                    inXML ? DONT_ESCAPE : '"');
3505                 if (!rval)
3506                     return NULL;
3507                 todo = STR2OFF(&ss->sprinter, rval);
3508               END_LITOPX_CASE
3509 
3510               case JSOP_OBJECT:
3511               case JSOP_REGEXP:
3512               case JSOP_ANONFUNOBJ:
3513               case JSOP_NAMEDFUNOBJ:
3514                 atomIndex = GET_ATOM_INDEX(pc);
3515 
3516               do_JSOP_OBJECT:
3517               do_JSOP_REGEXP:
3518               do_JSOP_ANONFUNOBJ:
3519               do_JSOP_NAMEDFUNOBJ:
3520                 atom = js_GetAtom(cx, &jp->script->atomMap, atomIndex);
3521                 if (op == JSOP_OBJECT || op == JSOP_REGEXP) {
3522                     if (!js_regexp_toString(cx, ATOM_TO_OBJECT(atom), 0, NULL,
3523                                             &val)) {
3524                         return NULL;
3525                     }
3526                 } else {
3527                     if (!js_fun_toString(cx, ATOM_TO_OBJECT(atom),
3528                                          JS_IN_GROUP_CONTEXT |
3529                                          JS_DONT_PRETTY_PRINT,
3530                                          0, NULL, &val)) {
3531                         return NULL;
3532                     }
3533                 }
3534                 str = JSVAL_TO_STRING(val);
3535                 todo = SprintPut(&ss->sprinter, JS_GetStringBytes(str),
3536                                  JSSTRING_LENGTH(str));
3537                 break;
3538 
3539               case JSOP_TABLESWITCH:
3540               case JSOP_TABLESWITCHX:
3541               {
3542                 ptrdiff_t jmplen, off, off2;
3543                 jsint j, n, low, high;
3544                 TableEntry *table, pivot;
3545 
3546                 sn = js_GetSrcNote(jp->script, pc);
3547                 LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH);
3548                 len = js_GetSrcNoteOffset(sn, 0);
3549                 jmplen = (op == JSOP_TABLESWITCH) ? JUMP_OFFSET_LEN
3550                                                   : JUMPX_OFFSET_LEN;
3551                 pc2 = pc;
3552                 off = GetJumpOffset(pc, pc2);
3553                 pc2 += jmplen;
3554                 low = GET_JUMP_OFFSET(pc2);
3555                 pc2 += JUMP_OFFSET_LEN;
3556                 high = GET_JUMP_OFFSET(pc2);
3557                 pc2 += JUMP_OFFSET_LEN;
3558 
3559                 n = high - low + 1;
3560                 if (n == 0) {
3561                     table = NULL;
3562                     j = 0;
3563                 } else {
3564                     table = (TableEntry *)
3565                             JS_malloc(cx, (size_t)n * sizeof *table);
3566                     if (!table)
3567                         return NULL;
3568                     for (i = j = 0; i < n; i++) {
3569                         table[j].label = NULL;
3570                         off2 = GetJumpOffset(pc, pc2);
3571                         if (off2) {
3572                             sn = js_GetSrcNote(jp->script, pc2);
3573                             if (sn) {
3574                                 LOCAL_ASSERT(SN_TYPE(sn) == SRC_LABEL);
3575                                 table[j].label =
3576                                     js_GetAtom(cx, &jp->script->atomMap,
3577                                                (jsatomid)
3578                                                js_GetSrcNoteOffset(sn, 0));
3579                             }
3580                             table[j].key = INT_TO_JSVAL(low + i);
3581                             table[j].offset = off2;
3582                             table[j].order = j;
3583                             j++;
3584                         }
3585                         pc2 += jmplen;
3586                     }
3587                     js_HeapSort(table, (size_t) j, &pivot, sizeof(TableEntry),
3588                                 CompareOffsets, NULL);
3589                 }
3590 
3591                 ok = DecompileSwitch(ss, table, (uintN)j, pc, len, off,
3592                                      JS_FALSE);
3593                 JS_free(cx, table);
3594                 if (!ok)
3595                     return NULL;
3596                 todo = -2;
3597                 break;
3598               }
3599 
3600               case JSOP_LOOKUPSWITCH:
3601               case JSOP_LOOKUPSWITCHX:
3602               {
3603                 ptrdiff_t jmplen, off, off2;
3604                 jsatomid npairs, k;
3605                 TableEntry *table;
3606 
3607                 sn = js_GetSrcNote(jp->script, pc);
3608                 LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH);
3609                 len = js_GetSrcNoteOffset(sn, 0);
3610                 jmplen = (op == JSOP_LOOKUPSWITCH) ? JUMP_OFFSET_LEN
3611                                                    : JUMPX_OFFSET_LEN;
3612                 pc2 = pc;
3613                 off = GetJumpOffset(pc, pc2);
3614                 pc2 += jmplen;
3615                 npairs = GET_ATOM_INDEX(pc2);
3616                 pc2 += ATOM_INDEX_LEN;
3617 
3618                 table = (TableEntry *)
3619                     JS_malloc(cx, (size_t)npairs * sizeof *table);
3620                 if (!table)
3621                     return NULL;
3622                 for (k = 0; k < npairs; k++) {
3623                     sn = js_GetSrcNote(jp->script, pc2);
3624                     if (sn) {
3625                         LOCAL_ASSERT(SN_TYPE(sn) == SRC_LABEL);
3626                         table[k].label =
3627                             js_GetAtom(cx, &jp->script->atomMap, (jsatomid)
3628                                        js_GetSrcNoteOffset(sn, 0));
3629                     } else {
3630                         table[k].label = NULL;
3631                     }
3632                     atom = GET_ATOM(cx, jp->script, pc2);
3633                     pc2 += ATOM_INDEX_LEN;
3634                     off2 = GetJumpOffset(pc, pc2);
3635                     pc2 += jmplen;
3636                     table[k].key = ATOM_KEY(atom);
3637                     table[k].offset = off2;
3638                 }
3639 
3640                 ok = DecompileSwitch(ss, table, (uintN)npairs, pc, len, off,
3641                                      JS_FALSE);
3642                 JS_free(cx, table);
3643                 if (!ok)
3644                     return NULL;
3645                 todo = -2;
3646                 break;
3647               }
3648 
3649               case JSOP_CONDSWITCH:
3650               {
3651                 ptrdiff_t off, off2, caseOff;
3652                 jsint ncases;
3653                 TableEntry *table;
3654 
3655                 sn = js_GetSrcNote(jp->script, pc);
3656                 LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_SWITCH);
3657                 len = js_GetSrcNoteOffset(sn, 0);
3658                 off = js_GetSrcNoteOffset(sn, 1);
3659 
3660                 /*
3661                  * Count the cases using offsets from switch to first case,
3662                  * and case to case, stored in srcnote immediates.
3663                  */
3664                 pc2 = pc;
3665                 off2 = off;
3666                 for (ncases = 0; off2 != 0; ncases++) {
3667                     pc2 += off2;
3668                     LOCAL_ASSERT(*pc2 == JSOP_CASE || *pc2 == JSOP_DEFAULT ||
3669                                  *pc2 == JSOP_CASEX || *pc2 == JSOP_DEFAULTX);
3670                     if (*pc2 == JSOP_DEFAULT || *pc2 == JSOP_DEFAULTX) {
3671                         /* End of cases, but count default as a case. */
3672                         off2 = 0;
3673                     } else {
3674                         sn = js_GetSrcNote(jp->script, pc2);
3675                         LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_PCDELTA);
3676                         off2 = js_GetSrcNoteOffset(sn, 0);
3677                     }
3678                 }
3679 
3680                 /*
3681                  * Allocate table and rescan the cases using their srcnotes,
3682                  * stashing each case's delta from switch top in table[i].key,
3683                  * and the distance to its statements in table[i].offset.
3684                  */
3685                 table = (TableEntry *)
3686                     JS_malloc(cx, (size_t)ncases * sizeof *table);
3687                 if (!table)
3688                     return NULL;
3689                 pc2 = pc;
3690                 off2 = off;
3691                 for (i = 0; i < ncases; i++) {
3692                     pc2 += off2;
3693                     LOCAL_ASSERT(*pc2 == JSOP_CASE || *pc2 == JSOP_DEFAULT ||
3694                                  *pc2 == JSOP_CASEX || *pc2 == JSOP_DEFAULTX);
3695                     caseOff = pc2 - pc;
3696                     table[i].key = INT_TO_JSVAL((jsint) caseOff);
3697                     table[i].offset = caseOff + GetJumpOffset(pc2, pc2);
3698                     if (*pc2 == JSOP_CASE || *pc2 == JSOP_CASEX) {
3699                         sn = js_GetSrcNote(jp->script, pc2);
3700                         LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_PCDELTA);
3701                         off2 = js_GetSrcNoteOffset(sn, 0);
3702                     }
3703                 }
3704 
3705                 /*
3706                  * Find offset of default code by fetching the default offset
3707                  * from the end of table.  JSOP_CONDSWITCH always has a default
3708                  * case at the end.
3709                  */
3710                 off = JSVAL_TO_INT(table[ncases-1].key);
3711                 pc2 = pc + off;
3712                 off += GetJumpOffset(pc2, pc2);
3713 
3714                 ok = DecompileSwitch(ss, table, (uintN)ncases, pc, len, off,
3715                                      JS_TRUE);
3716                 JS_free(cx, table);
3717                 if (!ok)
3718                     return NULL;
3719                 todo = -2;
3720                 break;
3721               }
3722 
3723               case JSOP_CASE:
3724               case JSOP_CASEX:
3725               {
3726                 lval = POP_STR();
3727                 if (!lval)
3728                     return NULL;
3729                 js_printf(jp, "\tcase %s:\n", lval);
3730                 todo = -2;
3731                 break;
3732               }
3733 
3734               case JSOP_NEW_EQ:
3735               case JSOP_NEW_NE:
3736                 rval = POP_STR();
3737                 lval = POP_STR();
3738                 todo = Sprint(&ss->sprinter, "%s %c== %s",
3739                               lval, (op == JSOP_NEW_EQ) ? '=' : '!', rval);
3740                 break;
3741 
3742               BEGIN_LITOPX_CASE(JSOP_CLOSURE)
3743                 LOCAL_ASSERT(ATOM_IS_OBJECT(atom));
3744                 todo = -2;
3745                 goto do_function;
3746               END_LITOPX_CASE
3747 
3748 #if JS_HAS_EXPORT_IMPORT
3749               case JSOP_EXPORTALL:
3750                 js_printf(jp, "\texport *;\n");
3751                 todo = -2;
3752                 break;
3753 
3754               BEGIN_LITOPX_CASE(JSOP_EXPORTNAME)
3755                 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3756                 if (!rval)
3757                     return NULL;
3758                 RETRACT(&ss->sprinter, rval);
3759                 js_printf(jp, "\texport %s;\n", rval);
3760                 todo = -2;
3761               END_LITOPX_CASE
3762 
3763               case JSOP_IMPORTALL:
3764                 lval = POP_STR();
3765                 js_printf(jp, "\timport %s.*;\n", lval);
3766                 todo = -2;
3767                 break;
3768 
3769               case JSOP_IMPORTPROP:
3770               do_importprop:
3771                 GET_ATOM_QUOTE_AND_FMT("\timport %s[%s];\n",
3772                                        "\timport %s.%s;\n",
3773                                        rval);
3774                 lval = POP_STR();
3775                 js_printf(jp, fmt, lval, rval);
3776                 todo = -2;
3777                 break;
3778 
3779               case JSOP_IMPORTELEM:
3780                 xval = POP_STR();
3781                 op = JSOP_GETELEM;
3782                 if (js_CodeSpec[lastop].format & JOF_XMLNAME)
3783                     goto do_importprop;
3784                 lval = POP_STR();
3785                 js_printf(jp, "\timport %s[%s];\n", lval, xval);
3786                 todo = -2;
3787                 break;
3788 #endif /* JS_HAS_EXPORT_IMPORT */
3789 
3790               case JSOP_TRAP:
3791                 op = JS_GetTrapOpcode(cx, jp->script, pc);
3792                 if (op == JSOP_LIMIT)
3793                     return NULL;
3794                 saveop = op;
3795                 *pc = op;
3796                 cs = &js_CodeSpec[op];
3797                 len = cs->length;
3798                 DECOMPILE_CODE(pc, len);
3799                 *pc = JSOP_TRAP;
3800                 todo = -2;
3801                 break;
3802 
3803               case JSOP_NEWINIT:
3804               {
3805                 JSBool isArray;
3806 
3807                 LOCAL_ASSERT(ss->top >= 2);
3808                 (void) PopOff(ss, op);
3809                 lval = POP_STR();
3810                 isArray = (*lval == 'A');
3811                 todo = ss->sprinter.offset;
3812 #if JS_HAS_SHARP_VARS
3813                 op = (JSOp)pc[len];
3814                 if (op == JSOP_DEFSHARP) {
3815                     pc += len;
3816                     cs = &js_CodeSpec[op];
3817                     len = cs->length;
3818                     i = (jsint) GET_ATOM_INDEX(pc);
3819                     if (Sprint(&ss->sprinter, "#%u=", (unsigned) i) < 0)
3820                         return NULL;
3821                 }
3822 #endif /* JS_HAS_SHARP_VARS */
3823                 if (isArray) {
3824                     ++ss->inArrayInit;
3825                     if (SprintCString(&ss->sprinter, "[") < 0)
3826                         return NULL;
3827                 } else {
3828                     if (SprintCString(&ss->sprinter, "{") < 0)
3829                         return NULL;
3830                 }
3831                 break;
3832               }
3833 
3834               case JSOP_ENDINIT:
3835                 op = JSOP_NOP;           /* turn off parens */
3836                 rval = POP_STR();
3837                 sn = js_GetSrcNote(jp->script, pc);
3838 
3839                 /* Skip any #n= prefix to find the opening bracket. */
3840                 for (xval = rval; *xval != '[' && *xval != '{'; xval++)
3841                     continue;
3842                 if (*xval == '[')
3843                     --ss->inArrayInit;
3844                 todo = Sprint(&ss->sprinter, "%s%s%c",
3845                               rval,
3846                               (sn && SN_TYPE(sn) == SRC_CONTINUE) ? ", " : "",
3847                               (*xval == '[') ? ']' : '}');
3848                 break;
3849 
3850               case JSOP_INITPROP:
3851                 atom = GET_ATOM(cx, jp->script, pc);
3852                 xval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom),
3853                                    (jschar)
3854                                    (ATOM_IS_IDENTIFIER(atom) ? 0 : '\''));
3855                 if (!xval)
3856                     return NULL;
3857                 rval = POP_STR();
3858                 lval = POP_STR();
3859               do_initprop:
3860 #ifdef OLD_GETTER_SETTER
3861                 todo = Sprint(&ss->sprinter, "%s%s%s%s%s:%s",
3862                               lval,
3863                               (lval[1] != '\0') ? ", " : "",
3864                               xval,
3865                               (lastop == JSOP_GETTER || lastop == JSOP_SETTER)
3866                               ? " " : "",
3867                               (lastop == JSOP_GETTER) ? js_getter_str :
3868                               (lastop == JSOP_SETTER) ? js_setter_str :
3869                               "",
3870                               rval);
3871 #else
3872                 if (lastop == JSOP_GETTER || lastop == JSOP_SETTER) {
3873                     if (!atom || !ATOM_IS_STRING(atom) ||
3874                         !ATOM_IS_IDENTIFIER(atom) ||
3875                         ATOM_IS_KEYWORD(atom) ||
3876                         ((ss->opcodes[ss->top+1] != JSOP_ANONFUNOBJ ||
3877                           strncmp(rval, js_function_str, 8) != 0) &&
3878                          ss->opcodes[ss->top+1] != JSOP_NAMEDFUNOBJ)) {
3879                         todo = Sprint(&ss->sprinter, "%s%s%s%s%s:%s", lval,
3880                                       (lval[1] != '\0') ? ", " : "", xval,
3881                                       (lastop == JSOP_GETTER ||
3882                                        lastop == JSOP_SETTER)
3883                                         ? " " : "",
3884                                       (lastop == JSOP_GETTER) ? js_getter_str :
3885                                       (lastop == JSOP_SETTER) ? js_setter_str :
3886                                       "",
3887                                       rval);
3888                     } else {
3889                         rval += 8 + 1;
3890                         LOCAL_ASSERT(rval[strlen(rval)-1] == '}');
3891                         todo = Sprint(&ss->sprinter, "%s%s%s %s%s",
3892                                       lval,
3893                                       (lval[1] != '\0') ? ", " : "",
3894                                       (lastop == JSOP_GETTER)
3895                                       ? js_get_str : js_set_str,
3896                                       xval,
3897                                       rval);
3898                     }
3899                 } else {
3900                     todo = Sprint(&ss->sprinter, "%s%s%s:%s",
3901                                   lval,
3902                                   (lval[1] != '\0') ? ", " : "",
3903                                   xval,
3904                                   rval);
3905                 }
3906 #endif
3907                 break;
3908 
3909               case JSOP_INITELEM:
3910                 rval = POP_STR();
3911                 xval = POP_STR();
3912                 lval = POP_STR();
3913                 sn = js_GetSrcNote(jp->script, pc);
3914                 if (sn && SN_TYPE(sn) == SRC_INITPROP) {
3915                     atom = NULL;
3916                     goto do_initprop;
3917                 }
3918                 todo = Sprint(&ss->sprinter, "%s%s%s",
3919                               lval,
3920                               (lval[1] != '\0' || *xval != '0') ? ", " : "",
3921                               rval);
3922                 break;
3923 
3924 #if JS_HAS_SHARP_VARS
3925               case JSOP_DEFSHARP:
3926                 i = (jsint) GET_ATOM_INDEX(pc);
3927                 rval = POP_STR();
3928                 todo = Sprint(&ss->sprinter, "#%u=%s", (unsigned) i, rval);
3929                 break;
3930 
3931               case JSOP_USESHARP:
3932                 i = (jsint) GET_ATOM_INDEX(pc);
3933                 todo = Sprint(&ss->sprinter, "#%u#", (unsigned) i);
3934                 break;
3935 #endif /* JS_HAS_SHARP_VARS */
3936 
3937 #if JS_HAS_DEBUGGER_KEYWORD
3938               case JSOP_DEBUGGER:
3939                 js_printf(jp, "\tdebugger;\n");
3940                 todo = -2;
3941                 break;
3942 #endif /* JS_HAS_DEBUGGER_KEYWORD */
3943 
3944 #if JS_HAS_XML_SUPPORT
3945               case JSOP_STARTXML:
3946               case JSOP_STARTXMLEXPR:
3947                 inXML = op == JSOP_STARTXML;
3948                 todo = -2;
3949                 break;
3950 
3951               case JSOP_DEFXMLNS:
3952                 rval = POP_STR();
3953                 js_printf(jp, "\t%s %s %s = %s;\n",
3954                           js_default_str, js_xml_str, js_namespace_str, rval);
3955                 todo = -2;
3956                 break;
3957 
3958               case JSOP_ANYNAME:
3959                 if (pc[JSOP_ANYNAME_LENGTH] == JSOP_TOATTRNAME) {
3960                     len += JSOP_TOATTRNAME_LENGTH;
3961                     todo = SprintPut(&ss->sprinter, "@*", 2);
3962                 } else {
3963                     todo = SprintPut(&ss->sprinter, "*", 1);
3964                 }
3965                 break;
3966 
3967               BEGIN_LITOPX_CASE(JSOP_QNAMEPART)
3968                 if (pc[JSOP_QNAMEPART_LENGTH] == JSOP_TOATTRNAME) {
3969                     saveop = JSOP_TOATTRNAME;
3970                     len += JSOP_TOATTRNAME_LENGTH;
3971                     lval = "@";
3972                     goto do_qname;
3973                 }
3974                 goto do_name;
3975               END_LITOPX_CASE
3976 
3977               BEGIN_LITOPX_CASE(JSOP_QNAMECONST)
3978                 rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
3979                 if (!rval)
3980                     return NULL;
3981                 RETRACT(&ss->sprinter, rval);
3982                 lval = POP_STR();
3983                 todo = Sprint(&ss->sprinter, "%s::%s", lval, rval);
3984               END_LITOPX_CASE
3985 
3986               case JSOP_QNAME:
3987                 rval = POP_STR();
3988                 lval = POP_STR();
3989                 todo = Sprint(&ss->sprinter, "%s::[%s]", lval, rval);
3990                 break;
3991 
3992               case JSOP_TOATTRNAME:
3993                 op = JSOP_NOP;           /* turn off parens */
3994                 rval = POP_STR();
3995                 todo = Sprint(&ss->sprinter, "@[%s]", rval);
3996                 break;
3997 
3998               case JSOP_TOATTRVAL:
3999                 todo = -2;
4000                 break;
4001 
4002               case JSOP_ADDATTRNAME:
4003                 rval = POP_STR();
4004                 lval = POP_STR();
4005                 todo = Sprint(&ss->sprinter, "%s %s", lval, rval);
4006                 /* This gets reset by all XML tag expressions. */
4007                 quoteAttr = JS_TRUE;
4008                 break;
4009 
4010               case JSOP_ADDATTRVAL:
4011                 rval = POP_STR();
4012                 lval = POP_STR();
4013                 if (quoteAttr)
4014                     todo = Sprint(&ss->sprinter, "%s=\"%s\"", lval, rval);
4015                 else
4016                     todo = Sprint(&ss->sprinter, "%s=%s", lval, rval);
4017                 break;
4018 
4019               case JSOP_BINDXMLNAME:
4020                 /* Leave the name stacked and push a dummy string. */
4021                 todo = Sprint(&ss->sprinter, "");
4022                 break;
4023 
4024               case JSOP_SETXMLNAME:
4025                 /* Pop the r.h.s., the dummy string, and the name. */
4026                 rval = POP_STR();
4027                 (void) PopOff(ss, op);
4028                 lval = POP_STR();
4029                 goto do_setlval;
4030 
4031               case JSOP_XMLELTEXPR:
4032               case JSOP_XMLTAGEXPR:
4033                 todo = Sprint(&ss->sprinter, "{%s}", POP_STR());
4034                 inXML = JS_TRUE;
4035                 /* If we're an attribute value, we shouldn't quote this. */
4036                 quoteAttr = JS_FALSE;
4037                 break;
4038 
4039               case JSOP_TOXMLLIST:
4040                 op = JSOP_NOP;           /* turn off parens */
4041                 todo = Sprint(&ss->sprinter, "<>%s</>", POP_STR());
4042                 inXML = JS_FALSE;
4043                 break;
4044 
4045               case JSOP_FOREACH:
4046                 foreach = JS_TRUE;
4047                 todo = -2;
4048                 break;
4049 
4050               case JSOP_TOXML:
4051                 inXML = JS_FALSE;
4052                 /* FALL THROUGH */
4053 
4054               case JSOP_XMLNAME:
4055               case JSOP_FILTER:
4056                 /* Conversion and prefix ops do nothing in the decompiler. */
4057                 todo = -2;
4058                 break;
4059 
4060               case JSOP_ENDFILTER:
4061                 rval = POP_STR();
4062                 lval = POP_STR();
4063                 todo = Sprint(&ss->sprinter, "%s.(%s)", lval, rval);
4064                 break;
4065 
4066               case JSOP_DESCENDANTS:
4067                 rval = POP_STR();
4068                 lval = POP_STR();
4069                 todo = Sprint(&ss->sprinter, "%s..%s", lval, rval);
4070                 break;
4071 
4072               BEGIN_LITOPX_CASE(JSOP_XMLOBJECT)
4073                 todo = Sprint(&ss->sprinter, "<xml address='%p'>",
4074                               ATOM_TO_OBJECT(atom));
4075               END_LITOPX_CASE
4076 
4077               BEGIN_LITOPX_CASE(JSOP_XMLCDATA)
4078                 todo = SprintPut(&ss->sprinter, "<![CDATA[", 9);
4079                 if (!QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0))
4080                     return NULL;
4081                 SprintPut(&ss->sprinter, "]]>", 3);
4082               END_LITOPX_CASE
4083 
4084               BEGIN_LITOPX_CASE(JSOP_XMLCOMMENT)
4085                 todo = SprintPut(&ss->sprinter, "<!--", 4);
4086                 if (!QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0))
4087                     return NULL;
4088                 SprintPut(&ss->sprinter, "-->", 3);
4089               END_LITOPX_CASE
4090 
4091               BEGIN_LITOPX_CASE(JSOP_XMLPI)
4092                 rval = JS_strdup(cx, POP_STR());
4093                 if (!rval)
4094                     return NULL;
4095                 todo = SprintPut(&ss->sprinter, "<?", 2);
4096                 ok = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0) &&
4097                      (*rval == '\0' ||
4098                       (SprintPut(&ss->sprinter, " ", 1) >= 0 &&
4099                        SprintCString(&ss->sprinter, rval)));
4100                 JS_free(cx, (char *)rval);
4101                 if (!ok)
4102                     return NULL;
4103                 SprintPut(&ss->sprinter, "?>", 2);
4104               END_LITOPX_CASE
4105 
4106               case JSOP_GETFUNNS:
4107                 todo = SprintPut(&ss->sprinter, js_function_str, 8);
4108                 break;
4109 #endif /* JS_HAS_XML_SUPPORT */
4110 
4111               default:
4112                 todo = -2;
4113                 break;
4114 
4115 #undef BEGIN_LITOPX_CASE
4116 #undef END_LITOPX_CASE
4117             }
4118         }
4119 
4120         if (todo < 0) {
4121             /* -2 means "don't push", -1 means reported error. */
4122             if (todo == -1)
4123                 return NULL;
4124         } else {
4125             if (!PushOff(ss, todo, saveop))
4126                 return NULL;
4127         }
4128         pc += len;
4129     }
4130 
4131 /*
4132  * Undefine local macros.
4133  */
4134 #undef inXML
4135 #undef DECOMPILE_CODE
4136 #undef POP_STR
4137 #undef LOCAL_ASSERT
4138 #undef ATOM_IS_IDENTIFIER
4139 #undef GET_QUOTE_AND_FMT
4140 #undef GET_ATOM_QUOTE_AND_FMT
4141 
4142     return pc;
4143 }
4144 
4145 static JSBool
InitSprintStack(JSContext * cx,SprintStack * ss,JSPrinter * jp,uintN depth)4146 InitSprintStack(JSContext *cx, SprintStack *ss, JSPrinter *jp, uintN depth)
4147 {
4148     size_t offsetsz, opcodesz;
4149     void *space;
4150 
4151     INIT_SPRINTER(cx, &ss->sprinter, &cx->tempPool, PAREN_SLOP);
4152 
4153     /* Allocate the parallel (to avoid padding) offset and opcode stacks. */
4154     offsetsz = depth * sizeof(ptrdiff_t);
4155     opcodesz = depth * sizeof(jsbytecode);
4156     JS_ARENA_ALLOCATE(space, &cx->tempPool, offsetsz + opcodesz);
4157     if (!space)
4158         return JS_FALSE;
4159     ss->offsets = (ptrdiff_t *) space;
4160     ss->opcodes = (jsbytecode *) ((char *)space + offsetsz);
4161 
4162     ss->top = ss->inArrayInit = 0;
4163     ss->printer = jp;
4164     return JS_TRUE;
4165 }
4166 
4167 JSBool
js_DecompileCode(JSPrinter * jp,JSScript * script,jsbytecode * pc,uintN len,uintN pcdepth)4168 js_DecompileCode(JSPrinter *jp, JSScript *script, jsbytecode *pc, uintN len,
4169                  uintN pcdepth)
4170 {
4171     uintN depth, i;
4172     SprintStack ss;
4173     JSContext *cx;
4174     void *mark;
4175     JSBool ok;
4176     JSScript *oldscript;
4177     char *last;
4178 
4179     depth = script->depth;
4180     JS_ASSERT(pcdepth <= depth);
4181 
4182     /* Initialize a sprinter for use with the offset stack. */
4183     cx = jp->sprinter.context;
4184     mark = JS_ARENA_MARK(&cx->tempPool);
4185     ok = InitSprintStack(cx, &ss, jp, depth);
4186     if (!ok)
4187         goto out;
4188 
4189     /*
4190      * If we are called from js_DecompileValueGenerator with a portion of
4191      * script's bytecode that starts with a non-zero model stack depth given
4192      * by pcdepth, attempt to initialize the missing string offsets in ss to
4193      * |spindex| negative indexes from fp->sp for the activation fp in which
4194      * the error arose.
4195      *
4196      * See js_DecompileValueGenerator for how its |spindex| parameter is used,
4197      * and see also GetOff, which makes use of the ss.offsets[i] < -1 that are
4198      * potentially stored below.
4199      */
4200     ss.top = pcdepth;
4201     if (pcdepth != 0) {
4202         JSStackFrame *fp;
4203         ptrdiff_t top;
4204 
4205         for (fp = cx->fp; fp && !fp->script; fp = fp->down)
4206             continue;
4207         top = fp ? fp->sp - fp->spbase : 0;
4208         for (i = 0; i < pcdepth; i++) {
4209             ss.offsets[i] = -1;
4210             ss.opcodes[i] = JSOP_NOP;
4211         }
4212         if (fp && fp->pc == pc && (uintN)top == pcdepth) {
4213             for (i = 0; i < pcdepth; i++) {
4214                 ptrdiff_t off;
4215                 jsbytecode *genpc;
4216 
4217                 off = (intN)i - (intN)depth;
4218                 genpc = (jsbytecode *) fp->spbase[off];
4219                 if (JS_UPTRDIFF(genpc, script->code) < script->length) {
4220                     ss.offsets[i] += (ptrdiff_t)i - top;
4221                     ss.opcodes[i] = *genpc;
4222                 }
4223             }
4224         }
4225     }
4226 
4227     /* Call recursive subroutine to do the hard work. */
4228     oldscript = jp->script;
4229     jp->script = script;
4230     ok = Decompile(&ss, pc, len) != NULL;
4231     jp->script = oldscript;
4232 
4233     /* If the given code didn't empty the stack, do it now. */
4234     if (ss.top) {
4235         do {
4236             last = OFF2STR(&ss.sprinter, PopOff(&ss, JSOP_POP));
4237         } while (ss.top > pcdepth);
4238         js_printf(jp, "%s", last);
4239     }
4240 
4241 out:
4242     /* Free all temporary stuff allocated under this call. */
4243     JS_ARENA_RELEASE(&cx->tempPool, mark);
4244     return ok;
4245 }
4246 
4247 JSBool
js_DecompileScript(JSPrinter * jp,JSScript * script)4248 js_DecompileScript(JSPrinter *jp, JSScript *script)
4249 {
4250     return js_DecompileCode(jp, script, script->code, (uintN)script->length, 0);
4251 }
4252 
4253 static const char native_code_str[] = "\t[native code]\n";
4254 
4255 JSBool
js_DecompileFunctionBody(JSPrinter * jp,JSFunction * fun)4256 js_DecompileFunctionBody(JSPrinter *jp, JSFunction *fun)
4257 {
4258     JSScript *script;
4259     JSScope *scope, *save;
4260     JSBool ok;
4261 
4262     if (!FUN_INTERPRETED(fun)) {
4263         js_printf(jp, native_code_str);
4264         return JS_TRUE;
4265     }
4266     script = fun->u.i.script;
4267     scope = fun->object ? OBJ_SCOPE(fun->object) : NULL;
4268     save = jp->scope;
4269     jp->scope = scope;
4270     ok = js_DecompileCode(jp, script, script->code, (uintN)script->length, 0);
4271     jp->scope = save;
4272     return ok;
4273 }
4274 
4275 JSBool
js_DecompileFunction(JSPrinter * jp,JSFunction * fun)4276 js_DecompileFunction(JSPrinter *jp, JSFunction *fun)
4277 {
4278     JSContext *cx;
4279     uintN i, nargs, indent;
4280     void *mark;
4281     JSAtom **params;
4282     JSScope *scope, *oldscope;
4283     JSScopeProperty *sprop;
4284     jsbytecode *pc, *endpc;
4285     ptrdiff_t len;
4286     JSBool ok;
4287 
4288     /*
4289      * If pretty, conform to ECMA-262 Edition 3, 15.3.4.2, by decompiling a
4290      * FunctionDeclaration.  Otherwise, check the JSFUN_LAMBDA flag and force
4291      * an expression by parenthesizing.
4292      */
4293     if (jp->pretty) {
4294         js_printf(jp, "\t");
4295     } else {
4296         if (!jp->grouped && (fun->flags & JSFUN_LAMBDA))
4297             js_puts(jp, "(");
4298     }
4299     if (JSFUN_GETTER_TEST(fun->flags))
4300         js_printf(jp, "%s ", js_getter_str);
4301     else if (JSFUN_SETTER_TEST(fun->flags))
4302         js_printf(jp, "%s ", js_setter_str);
4303 
4304     js_printf(jp, "%s ", js_function_str);
4305     if (fun->atom && !QuoteString(&jp->sprinter, ATOM_TO_STRING(fun->atom), 0))
4306         return JS_FALSE;
4307     js_puts(jp, "(");
4308 
4309     if (FUN_INTERPRETED(fun) && fun->object) {
4310         size_t paramsize;
4311 #ifdef JS_HAS_DESTRUCTURING
4312         SprintStack ss;
4313         JSScript *oldscript;
4314 #endif
4315 
4316         /*
4317          * Print the parameters.
4318          *
4319          * This code is complicated by the need to handle duplicate parameter
4320          * names, as required by ECMA (bah!).  A duplicate parameter is stored
4321          * as another node with the same id (the parameter name) but different
4322          * shortid (the argument index) along the property tree ancestor line
4323          * starting at SCOPE_LAST_PROP(scope).  Only the last duplicate param
4324          * is mapped by the scope's hash table.
4325          */
4326         cx = jp->sprinter.context;
4327         nargs = fun->nargs;
4328         mark = JS_ARENA_MARK(&cx->tempPool);
4329         paramsize = nargs * sizeof(JSAtom *);
4330         JS_ARENA_ALLOCATE_CAST(params, JSAtom **, &cx->tempPool, paramsize);
4331         if (!params) {
4332             JS_ReportOutOfMemory(cx);
4333             return JS_FALSE;
4334         }
4335 
4336         memset(params, 0, paramsize);
4337         scope = OBJ_SCOPE(fun->object);
4338         for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) {
4339             if (sprop->getter != js_GetArgument)
4340                 continue;
4341             JS_ASSERT(sprop->flags & SPROP_HAS_SHORTID);
4342             JS_ASSERT((uint16) sprop->shortid < nargs);
4343             JS_ASSERT(JSID_IS_ATOM(sprop->id));
4344             params[(uint16) sprop->shortid] = JSID_TO_ATOM(sprop->id);
4345         }
4346 
4347         pc = fun->u.i.script->main;
4348         endpc = pc + fun->u.i.script->length;
4349         ok = JS_TRUE;
4350 
4351 #ifdef JS_HAS_DESTRUCTURING
4352         /* Skip JSOP_GENERATOR in case of destructuring parameters. */
4353         if (*pc == JSOP_GENERATOR)
4354             pc += JSOP_GENERATOR_LENGTH;
4355 
4356         ss.printer = NULL;
4357         oldscript = jp->script;
4358         jp->script = fun->u.i.script;
4359         oldscope = jp->scope;
4360         jp->scope = scope;
4361 #endif
4362 
4363         for (i = 0; i < nargs; i++) {
4364             if (i > 0)
4365                 js_puts(jp, ", ");
4366 
4367 #if JS_HAS_DESTRUCTURING
4368 #define LOCAL_ASSERT(expr)      LOCAL_ASSERT_RV(expr, JS_FALSE)
4369 
4370             if (!params[i]) {
4371                 ptrdiff_t todo;
4372                 const char *lval;
4373 
4374                 LOCAL_ASSERT(*pc == JSOP_GETARG);
4375                 pc += JSOP_GETARG_LENGTH;
4376                 LOCAL_ASSERT(*pc == JSOP_DUP);
4377                 if (!ss.printer) {
4378                     ok = InitSprintStack(cx, &ss, jp, fun->u.i.script->depth);
4379                     if (!ok)
4380                         break;
4381                 }
4382                 pc = DecompileDestructuring(&ss, pc, endpc);
4383                 if (!pc) {
4384                     ok = JS_FALSE;
4385                     break;
4386                 }
4387                 LOCAL_ASSERT(*pc == JSOP_POP);
4388                 pc += JSOP_POP_LENGTH;
4389                 lval = PopStr(&ss, JSOP_NOP);
4390                 todo = SprintCString(&jp->sprinter, lval);
4391                 if (todo < 0) {
4392                     ok = JS_FALSE;
4393                     break;
4394                 }
4395                 continue;
4396             }
4397 
4398 #undef LOCAL_ASSERT
4399 #endif
4400 
4401             if (!QuoteString(&jp->sprinter, ATOM_TO_STRING(params[i]), 0)) {
4402                 ok = JS_FALSE;
4403                 break;
4404             }
4405         }
4406 
4407 #ifdef JS_HAS_DESTRUCTURING
4408         jp->script = oldscript;
4409         jp->scope = oldscope;
4410 #endif
4411         JS_ARENA_RELEASE(&cx->tempPool, mark);
4412         if (!ok)
4413             return JS_FALSE;
4414 #ifdef __GNUC__
4415     } else {
4416         scope = NULL;
4417         pc = NULL;
4418 #endif
4419     }
4420 
4421     js_printf(jp, ") {\n");
4422     indent = jp->indent;
4423     jp->indent += 4;
4424     if (FUN_INTERPRETED(fun) && fun->object) {
4425         oldscope = jp->scope;
4426         jp->scope = scope;
4427         len = fun->u.i.script->code + fun->u.i.script->length - pc;
4428         ok = js_DecompileCode(jp, fun->u.i.script, pc, (uintN)len, 0);
4429         jp->scope = oldscope;
4430         if (!ok) {
4431             jp->indent = indent;
4432             return JS_FALSE;
4433         }
4434     } else {
4435         js_printf(jp, native_code_str);
4436     }
4437     jp->indent -= 4;
4438     js_printf(jp, "\t}");
4439 
4440     if (!jp->pretty) {
4441         if (!jp->grouped && (fun->flags & JSFUN_LAMBDA))
4442             js_puts(jp, ")");
4443     }
4444     return JS_TRUE;
4445 }
4446 
4447 #undef LOCAL_ASSERT_RV
4448 
4449 JSString *
js_DecompileValueGenerator(JSContext * cx,intN spindex,jsval v,JSString * fallback)4450 js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v,
4451                            JSString *fallback)
4452 {
4453     JSStackFrame *fp, *down;
4454     jsbytecode *pc, *begin, *end;
4455     jsval *sp, *spbase, *base, *limit;
4456     intN depth, pcdepth;
4457     JSScript *script;
4458     JSOp op;
4459     const JSCodeSpec *cs;
4460     jssrcnote *sn;
4461     ptrdiff_t len, oplen;
4462     JSPrinter *jp;
4463     JSString *name;
4464 
4465     for (fp = cx->fp; fp && !fp->script; fp = fp->down)
4466         continue;
4467     if (!fp)
4468         goto do_fallback;
4469 
4470     /* Try to find sp's generating pc depth slots under it on the stack. */
4471     pc = fp->pc;
4472     sp = fp->sp;
4473     spbase = fp->spbase;
4474     if ((uintN)(sp - spbase) > fp->script->depth) {
4475         /*
4476          * Preparing to make an internal invocation, using an argv stack
4477          * segment pushed just above fp's operand stack space.  Such an argv
4478          * stack has no generating pc "basement", so we must fall back.
4479          */
4480         goto do_fallback;
4481     }
4482 
4483     if (spindex == JSDVG_SEARCH_STACK) {
4484         if (!pc) {
4485             /*
4486              * Current frame is native: look under it for a scripted call
4487              * in which a decompilable bytecode string that generated the
4488              * value as an actual argument might exist.
4489              */
4490             JS_ASSERT(!fp->script && !(fp->fun && FUN_INTERPRETED(fp->fun)));
4491             down = fp->down;
4492             if (!down)
4493                 goto do_fallback;
4494             script = down->script;
4495             spbase = down->spbase;
4496             base = fp->argv;
4497             limit = base + fp->argc;
4498         } else {
4499             /*
4500              * This should be a script activation, either a top-level
4501              * script or a scripted function.  But be paranoid about calls
4502              * to js_DecompileValueGenerator from code that hasn't fully
4503              * initialized a (default-all-zeroes) frame.
4504              */
4505             script = fp->script;
4506             spbase = base = fp->spbase;
4507             limit = fp->sp;
4508         }
4509 
4510         /*
4511          * Pure paranoia about default-zeroed frames being active while
4512          * js_DecompileValueGenerator is called.  It can't hurt much now;
4513          * error reporting performance is not an issue.
4514          */
4515         if (!script || !base || !limit)
4516             goto do_fallback;
4517 
4518         /*
4519          * Try to find operand-generating pc depth slots below sp.
4520          *
4521          * In the native case, we know the arguments have generating pc's
4522          * under them, on account of fp->down->script being non-null: all
4523          * compiled scripts get depth slots for generating pc's allocated
4524          * upon activation, at the top of js_Interpret.
4525          *
4526          * In the script or scripted function case, the same reasoning
4527          * applies to fp rather than to fp->down.
4528          *
4529          * We search from limit to base to find the most recently calculated
4530          * value matching v under assumption that it is it that caused
4531          * exception, see bug 328664.
4532          */
4533         for (sp = limit;;) {
4534             if (sp <= base)
4535                 goto do_fallback;
4536             --sp;
4537             if (*sp == v) {
4538                 depth = (intN)script->depth;
4539                 sp -= depth;
4540                 pc = (jsbytecode *) *sp;
4541                 break;
4542             }
4543         }
4544     } else {
4545         /*
4546          * At this point, pc may or may not be null, i.e., we could be in
4547          * a script activation, or we could be in a native frame that was
4548          * called by another native function.  Check pc and script.
4549          */
4550         if (!pc)
4551             goto do_fallback;
4552         script = fp->script;
4553         if (!script)
4554             goto do_fallback;
4555 
4556         if (spindex != JSDVG_IGNORE_STACK) {
4557             JS_ASSERT(spindex < 0);
4558             depth = (intN)script->depth;
4559 #if !JS_HAS_NO_SUCH_METHOD
4560             JS_ASSERT(-depth <= spindex);
4561 #endif
4562             spindex -= depth;
4563 
4564             base = (jsval *) cx->stackPool.current->base;
4565             limit = (jsval *) cx->stackPool.current->avail;
4566             sp = fp->sp + spindex;
4567             if (JS_UPTRDIFF(sp, base) < JS_UPTRDIFF(limit, base))
4568                 pc = (jsbytecode *) *sp;
4569         }
4570     }
4571 
4572     /*
4573      * Again, be paranoid, this time about possibly loading an invalid pc
4574      * from fp->sp[-(1+depth)].
4575      */
4576     if (JS_UPTRDIFF(pc, script->code) >= (jsuword)script->length) {
4577         pc = fp->pc;
4578         if (!pc)
4579             goto do_fallback;
4580     }
4581     op = (JSOp) *pc;
4582     if (op == JSOP_TRAP)
4583         op = JS_GetTrapOpcode(cx, script, pc);
4584 
4585     /* None of these stack-writing ops generates novel values. */
4586     JS_ASSERT(op != JSOP_CASE && op != JSOP_CASEX &&
4587               op != JSOP_DUP && op != JSOP_DUP2 &&
4588               op != JSOP_SWAP);
4589 
4590     /*
4591      * |this| could convert to a very long object initialiser, so cite it by
4592      * its keyword name instead.
4593      */
4594     if (op == JSOP_THIS)
4595         return JS_NewStringCopyZ(cx, js_this_str);
4596 
4597     /*
4598      * JSOP_BINDNAME is special: it generates a value, the base object of a
4599      * reference.  But if it is the generating op for a diagnostic produced by
4600      * js_DecompileValueGenerator, the name being bound is irrelevant.  Just
4601      * fall back to the base object.
4602      */
4603     if (op == JSOP_BINDNAME)
4604         goto do_fallback;
4605 
4606     /* NAME ops are self-contained, others require left or right context. */
4607     cs = &js_CodeSpec[op];
4608     begin = pc;
4609     end = pc + cs->length;
4610     if ((cs->format & JOF_MODEMASK) != JOF_NAME) {
4611         JSSrcNoteType noteType;
4612 
4613         sn = js_GetSrcNote(script, pc);
4614         if (!sn)
4615             goto do_fallback;
4616         noteType = SN_TYPE(sn);
4617         if (noteType == SRC_PCBASE) {
4618             begin -= js_GetSrcNoteOffset(sn, 0);
4619         } else if (noteType == SRC_PCDELTA) {
4620             end = begin + js_GetSrcNoteOffset(sn, 0);
4621             begin += cs->length;
4622         } else {
4623             goto do_fallback;
4624         }
4625     }
4626     len = PTRDIFF(end, begin, jsbytecode);
4627     if (len <= 0)
4628         goto do_fallback;
4629 
4630     /*
4631      * Walk forward from script->main and compute starting stack depth.
4632      * FIXME: Code to compute oplen copied from js_Disassemble1 and reduced.
4633      * FIXME: Optimize to use last empty-stack sequence point.
4634      */
4635     pcdepth = 0;
4636     for (pc = script->main; pc < begin; pc += oplen) {
4637         jsbytecode *pc2;
4638         uint32 type;
4639         intN nuses, ndefs;
4640 
4641         /* Let pc2 be non-null only for JSOP_LITOPX. */
4642         pc2 = NULL;
4643         op = (JSOp) *pc;
4644         if (op == JSOP_TRAP)
4645             op = JS_GetTrapOpcode(cx, script, pc);
4646         cs = &js_CodeSpec[op];
4647         oplen = cs->length;
4648 
4649         if (op == JSOP_SETSP) {
4650             pcdepth = GET_UINT16(pc);
4651             continue;
4652         }
4653 
4654         /*
4655          * A (C ? T : E) expression requires skipping either T (if begin is in
4656          * E) or both T and E (if begin is after the whole expression) before
4657          * adjusting pcdepth based on the JSOP_IFEQ or JSOP_IFEQX at pc that
4658          * tests condition C.  We know that the stack depth can't change from
4659          * what it was with C on top of stack.
4660          */
4661         sn = js_GetSrcNote(script, pc);
4662         if (sn && SN_TYPE(sn) == SRC_COND) {
4663             ptrdiff_t jmpoff, jmplen;
4664 
4665             jmpoff = js_GetSrcNoteOffset(sn, 0);
4666             if (pc + jmpoff < begin) {
4667                 pc += jmpoff;
4668                 op = *pc;
4669                 JS_ASSERT(op == JSOP_GOTO || op == JSOP_GOTOX);
4670                 cs = &js_CodeSpec[op];
4671                 oplen = cs->length;
4672                 jmplen = GetJumpOffset(pc, pc);
4673                 if (pc + jmplen < begin) {
4674                     oplen = (uintN) jmplen;
4675                     continue;
4676                 }
4677 
4678                 /*
4679                  * Ok, begin lies in E.  Manually pop C off the model stack,
4680                  * since we have moved beyond the IFEQ now.
4681                  */
4682                 --pcdepth;
4683             }
4684         }
4685 
4686         type = cs->format & JOF_TYPEMASK;
4687         switch (type) {
4688           case JOF_TABLESWITCH:
4689           case JOF_TABLESWITCHX:
4690           {
4691             jsint jmplen, i, low, high;
4692 
4693             jmplen = (type == JOF_TABLESWITCH) ? JUMP_OFFSET_LEN
4694                                                : JUMPX_OFFSET_LEN;
4695             pc2 = pc;
4696             pc2 += jmplen;
4697             low = GET_JUMP_OFFSET(pc2);
4698             pc2 += JUMP_OFFSET_LEN;
4699             high = GET_JUMP_OFFSET(pc2);
4700             pc2 += JUMP_OFFSET_LEN;
4701             for (i = low; i <= high; i++)
4702                 pc2 += jmplen;
4703             oplen = 1 + pc2 - pc;
4704             break;
4705           }
4706 
4707           case JOF_LOOKUPSWITCH:
4708           case JOF_LOOKUPSWITCHX:
4709           {
4710             jsint jmplen;
4711             jsbytecode *pc2;
4712             jsatomid npairs;
4713 
4714             jmplen = (type == JOF_LOOKUPSWITCH) ? JUMP_OFFSET_LEN
4715                                                 : JUMPX_OFFSET_LEN;
4716             pc2 = pc;
4717             pc2 += jmplen;
4718             npairs = GET_ATOM_INDEX(pc2);
4719             pc2 += ATOM_INDEX_LEN;
4720             while (npairs) {
4721                 pc2 += ATOM_INDEX_LEN;
4722                 pc2 += jmplen;
4723                 npairs--;
4724             }
4725             oplen = 1 + pc2 - pc;
4726             break;
4727           }
4728 
4729           case JOF_LITOPX:
4730             pc2 = pc + 1 + LITERAL_INDEX_LEN;
4731             op = *pc2;
4732             cs = &js_CodeSpec[op];
4733             JS_ASSERT(cs->length > ATOM_INDEX_LEN);
4734             oplen += cs->length - (1 + ATOM_INDEX_LEN);
4735             break;
4736 
4737           default:;
4738         }
4739 
4740         if (sn && SN_TYPE(sn) == SRC_HIDDEN)
4741             continue;
4742 
4743         nuses = cs->nuses;
4744         if (nuses < 0) {
4745             /* Call opcode pushes [callee, this, argv...]. */
4746             nuses = 2 + GET_ARGC(pc);
4747         } else if (op == JSOP_RETSUB) {
4748             /* Pop [exception or hole, retsub pc-index]. */
4749             JS_ASSERT(nuses == 0);
4750             nuses = 2;
4751         } else if (op == JSOP_LEAVEBLOCK || op == JSOP_LEAVEBLOCKEXPR) {
4752             JS_ASSERT(nuses == 0);
4753             nuses = GET_UINT16(pc);
4754         }
4755         pcdepth -= nuses;
4756         JS_ASSERT(pcdepth >= 0);
4757 
4758         ndefs = cs->ndefs;
4759         if (op == JSOP_FINALLY) {
4760             /* Push [exception or hole, retsub pc-index]. */
4761             JS_ASSERT(ndefs == 0);
4762             ndefs = 2;
4763         } else if (op == JSOP_ENTERBLOCK) {
4764             jsatomid atomIndex;
4765             JSAtom *atom;
4766             JSObject *obj;
4767 
4768             JS_ASSERT(ndefs == 0);
4769             atomIndex = pc2 ? GET_LITERAL_INDEX(pc) : GET_ATOM_INDEX(pc);
4770             atom = js_GetAtom(cx, &script->atomMap, atomIndex);
4771             obj = ATOM_TO_OBJECT(atom);
4772             JS_ASSERT(OBJ_BLOCK_DEPTH(cx, obj) == pcdepth);
4773             ndefs = OBJ_BLOCK_COUNT(cx, obj);
4774         }
4775         pcdepth += ndefs;
4776     }
4777 
4778     name = NULL;
4779     jp = js_NewPrinter(cx, "js_DecompileValueGenerator", 0, JS_FALSE);
4780     if (jp) {
4781         if (fp->fun && fp->fun->object) {
4782             JS_ASSERT(OBJ_IS_NATIVE(fp->fun->object));
4783             jp->scope = OBJ_SCOPE(fp->fun->object);
4784         }
4785         jp->dvgfence = end;
4786         if (js_DecompileCode(jp, script, begin, (uintN)len, (uintN)pcdepth))
4787             name = js_GetPrinterOutput(jp);
4788         js_DestroyPrinter(jp);
4789     }
4790     return name;
4791 
4792   do_fallback:
4793     return fallback ? fallback : js_ValueToSource(cx, v);
4794 }
4795