js/src/jsopcode.cpp

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

mercurial