Line data Source code
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 "vm/BytecodeUtil-inl.h"
12 :
13 : #define __STDC_FORMAT_MACROS
14 :
15 : #include "mozilla/ArrayUtils.h"
16 : #include "mozilla/Attributes.h"
17 : #include "mozilla/Sprintf.h"
18 : #include "mozilla/Vector.h"
19 :
20 : #include <algorithm>
21 : #include <ctype.h>
22 : #include <inttypes.h>
23 : #include <stdio.h>
24 : #include <string.h>
25 :
26 : #include "jsapi.h"
27 : #include "jsnum.h"
28 : #include "jstypes.h"
29 : #include "jsutil.h"
30 :
31 : #include "builtin/String.h"
32 : #include "frontend/BytecodeCompiler.h"
33 : #include "frontend/SourceNotes.h"
34 : #include "gc/FreeOp.h"
35 : #include "gc/GCInternals.h"
36 : #include "js/CharacterEncoding.h"
37 : #include "js/Printf.h"
38 : #include "util/StringBuffer.h"
39 : #include "util/Text.h"
40 : #include "vm/CodeCoverage.h"
41 : #include "vm/EnvironmentObject.h"
42 : #include "vm/JSAtom.h"
43 : #include "vm/JSContext.h"
44 : #include "vm/JSFunction.h"
45 : #include "vm/JSObject.h"
46 : #include "vm/JSScript.h"
47 : #include "vm/Opcodes.h"
48 : #include "vm/Realm.h"
49 : #include "vm/Shape.h"
50 :
51 : #include "gc/PrivateIterators-inl.h"
52 : #include "vm/JSContext-inl.h"
53 : #include "vm/JSObject-inl.h"
54 : #include "vm/JSScript-inl.h"
55 : #include "vm/Realm-inl.h"
56 :
57 : using namespace js;
58 : using namespace js::gc;
59 :
60 : using js::frontend::IsIdentifier;
61 :
62 : /*
63 : * Index limit must stay within 32 bits.
64 : */
65 : JS_STATIC_ASSERT(sizeof(uint32_t) * CHAR_BIT >= INDEX_LIMIT_LOG2 + 1);
66 :
67 : const JSCodeSpec js::CodeSpec[] = {
68 : #define MAKE_CODESPEC(op,val,name,token,length,nuses,ndefs,format) {length,nuses,ndefs,format},
69 : FOR_EACH_OPCODE(MAKE_CODESPEC)
70 : #undef MAKE_CODESPEC
71 : };
72 :
73 : const unsigned js::NumCodeSpecs = mozilla::ArrayLength(CodeSpec);
74 :
75 : /*
76 : * Each element of the array is either a source literal associated with JS
77 : * bytecode or null.
78 : */
79 : static const char * const CodeToken[] = {
80 : #define TOKEN(op, val, name, token, ...) token,
81 : FOR_EACH_OPCODE(TOKEN)
82 : #undef TOKEN
83 : };
84 :
85 : /*
86 : * Array of JS bytecode names used by PC count JSON, DEBUG-only Disassemble
87 : * and JIT debug spew.
88 : */
89 : const char * const js::CodeName[] = {
90 : #define OPNAME(op, val, name, ...) name,
91 : FOR_EACH_OPCODE(OPNAME)
92 : #undef OPNAME
93 : };
94 :
95 : /************************************************************************/
96 :
97 : static bool
98 : DecompileArgumentFromStack(JSContext* cx, int formalIndex, char** res);
99 :
100 : size_t
101 580 : js::GetVariableBytecodeLength(jsbytecode* pc)
102 : {
103 0 : JSOp op = JSOp(*pc);
104 0 : MOZ_ASSERT(CodeSpec[op].length == -1);
105 580 : switch (op) {
106 : case JSOP_TABLESWITCH: {
107 : /* Structure: default-jump case-low case-high case1-jump ... */
108 0 : pc += JUMP_OFFSET_LEN;
109 0 : int32_t low = GET_JUMP_OFFSET(pc);
110 0 : pc += JUMP_OFFSET_LEN;
111 0 : int32_t high = GET_JUMP_OFFSET(pc);
112 0 : unsigned ncases = unsigned(high - low + 1);
113 580 : return 1 + 3 * JUMP_OFFSET_LEN + ncases * JUMP_OFFSET_LEN;
114 : }
115 : default:
116 0 : MOZ_CRASH("Unexpected op");
117 : }
118 : }
119 :
120 : const char * PCCounts::numExecName = "interp";
121 :
122 : static MOZ_MUST_USE bool
123 0 : DumpIonScriptCounts(Sprinter* sp, HandleScript script, jit::IonScriptCounts* ionCounts)
124 : {
125 0 : if (!sp->jsprintf("IonScript [%zu blocks]:\n", ionCounts->numBlocks()))
126 : return false;
127 :
128 0 : for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
129 0 : const jit::IonBlockCounts& block = ionCounts->block(i);
130 0 : unsigned lineNumber = 0, columnNumber = 0;
131 0 : lineNumber = PCToLineNumber(script, script->offsetToPC(block.offset()), &columnNumber);
132 0 : if (!sp->jsprintf("BB #%" PRIu32 " [%05u,%u,%u]",
133 : block.id(), block.offset(), lineNumber, columnNumber))
134 : {
135 0 : return false;
136 : }
137 0 : if (block.description()) {
138 0 : if (!sp->jsprintf(" [inlined %s]", block.description()))
139 : return false;
140 : }
141 0 : for (size_t j = 0; j < block.numSuccessors(); j++) {
142 0 : if (!sp->jsprintf(" -> #%" PRIu32, block.successor(j)))
143 : return false;
144 : }
145 0 : if (!sp->jsprintf(" :: %" PRIu64 " hits\n", block.hitCount()))
146 : return false;
147 0 : if (!sp->jsprintf("%s\n", block.code()))
148 : return false;
149 : }
150 :
151 : return true;
152 : }
153 :
154 : static MOZ_MUST_USE bool
155 0 : DumpPCCounts(JSContext* cx, HandleScript script, Sprinter* sp)
156 : {
157 0 : MOZ_ASSERT(script->hasScriptCounts());
158 :
159 : #ifdef DEBUG
160 0 : jsbytecode* pc = script->code();
161 0 : while (pc < script->codeEnd()) {
162 0 : jsbytecode* next = GetNextPc(pc);
163 :
164 0 : if (!Disassemble1(cx, script, pc, script->pcToOffset(pc), true, sp))
165 : return false;
166 :
167 0 : if (!sp->put(" {"))
168 : return false;
169 :
170 0 : PCCounts* counts = script->maybeGetPCCounts(pc);
171 0 : if (double val = counts ? counts->numExec() : 0.0) {
172 0 : if (!sp->jsprintf("\"%s\": %.0f", PCCounts::numExecName, val))
173 : return false;
174 : }
175 0 : if (!sp->put("}\n"))
176 : return false;
177 :
178 : pc = next;
179 : }
180 : #endif
181 :
182 0 : jit::IonScriptCounts* ionCounts = script->getIonCounts();
183 0 : while (ionCounts) {
184 0 : if (!DumpIonScriptCounts(sp, script, ionCounts))
185 : return false;
186 :
187 0 : ionCounts = ionCounts->previous();
188 : }
189 :
190 : return true;
191 : }
192 :
193 : bool
194 0 : js::DumpCompartmentPCCounts(JSContext* cx)
195 : {
196 0 : Rooted<GCVector<JSScript*>> scripts(cx, GCVector<JSScript*>(cx));
197 0 : for (auto iter = cx->zone()->cellIter<JSScript>(); !iter.done(); iter.next()) {
198 0 : JSScript* script = iter;
199 0 : if (script->compartment() != cx->compartment())
200 0 : continue;
201 0 : if (script->hasScriptCounts()) {
202 0 : if (!scripts.append(script))
203 0 : return false;
204 : }
205 : }
206 :
207 0 : for (uint32_t i = 0; i < scripts.length(); i++) {
208 0 : HandleScript script = scripts[i];
209 0 : Sprinter sprinter(cx);
210 0 : if (!sprinter.init())
211 0 : return false;
212 :
213 0 : fprintf(stdout, "--- SCRIPT %s:%u ---\n", script->filename(), script->lineno());
214 0 : if (!DumpPCCounts(cx, script, &sprinter))
215 : return false;
216 0 : fputs(sprinter.string(), stdout);
217 0 : fprintf(stdout, "--- END SCRIPT %s:%u ---\n", script->filename(), script->lineno());
218 : }
219 :
220 : return true;
221 : }
222 :
223 : /////////////////////////////////////////////////////////////////////
224 : // Bytecode Parser
225 : /////////////////////////////////////////////////////////////////////
226 :
227 : // Stores the information about the stack slot, where the value comes from.
228 : // Elements of BytecodeParser::Bytecode.{offsetStack,offsetStackAfter} arrays.
229 : struct OffsetAndDefIndex {
230 : // To make this struct a POD type, keep these properties public.
231 : // Use accessors instead of directly accessing them.
232 :
233 : // The offset of the PC that pushed the value for this slot.
234 : uint32_t offset_;
235 :
236 : // The index in `ndefs` for the PC (0-origin)
237 : uint8_t defIndex_;
238 :
239 : enum : uint8_t {
240 : Normal = 0,
241 :
242 : // Ignored this value in the expression decompilation.
243 : // Used by JSOP_NOP_DESTRUCTURING. See BytecodeParser::simulateOp.
244 : Ignored,
245 :
246 : // The value in this slot comes from 2 or more paths.
247 : // offset_ and defIndex_ holds the information for the path that
248 : // reaches here first.
249 : Merged,
250 : } type_;
251 :
252 0 : uint32_t offset() const {
253 0 : MOZ_ASSERT(!isSpecial());
254 3 : return offset_;
255 : };
256 0 : uint32_t specialOffset() const {
257 0 : MOZ_ASSERT(isSpecial());
258 0 : return offset_;
259 : };
260 :
261 0 : uint8_t defIndex() const {
262 0 : MOZ_ASSERT(!isSpecial());
263 3 : return defIndex_;
264 : }
265 0 : uint8_t specialDefIndex() const {
266 0 : MOZ_ASSERT(isSpecial());
267 0 : return defIndex_;
268 : }
269 :
270 : bool isSpecial() const {
271 6 : return type_ != Normal;
272 : }
273 : bool isMerged() const {
274 : return type_ == Merged;
275 : }
276 : bool isIgnored() const {
277 : return type_ == Ignored;
278 : }
279 :
280 : void set(uint32_t aOffset, uint8_t aDefIndex) {
281 0 : offset_ = aOffset;
282 0 : defIndex_ = aDefIndex;
283 2963 : type_ = Normal;
284 : }
285 :
286 : // Keep offset_ and defIndex_ values for stack dump.
287 : void setMerged() {
288 209 : type_ = Merged;
289 : }
290 : void setIgnored() {
291 0 : type_ = Ignored;
292 : }
293 :
294 : bool operator==(const OffsetAndDefIndex& rhs) const {
295 0 : return offset_ == rhs.offset_ &&
296 684 : defIndex_ == rhs.defIndex_;
297 : }
298 :
299 : bool operator!=(const OffsetAndDefIndex& rhs) const {
300 893 : return !(*this == rhs);
301 : }
302 : };
303 :
304 : namespace mozilla {
305 :
306 : template <>
307 : struct IsPod<OffsetAndDefIndex> : TrueType {};
308 :
309 : } // namespace mozilla
310 :
311 : namespace {
312 :
313 34 : class BytecodeParser
314 : {
315 : public:
316 : enum class JumpKind {
317 : Simple,
318 : SwitchCase,
319 : SwitchDefault,
320 : TryCatch,
321 : TryFinally
322 : };
323 :
324 : private:
325 : class Bytecode
326 : {
327 : public:
328 : explicit Bytecode(const LifoAllocPolicy<Fallible>& alloc)
329 6372 : : parsed(false),
330 : stackDepth(0),
331 : offsetStack(nullptr)
332 : #ifdef DEBUG
333 : ,
334 : stackDepthAfter(0),
335 : offsetStackAfter(nullptr),
336 12744 : jumpOrigins(alloc)
337 : #endif /* DEBUG */
338 : {}
339 :
340 : // Whether this instruction has been analyzed to get its output defines
341 : // and stack.
342 : bool parsed : 1;
343 :
344 : // Stack depth before this opcode.
345 : uint32_t stackDepth;
346 :
347 : // Pointer to array of |stackDepth| offsets. An element at position N
348 : // in the array is the offset of the opcode that defined the
349 : // corresponding stack slot. The top of the stack is at position
350 : // |stackDepth - 1|.
351 : OffsetAndDefIndex* offsetStack;
352 :
353 : #ifdef DEBUG
354 : // stack depth after this opcode.
355 : uint32_t stackDepthAfter;
356 :
357 : // Pointer to array of |stackDepthAfter| offsets.
358 : OffsetAndDefIndex* offsetStackAfter;
359 :
360 : struct JumpInfo {
361 : uint32_t from;
362 : JumpKind kind;
363 :
364 : JumpInfo(uint32_t from_, JumpKind kind_)
365 0 : : from(from_),
366 0 : kind(kind_)
367 : {}
368 : };
369 :
370 : // A list of offsets of the bytecode that jumps to this bytecode,
371 : // exclusing previous bytecode.
372 : Vector<JumpInfo, 0, LifoAllocPolicy<Fallible>> jumpOrigins;
373 : #endif /* DEBUG */
374 :
375 0 : bool captureOffsetStack(LifoAlloc& alloc, const OffsetAndDefIndex* stack, uint32_t depth) {
376 0 : stackDepth = depth;
377 0 : offsetStack = alloc.newArray<OffsetAndDefIndex>(stackDepth);
378 6338 : if (!offsetStack)
379 : return false;
380 0 : if (stackDepth) {
381 0 : for (uint32_t n = 0; n < stackDepth; n++)
382 20435 : offsetStack[n] = stack[n];
383 : }
384 : return true;
385 : }
386 :
387 : #ifdef DEBUG
388 0 : bool captureOffsetStackAfter(LifoAlloc& alloc, const OffsetAndDefIndex* stack,
389 : uint32_t depth) {
390 0 : stackDepthAfter = depth;
391 0 : offsetStackAfter = alloc.newArray<OffsetAndDefIndex>(stackDepthAfter);
392 0 : if (!offsetStackAfter)
393 : return false;
394 0 : if (stackDepthAfter) {
395 0 : for (uint32_t n = 0; n < stackDepthAfter; n++)
396 0 : offsetStackAfter[n] = stack[n];
397 : }
398 : return true;
399 : }
400 :
401 : bool addJump(uint32_t from, JumpKind kind) {
402 0 : return jumpOrigins.append(JumpInfo(from, kind));
403 : }
404 : #endif /* DEBUG */
405 :
406 : // When control-flow merges, intersect the stacks, marking slots that
407 : // are defined by different offsets and/or defIndices merged.
408 : // This is sufficient for forward control-flow. It doesn't grok loops
409 : // -- for that you would have to iterate to a fixed point -- but there
410 : // shouldn't be operands on the stack at a loop back-edge anyway.
411 0 : void mergeOffsetStack(const OffsetAndDefIndex* stack, uint32_t depth) {
412 0 : MOZ_ASSERT(depth == stackDepth);
413 0 : for (uint32_t n = 0; n < stackDepth; n++) {
414 893 : if (stack[n].isIgnored())
415 : continue;
416 0 : if (offsetStack[n].isIgnored())
417 0 : offsetStack[n] = stack[n];
418 0 : if (offsetStack[n] != stack[n])
419 209 : offsetStack[n].setMerged();
420 : }
421 301 : }
422 : };
423 :
424 : JSContext* cx_;
425 : LifoAllocScope allocScope_;
426 : RootedScript script_;
427 :
428 : Bytecode** codeArray_;
429 :
430 : #ifdef DEBUG
431 : // Dedicated mode for stack dump.
432 : // Capture stack after each opcode, and also enable special handling for
433 : // some opcodes to make stack transition clearer.
434 : bool isStackDump;
435 : #endif /* DEBUG */
436 :
437 : public:
438 0 : BytecodeParser(JSContext* cx, JSScript* script)
439 0 : : cx_(cx),
440 68 : allocScope_(&cx->tempLifoAlloc()),
441 : script_(cx, script),
442 : codeArray_(nullptr)
443 : #ifdef DEBUG
444 : ,
445 102 : isStackDump(false)
446 : #endif /* DEBUG */
447 34 : {}
448 :
449 : bool parse();
450 :
451 : #ifdef DEBUG
452 30 : bool isReachable(const jsbytecode* pc) { return maybeCode(pc); }
453 : #endif /* DEBUG */
454 :
455 : uint32_t stackDepthAtPC(uint32_t offset) {
456 : // Sometimes the code generator in debug mode asks about the stack depth
457 : // of unreachable code (bug 932180 comment 22). Assume that unreachable
458 : // code has no operands on the stack.
459 34 : return getCode(offset).stackDepth;
460 : }
461 0 : uint32_t stackDepthAtPC(const jsbytecode* pc) {
462 102 : return stackDepthAtPC(script_->pcToOffset(pc));
463 : }
464 :
465 : #ifdef DEBUG
466 : uint32_t stackDepthAfterPC(uint32_t offset) {
467 0 : return getCode(offset).stackDepthAfter;
468 : }
469 0 : uint32_t stackDepthAfterPC(const jsbytecode* pc) {
470 0 : return stackDepthAfterPC(script_->pcToOffset(pc));
471 : }
472 : #endif
473 :
474 0 : const OffsetAndDefIndex& offsetForStackOperand(uint32_t offset, int operand) {
475 0 : Bytecode& code = getCode(offset);
476 0 : if (operand < 0) {
477 0 : operand += code.stackDepth;
478 1 : MOZ_ASSERT(operand >= 0);
479 : }
480 0 : MOZ_ASSERT(uint32_t(operand) < code.stackDepth);
481 3 : return code.offsetStack[operand];
482 : }
483 0 : jsbytecode* pcForStackOperand(jsbytecode* pc, int operand, uint8_t* defIndex) {
484 0 : size_t offset = script_->pcToOffset(pc);
485 0 : const OffsetAndDefIndex& offsetAndDefIndex = offsetForStackOperand(offset, operand);
486 2 : if (offsetAndDefIndex.isSpecial())
487 : return nullptr;
488 0 : *defIndex = offsetAndDefIndex.defIndex();
489 4 : return script_->offsetToPC(offsetAndDefIndex.offset());
490 : }
491 :
492 : #ifdef DEBUG
493 0 : const OffsetAndDefIndex& offsetForStackOperandAfterPC(uint32_t offset, int operand) {
494 0 : Bytecode& code = getCode(offset);
495 0 : if (operand < 0) {
496 0 : operand += code.stackDepthAfter;
497 0 : MOZ_ASSERT(operand >= 0);
498 : }
499 0 : MOZ_ASSERT(uint32_t(operand) < code.stackDepthAfter);
500 0 : return code.offsetStackAfter[operand];
501 : }
502 :
503 : template <typename Callback>
504 0 : bool forEachJumpOrigins(jsbytecode* pc, Callback callback) {
505 0 : Bytecode& code = getCode(script_->pcToOffset(pc));
506 :
507 0 : for (Bytecode::JumpInfo& info : code.jumpOrigins) {
508 0 : if (!callback(script_->offsetToPC(info.from), info.kind))
509 : return false;
510 : }
511 :
512 : return true;
513 : }
514 :
515 : void setStackDump() {
516 0 : isStackDump = true;
517 : }
518 : #endif /* DEBUG */
519 :
520 : private:
521 : LifoAlloc& alloc() {
522 19150 : return allocScope_.alloc();
523 : }
524 :
525 : void reportOOM() {
526 0 : allocScope_.releaseEarly();
527 0 : ReportOutOfMemory(cx_);
528 : }
529 :
530 : uint32_t maximumStackDepth() {
531 19320 : return script_->nslots() - script_->nfixed();
532 : }
533 :
534 0 : Bytecode& getCode(uint32_t offset) {
535 0 : MOZ_ASSERT(offset < script_->length());
536 0 : MOZ_ASSERT(codeArray_[offset]);
537 37 : return *codeArray_[offset];
538 : }
539 :
540 0 : Bytecode* maybeCode(uint32_t offset) {
541 0 : MOZ_ASSERT(offset < script_->length());
542 9528 : return codeArray_[offset];
543 : }
544 :
545 : #ifdef DEBUG
546 60 : Bytecode* maybeCode(const jsbytecode* pc) { return maybeCode(script_->pcToOffset(pc)); }
547 : #endif
548 :
549 : uint32_t simulateOp(JSOp op, uint32_t offset, OffsetAndDefIndex* offsetStack,
550 : uint32_t stackDepth);
551 :
552 : inline bool recordBytecode(uint32_t offset, const OffsetAndDefIndex* offsetStack,
553 : uint32_t stackDepth);
554 :
555 : inline bool addJump(uint32_t offset, uint32_t* currentOffset,
556 : uint32_t stackDepth, const OffsetAndDefIndex* offsetStack,
557 : jsbytecode* pc, JumpKind kind);
558 : };
559 :
560 : } // anonymous namespace
561 :
562 : uint32_t
563 6372 : BytecodeParser::simulateOp(JSOp op, uint32_t offset, OffsetAndDefIndex* offsetStack,
564 : uint32_t stackDepth)
565 : {
566 0 : jsbytecode* pc = script_->offsetToPC(offset);
567 0 : uint32_t nuses = GetUseCount(pc);
568 6372 : uint32_t ndefs = GetDefCount(pc);
569 :
570 0 : MOZ_ASSERT(stackDepth >= nuses);
571 0 : stackDepth -= nuses;
572 12744 : MOZ_ASSERT(stackDepth + ndefs <= maximumStackDepth());
573 :
574 : #ifdef DEBUG
575 6372 : if (isStackDump) {
576 : // Opcodes that modifies the object but keeps it on the stack while
577 : // initialization should be listed here instead of switch below.
578 : // For error message, they shouldn't be shown as the original object
579 : // after adding properties.
580 : // For stack dump, keeping the input is better.
581 0 : switch (op) {
582 : case JSOP_INITHIDDENPROP:
583 : case JSOP_INITHIDDENPROP_GETTER:
584 : case JSOP_INITHIDDENPROP_SETTER:
585 : case JSOP_INITLOCKEDPROP:
586 : case JSOP_INITPROP:
587 : case JSOP_INITPROP_GETTER:
588 : case JSOP_INITPROP_SETTER:
589 : case JSOP_SETFUNNAME:
590 : // Keep the second value.
591 0 : MOZ_ASSERT(nuses == 2);
592 0 : MOZ_ASSERT(ndefs == 1);
593 : goto end;
594 :
595 : case JSOP_INITELEM:
596 : case JSOP_INITELEM_GETTER:
597 : case JSOP_INITELEM_SETTER:
598 : case JSOP_INITHIDDENELEM:
599 : case JSOP_INITHIDDENELEM_GETTER:
600 : case JSOP_INITHIDDENELEM_SETTER:
601 : // Keep the third value.
602 0 : MOZ_ASSERT(nuses == 3);
603 0 : MOZ_ASSERT(ndefs == 1);
604 : goto end;
605 :
606 : default:
607 : break;
608 : }
609 : }
610 : #endif /* DEBUG */
611 :
612 : // Mark the current offset as defining its values on the offset stack,
613 : // unless it just reshuffles the stack. In that case we want to preserve
614 : // the opcode that generated the original value.
615 6372 : switch (op) {
616 : default:
617 0 : for (uint32_t n = 0; n != ndefs; ++n)
618 5926 : offsetStack[stackDepth + n].set(offset, n);
619 : break;
620 :
621 : case JSOP_NOP_DESTRUCTURING:
622 : // Poison the last offset to not obfuscate the error message.
623 0 : offsetStack[stackDepth - 1].setIgnored();
624 : break;
625 :
626 : case JSOP_CASE:
627 : // Keep the switch value.
628 0 : MOZ_ASSERT(ndefs == 1);
629 : break;
630 :
631 : case JSOP_DUP:
632 0 : MOZ_ASSERT(ndefs == 2);
633 0 : offsetStack[stackDepth + 1] = offsetStack[stackDepth];
634 283 : break;
635 :
636 : case JSOP_DUP2:
637 0 : MOZ_ASSERT(ndefs == 4);
638 0 : offsetStack[stackDepth + 2] = offsetStack[stackDepth];
639 0 : offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1];
640 18 : break;
641 :
642 : case JSOP_DUPAT: {
643 0 : MOZ_ASSERT(ndefs == 1);
644 0 : unsigned n = GET_UINT24(pc);
645 0 : MOZ_ASSERT(n < stackDepth);
646 0 : offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n];
647 141 : break;
648 : }
649 :
650 : case JSOP_SWAP: {
651 0 : MOZ_ASSERT(ndefs == 2);
652 0 : OffsetAndDefIndex tmp = offsetStack[stackDepth + 1];
653 0 : offsetStack[stackDepth + 1] = offsetStack[stackDepth];
654 199 : offsetStack[stackDepth] = tmp;
655 : break;
656 : }
657 :
658 : case JSOP_PICK: {
659 0 : unsigned n = GET_UINT8(pc);
660 0 : MOZ_ASSERT(ndefs == n + 1);
661 0 : uint32_t top = stackDepth + n;
662 0 : OffsetAndDefIndex tmp = offsetStack[stackDepth];
663 0 : for (uint32_t i = stackDepth; i < top; i++)
664 0 : offsetStack[i] = offsetStack[i + 1];
665 88 : offsetStack[top] = tmp;
666 : break;
667 : }
668 :
669 : case JSOP_UNPICK: {
670 0 : unsigned n = GET_UINT8(pc);
671 0 : MOZ_ASSERT(ndefs == n + 1);
672 0 : uint32_t top = stackDepth + n;
673 0 : OffsetAndDefIndex tmp = offsetStack[top];
674 0 : for (uint32_t i = top; i > stackDepth; i--)
675 0 : offsetStack[i] = offsetStack[i - 1];
676 18 : offsetStack[stackDepth] = tmp;
677 : break;
678 : }
679 :
680 : case JSOP_AND:
681 : case JSOP_CHECKISOBJ:
682 : case JSOP_CHECKISCALLABLE:
683 : case JSOP_CHECKOBJCOERCIBLE:
684 : case JSOP_CHECKTHIS:
685 : case JSOP_CHECKTHISREINIT:
686 : case JSOP_CHECKCLASSHERITAGE:
687 : case JSOP_DEBUGCHECKSELFHOSTED:
688 : case JSOP_INITGLEXICAL:
689 : case JSOP_INITLEXICAL:
690 : case JSOP_OR:
691 : case JSOP_SETALIASEDVAR:
692 : case JSOP_SETARG:
693 : case JSOP_SETINTRINSIC:
694 : case JSOP_SETLOCAL:
695 : case JSOP_THROWSETALIASEDCONST:
696 : case JSOP_THROWSETCALLEE:
697 : case JSOP_THROWSETCONST:
698 : case JSOP_INITALIASEDLEXICAL:
699 : case JSOP_INITIALYIELD:
700 : case JSOP_ITERNEXT:
701 : // Keep the top value.
702 0 : MOZ_ASSERT(nuses == 1);
703 511 : MOZ_ASSERT(ndefs == 1);
704 : break;
705 :
706 : case JSOP_INITHOMEOBJECT:
707 : // Keep the top 2 values.
708 0 : MOZ_ASSERT(nuses == 2);
709 0 : MOZ_ASSERT(ndefs == 2);
710 : break;
711 :
712 : case JSOP_SETGNAME:
713 : case JSOP_SETNAME:
714 : case JSOP_SETPROP:
715 : case JSOP_STRICTSETGNAME:
716 : case JSOP_STRICTSETNAME:
717 : case JSOP_STRICTSETPROP:
718 : // Keep the top value, removing other 1 value.
719 0 : MOZ_ASSERT(nuses == 2);
720 0 : MOZ_ASSERT(ndefs == 1);
721 0 : offsetStack[stackDepth] = offsetStack[stackDepth + 1];
722 38 : break;
723 :
724 : case JSOP_SETPROP_SUPER:
725 : case JSOP_STRICTSETPROP_SUPER:
726 : // Keep the top value, removing other 2 values.
727 0 : MOZ_ASSERT(nuses == 3);
728 0 : MOZ_ASSERT(ndefs == 1);
729 0 : offsetStack[stackDepth] = offsetStack[stackDepth + 2];
730 0 : break;
731 :
732 : case JSOP_SETELEM_SUPER:
733 : case JSOP_STRICTSETELEM_SUPER:
734 : // Keep the top value, removing other 3 values.
735 0 : MOZ_ASSERT(nuses == 4);
736 0 : MOZ_ASSERT(ndefs == 1);
737 0 : offsetStack[stackDepth] = offsetStack[stackDepth + 3];
738 0 : break;
739 :
740 : case JSOP_ISGENCLOSING:
741 : case JSOP_ISNOITER:
742 : case JSOP_MOREITER:
743 : case JSOP_OPTIMIZE_SPREADCALL:
744 : // Keep the top value and push one more value.
745 0 : MOZ_ASSERT(nuses == 1);
746 0 : MOZ_ASSERT(ndefs == 2);
747 0 : offsetStack[stackDepth + 1].set(offset, 1);
748 : break;
749 : }
750 :
751 : #ifdef DEBUG
752 : end:
753 : #endif /* DEBUG */
754 :
755 0 : stackDepth += ndefs;
756 6372 : return stackDepth;
757 : }
758 :
759 : bool
760 6639 : BytecodeParser::recordBytecode(uint32_t offset, const OffsetAndDefIndex* offsetStack,
761 : uint32_t stackDepth)
762 : {
763 13278 : MOZ_ASSERT(offset < script_->length());
764 :
765 0 : Bytecode*& code = codeArray_[offset];
766 0 : if (!code) {
767 0 : code = alloc().new_<Bytecode>(alloc());
768 0 : if (!code ||
769 6338 : !code->captureOffsetStack(alloc(), offsetStack, stackDepth))
770 : {
771 0 : reportOOM();
772 0 : return false;
773 : }
774 : } else {
775 301 : code->mergeOffsetStack(offsetStack, stackDepth);
776 : }
777 :
778 : return true;
779 : }
780 :
781 : bool
782 514 : BytecodeParser::addJump(uint32_t offset, uint32_t* currentOffset,
783 : uint32_t stackDepth, const OffsetAndDefIndex* offsetStack,
784 : jsbytecode* pc, JumpKind kind)
785 : {
786 514 : if (!recordBytecode(offset, offsetStack, stackDepth))
787 : return false;
788 :
789 : #ifdef DEBUG
790 0 : if (isStackDump) {
791 0 : if (!codeArray_[offset]->addJump(script_->pcToOffset(pc), kind)) {
792 0 : reportOOM();
793 0 : return false;
794 : }
795 : }
796 : #endif /* DEBUG */
797 :
798 0 : Bytecode*& code = codeArray_[offset];
799 514 : if (offset < *currentOffset && !code->parsed) {
800 : // Backedge in a while/for loop, whose body has not been parsed due
801 : // to a lack of fallthrough at the loop head. Roll back the offset
802 : // to analyze the body.
803 50 : *currentOffset = offset;
804 : }
805 :
806 : return true;
807 : }
808 :
809 : bool
810 34 : BytecodeParser::parse()
811 : {
812 34 : MOZ_ASSERT(!codeArray_);
813 :
814 0 : uint32_t length = script_->length();
815 102 : codeArray_ = alloc().newArray<Bytecode*>(length);
816 :
817 0 : if (!codeArray_) {
818 0 : reportOOM();
819 0 : return false;
820 : }
821 :
822 68 : mozilla::PodZero(codeArray_, length);
823 :
824 : // Fill in stack depth and definitions at initial bytecode.
825 0 : Bytecode* startcode = alloc().new_<Bytecode>(alloc());
826 0 : if (!startcode) {
827 0 : reportOOM();
828 0 : return false;
829 : }
830 :
831 : // Fill in stack depth and definitions at initial bytecode.
832 0 : OffsetAndDefIndex* offsetStack = alloc().newArray<OffsetAndDefIndex>(maximumStackDepth());
833 0 : if (maximumStackDepth() && !offsetStack) {
834 0 : reportOOM();
835 0 : return false;
836 : }
837 :
838 0 : startcode->stackDepth = 0;
839 34 : codeArray_[0] = startcode;
840 :
841 0 : uint32_t offset, nextOffset = 0;
842 0 : while (nextOffset < length) {
843 9498 : offset = nextOffset;
844 :
845 0 : Bytecode* code = maybeCode(offset);
846 18996 : jsbytecode* pc = script_->offsetToPC(offset);
847 :
848 0 : JSOp op = (JSOp)*pc;
849 9498 : MOZ_ASSERT(op < JSOP_LIMIT);
850 :
851 : // Immediate successor of this bytecode.
852 9498 : uint32_t successorOffset = offset + GetBytecodeLength(pc);
853 :
854 : // Next bytecode to analyze. This is either the successor, or is an
855 : // earlier bytecode if this bytecode has a loop backedge.
856 9498 : nextOffset = successorOffset;
857 :
858 9498 : if (!code) {
859 : // Haven't found a path by which this bytecode is reachable.
860 : continue;
861 : }
862 :
863 : // On a jump target, we reload the offsetStack saved for the current
864 : // bytecode, as it contains either the original offset stack, or the
865 : // merged offset stack.
866 0 : if (BytecodeIsJumpTarget(op)) {
867 0 : for (uint32_t n = 0; n < code->stackDepth; ++n)
868 2674 : offsetStack[n] = code->offsetStack[n];
869 : }
870 :
871 6662 : if (code->parsed) {
872 : // No need to reparse.
873 : continue;
874 : }
875 :
876 6372 : code->parsed = true;
877 :
878 6372 : uint32_t stackDepth = simulateOp(op, offset, offsetStack, code->stackDepth);
879 :
880 : #ifdef DEBUG
881 0 : if (isStackDump) {
882 0 : if (!code->captureOffsetStackAfter(alloc(), offsetStack, stackDepth)) {
883 0 : reportOOM();
884 0 : return false;
885 : }
886 : }
887 : #endif /* DEBUG */
888 :
889 6372 : switch (op) {
890 : case JSOP_TABLESWITCH: {
891 0 : uint32_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
892 0 : jsbytecode* pc2 = pc + JUMP_OFFSET_LEN;
893 0 : int32_t low = GET_JUMP_OFFSET(pc2);
894 0 : pc2 += JUMP_OFFSET_LEN;
895 0 : int32_t high = GET_JUMP_OFFSET(pc2);
896 0 : pc2 += JUMP_OFFSET_LEN;
897 :
898 0 : if (!addJump(defaultOffset, &nextOffset, stackDepth, offsetStack,
899 : pc, JumpKind::SwitchDefault))
900 : {
901 : return false;
902 : }
903 :
904 0 : for (int32_t i = low; i <= high; i++) {
905 0 : uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc2);
906 0 : if (targetOffset != offset) {
907 0 : if (!addJump(targetOffset, &nextOffset, stackDepth, offsetStack,
908 : pc, JumpKind::SwitchCase))
909 : {
910 : return false;
911 : }
912 : }
913 0 : pc2 += JUMP_OFFSET_LEN;
914 : }
915 : break;
916 : }
917 :
918 : case JSOP_TRY: {
919 : // Everything between a try and corresponding catch or finally is conditional.
920 : // Note that there is no problem with code which is skipped by a thrown
921 : // exception but is not caught by a later handler in the same function:
922 : // no more code will execute, and it does not matter what is defined.
923 0 : JSTryNote* tn = script_->trynotes()->vector;
924 0 : JSTryNote* tnlimit = tn + script_->trynotes()->length;
925 0 : for (; tn < tnlimit; tn++) {
926 0 : uint32_t startOffset = script_->mainOffset() + tn->start;
927 0 : if (startOffset == offset + 1) {
928 0 : uint32_t catchOffset = startOffset + tn->length;
929 0 : if (tn->kind == JSTRY_CATCH) {
930 38 : if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack,
931 : pc, JumpKind::TryCatch))
932 : {
933 : return false;
934 : }
935 0 : } else if (tn->kind == JSTRY_FINALLY) {
936 0 : if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack,
937 : pc, JumpKind::TryFinally))
938 : {
939 : return false;
940 : }
941 : }
942 : }
943 : }
944 : break;
945 : }
946 :
947 : default:
948 : break;
949 : }
950 :
951 : // Check basic jump opcodes, which may or may not have a fallthrough.
952 6372 : if (IsJumpOpcode(op)) {
953 : // Case instructions do not push the lvalue back when branching.
954 0 : uint32_t newStackDepth = stackDepth;
955 0 : if (op == JSOP_CASE)
956 0 : newStackDepth--;
957 :
958 0 : uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc);
959 476 : if (!addJump(targetOffset, &nextOffset, newStackDepth, offsetStack,
960 : pc, JumpKind::Simple))
961 : return false;
962 : }
963 :
964 : // Handle any fallthrough from this opcode.
965 0 : if (BytecodeFallsThrough(op)) {
966 6125 : if (!recordBytecode(successorOffset, offsetStack, stackDepth))
967 : return false;
968 : }
969 : }
970 :
971 : return true;
972 : }
973 :
974 : #ifdef DEBUG
975 :
976 : bool
977 30 : js::ReconstructStackDepth(JSContext* cx, JSScript* script, jsbytecode* pc, uint32_t* depth, bool* reachablePC)
978 : {
979 0 : BytecodeParser parser(cx, script);
980 30 : if (!parser.parse())
981 : return false;
982 :
983 30 : *reachablePC = parser.isReachable(pc);
984 :
985 0 : if (*reachablePC)
986 30 : *depth = parser.stackDepthAtPC(pc);
987 :
988 : return true;
989 : }
990 :
991 : static unsigned
992 : Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
993 : unsigned loc, bool lines, BytecodeParser* parser, Sprinter* sp);
994 :
995 : /*
996 : * If pc != nullptr, include a prefix indicating whether the PC is at the
997 : * current line. If showAll is true, include the source note type and the
998 : * entry stack depth.
999 : */
1000 : static MOZ_MUST_USE bool
1001 0 : DisassembleAtPC(JSContext* cx, JSScript* scriptArg, bool lines,
1002 : jsbytecode* pc, bool showAll, Sprinter* sp)
1003 : {
1004 0 : RootedScript script(cx, scriptArg);
1005 0 : BytecodeParser parser(cx, script);
1006 0 : parser.setStackDump();
1007 0 : if (!parser.parse())
1008 : return false;
1009 :
1010 0 : if (showAll) {
1011 0 : if (!sp->jsprintf("%s:%u\n", script->filename(), unsigned(script->lineno())))
1012 : return false;
1013 : }
1014 :
1015 0 : if (pc != nullptr) {
1016 0 : if (!sp->put(" "))
1017 : return false;
1018 : }
1019 0 : if (showAll) {
1020 0 : if (!sp->put("sn stack "))
1021 : return false;
1022 : }
1023 0 : if (!sp->put("loc "))
1024 : return false;
1025 0 : if (lines) {
1026 0 : if (!sp->put("line"))
1027 : return false;
1028 : }
1029 0 : if (!sp->put(" op\n"))
1030 : return false;
1031 :
1032 0 : if (pc != nullptr) {
1033 0 : if (!sp->put(" "))
1034 : return false;
1035 : }
1036 0 : if (showAll) {
1037 0 : if (!sp->put("-- ----- "))
1038 : return false;
1039 : }
1040 0 : if (!sp->put("----- "))
1041 : return false;
1042 0 : if (lines) {
1043 0 : if (!sp->put("----"))
1044 : return false;
1045 : }
1046 0 : if (!sp->put(" --\n"))
1047 : return false;
1048 :
1049 0 : jsbytecode* next = script->code();
1050 0 : jsbytecode* end = script->codeEnd();
1051 0 : while (next < end) {
1052 0 : if (next == script->main()) {
1053 0 : if (!sp->put("main:\n"))
1054 : return false;
1055 : }
1056 0 : if (pc != nullptr) {
1057 0 : if (!sp->put(pc == next ? "--> " : " "))
1058 : return false;
1059 : }
1060 0 : if (showAll) {
1061 0 : jssrcnote* sn = GetSrcNote(cx, script, next);
1062 0 : if (sn) {
1063 0 : MOZ_ASSERT(!SN_IS_TERMINATOR(sn));
1064 0 : jssrcnote* next = SN_NEXT(sn);
1065 0 : while (!SN_IS_TERMINATOR(next) && SN_DELTA(next) == 0) {
1066 0 : if (!sp->jsprintf("%02u\n ", SN_TYPE(sn)))
1067 : return false;
1068 0 : sn = next;
1069 0 : next = SN_NEXT(sn);
1070 : }
1071 0 : if (!sp->jsprintf("%02u ", SN_TYPE(sn)))
1072 : return false;
1073 : } else {
1074 0 : if (!sp->put(" "))
1075 : return false;
1076 : }
1077 0 : if (parser.isReachable(next)) {
1078 0 : if (!sp->jsprintf("%05u ", parser.stackDepthAtPC(next)))
1079 : return false;
1080 : } else {
1081 0 : if (!sp->put(" "))
1082 : return false;
1083 : }
1084 : }
1085 0 : unsigned len = Disassemble1(cx, script, next, script->pcToOffset(next), lines,
1086 0 : &parser, sp);
1087 0 : if (!len)
1088 : return false;
1089 :
1090 0 : next += len;
1091 : }
1092 :
1093 : return true;
1094 : }
1095 :
1096 : bool
1097 0 : js::Disassemble(JSContext* cx, HandleScript script, bool lines, Sprinter* sp)
1098 : {
1099 0 : return DisassembleAtPC(cx, script, lines, nullptr, false, sp);
1100 : }
1101 :
1102 : JS_FRIEND_API(bool)
1103 0 : js::DumpPC(JSContext* cx, FILE* fp)
1104 : {
1105 0 : gc::AutoSuppressGC suppressGC(cx);
1106 0 : Sprinter sprinter(cx);
1107 0 : if (!sprinter.init())
1108 : return false;
1109 0 : ScriptFrameIter iter(cx);
1110 0 : if (iter.done()) {
1111 0 : fprintf(fp, "Empty stack.\n");
1112 0 : return true;
1113 : }
1114 0 : RootedScript script(cx, iter.script());
1115 0 : bool ok = DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter);
1116 0 : fprintf(fp, "%s", sprinter.string());
1117 : return ok;
1118 : }
1119 :
1120 : JS_FRIEND_API(bool)
1121 0 : js::DumpScript(JSContext* cx, JSScript* scriptArg, FILE* fp)
1122 : {
1123 0 : gc::AutoSuppressGC suppressGC(cx);
1124 0 : Sprinter sprinter(cx);
1125 0 : if (!sprinter.init())
1126 : return false;
1127 0 : RootedScript script(cx, scriptArg);
1128 0 : bool ok = Disassemble(cx, script, true, &sprinter);
1129 0 : fprintf(fp, "%s", sprinter.string());
1130 : return ok;
1131 : }
1132 :
1133 : static bool
1134 0 : ToDisassemblySource(JSContext* cx, HandleValue v, JSAutoByteString* bytes)
1135 : {
1136 0 : if (v.isString()) {
1137 0 : Sprinter sprinter(cx);
1138 0 : if (!sprinter.init())
1139 : return false;
1140 0 : char* nbytes = QuoteString(&sprinter, v.toString(), '"');
1141 0 : if (!nbytes)
1142 : return false;
1143 0 : UniqueChars copy = JS_smprintf("%s", nbytes);
1144 0 : if (!copy) {
1145 0 : ReportOutOfMemory(cx);
1146 0 : return false;
1147 : }
1148 0 : bytes->initBytes(std::move(copy));
1149 0 : return true;
1150 : }
1151 :
1152 0 : if (JS::RuntimeHeapIsBusy() || !cx->isAllocAllowed()) {
1153 0 : UniqueChars source = JS_smprintf("<value>");
1154 0 : if (!source) {
1155 0 : ReportOutOfMemory(cx);
1156 0 : return false;
1157 : }
1158 0 : bytes->initBytes(std::move(source));
1159 0 : return true;
1160 : }
1161 :
1162 0 : if (v.isObject()) {
1163 0 : JSObject& obj = v.toObject();
1164 :
1165 0 : if (obj.is<JSFunction>()) {
1166 0 : RootedFunction fun(cx, &obj.as<JSFunction>());
1167 : JSString* str = JS_DecompileFunction(cx, fun);
1168 0 : if (!str)
1169 : return false;
1170 0 : return bytes->encodeLatin1(cx, str);
1171 : }
1172 :
1173 0 : if (obj.is<RegExpObject>()) {
1174 0 : JSString* source = obj.as<RegExpObject>().toString(cx);
1175 0 : if (!source)
1176 : return false;
1177 0 : return bytes->encodeLatin1(cx, source);
1178 : }
1179 : }
1180 :
1181 0 : return !!ValueToPrintableLatin1(cx, v, bytes, true);
1182 : }
1183 :
1184 : static bool
1185 0 : ToDisassemblySource(JSContext* cx, HandleScope scope, JSAutoByteString* bytes)
1186 : {
1187 0 : UniqueChars source = JS_smprintf("%s {", ScopeKindString(scope->kind()));
1188 0 : if (!source) {
1189 0 : ReportOutOfMemory(cx);
1190 0 : return false;
1191 : }
1192 :
1193 0 : for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
1194 0 : JSAutoByteString nameBytes;
1195 0 : if (!AtomToPrintableString(cx, bi.name(), &nameBytes))
1196 0 : return false;
1197 :
1198 0 : source = JS_sprintf_append(std::move(source), "%s: ", nameBytes.ptr());
1199 0 : if (!source) {
1200 0 : ReportOutOfMemory(cx);
1201 0 : return false;
1202 : }
1203 :
1204 0 : BindingLocation loc = bi.location();
1205 0 : switch (loc.kind()) {
1206 : case BindingLocation::Kind::Global:
1207 0 : source = JS_sprintf_append(std::move(source), "global");
1208 0 : break;
1209 :
1210 : case BindingLocation::Kind::Frame:
1211 0 : source = JS_sprintf_append(std::move(source), "frame slot %u", loc.slot());
1212 0 : break;
1213 :
1214 : case BindingLocation::Kind::Environment:
1215 0 : source = JS_sprintf_append(std::move(source), "env slot %u", loc.slot());
1216 0 : break;
1217 :
1218 : case BindingLocation::Kind::Argument:
1219 0 : source = JS_sprintf_append(std::move(source), "arg slot %u", loc.slot());
1220 0 : break;
1221 :
1222 : case BindingLocation::Kind::NamedLambdaCallee:
1223 0 : source = JS_sprintf_append(std::move(source), "named lambda callee");
1224 0 : break;
1225 :
1226 : case BindingLocation::Kind::Import:
1227 0 : source = JS_sprintf_append(std::move(source), "import");
1228 0 : break;
1229 : }
1230 :
1231 0 : if (!source) {
1232 0 : ReportOutOfMemory(cx);
1233 0 : return false;
1234 : }
1235 :
1236 0 : if (!bi.isLast()) {
1237 0 : source = JS_sprintf_append(std::move(source), ", ");
1238 0 : if (!source) {
1239 0 : ReportOutOfMemory(cx);
1240 0 : return false;
1241 : }
1242 : }
1243 : }
1244 :
1245 0 : source = JS_sprintf_append(std::move(source), "}");
1246 0 : if (!source) {
1247 0 : ReportOutOfMemory(cx);
1248 0 : return false;
1249 : }
1250 :
1251 0 : bytes->initBytes(std::move(source));
1252 0 : return true;
1253 : }
1254 :
1255 : static bool
1256 0 : DumpJumpOrigins(HandleScript script, jsbytecode* pc, BytecodeParser* parser, Sprinter* sp)
1257 : {
1258 0 : bool called = false;
1259 0 : auto callback = [&script, &sp, &called](jsbytecode* pc, BytecodeParser::JumpKind kind) {
1260 0 : if (!called) {
1261 0 : called = true;
1262 0 : if (!sp->put("\n# "))
1263 : return false;
1264 : } else {
1265 0 : if (!sp->put(", "))
1266 : return false;
1267 : }
1268 :
1269 0 : switch (kind) {
1270 : case BytecodeParser::JumpKind::Simple:
1271 : break;
1272 :
1273 : case BytecodeParser::JumpKind::SwitchCase:
1274 0 : if (!sp->put("switch-case "))
1275 : return false;
1276 : break;
1277 :
1278 : case BytecodeParser::JumpKind::SwitchDefault:
1279 0 : if (!sp->put("switch-default "))
1280 : return false;
1281 : break;
1282 :
1283 : case BytecodeParser::JumpKind::TryCatch:
1284 0 : if (!sp->put("try-catch "))
1285 : return false;
1286 : break;
1287 :
1288 : case BytecodeParser::JumpKind::TryFinally:
1289 0 : if (!sp->put("try-finally "))
1290 : return false;
1291 : break;
1292 : }
1293 :
1294 0 : if (!sp->jsprintf("from %s @ %05u", CodeName[*pc], unsigned(script->pcToOffset(pc))))
1295 : return false;
1296 :
1297 0 : return true;
1298 0 : };
1299 0 : if (!parser->forEachJumpOrigins(pc, callback))
1300 : return false;
1301 0 : if (called) {
1302 0 : if (!sp->put("\n"))
1303 : return false;
1304 : }
1305 :
1306 : return true;
1307 : }
1308 :
1309 : static bool
1310 : DecompileAtPCForStackDump(JSContext* cx, HandleScript script,
1311 : const OffsetAndDefIndex& offsetAndDefIndex, Sprinter* sp);
1312 :
1313 : static unsigned
1314 0 : Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
1315 : unsigned loc, bool lines, BytecodeParser* parser, Sprinter* sp)
1316 : {
1317 0 : if (parser && parser->isReachable(pc)) {
1318 0 : if (!DumpJumpOrigins(script, pc, parser, sp))
1319 : return 0;
1320 : }
1321 :
1322 0 : size_t before = sp->stringEnd() - sp->string();
1323 0 : bool stackDumped = false;
1324 0 : auto dumpStack = [&cx, &script, &pc, &parser, &sp, &before, &stackDumped]() {
1325 0 : if (!parser)
1326 : return true;
1327 0 : if (stackDumped)
1328 : return true;
1329 0 : stackDumped = true;
1330 :
1331 0 : size_t after = sp->stringEnd() - sp->string();
1332 0 : MOZ_ASSERT(after >= before);
1333 :
1334 : static const size_t stack_column = 40;
1335 0 : for (size_t i = after - before; i < stack_column - 1; i++) {
1336 0 : if (!sp->put(" "))
1337 : return false;
1338 : }
1339 :
1340 0 : if (!sp->put(" # "))
1341 : return false;
1342 :
1343 0 : if (!parser->isReachable(pc)) {
1344 0 : if (!sp->put("!!! UNREACHABLE !!!"))
1345 : return false;
1346 : } else {
1347 0 : uint32_t depth = parser->stackDepthAfterPC(pc);
1348 :
1349 0 : for (uint32_t i = 0; i < depth; i++) {
1350 0 : if (i) {
1351 0 : if (!sp->put(" "))
1352 : return false;
1353 : }
1354 :
1355 : const OffsetAndDefIndex& offsetAndDefIndex
1356 0 : = parser->offsetForStackOperandAfterPC(script->pcToOffset(pc), i);
1357 : // This will decompile the stack for the same PC many times.
1358 : // We'll avoid optimizing it since this is a testing function
1359 : // and it won't be worth managing cached expression here.
1360 0 : if (!DecompileAtPCForStackDump(cx, script, offsetAndDefIndex, sp))
1361 : return false;
1362 : }
1363 : }
1364 :
1365 : return true;
1366 0 : };
1367 :
1368 0 : JSOp op = (JSOp)*pc;
1369 0 : if (op >= JSOP_LIMIT) {
1370 : char numBuf1[12], numBuf2[12];
1371 0 : SprintfLiteral(numBuf1, "%d", op);
1372 0 : SprintfLiteral(numBuf2, "%d", JSOP_LIMIT);
1373 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BYTECODE_TOO_BIG,
1374 0 : numBuf1, numBuf2);
1375 : return 0;
1376 : }
1377 0 : const JSCodeSpec* cs = &CodeSpec[op];
1378 0 : ptrdiff_t len = (ptrdiff_t) cs->length;
1379 0 : if (!sp->jsprintf("%05u:", loc))
1380 : return 0;
1381 0 : if (lines) {
1382 0 : if (!sp->jsprintf("%4u", PCToLineNumber(script, pc)))
1383 : return 0;
1384 : }
1385 0 : if (!sp->jsprintf(" %s", CodeName[op]))
1386 : return 0;
1387 :
1388 : int i;
1389 0 : switch (JOF_TYPE(cs->format)) {
1390 : case JOF_BYTE:
1391 : // Scan the trynotes to find the associated catch block
1392 : // and make the try opcode look like a jump instruction
1393 : // with an offset. This simplifies code coverage analysis
1394 : // based on this disassembled output.
1395 0 : if (op == JSOP_TRY) {
1396 0 : TryNoteArray* trynotes = script->trynotes();
1397 : uint32_t i;
1398 0 : size_t mainOffset = script->mainOffset();
1399 0 : for(i = 0; i < trynotes->length; i++) {
1400 0 : JSTryNote note = trynotes->vector[i];
1401 0 : if (note.kind == JSTRY_CATCH && note.start + mainOffset == loc + 1) {
1402 0 : if (!sp->jsprintf(" %u (%+d)",
1403 0 : unsigned(loc + note.length + 1),
1404 0 : int(note.length + 1)))
1405 : {
1406 : return 0;
1407 : }
1408 : break;
1409 : }
1410 : }
1411 : }
1412 : break;
1413 :
1414 : case JOF_JUMP: {
1415 0 : ptrdiff_t off = GET_JUMP_OFFSET(pc);
1416 0 : if (!sp->jsprintf(" %u (%+d)", unsigned(loc + int(off)), int(off)))
1417 : return 0;
1418 : break;
1419 : }
1420 :
1421 : case JOF_SCOPE: {
1422 0 : RootedScope scope(cx, script->getScope(GET_UINT32_INDEX(pc)));
1423 0 : JSAutoByteString bytes;
1424 0 : if (!ToDisassemblySource(cx, scope, &bytes))
1425 0 : return 0;
1426 0 : if (!sp->jsprintf(" %s", bytes.ptr()))
1427 : return 0;
1428 0 : break;
1429 : }
1430 :
1431 : case JOF_ENVCOORD: {
1432 : RootedValue v(cx,
1433 0 : StringValue(EnvironmentCoordinateName(cx->caches().envCoordinateNameCache, script, pc)));
1434 0 : JSAutoByteString bytes;
1435 0 : if (!ToDisassemblySource(cx, v, &bytes))
1436 0 : return 0;
1437 0 : EnvironmentCoordinate ec(pc);
1438 0 : if (!sp->jsprintf(" %s (hops = %u, slot = %u)", bytes.ptr(), ec.hops(), ec.slot()))
1439 : return 0;
1440 0 : break;
1441 : }
1442 :
1443 : case JOF_ATOM: {
1444 0 : RootedValue v(cx, StringValue(script->getAtom(GET_UINT32_INDEX(pc))));
1445 0 : JSAutoByteString bytes;
1446 0 : if (!ToDisassemblySource(cx, v, &bytes))
1447 0 : return 0;
1448 0 : if (!sp->jsprintf(" %s", bytes.ptr()))
1449 : return 0;
1450 0 : break;
1451 : }
1452 :
1453 : case JOF_DOUBLE: {
1454 0 : RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc)));
1455 0 : JSAutoByteString bytes;
1456 0 : if (!ToDisassemblySource(cx, v, &bytes))
1457 0 : return 0;
1458 0 : if (!sp->jsprintf(" %s", bytes.ptr()))
1459 : return 0;
1460 0 : break;
1461 : }
1462 :
1463 : case JOF_OBJECT: {
1464 : /* Don't call obj.toSource if analysis/inference is active. */
1465 0 : if (script->zone()->types.activeAnalysis) {
1466 0 : if (!sp->jsprintf(" object"))
1467 : return 0;
1468 : break;
1469 : }
1470 :
1471 0 : JSObject* obj = script->getObject(GET_UINT32_INDEX(pc));
1472 : {
1473 0 : JSAutoByteString bytes;
1474 0 : RootedValue v(cx, ObjectValue(*obj));
1475 0 : if (!ToDisassemblySource(cx, v, &bytes))
1476 0 : return 0;
1477 0 : if (!sp->jsprintf(" %s", bytes.ptr()))
1478 : return 0;
1479 : }
1480 : break;
1481 : }
1482 :
1483 : case JOF_REGEXP: {
1484 0 : js::RegExpObject* obj = script->getRegExp(pc);
1485 0 : JSAutoByteString bytes;
1486 0 : RootedValue v(cx, ObjectValue(*obj));
1487 0 : if (!ToDisassemblySource(cx, v, &bytes))
1488 0 : return 0;
1489 0 : if (!sp->jsprintf(" %s", bytes.ptr()))
1490 : return 0;
1491 0 : break;
1492 : }
1493 :
1494 : case JOF_TABLESWITCH:
1495 : {
1496 : int32_t i, low, high;
1497 :
1498 0 : ptrdiff_t off = GET_JUMP_OFFSET(pc);
1499 0 : jsbytecode* pc2 = pc + JUMP_OFFSET_LEN;
1500 0 : low = GET_JUMP_OFFSET(pc2);
1501 0 : pc2 += JUMP_OFFSET_LEN;
1502 0 : high = GET_JUMP_OFFSET(pc2);
1503 0 : pc2 += JUMP_OFFSET_LEN;
1504 0 : if (!sp->jsprintf(" defaultOffset %d low %d high %d", int(off), low, high))
1505 : return 0;
1506 :
1507 : // Display stack dump before diplaying the offsets for each case.
1508 0 : if (!dumpStack())
1509 : return 0;
1510 :
1511 0 : for (i = low; i <= high; i++) {
1512 0 : off = GET_JUMP_OFFSET(pc2);
1513 0 : if (!sp->jsprintf("\n\t%d: %d", i, int(off)))
1514 : return 0;
1515 0 : pc2 += JUMP_OFFSET_LEN;
1516 : }
1517 0 : len = 1 + pc2 - pc;
1518 0 : break;
1519 : }
1520 :
1521 : case JOF_QARG:
1522 0 : if (!sp->jsprintf(" %u", GET_ARGNO(pc)))
1523 : return 0;
1524 : break;
1525 :
1526 : case JOF_LOCAL:
1527 0 : if (!sp->jsprintf(" %u", GET_LOCALNO(pc)))
1528 : return 0;
1529 : break;
1530 :
1531 : case JOF_UINT32:
1532 0 : if (!sp->jsprintf(" %u", GET_UINT32(pc)))
1533 : return 0;
1534 : break;
1535 :
1536 : case JOF_UINT16:
1537 0 : i = (int)GET_UINT16(pc);
1538 0 : goto print_int;
1539 :
1540 : case JOF_UINT24:
1541 0 : MOZ_ASSERT(len == 4);
1542 0 : i = (int)GET_UINT24(pc);
1543 0 : goto print_int;
1544 :
1545 : case JOF_UINT8:
1546 0 : i = GET_UINT8(pc);
1547 0 : goto print_int;
1548 :
1549 : case JOF_INT8:
1550 0 : i = GET_INT8(pc);
1551 0 : goto print_int;
1552 :
1553 : case JOF_INT32:
1554 0 : MOZ_ASSERT(op == JSOP_INT32);
1555 0 : i = GET_INT32(pc);
1556 : print_int:
1557 0 : if (!sp->jsprintf(" %d", i))
1558 : return 0;
1559 : break;
1560 :
1561 : default: {
1562 : char numBuf[12];
1563 0 : SprintfLiteral(numBuf, "%x", cs->format);
1564 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNKNOWN_FORMAT, numBuf);
1565 : return 0;
1566 : }
1567 : }
1568 :
1569 0 : if (!dumpStack())
1570 : return 0;
1571 :
1572 0 : if (!sp->put("\n"))
1573 : return 0;
1574 0 : return len;
1575 : }
1576 :
1577 : unsigned
1578 0 : js::Disassemble1(JSContext* cx, JS::Handle<JSScript*> script, jsbytecode* pc, unsigned loc,
1579 : bool lines, Sprinter* sp)
1580 : {
1581 0 : return Disassemble1(cx, script, pc, loc, lines, nullptr, sp);
1582 : }
1583 :
1584 : #endif /* DEBUG */
1585 :
1586 : namespace {
1587 : /*
1588 : * The expression decompiler is invoked by error handling code to produce a
1589 : * string representation of the erroring expression. As it's only a debugging
1590 : * tool, it only supports basic expressions. For anything complicated, it simply
1591 : * puts "(intermediate value)" into the error result.
1592 : *
1593 : * Here's the basic algorithm:
1594 : *
1595 : * 1. Find the stack location of the value whose expression we wish to
1596 : * decompile. The error handler can explicitly pass this as an
1597 : * argument. Otherwise, we search backwards down the stack for the offending
1598 : * value.
1599 : *
1600 : * 2. Instantiate and run a BytecodeParser for the current frame. This creates a
1601 : * stack of pcs parallel to the interpreter stack; given an interpreter stack
1602 : * location, the corresponding pc stack location contains the opcode that pushed
1603 : * the value in the interpreter. Now, with the result of step 1, we have the
1604 : * opcode responsible for pushing the value we want to decompile.
1605 : *
1606 : * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler
1607 : * routine, responsible for a string representation of the expression that
1608 : * generated a certain stack location. decompilePC looks at one opcode and
1609 : * returns the JS source equivalent of that opcode.
1610 : *
1611 : * 4. Expressions can, of course, contain subexpressions. For example, the
1612 : * literals "4" and "5" are subexpressions of the addition operator in "4 +
1613 : * 5". If we need to decompile a subexpression, we call decompilePC (step 2)
1614 : * recursively on the operands' pcs. The result is a depth-first traversal of
1615 : * the expression tree.
1616 : *
1617 : */
1618 4 : struct ExpressionDecompiler
1619 : {
1620 : JSContext* cx;
1621 : RootedScript script;
1622 : BytecodeParser parser;
1623 : Sprinter sprinter;
1624 :
1625 : #ifdef DEBUG
1626 : // Dedicated mode for stack dump.
1627 : // Generates an expression for stack dump, including internal state,
1628 : // and also disables special handling for self-hosted code.
1629 : bool isStackDump;
1630 : #endif /* DEBUG */
1631 :
1632 0 : ExpressionDecompiler(JSContext* cx, JSScript* script)
1633 2 : : cx(cx),
1634 : script(cx, script),
1635 : parser(cx, script),
1636 : sprinter(cx)
1637 : #ifdef DEBUG
1638 : ,
1639 2 : isStackDump(false)
1640 : #endif /* DEBUG */
1641 2 : {}
1642 : bool init();
1643 : bool decompilePCForStackOperand(jsbytecode* pc, int i);
1644 : bool decompilePC(jsbytecode* pc, uint8_t defIndex);
1645 : bool decompilePC(const OffsetAndDefIndex& offsetAndDefIndex);
1646 : JSAtom* getArg(unsigned slot);
1647 : JSAtom* loadAtom(jsbytecode* pc);
1648 : bool quote(JSString* s, uint32_t quote);
1649 : bool write(const char* s);
1650 : bool write(JSString* str);
1651 : bool getOutput(char** out);
1652 : #ifdef DEBUG
1653 : void setStackDump() {
1654 0 : isStackDump = true;
1655 0 : parser.setStackDump();
1656 : }
1657 : #endif /* DEBUG */
1658 : };
1659 :
1660 : bool
1661 1 : ExpressionDecompiler::decompilePCForStackOperand(jsbytecode* pc, int i)
1662 : {
1663 2 : return decompilePC(parser.offsetForStackOperand(script->pcToOffset(pc), i));
1664 : }
1665 :
1666 : bool
1667 3 : ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex)
1668 : {
1669 6 : MOZ_ASSERT(script->containsPC(pc));
1670 :
1671 3 : JSOp op = (JSOp)*pc;
1672 :
1673 0 : if (const char* token = CodeToken[op]) {
1674 0 : MOZ_ASSERT(defIndex == 0);
1675 0 : MOZ_ASSERT(CodeSpec[op].ndefs == 1);
1676 :
1677 : // Handle simple cases of binary and unary operators.
1678 0 : switch (CodeSpec[op].nuses) {
1679 : case 2: {
1680 0 : jssrcnote* sn = GetSrcNote(cx, script, pc);
1681 0 : if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP)
1682 0 : return write("(") &&
1683 0 : decompilePCForStackOperand(pc, -2) &&
1684 0 : write(" ") &&
1685 0 : write(token) &&
1686 0 : write(" ") &&
1687 0 : decompilePCForStackOperand(pc, -1) &&
1688 0 : write(")");
1689 : break;
1690 : }
1691 : case 1:
1692 0 : return write("(") &&
1693 0 : write(token) &&
1694 0 : decompilePCForStackOperand(pc, -1) &&
1695 0 : write(")");
1696 : default:
1697 : break;
1698 : }
1699 : }
1700 :
1701 3 : switch (op) {
1702 : case JSOP_DELNAME:
1703 0 : return write("(delete ") &&
1704 0 : write(loadAtom(pc)) &&
1705 0 : write(")");
1706 :
1707 : case JSOP_GETGNAME:
1708 : case JSOP_GETNAME:
1709 : case JSOP_GETINTRINSIC:
1710 1 : return write(loadAtom(pc));
1711 : case JSOP_GETARG: {
1712 1 : unsigned slot = GET_ARGNO(pc);
1713 :
1714 : // For self-hosted scripts that are called from non-self-hosted code,
1715 : // decompiling the parameter name in the self-hosted script is
1716 : // unhelpful. Decompile the argument name instead.
1717 4 : if (script->selfHosted()
1718 : #ifdef DEBUG
1719 : // For stack dump, argument name is not necessary.
1720 1 : && !isStackDump
1721 : #endif /* DEBUG */
1722 : )
1723 : {
1724 : char* result;
1725 0 : if (!DecompileArgumentFromStack(cx, slot, &result))
1726 0 : return false;
1727 :
1728 : // Note that decompiling the argument in the parent frame might
1729 : // not succeed.
1730 0 : if (result) {
1731 0 : bool ok = write(result);
1732 0 : js_free(result);
1733 0 : return ok;
1734 : }
1735 : }
1736 :
1737 0 : JSAtom* atom = getArg(slot);
1738 1 : if (!atom)
1739 : return false;
1740 1 : return write(atom);
1741 : }
1742 : case JSOP_GETLOCAL: {
1743 0 : JSAtom* atom = FrameSlotName(script, pc);
1744 0 : MOZ_ASSERT(atom);
1745 0 : return write(atom);
1746 : }
1747 : case JSOP_GETALIASEDVAR: {
1748 0 : JSAtom* atom = EnvironmentCoordinateName(cx->caches().envCoordinateNameCache, script, pc);
1749 0 : MOZ_ASSERT(atom);
1750 0 : return write(atom);
1751 : }
1752 :
1753 : case JSOP_DELPROP:
1754 : case JSOP_STRICTDELPROP:
1755 : case JSOP_LENGTH:
1756 : case JSOP_GETPROP:
1757 : case JSOP_GETBOUNDNAME:
1758 : case JSOP_CALLPROP: {
1759 0 : bool hasDelete = op == JSOP_DELPROP || op == JSOP_STRICTDELPROP;
1760 0 : RootedAtom prop(cx, (op == JSOP_LENGTH) ? cx->names().length : loadAtom(pc));
1761 0 : MOZ_ASSERT(prop);
1762 0 : return (hasDelete ? write("(delete ") : true) &&
1763 0 : decompilePCForStackOperand(pc, -1) &&
1764 0 : (IsIdentifier(prop)
1765 0 : ? write(".") && quote(prop, '\0')
1766 0 : : write("[") && quote(prop, '\'') && write("]")) &&
1767 0 : (hasDelete ? write(")") : true);
1768 : }
1769 : case JSOP_GETPROP_SUPER:
1770 : {
1771 0 : RootedAtom prop(cx, loadAtom(pc));
1772 0 : return write("super.") &&
1773 0 : quote(prop, '\0');
1774 : }
1775 : case JSOP_SETELEM:
1776 : case JSOP_STRICTSETELEM:
1777 : // NOTE: We don't show the right hand side of the operation because
1778 : // it's used in error messages like: "a[0] is not readable".
1779 : //
1780 : // We could though.
1781 0 : return decompilePCForStackOperand(pc, -3) &&
1782 0 : write("[") &&
1783 0 : decompilePCForStackOperand(pc, -2) &&
1784 0 : write("]");
1785 :
1786 : case JSOP_DELELEM:
1787 : case JSOP_STRICTDELELEM:
1788 : case JSOP_GETELEM:
1789 : case JSOP_CALLELEM: {
1790 0 : bool hasDelete = (op == JSOP_DELELEM || op == JSOP_STRICTDELELEM);
1791 0 : return (hasDelete ? write("(delete ") : true) &&
1792 0 : decompilePCForStackOperand(pc, -2) &&
1793 0 : write("[") &&
1794 0 : decompilePCForStackOperand(pc, -1) &&
1795 0 : write("]") &&
1796 0 : (hasDelete ? write(")") : true);
1797 : }
1798 :
1799 : case JSOP_GETELEM_SUPER:
1800 0 : return write("super[") &&
1801 0 : decompilePCForStackOperand(pc, -3) &&
1802 0 : write("]");
1803 : case JSOP_NULL:
1804 0 : return write(js_null_str);
1805 : case JSOP_TRUE:
1806 0 : return write(js_true_str);
1807 : case JSOP_FALSE:
1808 0 : return write(js_false_str);
1809 : case JSOP_ZERO:
1810 : case JSOP_ONE:
1811 : case JSOP_INT8:
1812 : case JSOP_UINT16:
1813 : case JSOP_UINT24:
1814 : case JSOP_INT32:
1815 0 : return sprinter.printf("%d", GetBytecodeInteger(pc));
1816 : case JSOP_STRING:
1817 0 : return quote(loadAtom(pc), '"');
1818 : case JSOP_SYMBOL: {
1819 0 : unsigned i = uint8_t(pc[1]);
1820 0 : MOZ_ASSERT(i < JS::WellKnownSymbolLimit);
1821 0 : if (i < JS::WellKnownSymbolLimit)
1822 0 : return write(cx->names().wellKnownSymbolDescriptions()[i]);
1823 : break;
1824 : }
1825 : case JSOP_UNDEFINED:
1826 0 : return write(js_undefined_str);
1827 : case JSOP_GLOBALTHIS:
1828 : // |this| could convert to a very long object initialiser, so cite it by
1829 : // its keyword name.
1830 0 : return write(js_this_str);
1831 : case JSOP_NEWTARGET:
1832 0 : return write("new.target");
1833 : case JSOP_CALL:
1834 : case JSOP_CALL_IGNORES_RV:
1835 : case JSOP_CALLITER:
1836 : case JSOP_FUNCALL:
1837 : case JSOP_FUNAPPLY:
1838 0 : return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) &&
1839 0 : write("(...)");
1840 : case JSOP_SPREADCALL:
1841 0 : return decompilePCForStackOperand(pc, -3) &&
1842 0 : write("(...)");
1843 : case JSOP_NEWARRAY:
1844 0 : return write("[]");
1845 : case JSOP_REGEXP: {
1846 0 : RootedObject obj(cx, script->getObject(GET_UINT32_INDEX(pc)));
1847 0 : JSString* str = obj->as<RegExpObject>().toString(cx);
1848 0 : if (!str)
1849 : return false;
1850 0 : return write(str);
1851 : }
1852 : case JSOP_NEWARRAY_COPYONWRITE: {
1853 0 : RootedObject obj(cx, script->getObject(GET_UINT32_INDEX(pc)));
1854 0 : Handle<ArrayObject*> aobj = obj.as<ArrayObject>();
1855 0 : if (!write("["))
1856 : return false;
1857 0 : for (size_t i = 0; i < aobj->getDenseInitializedLength(); i++) {
1858 0 : if (i > 0 && !write(", "))
1859 0 : return false;
1860 :
1861 0 : RootedValue v(cx, aobj->getDenseElement(i));
1862 0 : MOZ_RELEASE_ASSERT(v.isPrimitive() && !v.isMagic());
1863 :
1864 0 : JSString* str = ValueToSource(cx, v);
1865 0 : if (!str || !write(str))
1866 0 : return false;
1867 : }
1868 0 : return write("]");
1869 : }
1870 : case JSOP_OBJECT: {
1871 0 : JSObject* obj = script->getObject(GET_UINT32_INDEX(pc));
1872 0 : RootedValue objv(cx, ObjectValue(*obj));
1873 0 : JSString* str = ValueToSource(cx, objv);
1874 0 : if (!str)
1875 : return false;
1876 0 : return write(str);
1877 : }
1878 : case JSOP_VOID:
1879 0 : return write("(void ") &&
1880 0 : decompilePCForStackOperand(pc, -1) &&
1881 0 : write(")");
1882 :
1883 : case JSOP_SUPERCALL:
1884 : case JSOP_SPREADSUPERCALL:
1885 0 : return write("super(...)");
1886 : case JSOP_SUPERFUN:
1887 0 : return write("super");
1888 :
1889 : case JSOP_EVAL:
1890 : case JSOP_SPREADEVAL:
1891 : case JSOP_STRICTEVAL:
1892 : case JSOP_STRICTSPREADEVAL:
1893 0 : return write("eval(...)");
1894 :
1895 : case JSOP_NEW:
1896 0 : return write("(new ") &&
1897 0 : decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 3)) &&
1898 0 : write("(...))");
1899 :
1900 : case JSOP_SPREADNEW:
1901 0 : return write("(new ") &&
1902 0 : decompilePCForStackOperand(pc, -4) &&
1903 0 : write("(...))");
1904 :
1905 : case JSOP_TYPEOF:
1906 : case JSOP_TYPEOFEXPR:
1907 0 : return write("(typeof ") &&
1908 0 : decompilePCForStackOperand(pc, -1) &&
1909 0 : write(")");
1910 :
1911 : case JSOP_INITELEM_ARRAY:
1912 0 : return write("[...]");
1913 :
1914 : case JSOP_INITELEM_INC:
1915 0 : if (defIndex == 0)
1916 0 : return write("[...]");
1917 0 : MOZ_ASSERT(defIndex == 1);
1918 : #ifdef DEBUG
1919 : // INDEX won't be be exposed to error message.
1920 0 : if (isStackDump)
1921 0 : return write("INDEX");
1922 : #endif
1923 : break;
1924 :
1925 : default:
1926 : break;
1927 : }
1928 :
1929 : #ifdef DEBUG
1930 0 : if (isStackDump) {
1931 : // Special decompilation for stack dump.
1932 0 : switch (op) {
1933 : case JSOP_ARGUMENTS:
1934 0 : return write("arguments");
1935 :
1936 : case JSOP_BINDGNAME:
1937 0 : return write("GLOBAL");
1938 :
1939 : case JSOP_BINDNAME:
1940 : case JSOP_BINDVAR:
1941 0 : return write("ENV");
1942 :
1943 : case JSOP_CALLEE:
1944 0 : return write("CALLEE");
1945 :
1946 : case JSOP_CALLSITEOBJ:
1947 0 : return write("OBJ");
1948 :
1949 : case JSOP_CLASSCONSTRUCTOR:
1950 : case JSOP_DERIVEDCONSTRUCTOR:
1951 0 : return write("CONSTRUCTOR");
1952 :
1953 : case JSOP_DOUBLE:
1954 0 : return sprinter.printf("%lf", script->getConst(GET_UINT32_INDEX(pc)).toDouble());
1955 :
1956 : case JSOP_EXCEPTION:
1957 0 : return write("EXCEPTION");
1958 :
1959 : case JSOP_FINALLY:
1960 0 : if (defIndex == 0)
1961 0 : return write("THROWING");
1962 0 : MOZ_ASSERT(defIndex == 1);
1963 0 : return write("PC");
1964 :
1965 : case JSOP_GIMPLICITTHIS:
1966 : case JSOP_FUNCTIONTHIS:
1967 : case JSOP_IMPLICITTHIS:
1968 0 : return write("THIS");
1969 :
1970 : case JSOP_FUNWITHPROTO:
1971 0 : return write("FUN");
1972 :
1973 : case JSOP_GENERATOR:
1974 0 : return write("GENERATOR");
1975 :
1976 : case JSOP_GETIMPORT:
1977 0 : return write("VAL");
1978 :
1979 : case JSOP_GETRVAL:
1980 0 : return write("RVAL");
1981 :
1982 : case JSOP_HOLE:
1983 0 : return write("HOLE");
1984 :
1985 : case JSOP_ISGENCLOSING:
1986 : // For stack dump, defIndex == 0 is not used.
1987 0 : MOZ_ASSERT(defIndex == 1);
1988 0 : return write("ISGENCLOSING");
1989 :
1990 : case JSOP_ISNOITER:
1991 : // For stack dump, defIndex == 0 is not used.
1992 0 : MOZ_ASSERT(defIndex == 1);
1993 0 : return write("ISNOITER");
1994 :
1995 : case JSOP_IS_CONSTRUCTING:
1996 0 : return write("JS_IS_CONSTRUCTING");
1997 :
1998 : case JSOP_ITER:
1999 0 : return write("ITER");
2000 :
2001 : case JSOP_LAMBDA:
2002 : case JSOP_LAMBDA_ARROW:
2003 : case JSOP_TOASYNC:
2004 : case JSOP_TOASYNCGEN:
2005 0 : return write("FUN");
2006 :
2007 : case JSOP_TOASYNCITER:
2008 0 : return write("ASYNCITER");
2009 :
2010 : case JSOP_MOREITER:
2011 : // For stack dump, defIndex == 0 is not used.
2012 0 : MOZ_ASSERT(defIndex == 1);
2013 0 : return write("MOREITER");
2014 :
2015 : case JSOP_MUTATEPROTO:
2016 0 : return write("SUCCEEDED");
2017 :
2018 : case JSOP_NEWINIT:
2019 : case JSOP_NEWOBJECT:
2020 : case JSOP_OBJWITHPROTO:
2021 0 : return write("OBJ");
2022 :
2023 : case JSOP_OPTIMIZE_SPREADCALL:
2024 : // For stack dump, defIndex == 0 is not used.
2025 0 : MOZ_ASSERT(defIndex == 1);
2026 0 : return write("OPTIMIZED");
2027 :
2028 : case JSOP_REST:
2029 0 : return write("REST");
2030 :
2031 : case JSOP_RESUME:
2032 0 : return write("RVAL");
2033 :
2034 : case JSOP_SUPERBASE:
2035 0 : return write("HOMEOBJECTPROTO");
2036 :
2037 : case JSOP_TOID:
2038 0 : return write("TOID(") &&
2039 0 : decompilePCForStackOperand(pc, -1) &&
2040 0 : write(")");
2041 : case JSOP_TOSTRING:
2042 0 : return write("TOSTRING(") &&
2043 0 : decompilePCForStackOperand(pc, -1) &&
2044 0 : write(")");
2045 :
2046 : case JSOP_UNINITIALIZED:
2047 0 : return write("UNINITIALIZED");
2048 :
2049 : case JSOP_AWAIT:
2050 : case JSOP_YIELD:
2051 : // Printing "yield SOMETHING" is confusing since the operand doesn't
2052 : // match to the syntax, since the stack operand for "yield 10" is
2053 : // the result object, not 10.
2054 0 : return write("RVAL");
2055 :
2056 : default:
2057 : break;
2058 : }
2059 0 : return write("<unknown>");
2060 : }
2061 : #endif /* DEBUG */
2062 :
2063 0 : return write("(intermediate value)");
2064 : }
2065 :
2066 : bool
2067 1 : ExpressionDecompiler::decompilePC(const OffsetAndDefIndex& offsetAndDefIndex)
2068 : {
2069 1 : if (offsetAndDefIndex.isSpecial()) {
2070 : #ifdef DEBUG
2071 0 : if (isStackDump) {
2072 0 : if (offsetAndDefIndex.isMerged()) {
2073 0 : if (!write("merged<"))
2074 : return false;
2075 0 : } else if (offsetAndDefIndex.isIgnored()) {
2076 0 : if (!write("ignored<"))
2077 : return false;
2078 : }
2079 :
2080 0 : if (!decompilePC(script->offsetToPC(offsetAndDefIndex.specialOffset()),
2081 0 : offsetAndDefIndex.specialDefIndex()))
2082 : {
2083 : return false;
2084 : }
2085 :
2086 0 : if (!write(">"))
2087 : return false;
2088 :
2089 0 : return true;
2090 : }
2091 : #endif /* DEBUG */
2092 0 : return write("(intermediate value)");
2093 : }
2094 :
2095 0 : return decompilePC(script->offsetToPC(offsetAndDefIndex.offset()),
2096 2 : offsetAndDefIndex.defIndex());
2097 : }
2098 :
2099 : bool
2100 2 : ExpressionDecompiler::init()
2101 : {
2102 2 : assertSameCompartment(cx, script);
2103 :
2104 2 : if (!sprinter.init())
2105 : return false;
2106 :
2107 2 : if (!parser.parse())
2108 : return false;
2109 :
2110 2 : return true;
2111 : }
2112 :
2113 : bool
2114 : ExpressionDecompiler::write(const char* s)
2115 : {
2116 2 : return sprinter.put(s);
2117 : }
2118 :
2119 : bool
2120 2 : ExpressionDecompiler::write(JSString* str)
2121 : {
2122 0 : if (str == cx->names().dotThis)
2123 0 : return write("this");
2124 2 : return sprinter.putString(str);
2125 : }
2126 :
2127 : bool
2128 : ExpressionDecompiler::quote(JSString* s, uint32_t quote)
2129 : {
2130 1 : return QuoteString(&sprinter, s, quote) != nullptr;
2131 : }
2132 :
2133 : JSAtom*
2134 : ExpressionDecompiler::loadAtom(jsbytecode* pc)
2135 : {
2136 6 : return script->getAtom(pc);
2137 : }
2138 :
2139 : JSAtom*
2140 1 : ExpressionDecompiler::getArg(unsigned slot)
2141 : {
2142 0 : MOZ_ASSERT(script->functionNonDelazifying());
2143 2 : MOZ_ASSERT(slot < script->numArgs());
2144 :
2145 0 : for (PositionalFormalParameterIter fi(script); fi; fi++) {
2146 0 : if (fi.argumentSlot() == slot) {
2147 0 : if (!fi.isDestructured())
2148 2 : return fi.name();
2149 :
2150 : // Destructured arguments have no single binding name.
2151 : static const char destructuredParam[] = "(destructured parameter)";
2152 0 : return Atomize(cx, destructuredParam, strlen(destructuredParam));
2153 : }
2154 : }
2155 :
2156 0 : MOZ_CRASH("No binding");
2157 : }
2158 :
2159 : bool
2160 2 : ExpressionDecompiler::getOutput(char** res)
2161 : {
2162 0 : ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0);
2163 0 : *res = cx->pod_malloc<char>(len + 1);
2164 2 : if (!*res)
2165 : return false;
2166 0 : js_memcpy(*res, sprinter.stringAt(0), len);
2167 0 : (*res)[len] = 0;
2168 2 : return true;
2169 : }
2170 :
2171 : } // anonymous namespace
2172 :
2173 : #ifdef DEBUG
2174 : static bool
2175 0 : DecompileAtPCForStackDump(JSContext* cx, HandleScript script,
2176 : const OffsetAndDefIndex& offsetAndDefIndex, Sprinter* sp)
2177 : {
2178 0 : ExpressionDecompiler ed(cx, script);
2179 0 : ed.setStackDump();
2180 0 : if (!ed.init())
2181 : return false;
2182 :
2183 0 : if (!ed.decompilePC(offsetAndDefIndex))
2184 : return false;
2185 :
2186 : char* result;
2187 0 : if (!ed.getOutput(&result))
2188 : return false;
2189 :
2190 0 : if (!sp->put(result))
2191 : return false;
2192 :
2193 0 : return true;
2194 : }
2195 : #endif /* DEBUG */
2196 :
2197 : static bool
2198 2 : FindStartPC(JSContext* cx, const FrameIter& iter, int spindex, int skipStackHits, const Value& v,
2199 : jsbytecode** valuepc, uint8_t* defIndex)
2200 : {
2201 0 : jsbytecode* current = *valuepc;
2202 0 : *valuepc = nullptr;
2203 2 : *defIndex = 0;
2204 :
2205 2 : if (spindex == JSDVG_IGNORE_STACK)
2206 : return true;
2207 :
2208 : /*
2209 : * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the
2210 : * previous pc (see bug 831120).
2211 : */
2212 2 : if (iter.isIon())
2213 : return true;
2214 :
2215 0 : BytecodeParser parser(cx, iter.script());
2216 2 : if (!parser.parse())
2217 : return false;
2218 :
2219 0 : if (spindex < 0 && spindex + int(parser.stackDepthAtPC(current)) < 0)
2220 0 : spindex = JSDVG_SEARCH_STACK;
2221 :
2222 0 : if (spindex == JSDVG_SEARCH_STACK) {
2223 2 : size_t index = iter.numFrameSlots();
2224 :
2225 : // The decompiler may be called from inside functions that are not
2226 : // called from script, but via the C++ API directly, such as
2227 : // Invoke. In that case, the youngest script frame may have a
2228 : // completely unrelated pc and stack depth, so we give up.
2229 2 : if (index < size_t(parser.stackDepthAtPC(current)))
2230 : return true;
2231 :
2232 : // We search from fp->sp to base to find the most recently calculated
2233 : // value matching v under assumption that it is the value that caused
2234 : // the exception.
2235 : int stackHits = 0;
2236 : Value s;
2237 0 : do {
2238 2 : if (!index)
2239 : return true;
2240 0 : s = iter.frameSlotValue(--index);
2241 2 : } while (s != v || stackHits++ != skipStackHits);
2242 :
2243 :
2244 : // If the current PC has fewer values on the stack than the index we are
2245 : // looking for, the blamed value must be one pushed by the current
2246 : // bytecode (e.g. JSOP_MOREITER), so restore *valuepc.
2247 0 : if (index < size_t(parser.stackDepthAtPC(current))) {
2248 2 : *valuepc = parser.pcForStackOperand(current, index, defIndex);
2249 : } else {
2250 0 : *valuepc = current;
2251 0 : *defIndex = index - size_t(parser.stackDepthAtPC(current));
2252 : }
2253 : } else {
2254 0 : *valuepc = parser.pcForStackOperand(current, spindex, defIndex);
2255 : }
2256 : return true;
2257 : }
2258 :
2259 : static bool
2260 2 : DecompileExpressionFromStack(JSContext* cx, int spindex, int skipStackHits, HandleValue v, char** res)
2261 : {
2262 2 : MOZ_ASSERT(spindex < 0 ||
2263 : spindex == JSDVG_IGNORE_STACK ||
2264 : spindex == JSDVG_SEARCH_STACK);
2265 :
2266 2 : *res = nullptr;
2267 :
2268 : #ifdef JS_MORE_DETERMINISTIC
2269 : /*
2270 : * Give up if we need deterministic behavior for differential testing.
2271 : * IonMonkey doesn't use InterpreterFrames and this ensures we get the same
2272 : * error messages.
2273 : */
2274 : return true;
2275 : #endif
2276 :
2277 4 : FrameIter frameIter(cx);
2278 :
2279 6 : if (frameIter.done() || !frameIter.hasScript() || frameIter.compartment() != cx->compartment())
2280 : return true;
2281 :
2282 0 : RootedScript script(cx, frameIter.script());
2283 2 : jsbytecode* valuepc = frameIter.pc();
2284 :
2285 4 : MOZ_ASSERT(script->containsPC(valuepc));
2286 :
2287 : // Give up if in prologue.
2288 2 : if (valuepc < script->main())
2289 : return true;
2290 :
2291 : uint8_t defIndex;
2292 2 : if (!FindStartPC(cx, frameIter, spindex, skipStackHits, v, &valuepc, &defIndex))
2293 : return false;
2294 2 : if (!valuepc)
2295 : return true;
2296 :
2297 0 : ExpressionDecompiler ed(cx, script);
2298 2 : if (!ed.init())
2299 : return false;
2300 2 : if (!ed.decompilePC(valuepc, defIndex))
2301 : return false;
2302 :
2303 2 : return ed.getOutput(res);
2304 : }
2305 :
2306 : UniqueChars
2307 2 : js::DecompileValueGenerator(JSContext* cx, int spindex, HandleValue v,
2308 : HandleString fallbackArg, int skipStackHits)
2309 : {
2310 4 : RootedString fallback(cx, fallbackArg);
2311 : {
2312 : char* result;
2313 0 : if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result))
2314 0 : return nullptr;
2315 0 : if (result) {
2316 0 : if (strcmp(result, "(intermediate value)"))
2317 0 : return UniqueChars(result);
2318 0 : js_free(result);
2319 : }
2320 : }
2321 0 : if (!fallback) {
2322 0 : if (v.isUndefined())
2323 0 : return DuplicateString(cx, js_undefined_str); // Prevent users from seeing "(void 0)"
2324 0 : fallback = ValueToSource(cx, v);
2325 0 : if (!fallback)
2326 : return nullptr;
2327 : }
2328 :
2329 0 : return UniqueChars(JS_EncodeString(cx, fallback));
2330 : }
2331 :
2332 : static bool
2333 0 : DecompileArgumentFromStack(JSContext* cx, int formalIndex, char** res)
2334 : {
2335 0 : MOZ_ASSERT(formalIndex >= 0);
2336 :
2337 0 : *res = nullptr;
2338 :
2339 : #ifdef JS_MORE_DETERMINISTIC
2340 : /* See note in DecompileExpressionFromStack. */
2341 : return true;
2342 : #endif
2343 :
2344 : /*
2345 : * Settle on the nearest script frame, which should be the builtin that
2346 : * called the intrinsic.
2347 : */
2348 0 : FrameIter frameIter(cx);
2349 0 : MOZ_ASSERT(!frameIter.done());
2350 0 : MOZ_ASSERT(frameIter.script()->selfHosted());
2351 :
2352 : /*
2353 : * Get the second-to-top frame, the non-self-hosted caller of the builtin
2354 : * that called the intrinsic.
2355 : */
2356 0 : ++frameIter;
2357 0 : if (frameIter.done() ||
2358 0 : !frameIter.hasScript() ||
2359 0 : frameIter.script()->selfHosted() ||
2360 0 : frameIter.compartment() != cx->compartment())
2361 : {
2362 : return true;
2363 : }
2364 :
2365 0 : RootedScript script(cx, frameIter.script());
2366 0 : jsbytecode* current = frameIter.pc();
2367 :
2368 0 : MOZ_ASSERT(script->containsPC(current));
2369 :
2370 0 : if (current < script->main())
2371 : return true;
2372 :
2373 : /* Don't handle getters, setters or calls from fun.call/fun.apply. */
2374 0 : JSOp op = JSOp(*current);
2375 0 : if (op != JSOP_CALL && op != JSOP_CALL_IGNORES_RV && op != JSOP_NEW)
2376 : return true;
2377 :
2378 0 : if (static_cast<unsigned>(formalIndex) >= GET_ARGC(current))
2379 : return true;
2380 :
2381 0 : BytecodeParser parser(cx, script);
2382 0 : if (!parser.parse())
2383 : return false;
2384 :
2385 0 : bool pushedNewTarget = op == JSOP_NEW;
2386 0 : int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) - pushedNewTarget +
2387 0 : formalIndex;
2388 0 : MOZ_ASSERT(formalStackIndex >= 0);
2389 0 : if (uint32_t(formalStackIndex) >= parser.stackDepthAtPC(current))
2390 : return true;
2391 :
2392 0 : ExpressionDecompiler ed(cx, script);
2393 0 : if (!ed.init())
2394 : return false;
2395 0 : if (!ed.decompilePCForStackOperand(current, formalStackIndex))
2396 : return false;
2397 :
2398 0 : return ed.getOutput(res);
2399 : }
2400 :
2401 : UniqueChars
2402 0 : js::DecompileArgument(JSContext* cx, int formalIndex, HandleValue v)
2403 : {
2404 : {
2405 : char* result;
2406 0 : if (!DecompileArgumentFromStack(cx, formalIndex, &result))
2407 0 : return nullptr;
2408 0 : if (result) {
2409 0 : if (strcmp(result, "(intermediate value)"))
2410 0 : return UniqueChars(result);
2411 0 : js_free(result);
2412 : }
2413 : }
2414 0 : if (v.isUndefined())
2415 0 : return DuplicateString(cx, js_undefined_str); // Prevent users from seeing "(void 0)"
2416 :
2417 0 : RootedString fallback(cx, ValueToSource(cx, v));
2418 0 : if (!fallback)
2419 : return nullptr;
2420 :
2421 0 : return UniqueChars(JS_EncodeString(cx, fallback));
2422 : }
2423 :
2424 : bool
2425 0 : js::CallResultEscapes(jsbytecode* pc)
2426 : {
2427 : /*
2428 : * If we see any of these sequences, the result is unused:
2429 : * - call / pop
2430 : *
2431 : * If we see any of these sequences, the result is only tested for nullness:
2432 : * - call / ifeq
2433 : * - call / not / ifeq
2434 : */
2435 :
2436 0 : if (*pc == JSOP_CALL)
2437 0 : pc += JSOP_CALL_LENGTH;
2438 0 : else if (*pc == JSOP_CALL_IGNORES_RV)
2439 0 : pc += JSOP_CALL_IGNORES_RV_LENGTH;
2440 0 : else if (*pc == JSOP_SPREADCALL)
2441 0 : pc += JSOP_SPREADCALL_LENGTH;
2442 : else
2443 : return true;
2444 :
2445 0 : if (*pc == JSOP_POP)
2446 : return false;
2447 :
2448 0 : if (*pc == JSOP_NOT)
2449 0 : pc += JSOP_NOT_LENGTH;
2450 :
2451 0 : return *pc != JSOP_IFEQ;
2452 : }
2453 :
2454 : extern bool
2455 0 : js::IsValidBytecodeOffset(JSContext* cx, JSScript* script, size_t offset)
2456 : {
2457 : // This could be faster (by following jump instructions if the target is <= offset).
2458 0 : for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) {
2459 0 : size_t here = r.frontOffset();
2460 0 : if (here >= offset)
2461 0 : return here == offset;
2462 : }
2463 0 : return false;
2464 : }
2465 :
2466 : /*
2467 : * There are three possible PCCount profiling states:
2468 : *
2469 : * 1. None: Neither scripts nor the runtime have count information.
2470 : * 2. Profile: Active scripts have count information, the runtime does not.
2471 : * 3. Query: Scripts do not have count information, the runtime does.
2472 : *
2473 : * When starting to profile scripts, counting begins immediately, with all JIT
2474 : * code discarded and recompiled with counts as necessary. Active interpreter
2475 : * frames will not begin profiling until they begin executing another script
2476 : * (via a call or return).
2477 : *
2478 : * The below API functions manage transitions to new states, according
2479 : * to the table below.
2480 : *
2481 : * Old State
2482 : * -------------------------
2483 : * Function None Profile Query
2484 : * --------
2485 : * StartPCCountProfiling Profile Profile Profile
2486 : * StopPCCountProfiling None Query Query
2487 : * PurgePCCounts None None None
2488 : */
2489 :
2490 : static void
2491 0 : ReleaseScriptCounts(FreeOp* fop)
2492 : {
2493 0 : JSRuntime* rt = fop->runtime();
2494 0 : MOZ_ASSERT(rt->scriptAndCountsVector);
2495 :
2496 0 : fop->delete_(rt->scriptAndCountsVector.ref());
2497 0 : rt->scriptAndCountsVector = nullptr;
2498 0 : }
2499 :
2500 : JS_FRIEND_API(void)
2501 0 : js::StartPCCountProfiling(JSContext* cx)
2502 : {
2503 0 : JSRuntime* rt = cx->runtime();
2504 :
2505 0 : if (rt->profilingScripts)
2506 : return;
2507 :
2508 0 : if (rt->scriptAndCountsVector)
2509 0 : ReleaseScriptCounts(rt->defaultFreeOp());
2510 :
2511 0 : ReleaseAllJITCode(rt->defaultFreeOp());
2512 :
2513 0 : rt->profilingScripts = true;
2514 : }
2515 :
2516 : JS_FRIEND_API(void)
2517 0 : js::StopPCCountProfiling(JSContext* cx)
2518 : {
2519 0 : JSRuntime* rt = cx->runtime();
2520 :
2521 0 : if (!rt->profilingScripts)
2522 : return;
2523 0 : MOZ_ASSERT(!rt->scriptAndCountsVector);
2524 :
2525 0 : ReleaseAllJITCode(rt->defaultFreeOp());
2526 :
2527 0 : auto* vec = cx->new_<PersistentRooted<ScriptAndCountsVector>>(cx,
2528 0 : ScriptAndCountsVector(SystemAllocPolicy()));
2529 0 : if (!vec)
2530 : return;
2531 :
2532 0 : for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
2533 0 : for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
2534 0 : AutoSweepTypeScript sweep(script);
2535 0 : if (script->hasScriptCounts() && script->types(sweep)) {
2536 0 : if (!vec->append(script))
2537 0 : return;
2538 : }
2539 : }
2540 : }
2541 :
2542 0 : rt->profilingScripts = false;
2543 0 : rt->scriptAndCountsVector = vec;
2544 : }
2545 :
2546 : JS_FRIEND_API(void)
2547 0 : js::PurgePCCounts(JSContext* cx)
2548 : {
2549 0 : JSRuntime* rt = cx->runtime();
2550 :
2551 0 : if (!rt->scriptAndCountsVector)
2552 : return;
2553 0 : MOZ_ASSERT(!rt->profilingScripts);
2554 :
2555 0 : ReleaseScriptCounts(rt->defaultFreeOp());
2556 : }
2557 :
2558 : JS_FRIEND_API(size_t)
2559 0 : js::GetPCCountScriptCount(JSContext* cx)
2560 : {
2561 0 : JSRuntime* rt = cx->runtime();
2562 :
2563 0 : if (!rt->scriptAndCountsVector)
2564 : return 0;
2565 :
2566 0 : return rt->scriptAndCountsVector->length();
2567 : }
2568 :
2569 : enum MaybeComma {NO_COMMA, COMMA};
2570 :
2571 : static MOZ_MUST_USE bool
2572 0 : AppendJSONProperty(StringBuffer& buf, const char* name, MaybeComma comma = COMMA)
2573 : {
2574 0 : if (comma && !buf.append(','))
2575 : return false;
2576 :
2577 0 : return buf.append('\"') &&
2578 0 : buf.append(name, strlen(name)) &&
2579 0 : buf.append("\":", 2);
2580 : }
2581 :
2582 : JS_FRIEND_API(JSString*)
2583 0 : js::GetPCCountScriptSummary(JSContext* cx, size_t index)
2584 : {
2585 0 : JSRuntime* rt = cx->runtime();
2586 :
2587 0 : if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
2588 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
2589 0 : return nullptr;
2590 : }
2591 :
2592 0 : const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index];
2593 0 : RootedScript script(cx, sac.script);
2594 :
2595 : /*
2596 : * OOM on buffer appends here will not be caught immediately, but since
2597 : * StringBuffer uses a TempAllocPolicy will trigger an exception on the
2598 : * context if they occur, which we'll catch before returning.
2599 : */
2600 0 : StringBuffer buf(cx);
2601 :
2602 0 : if (!buf.append('{'))
2603 : return nullptr;
2604 :
2605 0 : if (!AppendJSONProperty(buf, "file", NO_COMMA))
2606 : return nullptr;
2607 0 : JSString* str = JS_NewStringCopyZ(cx, script->filename());
2608 0 : if (!str || !(str = StringToSource(cx, str)))
2609 : return nullptr;
2610 0 : if (!buf.append(str))
2611 : return nullptr;
2612 :
2613 0 : if (!AppendJSONProperty(buf, "line"))
2614 : return nullptr;
2615 0 : if (!NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf)) {
2616 : return nullptr;
2617 : }
2618 :
2619 0 : if (script->functionNonDelazifying()) {
2620 0 : JSAtom* atom = script->functionNonDelazifying()->displayAtom();
2621 0 : if (atom) {
2622 0 : if (!AppendJSONProperty(buf, "name"))
2623 : return nullptr;
2624 0 : if (!(str = StringToSource(cx, atom)))
2625 : return nullptr;
2626 0 : if (!buf.append(str))
2627 : return nullptr;
2628 : }
2629 : }
2630 :
2631 0 : uint64_t total = 0;
2632 :
2633 0 : jsbytecode* codeEnd = script->codeEnd();
2634 0 : for (jsbytecode* pc = script->code(); pc < codeEnd; pc = GetNextPc(pc)) {
2635 0 : const PCCounts* counts = sac.maybeGetPCCounts(pc);
2636 0 : if (!counts)
2637 : continue;
2638 0 : total += counts->numExec();
2639 : }
2640 :
2641 0 : if (!AppendJSONProperty(buf, "totals"))
2642 : return nullptr;
2643 0 : if (!buf.append('{'))
2644 : return nullptr;
2645 :
2646 0 : if (!AppendJSONProperty(buf, PCCounts::numExecName, NO_COMMA))
2647 : return nullptr;
2648 0 : if (!NumberValueToStringBuffer(cx, DoubleValue(total), buf))
2649 : return nullptr;
2650 :
2651 0 : uint64_t ionActivity = 0;
2652 0 : jit::IonScriptCounts* ionCounts = sac.getIonCounts();
2653 0 : while (ionCounts) {
2654 0 : for (size_t i = 0; i < ionCounts->numBlocks(); i++)
2655 0 : ionActivity += ionCounts->block(i).hitCount();
2656 0 : ionCounts = ionCounts->previous();
2657 : }
2658 0 : if (ionActivity) {
2659 0 : if (!AppendJSONProperty(buf, "ion", COMMA))
2660 : return nullptr;
2661 0 : if (!NumberValueToStringBuffer(cx, DoubleValue(ionActivity), buf))
2662 : return nullptr;
2663 : }
2664 :
2665 0 : if (!buf.append('}'))
2666 : return nullptr;
2667 0 : if (!buf.append('}'))
2668 : return nullptr;
2669 :
2670 0 : MOZ_ASSERT(!cx->isExceptionPending());
2671 :
2672 0 : return buf.finishString();
2673 : }
2674 :
2675 : static bool
2676 0 : GetPCCountJSON(JSContext* cx, const ScriptAndCounts& sac, StringBuffer& buf)
2677 : {
2678 0 : RootedScript script(cx, sac.script);
2679 :
2680 0 : if (!buf.append('{'))
2681 : return false;
2682 0 : if (!AppendJSONProperty(buf, "text", NO_COMMA))
2683 : return false;
2684 :
2685 0 : JSString* str = JS_DecompileScript(cx, script);
2686 0 : if (!str || !(str = StringToSource(cx, str)))
2687 : return false;
2688 :
2689 0 : if (!buf.append(str))
2690 : return false;
2691 :
2692 0 : if (!AppendJSONProperty(buf, "line"))
2693 : return false;
2694 0 : if (!NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf))
2695 : return false;
2696 :
2697 0 : if (!AppendJSONProperty(buf, "opcodes"))
2698 : return false;
2699 0 : if (!buf.append('['))
2700 : return false;
2701 0 : bool comma = false;
2702 :
2703 0 : SrcNoteLineScanner scanner(script->notes(), script->lineno());
2704 0 : uint64_t hits = 0;
2705 :
2706 0 : jsbytecode* end = script->codeEnd();
2707 0 : for (jsbytecode* pc = script->code(); pc < end; pc = GetNextPc(pc)) {
2708 0 : size_t offset = script->pcToOffset(pc);
2709 0 : JSOp op = JSOp(*pc);
2710 :
2711 : // If the current instruction is a jump target,
2712 : // then update the number of hits.
2713 0 : const PCCounts* counts = sac.maybeGetPCCounts(pc);
2714 0 : if (counts)
2715 0 : hits = counts->numExec();
2716 :
2717 0 : if (comma && !buf.append(','))
2718 : return false;
2719 0 : comma = true;
2720 :
2721 0 : if (!buf.append('{'))
2722 : return false;
2723 :
2724 0 : if (!AppendJSONProperty(buf, "id", NO_COMMA))
2725 : return false;
2726 0 : if (!NumberValueToStringBuffer(cx, Int32Value(offset), buf))
2727 : return false;
2728 :
2729 0 : scanner.advanceTo(offset);
2730 :
2731 0 : if (!AppendJSONProperty(buf, "line"))
2732 : return false;
2733 0 : if (!NumberValueToStringBuffer(cx, Int32Value(scanner.getLine()), buf))
2734 : return false;
2735 :
2736 : {
2737 0 : const char* name = CodeName[op];
2738 0 : if (!AppendJSONProperty(buf, "name"))
2739 : return false;
2740 0 : if (!buf.append('\"'))
2741 : return false;
2742 0 : if (!buf.append(name, strlen(name)))
2743 : return false;
2744 0 : if (!buf.append('\"'))
2745 : return false;
2746 : }
2747 :
2748 : {
2749 0 : ExpressionDecompiler ed(cx, script);
2750 0 : if (!ed.init())
2751 0 : return false;
2752 : // defIndex passed here is not used.
2753 0 : if (!ed.decompilePC(pc, /* defIndex = */ 0))
2754 : return false;
2755 : char* text;
2756 0 : if (!ed.getOutput(&text))
2757 : return false;
2758 0 : JSString* str = JS_NewStringCopyZ(cx, text);
2759 0 : js_free(text);
2760 0 : if (!AppendJSONProperty(buf, "text"))
2761 : return false;
2762 0 : if (!str || !(str = StringToSource(cx, str)))
2763 : return false;
2764 0 : if (!buf.append(str))
2765 : return false;
2766 : }
2767 :
2768 0 : if (!AppendJSONProperty(buf, "counts"))
2769 : return false;
2770 0 : if (!buf.append('{'))
2771 : return false;
2772 :
2773 0 : if (hits > 0) {
2774 0 : if (!AppendJSONProperty(buf, PCCounts::numExecName, NO_COMMA))
2775 : return false;
2776 0 : if (!NumberValueToStringBuffer(cx, DoubleValue(hits), buf))
2777 : return false;
2778 : }
2779 :
2780 0 : if (!buf.append('}'))
2781 : return false;
2782 0 : if (!buf.append('}'))
2783 : return false;
2784 :
2785 : // If the current instruction has thrown,
2786 : // then decrement the hit counts with the number of throws.
2787 0 : counts = sac.maybeGetThrowCounts(pc);
2788 0 : if (counts)
2789 0 : hits -= counts->numExec();
2790 : }
2791 :
2792 0 : if (!buf.append(']'))
2793 : return false;
2794 :
2795 0 : jit::IonScriptCounts* ionCounts = sac.getIonCounts();
2796 0 : if (ionCounts) {
2797 0 : if (!AppendJSONProperty(buf, "ion"))
2798 : return false;
2799 0 : if (!buf.append('['))
2800 : return false;
2801 : bool comma = false;
2802 0 : while (ionCounts) {
2803 0 : if (comma && !buf.append(','))
2804 : return false;
2805 0 : comma = true;
2806 :
2807 0 : if (!buf.append('['))
2808 : return false;
2809 0 : for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
2810 0 : if (i && !buf.append(','))
2811 : return false;
2812 0 : const jit::IonBlockCounts& block = ionCounts->block(i);
2813 :
2814 0 : if (!buf.append('{'))
2815 : return false;
2816 0 : if (!AppendJSONProperty(buf, "id", NO_COMMA))
2817 : return false;
2818 0 : if (!NumberValueToStringBuffer(cx, Int32Value(block.id()), buf))
2819 : return false;
2820 0 : if (!AppendJSONProperty(buf, "offset"))
2821 : return false;
2822 0 : if (!NumberValueToStringBuffer(cx, Int32Value(block.offset()), buf))
2823 : return false;
2824 0 : if (!AppendJSONProperty(buf, "successors"))
2825 : return false;
2826 0 : if (!buf.append('['))
2827 : return false;
2828 0 : for (size_t j = 0; j < block.numSuccessors(); j++) {
2829 0 : if (j && !buf.append(','))
2830 : return false;
2831 0 : if (!NumberValueToStringBuffer(cx, Int32Value(block.successor(j)), buf))
2832 : return false;
2833 : }
2834 0 : if (!buf.append(']'))
2835 : return false;
2836 0 : if (!AppendJSONProperty(buf, "hits"))
2837 : return false;
2838 0 : if (!NumberValueToStringBuffer(cx, DoubleValue(block.hitCount()), buf))
2839 : return false;
2840 :
2841 0 : if (!AppendJSONProperty(buf, "code"))
2842 : return false;
2843 0 : JSString* str = JS_NewStringCopyZ(cx, block.code());
2844 0 : if (!str || !(str = StringToSource(cx, str)))
2845 : return false;
2846 0 : if (!buf.append(str))
2847 : return false;
2848 0 : if (!buf.append('}'))
2849 : return false;
2850 : }
2851 0 : if (!buf.append(']'))
2852 : return false;
2853 :
2854 0 : ionCounts = ionCounts->previous();
2855 : }
2856 0 : if (!buf.append(']'))
2857 : return false;
2858 : }
2859 :
2860 0 : if (!buf.append('}'))
2861 : return false;
2862 :
2863 0 : MOZ_ASSERT(!cx->isExceptionPending());
2864 : return true;
2865 : }
2866 :
2867 : JS_FRIEND_API(JSString*)
2868 0 : js::GetPCCountScriptContents(JSContext* cx, size_t index)
2869 : {
2870 0 : JSRuntime* rt = cx->runtime();
2871 :
2872 0 : if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
2873 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
2874 0 : return nullptr;
2875 : }
2876 :
2877 0 : const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index];
2878 0 : JSScript* script = sac.script;
2879 :
2880 0 : StringBuffer buf(cx);
2881 :
2882 : {
2883 0 : AutoRealm ar(cx, &script->global());
2884 0 : if (!GetPCCountJSON(cx, sac, buf))
2885 0 : return nullptr;
2886 : }
2887 :
2888 0 : return buf.finishString();
2889 : }
2890 :
2891 : static bool
2892 0 : GenerateLcovInfo(JSContext* cx, JS::Realm* realm, GenericPrinter& out)
2893 : {
2894 0 : JSRuntime* rt = cx->runtime();
2895 :
2896 : // Collect the list of scripts which are part of the current realm.
2897 : {
2898 0 : js::gc::AutoPrepareForTracing apft(cx);
2899 : }
2900 0 : Rooted<ScriptVector> topScripts(cx, ScriptVector(cx));
2901 0 : for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
2902 0 : for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
2903 0 : if (script->realm() != realm ||
2904 0 : !script->isTopLevel() ||
2905 0 : !script->filename())
2906 : {
2907 : continue;
2908 : }
2909 :
2910 0 : if (!topScripts.append(script))
2911 0 : return false;
2912 : }
2913 : }
2914 :
2915 0 : if (topScripts.length() == 0)
2916 : return true;
2917 :
2918 : // Collect code coverage info for one realm.
2919 0 : coverage::LCovRealm realmCover;
2920 0 : for (JSScript* topLevel: topScripts) {
2921 0 : RootedScript topScript(cx, topLevel);
2922 :
2923 : // We found the top-level script, visit all the functions reachable
2924 : // from the top-level function, and delazify them.
2925 0 : Rooted<ScriptVector> queue(cx, ScriptVector(cx));
2926 0 : if (!queue.append(topLevel))
2927 0 : return false;
2928 :
2929 0 : RootedScript script(cx);
2930 0 : RootedFunction fun(cx);
2931 0 : do {
2932 0 : script = queue.popCopy();
2933 0 : if (script->filename())
2934 0 : realmCover.collectCodeCoverageInfo(realm, script, script->filename());
2935 :
2936 : // Iterate from the last to the first object in order to have
2937 : // the functions them visited in the opposite order when popping
2938 : // elements from the stack of remaining scripts, such that the
2939 : // functions are more-less listed with increasing line numbers.
2940 0 : if (!script->hasObjects())
2941 : continue;
2942 0 : size_t idx = script->objects()->length;
2943 0 : while (idx--) {
2944 0 : JSObject* obj = script->getObject(idx);
2945 :
2946 : // Only continue on JSFunction objects.
2947 0 : if (!obj->is<JSFunction>())
2948 0 : continue;
2949 0 : fun = &obj->as<JSFunction>();
2950 :
2951 : // Let's skip wasm for now.
2952 0 : if (!fun->isInterpreted())
2953 : continue;
2954 :
2955 : // Queue the script in the list of script associated to the
2956 : // current source.
2957 0 : JSScript* childScript = JSFunction::getOrCreateScript(cx, fun);
2958 0 : if (!childScript || !queue.append(childScript))
2959 0 : return false;
2960 : }
2961 0 : } while (!queue.empty());
2962 : }
2963 :
2964 0 : bool isEmpty = true;
2965 0 : realmCover.exportInto(out, &isEmpty);
2966 0 : if (out.hadOutOfMemory())
2967 : return false;
2968 0 : return true;
2969 : }
2970 :
2971 : JS_FRIEND_API(char*)
2972 0 : js::GetCodeCoverageSummary(JSContext* cx, size_t* length)
2973 : {
2974 0 : Sprinter out(cx);
2975 :
2976 0 : if (!out.init())
2977 : return nullptr;
2978 :
2979 0 : if (!GenerateLcovInfo(cx, cx->realm(), out)) {
2980 0 : JS_ReportOutOfMemory(cx);
2981 0 : return nullptr;
2982 : }
2983 :
2984 0 : if (out.hadOutOfMemory()) {
2985 0 : JS_ReportOutOfMemory(cx);
2986 0 : return nullptr;
2987 : }
2988 :
2989 0 : ptrdiff_t len = out.stringEnd() - out.string();
2990 0 : char* res = cx->pod_malloc<char>(len + 1);
2991 0 : if (!res) {
2992 0 : JS_ReportOutOfMemory(cx);
2993 0 : return nullptr;
2994 : }
2995 :
2996 0 : js_memcpy(res, out.string(), len);
2997 0 : res[len] = 0;
2998 0 : if (length)
2999 : *length = len;
3000 : return res;
3001 : }
|