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