1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /*
8  * JS bytecode descriptors, disassemblers, and (expression) decompilers.
9  */
10 
11 #include "jsopcodeinlines.h"
12 
13 #define __STDC_FORMAT_MACROS
14 
15 #include "mozilla/SizePrintfMacros.h"
16 
17 #include <algorithm>
18 #include <ctype.h>
19 #include <inttypes.h>
20 #include <stdio.h>
21 #include <string.h>
22 
23 #include "jsapi.h"
24 #include "jsatom.h"
25 #include "jscntxt.h"
26 #include "jscompartment.h"
27 #include "jsfun.h"
28 #include "jsnum.h"
29 #include "jsobj.h"
30 #include "jsprf.h"
31 #include "jsscript.h"
32 #include "jsstr.h"
33 #include "jstypes.h"
34 #include "jsutil.h"
35 
36 #include "asmjs/AsmJSModule.h"
37 #include "frontend/BytecodeCompiler.h"
38 #include "frontend/SourceNotes.h"
39 #include "gc/GCInternals.h"
40 #include "js/CharacterEncoding.h"
41 #include "vm/CodeCoverage.h"
42 #include "vm/Opcodes.h"
43 #include "vm/ScopeObject.h"
44 #include "vm/Shape.h"
45 #include "vm/StringBuffer.h"
46 
47 #include "jscntxtinlines.h"
48 #include "jscompartmentinlines.h"
49 #include "jsobjinlines.h"
50 #include "jsscriptinlines.h"
51 
52 using namespace js;
53 using namespace js::gc;
54 
55 using JS::AutoCheckCannotGC;
56 
57 using js::frontend::IsIdentifier;
58 
59 /*
60  * Index limit must stay within 32 bits.
61  */
62 JS_STATIC_ASSERT(sizeof(uint32_t) * JS_BITS_PER_BYTE >= INDEX_LIMIT_LOG2 + 1);
63 
64 const JSCodeSpec js::CodeSpec[] = {
65 #define MAKE_CODESPEC(op,val,name,token,length,nuses,ndefs,format)  {length,nuses,ndefs,format},
66     FOR_EACH_OPCODE(MAKE_CODESPEC)
67 #undef MAKE_CODESPEC
68 };
69 
70 const unsigned js::NumCodeSpecs = JS_ARRAY_LENGTH(CodeSpec);
71 
72 /*
73  * Each element of the array is either a source literal associated with JS
74  * bytecode or null.
75  */
76 static const char * const CodeToken[] = {
77 #define TOKEN(op, val, name, token, ...)  token,
78     FOR_EACH_OPCODE(TOKEN)
79 #undef TOKEN
80 };
81 
82 /*
83  * Array of JS bytecode names used by PC count JSON, DEBUG-only Disassemble
84  * and JIT debug spew.
85  */
86 const char * const js::CodeName[] = {
87 #define OPNAME(op, val, name, ...)  name,
88     FOR_EACH_OPCODE(OPNAME)
89 #undef OPNAME
90 };
91 
92 /************************************************************************/
93 
94 #define COUNTS_LEN 16
95 
96 size_t
GetVariableBytecodeLength(jsbytecode * pc)97 js::GetVariableBytecodeLength(jsbytecode* pc)
98 {
99     JSOp op = JSOp(*pc);
100     MOZ_ASSERT(CodeSpec[op].length == -1);
101     switch (op) {
102       case JSOP_TABLESWITCH: {
103         /* Structure: default-jump case-low case-high case1-jump ... */
104         pc += JUMP_OFFSET_LEN;
105         int32_t low = GET_JUMP_OFFSET(pc);
106         pc += JUMP_OFFSET_LEN;
107         int32_t high = GET_JUMP_OFFSET(pc);
108         unsigned ncases = unsigned(high - low + 1);
109         return 1 + 3 * JUMP_OFFSET_LEN + ncases * JUMP_OFFSET_LEN;
110       }
111       default:
112         MOZ_CRASH("Unexpected op");
113     }
114 }
115 
116 unsigned
StackUses(JSScript * script,jsbytecode * pc)117 js::StackUses(JSScript* script, jsbytecode* pc)
118 {
119     JSOp op = (JSOp) *pc;
120     const JSCodeSpec& cs = CodeSpec[op];
121     if (cs.nuses >= 0)
122         return cs.nuses;
123 
124     MOZ_ASSERT(CodeSpec[op].nuses == -1);
125     switch (op) {
126       case JSOP_POPN:
127         return GET_UINT16(pc);
128       case JSOP_NEW:
129       case JSOP_SUPERCALL:
130         return 2 + GET_ARGC(pc) + 1;
131       default:
132         /* stack: fun, this, [argc arguments] */
133         MOZ_ASSERT(op == JSOP_CALL || op == JSOP_EVAL || op == JSOP_CALLITER ||
134                    op == JSOP_STRICTEVAL || op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
135         return 2 + GET_ARGC(pc);
136     }
137 }
138 
139 unsigned
StackDefs(JSScript * script,jsbytecode * pc)140 js::StackDefs(JSScript* script, jsbytecode* pc)
141 {
142     JSOp op = (JSOp) *pc;
143     const JSCodeSpec& cs = CodeSpec[op];
144     MOZ_ASSERT(cs.ndefs >= 0);
145     return cs.ndefs;
146 }
147 
148 const char * PCCounts::numExecName = "interp";
149 
150 void
DumpIonScriptCounts(Sprinter * sp,jit::IonScriptCounts * ionCounts)151 js::DumpIonScriptCounts(Sprinter* sp, jit::IonScriptCounts* ionCounts)
152 {
153     Sprint(sp, "IonScript [%lu blocks]:\n", ionCounts->numBlocks());
154     for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
155         const jit::IonBlockCounts& block = ionCounts->block(i);
156         Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset());
157         if (block.description())
158             Sprint(sp, " [inlined %s]", block.description());
159         for (size_t j = 0; j < block.numSuccessors(); j++)
160             Sprint(sp, " -> #%lu", block.successor(j));
161         Sprint(sp, " :: %llu hits\n", block.hitCount());
162         Sprint(sp, "%s\n", block.code());
163     }
164 }
165 
166 void
DumpPCCounts(JSContext * cx,HandleScript script,Sprinter * sp)167 js::DumpPCCounts(JSContext* cx, HandleScript script, Sprinter* sp)
168 {
169     MOZ_ASSERT(script->hasScriptCounts());
170 
171 #ifdef DEBUG
172     jsbytecode* pc = script->code();
173     while (pc < script->codeEnd()) {
174         jsbytecode* next = GetNextPc(pc);
175 
176         if (!Disassemble1(cx, script, pc, script->pcToOffset(pc), true, sp))
177             return;
178 
179         Sprint(sp, "                  {");
180         PCCounts* counts = script->maybeGetPCCounts(pc);
181         double val = counts ? counts->numExec() : 0.0;
182         if (val)
183             Sprint(sp, "\"%s\": %.0f", PCCounts::numExecName, val);
184         Sprint(sp, "}\n");
185 
186         pc = next;
187     }
188 #endif
189 
190     jit::IonScriptCounts* ionCounts = script->getIonCounts();
191 
192     while (ionCounts) {
193         DumpIonScriptCounts(sp, ionCounts);
194         ionCounts = ionCounts->previous();
195     }
196 }
197 
198 void
DumpCompartmentPCCounts(JSContext * cx)199 js::DumpCompartmentPCCounts(JSContext* cx)
200 {
201     for (ZoneCellIter i(cx->zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
202         RootedScript script(cx, i.get<JSScript>());
203         if (script->compartment() != cx->compartment())
204             continue;
205 
206         if (script->hasScriptCounts()) {
207             Sprinter sprinter(cx);
208             if (!sprinter.init())
209                 return;
210 
211             fprintf(stdout, "--- SCRIPT %s:%" PRIuSIZE " ---\n", script->filename(), script->lineno());
212             DumpPCCounts(cx, script, &sprinter);
213             fputs(sprinter.string(), stdout);
214             fprintf(stdout, "--- END SCRIPT %s:%" PRIuSIZE " ---\n", script->filename(), script->lineno());
215         }
216     }
217 }
218 
219 /////////////////////////////////////////////////////////////////////
220 // Bytecode Parser
221 /////////////////////////////////////////////////////////////////////
222 
223 namespace {
224 
225 class BytecodeParser
226 {
227     class Bytecode
228     {
229       public:
Bytecode()230         Bytecode() { mozilla::PodZero(this); }
231 
232         // Whether this instruction has been analyzed to get its output defines
233         // and stack.
234         bool parsed : 1;
235 
236         // Stack depth before this opcode.
237         uint32_t stackDepth;
238 
239         // Pointer to array of |stackDepth| offsets.  An element at position N
240         // in the array is the offset of the opcode that defined the
241         // corresponding stack slot.  The top of the stack is at position
242         // |stackDepth - 1|.
243         uint32_t* offsetStack;
244 
captureOffsetStack(LifoAlloc & alloc,const uint32_t * stack,uint32_t depth)245         bool captureOffsetStack(LifoAlloc& alloc, const uint32_t* stack, uint32_t depth) {
246             stackDepth = depth;
247             offsetStack = alloc.newArray<uint32_t>(stackDepth);
248             if (!offsetStack)
249                 return false;
250             if (stackDepth) {
251                 for (uint32_t n = 0; n < stackDepth; n++)
252                     offsetStack[n] = stack[n];
253             }
254             return true;
255         }
256 
257         // When control-flow merges, intersect the stacks, marking slots that
258         // are defined by different offsets with the UINT32_MAX sentinel.
259         // This is sufficient for forward control-flow.  It doesn't grok loops
260         // -- for that you would have to iterate to a fixed point -- but there
261         // shouldn't be operands on the stack at a loop back-edge anyway.
mergeOffsetStack(const uint32_t * stack,uint32_t depth)262         void mergeOffsetStack(const uint32_t* stack, uint32_t depth) {
263             MOZ_ASSERT(depth == stackDepth);
264             for (uint32_t n = 0; n < stackDepth; n++)
265                 if (offsetStack[n] != stack[n])
266                     offsetStack[n] = UINT32_MAX;
267         }
268     };
269 
270     JSContext* cx_;
271     LifoAllocScope allocScope_;
272     RootedScript script_;
273 
274     Bytecode** codeArray_;
275 
276   public:
BytecodeParser(JSContext * cx,JSScript * script)277     BytecodeParser(JSContext* cx, JSScript* script)
278       : cx_(cx),
279         allocScope_(&cx->tempLifoAlloc()),
280         script_(cx, script),
281         codeArray_(nullptr) { }
282 
283     bool parse();
284 
285 #ifdef DEBUG
isReachable(uint32_t offset)286     bool isReachable(uint32_t offset) { return maybeCode(offset); }
isReachable(const jsbytecode * pc)287     bool isReachable(const jsbytecode* pc) { return maybeCode(pc); }
288 #endif
289 
stackDepthAtPC(uint32_t offset)290     uint32_t stackDepthAtPC(uint32_t offset) {
291         // Sometimes the code generator in debug mode asks about the stack depth
292         // of unreachable code (bug 932180 comment 22).  Assume that unreachable
293         // code has no operands on the stack.
294         return getCode(offset).stackDepth;
295     }
stackDepthAtPC(const jsbytecode * pc)296     uint32_t stackDepthAtPC(const jsbytecode* pc) { return stackDepthAtPC(script_->pcToOffset(pc)); }
297 
offsetForStackOperand(uint32_t offset,int operand)298     uint32_t offsetForStackOperand(uint32_t offset, int operand) {
299         Bytecode& code = getCode(offset);
300         if (operand < 0) {
301             operand += code.stackDepth;
302             MOZ_ASSERT(operand >= 0);
303         }
304         MOZ_ASSERT(uint32_t(operand) < code.stackDepth);
305         return code.offsetStack[operand];
306     }
pcForStackOperand(jsbytecode * pc,int operand)307     jsbytecode* pcForStackOperand(jsbytecode* pc, int operand) {
308         uint32_t offset = offsetForStackOperand(script_->pcToOffset(pc), operand);
309         if (offset == UINT32_MAX)
310             return nullptr;
311         return script_->offsetToPC(offsetForStackOperand(script_->pcToOffset(pc), operand));
312     }
313 
314   private:
alloc()315     LifoAlloc& alloc() {
316         return allocScope_.alloc();
317     }
318 
reportOOM()319     void reportOOM() {
320         allocScope_.releaseEarly();
321         ReportOutOfMemory(cx_);
322     }
323 
numSlots()324     uint32_t numSlots() {
325         return 1 + script_->nfixed() +
326                (script_->functionNonDelazifying() ? script_->functionNonDelazifying()->nargs() : 0);
327     }
328 
maximumStackDepth()329     uint32_t maximumStackDepth() {
330         return script_->nslots() - script_->nfixed();
331     }
332 
getCode(uint32_t offset)333     Bytecode& getCode(uint32_t offset) {
334         MOZ_ASSERT(offset < script_->length());
335         MOZ_ASSERT(codeArray_[offset]);
336         return *codeArray_[offset];
337     }
getCode(const jsbytecode * pc)338     Bytecode& getCode(const jsbytecode* pc) { return getCode(script_->pcToOffset(pc)); }
339 
maybeCode(uint32_t offset)340     Bytecode* maybeCode(uint32_t offset) {
341         MOZ_ASSERT(offset < script_->length());
342         return codeArray_[offset];
343     }
maybeCode(const jsbytecode * pc)344     Bytecode* maybeCode(const jsbytecode* pc) { return maybeCode(script_->pcToOffset(pc)); }
345 
346     uint32_t simulateOp(JSOp op, uint32_t offset, uint32_t* offsetStack, uint32_t stackDepth);
347 
348     inline bool addJump(uint32_t offset, uint32_t* currentOffset,
349                         uint32_t stackDepth, const uint32_t* offsetStack);
350 };
351 
352 }  // anonymous namespace
353 
354 uint32_t
simulateOp(JSOp op,uint32_t offset,uint32_t * offsetStack,uint32_t stackDepth)355 BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t* offsetStack, uint32_t stackDepth)
356 {
357     uint32_t nuses = GetUseCount(script_, offset);
358     uint32_t ndefs = GetDefCount(script_, offset);
359 
360     MOZ_ASSERT(stackDepth >= nuses);
361     stackDepth -= nuses;
362     MOZ_ASSERT(stackDepth + ndefs <= maximumStackDepth());
363 
364     // Mark the current offset as defining its values on the offset stack,
365     // unless it just reshuffles the stack.  In that case we want to preserve
366     // the opcode that generated the original value.
367     switch (op) {
368       default:
369         for (uint32_t n = 0; n != ndefs; ++n)
370             offsetStack[stackDepth + n] = offset;
371         break;
372 
373       case JSOP_CASE:
374         /* Keep the switch value. */
375         MOZ_ASSERT(ndefs == 1);
376         break;
377 
378       case JSOP_DUP:
379         MOZ_ASSERT(ndefs == 2);
380         if (offsetStack)
381             offsetStack[stackDepth + 1] = offsetStack[stackDepth];
382         break;
383 
384       case JSOP_DUP2:
385         MOZ_ASSERT(ndefs == 4);
386         if (offsetStack) {
387             offsetStack[stackDepth + 2] = offsetStack[stackDepth];
388             offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1];
389         }
390         break;
391 
392       case JSOP_DUPAT: {
393         MOZ_ASSERT(ndefs == 1);
394         jsbytecode* pc = script_->offsetToPC(offset);
395         unsigned n = GET_UINT24(pc);
396         MOZ_ASSERT(n < stackDepth);
397         if (offsetStack)
398             offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n];
399         break;
400       }
401 
402       case JSOP_SWAP:
403         MOZ_ASSERT(ndefs == 2);
404         if (offsetStack) {
405             uint32_t tmp = offsetStack[stackDepth + 1];
406             offsetStack[stackDepth + 1] = offsetStack[stackDepth];
407             offsetStack[stackDepth] = tmp;
408         }
409         break;
410     }
411     stackDepth += ndefs;
412     return stackDepth;
413 }
414 
415 bool
addJump(uint32_t offset,uint32_t * currentOffset,uint32_t stackDepth,const uint32_t * offsetStack)416 BytecodeParser::addJump(uint32_t offset, uint32_t* currentOffset,
417                         uint32_t stackDepth, const uint32_t* offsetStack)
418 {
419     MOZ_ASSERT(offset < script_->length());
420 
421     Bytecode*& code = codeArray_[offset];
422     if (!code) {
423         code = alloc().new_<Bytecode>();
424         if (!code ||
425             !code->captureOffsetStack(alloc(), offsetStack, stackDepth))
426         {
427             reportOOM();
428             return false;
429         }
430     } else {
431         code->mergeOffsetStack(offsetStack, stackDepth);
432     }
433 
434     if (offset < *currentOffset && !code->parsed) {
435         // Backedge in a while/for loop, whose body has not been parsed due
436         // to a lack of fallthrough at the loop head. Roll back the offset
437         // to analyze the body.
438         *currentOffset = offset;
439     }
440 
441     return true;
442 }
443 
444 bool
parse()445 BytecodeParser::parse()
446 {
447     MOZ_ASSERT(!codeArray_);
448 
449     uint32_t length = script_->length();
450     codeArray_ = alloc().newArray<Bytecode*>(length);
451 
452     if (!codeArray_) {
453         reportOOM();
454         return false;
455     }
456 
457     mozilla::PodZero(codeArray_, length);
458 
459     // Fill in stack depth and definitions at initial bytecode.
460     Bytecode* startcode = alloc().new_<Bytecode>();
461     if (!startcode) {
462         reportOOM();
463         return false;
464     }
465 
466     // Fill in stack depth and definitions at initial bytecode.
467     uint32_t* offsetStack = alloc().newArray<uint32_t>(maximumStackDepth());
468     if (maximumStackDepth() && !offsetStack) {
469         reportOOM();
470         return false;
471     }
472 
473     startcode->stackDepth = 0;
474     codeArray_[0] = startcode;
475 
476     uint32_t offset, nextOffset = 0;
477     while (nextOffset < length) {
478         offset = nextOffset;
479 
480         Bytecode* code = maybeCode(offset);
481         jsbytecode* pc = script_->offsetToPC(offset);
482 
483         JSOp op = (JSOp)*pc;
484         MOZ_ASSERT(op < JSOP_LIMIT);
485 
486         // Immediate successor of this bytecode.
487         uint32_t successorOffset = offset + GetBytecodeLength(pc);
488 
489         // Next bytecode to analyze.  This is either the successor, or is an
490         // earlier bytecode if this bytecode has a loop backedge.
491         nextOffset = successorOffset;
492 
493         if (!code) {
494             // Haven't found a path by which this bytecode is reachable.
495             continue;
496         }
497 
498         if (code->parsed) {
499             // No need to reparse.
500             continue;
501         }
502 
503         code->parsed = true;
504 
505         uint32_t stackDepth = simulateOp(op, offset, offsetStack, code->stackDepth);
506 
507         switch (op) {
508           case JSOP_TABLESWITCH: {
509             uint32_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
510             jsbytecode* pc2 = pc + JUMP_OFFSET_LEN;
511             int32_t low = GET_JUMP_OFFSET(pc2);
512             pc2 += JUMP_OFFSET_LEN;
513             int32_t high = GET_JUMP_OFFSET(pc2);
514             pc2 += JUMP_OFFSET_LEN;
515 
516             if (!addJump(defaultOffset, &nextOffset, stackDepth, offsetStack))
517                 return false;
518 
519             for (int32_t i = low; i <= high; i++) {
520                 uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc2);
521                 if (targetOffset != offset) {
522                     if (!addJump(targetOffset, &nextOffset, stackDepth, offsetStack))
523                         return false;
524                 }
525                 pc2 += JUMP_OFFSET_LEN;
526             }
527             break;
528           }
529 
530           case JSOP_TRY: {
531             // Everything between a try and corresponding catch or finally is conditional.
532             // Note that there is no problem with code which is skipped by a thrown
533             // exception but is not caught by a later handler in the same function:
534             // no more code will execute, and it does not matter what is defined.
535             JSTryNote* tn = script_->trynotes()->vector;
536             JSTryNote* tnlimit = tn + script_->trynotes()->length;
537             for (; tn < tnlimit; tn++) {
538                 uint32_t startOffset = script_->mainOffset() + tn->start;
539                 if (startOffset == offset + 1) {
540                     uint32_t catchOffset = startOffset + tn->length;
541                     if (tn->kind == JSTRY_CATCH || tn->kind == JSTRY_FINALLY) {
542                         if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack))
543                             return false;
544                     }
545                 }
546             }
547             break;
548           }
549 
550           default:
551             break;
552         }
553 
554         // Check basic jump opcodes, which may or may not have a fallthrough.
555         if (IsJumpOpcode(op)) {
556             // Case instructions do not push the lvalue back when branching.
557             uint32_t newStackDepth = stackDepth;
558             if (op == JSOP_CASE)
559                 newStackDepth--;
560 
561             uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc);
562             if (!addJump(targetOffset, &nextOffset, newStackDepth, offsetStack))
563                 return false;
564         }
565 
566         // Handle any fallthrough from this opcode.
567         if (BytecodeFallsThrough(op)) {
568             MOZ_ASSERT(successorOffset < script_->length());
569 
570             Bytecode*& nextcode = codeArray_[successorOffset];
571 
572             if (!nextcode) {
573                 nextcode = alloc().new_<Bytecode>();
574                 if (!nextcode) {
575                     reportOOM();
576                     return false;
577                 }
578                 if (!nextcode->captureOffsetStack(alloc(), offsetStack, stackDepth)) {
579                     reportOOM();
580                     return false;
581                 }
582             } else {
583                 nextcode->mergeOffsetStack(offsetStack, stackDepth);
584             }
585         }
586     }
587 
588     return true;
589 }
590 
591 #ifdef DEBUG
592 
593 bool
ReconstructStackDepth(JSContext * cx,JSScript * script,jsbytecode * pc,uint32_t * depth,bool * reachablePC)594 js::ReconstructStackDepth(JSContext* cx, JSScript* script, jsbytecode* pc, uint32_t* depth, bool* reachablePC)
595 {
596     BytecodeParser parser(cx, script);
597     if (!parser.parse())
598         return false;
599 
600     *reachablePC = parser.isReachable(pc);
601 
602     if (*reachablePC)
603         *depth = parser.stackDepthAtPC(pc);
604 
605     return true;
606 }
607 
608 /*
609  * If pc != nullptr, include a prefix indicating whether the PC is at the
610  * current line. If showAll is true, include the source note type and the
611  * entry stack depth.
612  */
613 static bool
DisassembleAtPC(JSContext * cx,JSScript * scriptArg,bool lines,jsbytecode * pc,bool showAll,Sprinter * sp)614 DisassembleAtPC(JSContext* cx, JSScript* scriptArg, bool lines,
615                 jsbytecode* pc, bool showAll, Sprinter* sp)
616 {
617     RootedScript script(cx, scriptArg);
618     BytecodeParser parser(cx, script);
619 
620     if (showAll && !parser.parse())
621         return false;
622 
623     if (showAll)
624         Sprint(sp, "%s:%" PRIuSIZE "\n", script->filename(), script->lineno());
625 
626     if (pc != nullptr)
627         sp->put("    ");
628     if (showAll)
629         sp->put("sn stack ");
630     sp->put("loc   ");
631     if (lines)
632         sp->put("line");
633     sp->put("  op\n");
634 
635     if (pc != nullptr)
636         sp->put("    ");
637     if (showAll)
638         sp->put("-- ----- ");
639     sp->put("----- ");
640     if (lines)
641         sp->put("----");
642     sp->put("  --\n");
643 
644     jsbytecode* next = script->code();
645     jsbytecode* end = script->codeEnd();
646     while (next < end) {
647         if (next == script->main())
648             sp->put("main:\n");
649         if (pc != nullptr) {
650             if (pc == next)
651                 sp->put("--> ");
652             else
653                 sp->put("    ");
654         }
655         if (showAll) {
656             jssrcnote* sn = GetSrcNote(cx, script, next);
657             if (sn) {
658                 MOZ_ASSERT(!SN_IS_TERMINATOR(sn));
659                 jssrcnote* next = SN_NEXT(sn);
660                 while (!SN_IS_TERMINATOR(next) && SN_DELTA(next) == 0) {
661                     Sprint(sp, "%02u\n    ", SN_TYPE(sn));
662                     sn = next;
663                     next = SN_NEXT(sn);
664                 }
665                 Sprint(sp, "%02u ", SN_TYPE(sn));
666             }
667             else
668                 sp->put("   ");
669             if (parser.isReachable(next))
670                 Sprint(sp, "%05u ", parser.stackDepthAtPC(next));
671             else
672                 Sprint(sp, "      ");
673         }
674         unsigned len = Disassemble1(cx, script, next, script->pcToOffset(next), lines, sp);
675         if (!len)
676             return false;
677         next += len;
678     }
679     return true;
680 }
681 
682 bool
Disassemble(JSContext * cx,HandleScript script,bool lines,Sprinter * sp)683 js::Disassemble(JSContext* cx, HandleScript script, bool lines, Sprinter* sp)
684 {
685     return DisassembleAtPC(cx, script, lines, nullptr, false, sp);
686 }
687 
JS_FRIEND_API(bool)688 JS_FRIEND_API(bool)
689 js::DumpPC(JSContext* cx)
690 {
691     gc::AutoSuppressGC suppressGC(cx);
692     Sprinter sprinter(cx);
693     if (!sprinter.init())
694         return false;
695     ScriptFrameIter iter(cx);
696     if (iter.done()) {
697         fprintf(stdout, "Empty stack.\n");
698         return true;
699     }
700     RootedScript script(cx, iter.script());
701     bool ok = DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter);
702     fprintf(stdout, "%s", sprinter.string());
703     return ok;
704 }
705 
JS_FRIEND_API(bool)706 JS_FRIEND_API(bool)
707 js::DumpScript(JSContext* cx, JSScript* scriptArg)
708 {
709     gc::AutoSuppressGC suppressGC(cx);
710     Sprinter sprinter(cx);
711     if (!sprinter.init())
712         return false;
713     RootedScript script(cx, scriptArg);
714     bool ok = Disassemble(cx, script, true, &sprinter);
715     fprintf(stdout, "%s", sprinter.string());
716     return ok;
717 }
718 
719 static bool
ToDisassemblySource(JSContext * cx,HandleValue v,JSAutoByteString * bytes)720 ToDisassemblySource(JSContext* cx, HandleValue v, JSAutoByteString* bytes)
721 {
722     if (v.isString()) {
723         Sprinter sprinter(cx);
724         if (!sprinter.init())
725             return false;
726         char* nbytes = QuoteString(&sprinter, v.toString(), '"');
727         if (!nbytes)
728             return false;
729         nbytes = JS_sprintf_append(nullptr, "%s", nbytes);
730         if (!nbytes) {
731             ReportOutOfMemory(cx);
732             return false;
733         }
734         bytes->initBytes(nbytes);
735         return true;
736     }
737 
738     JSRuntime* rt = cx->runtime();
739     if (rt->isHeapBusy() || !rt->gc.isAllocAllowed()) {
740         char* source = JS_sprintf_append(nullptr, "<value>");
741         if (!source) {
742             ReportOutOfMemory(cx);
743             return false;
744         }
745         bytes->initBytes(source);
746         return true;
747     }
748 
749     if (v.isObject()) {
750         JSObject& obj = v.toObject();
751         if (obj.is<StaticBlockObject>()) {
752             Rooted<StaticBlockObject*> block(cx, &obj.as<StaticBlockObject>());
753             char* source = JS_sprintf_append(nullptr, "depth %d {", block->localOffset());
754             if (!source) {
755                 ReportOutOfMemory(cx);
756                 return false;
757             }
758 
759             Shape::Range<CanGC> r(cx, block->lastProperty());
760 
761             while (!r.empty()) {
762                 Rooted<Shape*> shape(cx, &r.front());
763                 JSAtom* atom = JSID_IS_INT(shape->propid())
764                                ? cx->names().empty
765                                : JSID_TO_ATOM(shape->propid());
766 
767                 JSAutoByteString bytes;
768                 if (!AtomToPrintableString(cx, atom, &bytes))
769                     return false;
770 
771                 r.popFront();
772                 source = JS_sprintf_append(source, "%s: %d%s",
773                                            bytes.ptr(),
774                                            block->shapeToIndex(*shape),
775                                            !r.empty() ? ", " : "");
776                 if (!source) {
777                     ReportOutOfMemory(cx);
778                     return false;
779                 }
780             }
781 
782             source = JS_sprintf_append(source, "}");
783             if (!source) {
784                 ReportOutOfMemory(cx);
785                 return false;
786             }
787             bytes->initBytes(source);
788             return true;
789         }
790 
791         if (obj.is<JSFunction>()) {
792             RootedFunction fun(cx, &obj.as<JSFunction>());
793             JSString* str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT);
794             if (!str)
795                 return false;
796             return bytes->encodeLatin1(cx, str);
797         }
798 
799         if (obj.is<RegExpObject>()) {
800             JSString* source = obj.as<RegExpObject>().toString(cx);
801             if (!source)
802                 return false;
803             return bytes->encodeLatin1(cx, source);
804         }
805     }
806 
807     return !!ValueToPrintable(cx, v, bytes, true);
808 }
809 
810 unsigned
Disassemble1(JSContext * cx,HandleScript script,jsbytecode * pc,unsigned loc,bool lines,Sprinter * sp)811 js::Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
812                  unsigned loc, bool lines, Sprinter* sp)
813 {
814     JSOp op = (JSOp)*pc;
815     if (op >= JSOP_LIMIT) {
816         char numBuf1[12], numBuf2[12];
817         JS_snprintf(numBuf1, sizeof numBuf1, "%d", op);
818         JS_snprintf(numBuf2, sizeof numBuf2, "%d", JSOP_LIMIT);
819         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
820                              JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2);
821         return 0;
822     }
823     const JSCodeSpec* cs = &CodeSpec[op];
824     ptrdiff_t len = (ptrdiff_t) cs->length;
825     Sprint(sp, "%05u:", loc);
826     if (lines)
827         Sprint(sp, "%4u", PCToLineNumber(script, pc));
828     Sprint(sp, "  %s", CodeName[op]);
829 
830     switch (JOF_TYPE(cs->format)) {
831       case JOF_BYTE:
832           // Scan the trynotes to find the associated catch block
833           // and make the try opcode look like a jump instruction
834           // with an offset. This simplifies code coverage analysis
835           // based on this disassembled output.
836           if (op == JSOP_TRY) {
837               TryNoteArray* trynotes = script->trynotes();
838               uint32_t i;
839               for(i = 0; i < trynotes->length; i++) {
840                   JSTryNote note = trynotes->vector[i];
841                   if (note.kind == JSTRY_CATCH && note.start == loc + 1) {
842                       Sprint(sp, " %u (%+d)",
843                              (unsigned int) (loc+note.length+1),
844                              (int) (note.length+1));
845                       break;
846                   }
847               }
848           }
849         break;
850 
851       case JOF_JUMP: {
852         ptrdiff_t off = GET_JUMP_OFFSET(pc);
853         Sprint(sp, " %u (%+d)", loc + (int) off, (int) off);
854         break;
855       }
856 
857       case JOF_SCOPECOORD: {
858         RootedValue v(cx,
859             StringValue(ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc)));
860         JSAutoByteString bytes;
861         if (!ToDisassemblySource(cx, v, &bytes))
862             return 0;
863         ScopeCoordinate sc(pc);
864         Sprint(sp, " %s (hops = %u, slot = %u)", bytes.ptr(), sc.hops(), sc.slot());
865         break;
866       }
867 
868       case JOF_ATOM: {
869         RootedValue v(cx, StringValue(script->getAtom(GET_UINT32_INDEX(pc))));
870         JSAutoByteString bytes;
871         if (!ToDisassemblySource(cx, v, &bytes))
872             return 0;
873         Sprint(sp, " %s", bytes.ptr());
874         break;
875       }
876 
877       case JOF_DOUBLE: {
878         RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc)));
879         JSAutoByteString bytes;
880         if (!ToDisassemblySource(cx, v, &bytes))
881             return 0;
882         Sprint(sp, " %s", bytes.ptr());
883         break;
884       }
885 
886       case JOF_OBJECT: {
887         /* Don't call obj.toSource if analysis/inference is active. */
888         if (script->zone()->types.activeAnalysis) {
889             Sprint(sp, " object");
890             break;
891         }
892 
893         JSObject* obj = script->getObject(GET_UINT32_INDEX(pc));
894         {
895             JSAutoByteString bytes;
896             RootedValue v(cx, ObjectValue(*obj));
897             if (!ToDisassemblySource(cx, v, &bytes))
898                 return 0;
899             Sprint(sp, " %s", bytes.ptr());
900         }
901         break;
902       }
903 
904       case JOF_REGEXP: {
905         JSObject* obj = script->getRegExp(GET_UINT32_INDEX(pc));
906         JSAutoByteString bytes;
907         RootedValue v(cx, ObjectValue(*obj));
908         if (!ToDisassemblySource(cx, v, &bytes))
909             return 0;
910         Sprint(sp, " %s", bytes.ptr());
911         break;
912       }
913 
914       case JOF_TABLESWITCH:
915       {
916         int32_t i, low, high;
917 
918         ptrdiff_t off = GET_JUMP_OFFSET(pc);
919         jsbytecode* pc2 = pc + JUMP_OFFSET_LEN;
920         low = GET_JUMP_OFFSET(pc2);
921         pc2 += JUMP_OFFSET_LEN;
922         high = GET_JUMP_OFFSET(pc2);
923         pc2 += JUMP_OFFSET_LEN;
924         Sprint(sp, " defaultOffset %d low %d high %d", int(off), low, high);
925         for (i = low; i <= high; i++) {
926             off = GET_JUMP_OFFSET(pc2);
927             Sprint(sp, "\n\t%d: %d", i, int(off));
928             pc2 += JUMP_OFFSET_LEN;
929         }
930         len = 1 + pc2 - pc;
931         break;
932       }
933 
934       case JOF_QARG:
935         Sprint(sp, " %u", GET_ARGNO(pc));
936         break;
937 
938       case JOF_LOCAL:
939         Sprint(sp, " %u", GET_LOCALNO(pc));
940         break;
941 
942       case JOF_UINT32:
943         Sprint(sp, " %u", GET_UINT32(pc));
944         break;
945 
946       {
947         int i;
948 
949       case JOF_UINT16:
950         i = (int)GET_UINT16(pc);
951         goto print_int;
952 
953       case JOF_UINT24:
954         MOZ_ASSERT(len == 4);
955         i = (int)GET_UINT24(pc);
956         goto print_int;
957 
958       case JOF_UINT8:
959         i = GET_UINT8(pc);
960         goto print_int;
961 
962       case JOF_INT8:
963         i = GET_INT8(pc);
964         goto print_int;
965 
966       case JOF_INT32:
967         MOZ_ASSERT(op == JSOP_INT32);
968         i = GET_INT32(pc);
969       print_int:
970         Sprint(sp, " %d", i);
971         break;
972       }
973 
974       default: {
975         char numBuf[12];
976         JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) cs->format);
977         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
978                              JSMSG_UNKNOWN_FORMAT, numBuf);
979         return 0;
980       }
981     }
982     sp->put("\n");
983     return len;
984 }
985 
986 #endif /* DEBUG */
987 
988 namespace {
989 /*
990  * The expression decompiler is invoked by error handling code to produce a
991  * string representation of the erroring expression. As it's only a debugging
992  * tool, it only supports basic expressions. For anything complicated, it simply
993  * puts "(intermediate value)" into the error result.
994  *
995  * Here's the basic algorithm:
996  *
997  * 1. Find the stack location of the value whose expression we wish to
998  * decompile. The error handler can explicitly pass this as an
999  * argument. Otherwise, we search backwards down the stack for the offending
1000  * value.
1001  *
1002  * 2. Instantiate and run a BytecodeParser for the current frame. This creates a
1003  * stack of pcs parallel to the interpreter stack; given an interpreter stack
1004  * location, the corresponding pc stack location contains the opcode that pushed
1005  * the value in the interpreter. Now, with the result of step 1, we have the
1006  * opcode responsible for pushing the value we want to decompile.
1007  *
1008  * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler
1009  * routine, responsible for a string representation of the expression that
1010  * generated a certain stack location. decompilePC looks at one opcode and
1011  * returns the JS source equivalent of that opcode.
1012  *
1013  * 4. Expressions can, of course, contain subexpressions. For example, the
1014  * literals "4" and "5" are subexpressions of the addition operator in "4 +
1015  * 5". If we need to decompile a subexpression, we call decompilePC (step 2)
1016  * recursively on the operands' pcs. The result is a depth-first traversal of
1017  * the expression tree.
1018  *
1019  */
1020 struct ExpressionDecompiler
1021 {
1022     JSContext* cx;
1023     RootedScript script;
1024     RootedFunction fun;
1025     BytecodeParser parser;
1026     Sprinter sprinter;
1027 
ExpressionDecompiler__anonb587c78f0211::ExpressionDecompiler1028     ExpressionDecompiler(JSContext* cx, JSScript* script, JSFunction* fun)
1029         : cx(cx),
1030           script(cx, script),
1031           fun(cx, fun),
1032           parser(cx, script),
1033           sprinter(cx)
1034     {}
1035     bool init();
1036     bool decompilePCForStackOperand(jsbytecode* pc, int i);
1037     bool decompilePC(jsbytecode* pc);
1038     JSAtom* getLocal(uint32_t local, jsbytecode* pc);
1039     JSAtom* getArg(unsigned slot);
1040     JSAtom* loadAtom(jsbytecode* pc);
1041     bool quote(JSString* s, uint32_t quote);
1042     bool write(const char* s);
1043     bool write(JSString* str);
1044     bool getOutput(char** out);
1045 };
1046 
1047 bool
decompilePCForStackOperand(jsbytecode * pc,int i)1048 ExpressionDecompiler::decompilePCForStackOperand(jsbytecode* pc, int i)
1049 {
1050     pc = parser.pcForStackOperand(pc, i);
1051     if (!pc)
1052         return write("(intermediate value)");
1053     return decompilePC(pc);
1054 }
1055 
1056 bool
decompilePC(jsbytecode * pc)1057 ExpressionDecompiler::decompilePC(jsbytecode* pc)
1058 {
1059     MOZ_ASSERT(script->containsPC(pc));
1060 
1061     JSOp op = (JSOp)*pc;
1062 
1063     if (const char* token = CodeToken[op]) {
1064         // Handle simple cases of binary and unary operators.
1065         switch (CodeSpec[op].nuses) {
1066           case 2: {
1067             jssrcnote* sn = GetSrcNote(cx, script, pc);
1068             if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP)
1069                 return write("(") &&
1070                        decompilePCForStackOperand(pc, -2) &&
1071                        write(" ") &&
1072                        write(token) &&
1073                        write(" ") &&
1074                        decompilePCForStackOperand(pc, -1) &&
1075                        write(")");
1076             break;
1077           }
1078           case 1:
1079             return write(token) &&
1080                    write("(") &&
1081                    decompilePCForStackOperand(pc, -1) &&
1082                    write(")");
1083           default:
1084             break;
1085         }
1086     }
1087 
1088     switch (op) {
1089       case JSOP_GETGNAME:
1090       case JSOP_GETNAME:
1091       case JSOP_GETINTRINSIC:
1092         return write(loadAtom(pc));
1093       case JSOP_GETARG: {
1094         unsigned slot = GET_ARGNO(pc);
1095         JSAtom* atom = getArg(slot);
1096         return write(atom);
1097       }
1098       case JSOP_GETLOCAL: {
1099         uint32_t i = GET_LOCALNO(pc);
1100         if (JSAtom* atom = getLocal(i, pc))
1101             return write(atom);
1102         return write("(intermediate value)");
1103       }
1104       case JSOP_GETALIASEDVAR: {
1105         JSAtom* atom = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc);
1106         MOZ_ASSERT(atom);
1107         return write(atom);
1108       }
1109       case JSOP_LENGTH:
1110       case JSOP_GETPROP:
1111       case JSOP_CALLPROP: {
1112         RootedAtom prop(cx, (op == JSOP_LENGTH) ? cx->names().length : loadAtom(pc));
1113         if (!decompilePCForStackOperand(pc, -1))
1114             return false;
1115         if (IsIdentifier(prop)) {
1116             return write(".") &&
1117                    quote(prop, '\0');
1118         }
1119         return write("[") &&
1120                quote(prop, '\'') &&
1121                write("]");
1122       }
1123       case JSOP_GETPROP_SUPER:
1124       {
1125         RootedAtom prop(cx, loadAtom(pc));
1126         return write("super.") &&
1127                quote(prop, '\0');
1128       }
1129       case JSOP_GETELEM:
1130       case JSOP_CALLELEM:
1131         return decompilePCForStackOperand(pc, -2) &&
1132                write("[") &&
1133                decompilePCForStackOperand(pc, -1) &&
1134                write("]");
1135       case JSOP_GETELEM_SUPER:
1136         return write("super[") &&
1137                decompilePCForStackOperand(pc, -3) &&
1138                write("]");
1139       case JSOP_NULL:
1140         return write(js_null_str);
1141       case JSOP_TRUE:
1142         return write(js_true_str);
1143       case JSOP_FALSE:
1144         return write(js_false_str);
1145       case JSOP_ZERO:
1146       case JSOP_ONE:
1147       case JSOP_INT8:
1148       case JSOP_UINT16:
1149       case JSOP_UINT24:
1150       case JSOP_INT32:
1151         return sprinter.printf("%d", GetBytecodeInteger(pc)) >= 0;
1152       case JSOP_STRING:
1153         return quote(loadAtom(pc), '"');
1154       case JSOP_SYMBOL: {
1155         unsigned i = uint8_t(pc[1]);
1156         MOZ_ASSERT(i < JS::WellKnownSymbolLimit);
1157         if (i < JS::WellKnownSymbolLimit)
1158             return write(cx->names().wellKnownSymbolDescriptions()[i]);
1159         break;
1160       }
1161       case JSOP_UNDEFINED:
1162         return write(js_undefined_str);
1163       case JSOP_GLOBALTHIS:
1164         // |this| could convert to a very long object initialiser, so cite it by
1165         // its keyword name.
1166         return write(js_this_str);
1167       case JSOP_NEWTARGET:
1168         return write("new.target");
1169       case JSOP_CALL:
1170       case JSOP_CALLITER:
1171       case JSOP_FUNCALL:
1172         return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) &&
1173                write("(...)");
1174       case JSOP_SPREADCALL:
1175         return decompilePCForStackOperand(pc, -int32_t(3)) &&
1176                write("(...)");
1177       case JSOP_NEWARRAY:
1178         return write("[]");
1179       case JSOP_REGEXP:
1180       case JSOP_OBJECT:
1181       case JSOP_NEWARRAY_COPYONWRITE: {
1182         JSObject* obj = (op == JSOP_REGEXP)
1183                         ? script->getRegExp(GET_UINT32_INDEX(pc))
1184                         : script->getObject(GET_UINT32_INDEX(pc));
1185         RootedValue objv(cx, ObjectValue(*obj));
1186         JSString* str = ValueToSource(cx, objv);
1187         if (!str)
1188             return false;
1189         return write(str);
1190       }
1191       case JSOP_VOID:
1192         return write("void ") && decompilePCForStackOperand(pc, -1);
1193       default:
1194         break;
1195     }
1196     return write("(intermediate value)");
1197 }
1198 
1199 bool
init()1200 ExpressionDecompiler::init()
1201 {
1202     assertSameCompartment(cx, script);
1203 
1204     if (!sprinter.init())
1205         return false;
1206 
1207     if (!parser.parse())
1208         return false;
1209 
1210     return true;
1211 }
1212 
1213 bool
write(const char * s)1214 ExpressionDecompiler::write(const char* s)
1215 {
1216     return sprinter.put(s) >= 0;
1217 }
1218 
1219 bool
write(JSString * str)1220 ExpressionDecompiler::write(JSString* str)
1221 {
1222     if (str == cx->names().dotThis)
1223         return write("this");
1224     return sprinter.putString(str) >= 0;
1225 }
1226 
1227 bool
quote(JSString * s,uint32_t quote)1228 ExpressionDecompiler::quote(JSString* s, uint32_t quote)
1229 {
1230     return QuoteString(&sprinter, s, quote) != nullptr;
1231 }
1232 
1233 JSAtom*
loadAtom(jsbytecode * pc)1234 ExpressionDecompiler::loadAtom(jsbytecode* pc)
1235 {
1236     return script->getAtom(GET_UINT32_INDEX(pc));
1237 }
1238 
1239 JSAtom*
getArg(unsigned slot)1240 ExpressionDecompiler::getArg(unsigned slot)
1241 {
1242     MOZ_ASSERT(fun);
1243     MOZ_ASSERT(slot < script->bindings.numArgs());
1244 
1245     for (BindingIter bi(script); bi; bi++) {
1246         MOZ_ASSERT(bi->kind() == Binding::ARGUMENT);
1247         if (bi.argIndex() == slot)
1248             return bi->name();
1249     }
1250 
1251     MOZ_CRASH("No binding");
1252 }
1253 
1254 JSAtom*
getLocal(uint32_t local,jsbytecode * pc)1255 ExpressionDecompiler::getLocal(uint32_t local, jsbytecode* pc)
1256 {
1257     MOZ_ASSERT(local < script->nfixed());
1258     if (local < script->nbodyfixed()) {
1259         for (BindingIter bi(script); bi; bi++) {
1260             if (bi->kind() != Binding::ARGUMENT && !bi->aliased() && bi.frameIndex() == local)
1261                 return bi->name();
1262         }
1263 
1264         MOZ_CRASH("No binding");
1265     }
1266     for (NestedScopeObject* chain = script->getStaticBlockScope(pc);
1267          chain;
1268          chain = chain->enclosingNestedScope())
1269     {
1270         if (!chain->is<StaticBlockObject>())
1271             continue;
1272         StaticBlockObject& block = chain->as<StaticBlockObject>();
1273         if (local < block.localOffset())
1274             continue;
1275         local -= block.localOffset();
1276         if (local >= block.numVariables())
1277             return nullptr;
1278         for (Shape::Range<NoGC> r(block.lastProperty()); !r.empty(); r.popFront()) {
1279             const Shape& shape = r.front();
1280             if (block.shapeToIndex(shape) == local)
1281                 return JSID_TO_ATOM(shape.propid());
1282         }
1283         break;
1284     }
1285     return nullptr;
1286 }
1287 
1288 bool
getOutput(char ** res)1289 ExpressionDecompiler::getOutput(char** res)
1290 {
1291     ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0);
1292     *res = cx->pod_malloc<char>(len + 1);
1293     if (!*res)
1294         return false;
1295     js_memcpy(*res, sprinter.stringAt(0), len);
1296     (*res)[len] = 0;
1297     return true;
1298 }
1299 
1300 }  // anonymous namespace
1301 
1302 static bool
FindStartPC(JSContext * cx,const FrameIter & iter,int spindex,int skipStackHits,Value v,jsbytecode ** valuepc)1303 FindStartPC(JSContext* cx, const FrameIter& iter, int spindex, int skipStackHits, Value v,
1304             jsbytecode** valuepc)
1305 {
1306     jsbytecode* current = *valuepc;
1307 
1308     if (spindex == JSDVG_IGNORE_STACK)
1309         return true;
1310 
1311     /*
1312      * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the
1313      * previous pc (see bug 831120).
1314      */
1315     if (iter.isIon())
1316         return true;
1317 
1318     *valuepc = nullptr;
1319 
1320     BytecodeParser parser(cx, iter.script());
1321     if (!parser.parse())
1322         return false;
1323 
1324     if (spindex < 0 && spindex + int(parser.stackDepthAtPC(current)) < 0)
1325         spindex = JSDVG_SEARCH_STACK;
1326 
1327     if (spindex == JSDVG_SEARCH_STACK) {
1328         size_t index = iter.numFrameSlots();
1329 
1330         // The decompiler may be called from inside functions that are not
1331         // called from script, but via the C++ API directly, such as
1332         // Invoke. In that case, the youngest script frame may have a
1333         // completely unrelated pc and stack depth, so we give up.
1334         if (index < size_t(parser.stackDepthAtPC(current)))
1335             return true;
1336 
1337         // We search from fp->sp to base to find the most recently calculated
1338         // value matching v under assumption that it is the value that caused
1339         // the exception.
1340         int stackHits = 0;
1341         Value s;
1342         do {
1343             if (!index)
1344                 return true;
1345             s = iter.frameSlotValue(--index);
1346         } while (s != v || stackHits++ != skipStackHits);
1347 
1348         // If the current PC has fewer values on the stack than the index we are
1349         // looking for, the blamed value must be one pushed by the current
1350         // bytecode, so restore *valuepc.
1351         jsbytecode* pc = nullptr;
1352         if (index < size_t(parser.stackDepthAtPC(current)))
1353             pc = parser.pcForStackOperand(current, index);
1354         *valuepc = pc ? pc : current;
1355     } else {
1356         jsbytecode* pc = parser.pcForStackOperand(current, spindex);
1357         *valuepc = pc ? pc : current;
1358     }
1359     return true;
1360 }
1361 
1362 static bool
DecompileExpressionFromStack(JSContext * cx,int spindex,int skipStackHits,HandleValue v,char ** res)1363 DecompileExpressionFromStack(JSContext* cx, int spindex, int skipStackHits, HandleValue v, char** res)
1364 {
1365     MOZ_ASSERT(spindex < 0 ||
1366                spindex == JSDVG_IGNORE_STACK ||
1367                spindex == JSDVG_SEARCH_STACK);
1368 
1369     *res = nullptr;
1370 
1371 #ifdef JS_MORE_DETERMINISTIC
1372     /*
1373      * Give up if we need deterministic behavior for differential testing.
1374      * IonMonkey doesn't use InterpreterFrames and this ensures we get the same
1375      * error messages.
1376      */
1377     return true;
1378 #endif
1379 
1380     FrameIter frameIter(cx);
1381 
1382     if (frameIter.done() || !frameIter.hasScript())
1383         return true;
1384 
1385     RootedScript script(cx, frameIter.script());
1386     AutoCompartment ac(cx, &script->global());
1387     jsbytecode* valuepc = frameIter.pc();
1388     RootedFunction fun(cx, frameIter.isFunctionFrame()
1389                            ? frameIter.calleeTemplate()
1390                            : nullptr);
1391 
1392     MOZ_ASSERT(script->containsPC(valuepc));
1393 
1394     // Give up if in prologue.
1395     if (valuepc < script->main())
1396         return true;
1397 
1398     if (!FindStartPC(cx, frameIter, spindex, skipStackHits, v, &valuepc))
1399         return false;
1400     if (!valuepc)
1401         return true;
1402 
1403     ExpressionDecompiler ed(cx, script, fun);
1404     if (!ed.init())
1405         return false;
1406     if (!ed.decompilePC(valuepc))
1407         return false;
1408 
1409     return ed.getOutput(res);
1410 }
1411 
1412 typedef mozilla::UniquePtr<char[], JS::FreePolicy> UniquePtrChars;
1413 
1414 UniquePtrChars
DecompileValueGenerator(JSContext * cx,int spindex,HandleValue v,HandleString fallbackArg,int skipStackHits)1415 js::DecompileValueGenerator(JSContext* cx, int spindex, HandleValue v,
1416                             HandleString fallbackArg, int skipStackHits)
1417 {
1418     RootedString fallback(cx, fallbackArg);
1419     {
1420         char* result;
1421         if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result))
1422             return nullptr;
1423         if (result) {
1424             if (strcmp(result, "(intermediate value)"))
1425                 return UniquePtrChars(result);
1426             js_free(result);
1427         }
1428     }
1429     if (!fallback) {
1430         if (v.isUndefined())
1431             return UniquePtrChars(JS_strdup(cx, js_undefined_str)); // Prevent users from seeing "(void 0)"
1432         fallback = ValueToSource(cx, v);
1433         if (!fallback)
1434             return UniquePtrChars(nullptr);
1435     }
1436 
1437     return UniquePtrChars(JS_EncodeString(cx, fallback));
1438 }
1439 
1440 static bool
DecompileArgumentFromStack(JSContext * cx,int formalIndex,char ** res)1441 DecompileArgumentFromStack(JSContext* cx, int formalIndex, char** res)
1442 {
1443     MOZ_ASSERT(formalIndex >= 0);
1444 
1445     *res = nullptr;
1446 
1447 #ifdef JS_MORE_DETERMINISTIC
1448     /* See note in DecompileExpressionFromStack. */
1449     return true;
1450 #endif
1451 
1452     /*
1453      * Settle on the nearest script frame, which should be the builtin that
1454      * called the intrinsic.
1455      */
1456     FrameIter frameIter(cx);
1457     MOZ_ASSERT(!frameIter.done());
1458 
1459     /*
1460      * Get the second-to-top frame, the caller of the builtin that called the
1461      * intrinsic.
1462      */
1463     ++frameIter;
1464     if (frameIter.done() || !frameIter.hasScript())
1465         return true;
1466 
1467     RootedScript script(cx, frameIter.script());
1468     AutoCompartment ac(cx, &script->global());
1469     jsbytecode* current = frameIter.pc();
1470     RootedFunction fun(cx, frameIter.isFunctionFrame()
1471                            ? frameIter.calleeTemplate()
1472                            : nullptr);
1473 
1474     MOZ_ASSERT(script->containsPC(current));
1475 
1476     if (current < script->main())
1477         return true;
1478 
1479     /* Don't handle getters, setters or calls from fun.call/fun.apply. */
1480     if (JSOp(*current) != JSOP_CALL || static_cast<unsigned>(formalIndex) >= GET_ARGC(current))
1481         return true;
1482 
1483     BytecodeParser parser(cx, script);
1484     if (!parser.parse())
1485         return false;
1486 
1487     int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) + formalIndex;
1488     MOZ_ASSERT(formalStackIndex >= 0);
1489     if (uint32_t(formalStackIndex) >= parser.stackDepthAtPC(current))
1490         return true;
1491 
1492     ExpressionDecompiler ed(cx, script, fun);
1493     if (!ed.init())
1494         return false;
1495     if (!ed.decompilePCForStackOperand(current, formalStackIndex))
1496         return false;
1497 
1498     return ed.getOutput(res);
1499 }
1500 
1501 char*
DecompileArgument(JSContext * cx,int formalIndex,HandleValue v)1502 js::DecompileArgument(JSContext* cx, int formalIndex, HandleValue v)
1503 {
1504     {
1505         char* result;
1506         if (!DecompileArgumentFromStack(cx, formalIndex, &result))
1507             return nullptr;
1508         if (result) {
1509             if (strcmp(result, "(intermediate value)"))
1510                 return result;
1511             js_free(result);
1512         }
1513     }
1514     if (v.isUndefined())
1515         return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)"
1516 
1517     RootedString fallback(cx, ValueToSource(cx, v));
1518     if (!fallback)
1519         return nullptr;
1520 
1521     return JS_EncodeString(cx, fallback);
1522 }
1523 
1524 bool
CallResultEscapes(jsbytecode * pc)1525 js::CallResultEscapes(jsbytecode* pc)
1526 {
1527     /*
1528      * If we see any of these sequences, the result is unused:
1529      * - call / pop
1530      *
1531      * If we see any of these sequences, the result is only tested for nullness:
1532      * - call / ifeq
1533      * - call / not / ifeq
1534      */
1535 
1536     if (*pc == JSOP_CALL)
1537         pc += JSOP_CALL_LENGTH;
1538     else if (*pc == JSOP_SPREADCALL)
1539         pc += JSOP_SPREADCALL_LENGTH;
1540     else
1541         return true;
1542 
1543     if (*pc == JSOP_POP)
1544         return false;
1545 
1546     if (*pc == JSOP_NOT)
1547         pc += JSOP_NOT_LENGTH;
1548 
1549     return *pc != JSOP_IFEQ;
1550 }
1551 
1552 extern bool
IsValidBytecodeOffset(JSContext * cx,JSScript * script,size_t offset)1553 js::IsValidBytecodeOffset(JSContext* cx, JSScript* script, size_t offset)
1554 {
1555     // This could be faster (by following jump instructions if the target is <= offset).
1556     for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) {
1557         size_t here = r.frontOffset();
1558         if (here >= offset)
1559             return here == offset;
1560     }
1561     return false;
1562 }
1563 
1564 /*
1565  * There are three possible PCCount profiling states:
1566  *
1567  * 1. None: Neither scripts nor the runtime have count information.
1568  * 2. Profile: Active scripts have count information, the runtime does not.
1569  * 3. Query: Scripts do not have count information, the runtime does.
1570  *
1571  * When starting to profile scripts, counting begins immediately, with all JIT
1572  * code discarded and recompiled with counts as necessary. Active interpreter
1573  * frames will not begin profiling until they begin executing another script
1574  * (via a call or return).
1575  *
1576  * The below API functions manage transitions to new states, according
1577  * to the table below.
1578  *
1579  *                                  Old State
1580  *                          -------------------------
1581  * Function                 None      Profile   Query
1582  * --------
1583  * StartPCCountProfiling    Profile   Profile   Profile
1584  * StopPCCountProfiling     None      Query     Query
1585  * PurgePCCounts            None      None      None
1586  */
1587 
1588 static void
ReleaseScriptCounts(FreeOp * fop)1589 ReleaseScriptCounts(FreeOp* fop)
1590 {
1591     JSRuntime* rt = fop->runtime();
1592     MOZ_ASSERT(rt->scriptAndCountsVector);
1593 
1594     fop->delete_(rt->scriptAndCountsVector);
1595     rt->scriptAndCountsVector = nullptr;
1596 }
1597 
JS_FRIEND_API(void)1598 JS_FRIEND_API(void)
1599 js::StartPCCountProfiling(JSContext* cx)
1600 {
1601     JSRuntime* rt = cx->runtime();
1602 
1603     if (rt->profilingScripts)
1604         return;
1605 
1606     if (rt->scriptAndCountsVector)
1607         ReleaseScriptCounts(rt->defaultFreeOp());
1608 
1609     ReleaseAllJITCode(rt->defaultFreeOp());
1610 
1611     rt->profilingScripts = true;
1612 }
1613 
JS_FRIEND_API(void)1614 JS_FRIEND_API(void)
1615 js::StopPCCountProfiling(JSContext* cx)
1616 {
1617     JSRuntime* rt = cx->runtime();
1618 
1619     if (!rt->profilingScripts)
1620         return;
1621     MOZ_ASSERT(!rt->scriptAndCountsVector);
1622 
1623     ReleaseAllJITCode(rt->defaultFreeOp());
1624 
1625     auto* vec = cx->new_<PersistentRooted<ScriptAndCountsVector>>(cx,
1626         ScriptAndCountsVector(SystemAllocPolicy()));
1627     if (!vec)
1628         return;
1629 
1630     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
1631         for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
1632             JSScript* script = i.get<JSScript>();
1633             if (script->hasScriptCounts() && script->types()) {
1634                 if (!vec->append(script))
1635                     return;
1636             }
1637         }
1638     }
1639 
1640     rt->profilingScripts = false;
1641     rt->scriptAndCountsVector = vec;
1642 }
1643 
JS_FRIEND_API(void)1644 JS_FRIEND_API(void)
1645 js::PurgePCCounts(JSContext* cx)
1646 {
1647     JSRuntime* rt = cx->runtime();
1648 
1649     if (!rt->scriptAndCountsVector)
1650         return;
1651     MOZ_ASSERT(!rt->profilingScripts);
1652 
1653     ReleaseScriptCounts(rt->defaultFreeOp());
1654 }
1655 
JS_FRIEND_API(size_t)1656 JS_FRIEND_API(size_t)
1657 js::GetPCCountScriptCount(JSContext* cx)
1658 {
1659     JSRuntime* rt = cx->runtime();
1660 
1661     if (!rt->scriptAndCountsVector)
1662         return 0;
1663 
1664     return rt->scriptAndCountsVector->length();
1665 }
1666 
1667 enum MaybeComma {NO_COMMA, COMMA};
1668 
1669 static void
AppendJSONProperty(StringBuffer & buf,const char * name,MaybeComma comma=COMMA)1670 AppendJSONProperty(StringBuffer& buf, const char* name, MaybeComma comma = COMMA)
1671 {
1672     if (comma)
1673         buf.append(',');
1674 
1675     buf.append('\"');
1676     buf.append(name, strlen(name));
1677     buf.append("\":", 2);
1678 }
1679 
JS_FRIEND_API(JSString *)1680 JS_FRIEND_API(JSString*)
1681 js::GetPCCountScriptSummary(JSContext* cx, size_t index)
1682 {
1683     JSRuntime* rt = cx->runtime();
1684 
1685     if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
1686         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
1687         return nullptr;
1688     }
1689 
1690     const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index];
1691     RootedScript script(cx, sac.script);
1692 
1693     /*
1694      * OOM on buffer appends here will not be caught immediately, but since
1695      * StringBuffer uses a TempAllocPolicy will trigger an exception on the
1696      * context if they occur, which we'll catch before returning.
1697      */
1698     StringBuffer buf(cx);
1699 
1700     buf.append('{');
1701 
1702     AppendJSONProperty(buf, "file", NO_COMMA);
1703     JSString* str = JS_NewStringCopyZ(cx, script->filename());
1704     if (!str || !(str = StringToSource(cx, str)))
1705         return nullptr;
1706     buf.append(str);
1707 
1708     AppendJSONProperty(buf, "line");
1709     NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf);
1710 
1711     if (script->functionNonDelazifying()) {
1712         JSAtom* atom = script->functionNonDelazifying()->displayAtom();
1713         if (atom) {
1714             AppendJSONProperty(buf, "name");
1715             if (!(str = StringToSource(cx, atom)))
1716                 return nullptr;
1717             buf.append(str);
1718         }
1719     }
1720 
1721     uint64_t total = 0;
1722 
1723     jsbytecode* codeEnd = script->codeEnd();
1724     for (jsbytecode* pc = script->code(); pc < codeEnd; pc = GetNextPc(pc)) {
1725         const PCCounts* counts = sac.maybeGetPCCounts(pc);
1726         if (!counts)
1727             continue;
1728         total += counts->numExec();
1729     }
1730 
1731     AppendJSONProperty(buf, "totals");
1732     buf.append('{');
1733 
1734     AppendJSONProperty(buf, PCCounts::numExecName, NO_COMMA);
1735     NumberValueToStringBuffer(cx, DoubleValue(total), buf);
1736 
1737     uint64_t ionActivity = 0;
1738     jit::IonScriptCounts* ionCounts = sac.getIonCounts();
1739     while (ionCounts) {
1740         for (size_t i = 0; i < ionCounts->numBlocks(); i++)
1741             ionActivity += ionCounts->block(i).hitCount();
1742         ionCounts = ionCounts->previous();
1743     }
1744     if (ionActivity) {
1745         AppendJSONProperty(buf, "ion", COMMA);
1746         NumberValueToStringBuffer(cx, DoubleValue(ionActivity), buf);
1747     }
1748 
1749     buf.append('}');
1750     buf.append('}');
1751 
1752     if (cx->isExceptionPending())
1753         return nullptr;
1754 
1755     return buf.finishString();
1756 }
1757 
1758 static bool
GetPCCountJSON(JSContext * cx,const ScriptAndCounts & sac,StringBuffer & buf)1759 GetPCCountJSON(JSContext* cx, const ScriptAndCounts& sac, StringBuffer& buf)
1760 {
1761     RootedScript script(cx, sac.script);
1762 
1763     buf.append('{');
1764     AppendJSONProperty(buf, "text", NO_COMMA);
1765 
1766     JSString* str = JS_DecompileScript(cx, script, nullptr, 0);
1767     if (!str || !(str = StringToSource(cx, str)))
1768         return false;
1769 
1770     buf.append(str);
1771 
1772     AppendJSONProperty(buf, "line");
1773     NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf);
1774 
1775     AppendJSONProperty(buf, "opcodes");
1776     buf.append('[');
1777     bool comma = false;
1778 
1779     SrcNoteLineScanner scanner(script->notes(), script->lineno());
1780     uint64_t hits = 0;
1781 
1782     jsbytecode* end = script->codeEnd();
1783     for (jsbytecode* pc = script->code(); pc < end; pc = GetNextPc(pc)) {
1784         size_t offset = script->pcToOffset(pc);
1785         JSOp op = JSOp(*pc);
1786 
1787         // If the current instruction is a jump target,
1788         // then update the number of hits.
1789         const PCCounts* counts = sac.maybeGetPCCounts(pc);
1790         if (counts)
1791             hits = counts->numExec();
1792 
1793         if (comma)
1794             buf.append(',');
1795         comma = true;
1796 
1797         buf.append('{');
1798 
1799         AppendJSONProperty(buf, "id", NO_COMMA);
1800         NumberValueToStringBuffer(cx, Int32Value(offset), buf);
1801 
1802         scanner.advanceTo(offset);
1803 
1804         AppendJSONProperty(buf, "line");
1805         NumberValueToStringBuffer(cx, Int32Value(scanner.getLine()), buf);
1806 
1807         {
1808             const char* name = CodeName[op];
1809             AppendJSONProperty(buf, "name");
1810             buf.append('\"');
1811             buf.append(name, strlen(name));
1812             buf.append('\"');
1813         }
1814 
1815         {
1816             ExpressionDecompiler ed(cx, script, script->functionDelazifying());
1817             if (!ed.init())
1818                 return false;
1819             if (!ed.decompilePC(pc))
1820                 return false;
1821             char* text;
1822             if (!ed.getOutput(&text))
1823                 return false;
1824             AppendJSONProperty(buf, "text");
1825             JSString* str = JS_NewStringCopyZ(cx, text);
1826             js_free(text);
1827             if (!str || !(str = StringToSource(cx, str)))
1828                 return false;
1829             buf.append(str);
1830         }
1831 
1832         AppendJSONProperty(buf, "counts");
1833         buf.append('{');
1834 
1835         if (hits > 0) {
1836             AppendJSONProperty(buf, PCCounts::numExecName, NO_COMMA);
1837             NumberValueToStringBuffer(cx, DoubleValue(hits), buf);
1838         }
1839 
1840         buf.append('}');
1841         buf.append('}');
1842 
1843         // If the current instruction has thrown,
1844         // then decrement the hit counts with the number of throws.
1845         counts = sac.maybeGetThrowCounts(pc);
1846         if (counts)
1847             hits -= counts->numExec();
1848     }
1849 
1850     buf.append(']');
1851 
1852     jit::IonScriptCounts* ionCounts = sac.getIonCounts();
1853     if (ionCounts) {
1854         AppendJSONProperty(buf, "ion");
1855         buf.append('[');
1856         bool comma = false;
1857         while (ionCounts) {
1858             if (comma)
1859                 buf.append(',');
1860             comma = true;
1861 
1862             buf.append('[');
1863             for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
1864                 if (i)
1865                     buf.append(',');
1866                 const jit::IonBlockCounts& block = ionCounts->block(i);
1867 
1868                 buf.append('{');
1869                 AppendJSONProperty(buf, "id", NO_COMMA);
1870                 NumberValueToStringBuffer(cx, Int32Value(block.id()), buf);
1871                 AppendJSONProperty(buf, "offset");
1872                 NumberValueToStringBuffer(cx, Int32Value(block.offset()), buf);
1873                 AppendJSONProperty(buf, "successors");
1874                 buf.append('[');
1875                 for (size_t j = 0; j < block.numSuccessors(); j++) {
1876                     if (j)
1877                         buf.append(',');
1878                     NumberValueToStringBuffer(cx, Int32Value(block.successor(j)), buf);
1879                 }
1880                 buf.append(']');
1881                 AppendJSONProperty(buf, "hits");
1882                 NumberValueToStringBuffer(cx, DoubleValue(block.hitCount()), buf);
1883 
1884                 AppendJSONProperty(buf, "code");
1885                 JSString* str = JS_NewStringCopyZ(cx, block.code());
1886                 if (!str || !(str = StringToSource(cx, str)))
1887                     return false;
1888                 buf.append(str);
1889                 buf.append('}');
1890             }
1891             buf.append(']');
1892 
1893             ionCounts = ionCounts->previous();
1894         }
1895         buf.append(']');
1896     }
1897 
1898     buf.append('}');
1899 
1900     return !cx->isExceptionPending();
1901 }
1902 
JS_FRIEND_API(JSString *)1903 JS_FRIEND_API(JSString*)
1904 js::GetPCCountScriptContents(JSContext* cx, size_t index)
1905 {
1906     JSRuntime* rt = cx->runtime();
1907 
1908     if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
1909         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
1910         return nullptr;
1911     }
1912 
1913     const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index];
1914     JSScript* script = sac.script;
1915 
1916     StringBuffer buf(cx);
1917 
1918     {
1919         AutoCompartment ac(cx, &script->global());
1920         if (!GetPCCountJSON(cx, sac, buf))
1921             return nullptr;
1922     }
1923 
1924     return buf.finishString();
1925 }
1926 
1927 static bool
GenerateLcovInfo(JSContext * cx,JSCompartment * comp,GenericPrinter & out)1928 GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out)
1929 {
1930     JSRuntime* rt = cx->runtime();
1931 
1932     // Collect the list of scripts which are part of the current compartment.
1933     {
1934         js::gc::AutoPrepareForTracing apft(rt, SkipAtoms);
1935     }
1936     Rooted<ScriptVector> topScripts(cx, ScriptVector(cx));
1937     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
1938         for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
1939             JSScript* script = i.get<JSScript>();
1940             if (script->compartment() != comp ||
1941                 !script->isTopLevel() ||
1942                 !script->filename())
1943             {
1944                 continue;
1945             }
1946 
1947             if (!topScripts.append(script))
1948                 return false;
1949         }
1950     }
1951 
1952     if (topScripts.length() == 0)
1953         return true;
1954 
1955     // Collect code coverage info for one compartment.
1956     coverage::LCovCompartment compCover;
1957     for (JSScript* topLevel: topScripts) {
1958         RootedScript topScript(cx, topLevel);
1959         compCover.collectSourceFile(comp, &topScript->scriptSourceUnwrap());
1960 
1961         // We found the top-level script, visit all the functions reachable
1962         // from the top-level function, and delazify them.
1963         Rooted<ScriptVector> queue(cx, ScriptVector(cx));
1964         if (!queue.append(topLevel))
1965             return false;
1966 
1967         RootedScript script(cx);
1968         do {
1969             script = queue.popCopy();
1970             compCover.collectCodeCoverageInfo(comp, script->sourceObject(), script);
1971 
1972             // Iterate from the last to the first object in order to have
1973             // the functions them visited in the opposite order when popping
1974             // elements from the stack of remaining scripts, such that the
1975             // functions are more-less listed with increasing line numbers.
1976             if (!script->hasObjects())
1977                 continue;
1978             size_t idx = script->objects()->length;
1979             while (idx--) {
1980                 JSObject* obj = script->getObject(idx);
1981 
1982                 // Only continue on JSFunction objects.
1983                 if (!obj->is<JSFunction>())
1984                     continue;
1985                 JSFunction& fun = obj->as<JSFunction>();
1986 
1987                 // Let's skip asm.js for now.
1988                 if (!fun.isInterpreted())
1989                     continue;
1990 
1991                 // Queue the script in the list of script associated to the
1992                 // current source.
1993                 JSScript* childScript = fun.getOrCreateScript(cx);
1994                 if (!childScript || !queue.append(childScript))
1995                     return false;
1996             }
1997         } while (!queue.empty());
1998     }
1999 
2000     bool isEmpty = true;
2001     compCover.exportInto(out, &isEmpty);
2002     if (out.hadOutOfMemory())
2003         return false;
2004     return true;
2005 }
2006 
JS_FRIEND_API(char *)2007 JS_FRIEND_API(char*)
2008 js::GetCodeCoverageSummary(JSContext* cx, size_t* length)
2009 {
2010     Sprinter out(cx);
2011 
2012     if (!out.init())
2013         return nullptr;
2014 
2015     if (!GenerateLcovInfo(cx, cx->compartment(), out)) {
2016         JS_ReportOutOfMemory(cx);
2017         return nullptr;
2018     }
2019 
2020     if (out.hadOutOfMemory()) {
2021         JS_ReportOutOfMemory(cx);
2022         return nullptr;
2023     }
2024 
2025     ptrdiff_t len = out.stringEnd() - out.string();
2026     char* res = cx->pod_malloc<char>(len + 1);
2027     if (!res) {
2028         JS_ReportOutOfMemory(cx);
2029         return nullptr;
2030     }
2031 
2032     js_memcpy(res, out.string(), len);
2033     res[len] = 0;
2034     if (length)
2035         *length = len;
2036     return res;
2037 }
2038