Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / js / src / vm / BytecodeUtil.cpp
blobcc8f545387b36258ef64a74a27b72ebc11235e3b
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
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/. */
7 /*
8 * JS bytecode descriptors, disassemblers, and (expression) decompilers.
9 */
11 #include "vm/BytecodeUtil-inl.h"
13 #define __STDC_FORMAT_MACROS
15 #include "mozilla/Maybe.h"
16 #include "mozilla/ReverseIterator.h"
17 #include "mozilla/Sprintf.h"
19 #include <inttypes.h>
20 #include <stdio.h>
21 #include <string.h>
23 #include "jsapi.h"
24 #include "jstypes.h"
26 #include "gc/PublicIterators.h"
27 #include "jit/IonScript.h" // IonBlockCounts
28 #include "js/CharacterEncoding.h"
29 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin
30 #include "js/experimental/CodeCoverage.h"
31 #include "js/experimental/PCCountProfiling.h" // JS::{Start,Stop}PCCountProfiling, JS::PurgePCCounts, JS::GetPCCountScript{Count,Summary,Contents}
32 #include "js/friend/DumpFunctions.h" // js::DumpPC, js::DumpScript
33 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
34 #include "js/Printer.h"
35 #include "js/Printf.h"
36 #include "js/Symbol.h"
37 #include "util/DifferentialTesting.h"
38 #include "util/Identifier.h" // IsIdentifier
39 #include "util/Memory.h"
40 #include "util/Text.h"
41 #include "vm/BuiltinObjectKind.h"
42 #include "vm/BytecodeIterator.h" // for AllBytecodesIterable
43 #include "vm/BytecodeLocation.h"
44 #include "vm/CodeCoverage.h"
45 #include "vm/EnvironmentObject.h"
46 #include "vm/FrameIter.h" // js::{,Script}FrameIter
47 #include "vm/JSAtomUtils.h" // AtomToPrintableString, Atomize
48 #include "vm/JSContext.h"
49 #include "vm/JSFunction.h"
50 #include "vm/JSObject.h"
51 #include "vm/JSONPrinter.h"
52 #include "vm/JSScript.h"
53 #include "vm/Opcodes.h"
54 #include "vm/Realm.h"
55 #include "vm/Shape.h"
56 #include "vm/ToSource.h" // js::ValueToSource
58 #include "gc/GC-inl.h"
59 #include "vm/BytecodeIterator-inl.h"
60 #include "vm/JSContext-inl.h"
61 #include "vm/JSScript-inl.h"
62 #include "vm/Realm-inl.h"
64 using namespace js;
67 * Index limit must stay within 32 bits.
69 static_assert(sizeof(uint32_t) * CHAR_BIT >= INDEX_LIMIT_LOG2 + 1);
71 const JSCodeSpec js::CodeSpecTable[] = {
72 #define MAKE_CODESPEC(op, op_snake, token, length, nuses, ndefs, format) \
73 {length, nuses, ndefs, format},
74 FOR_EACH_OPCODE(MAKE_CODESPEC)
75 #undef MAKE_CODESPEC
79 * Each element of the array is either a source literal associated with JS
80 * bytecode or null.
82 static const char* const CodeToken[] = {
83 #define TOKEN(op, op_snake, token, ...) token,
84 FOR_EACH_OPCODE(TOKEN)
85 #undef TOKEN
89 * Array of JS bytecode names used by PC count JSON, DEBUG-only Disassemble
90 * and JIT debug spew.
92 const char* const js::CodeNameTable[] = {
93 #define OPNAME(op, ...) #op,
94 FOR_EACH_OPCODE(OPNAME)
95 #undef OPNAME
98 /************************************************************************/
100 static bool DecompileArgumentFromStack(JSContext* cx, int formalIndex,
101 UniqueChars* res);
103 /* static */ const char PCCounts::numExecName[] = "interp";
105 [[nodiscard]] static bool DumpIonScriptCounts(StringPrinter* sp,
106 HandleScript script,
107 jit::IonScriptCounts* ionCounts) {
108 sp->printf("IonScript [%zu blocks]:\n", ionCounts->numBlocks());
110 for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
111 const jit::IonBlockCounts& block = ionCounts->block(i);
112 unsigned lineNumber = 0;
113 JS::LimitedColumnNumberOneOrigin columnNumber;
114 lineNumber = PCToLineNumber(script, script->offsetToPC(block.offset()),
115 &columnNumber);
116 sp->printf("BB #%" PRIu32 " [%05u,%u,%u]", block.id(), block.offset(),
117 lineNumber, columnNumber.oneOriginValue());
118 if (block.description()) {
119 sp->printf(" [inlined %s]", block.description());
121 for (size_t j = 0; j < block.numSuccessors(); j++) {
122 sp->printf(" -> #%" PRIu32, block.successor(j));
124 sp->printf(" :: %" PRIu64 " hits\n", block.hitCount());
125 sp->printf("%s\n", block.code());
128 return true;
131 [[nodiscard]] static bool DumpPCCounts(JSContext* cx, HandleScript script,
132 StringPrinter* sp) {
133 MOZ_ASSERT(script->hasScriptCounts());
135 // Ensure the Disassemble1 call below does not discard the script counts.
136 gc::AutoSuppressGC suppress(cx);
138 #ifdef DEBUG
139 jsbytecode* pc = script->code();
140 while (pc < script->codeEnd()) {
141 jsbytecode* next = GetNextPc(pc);
143 if (!Disassemble1(cx, script, pc, script->pcToOffset(pc), true, sp)) {
144 return false;
147 sp->put(" {");
149 PCCounts* counts = script->maybeGetPCCounts(pc);
150 if (double val = counts ? counts->numExec() : 0.0) {
151 sp->printf("\"%s\": %.0f", PCCounts::numExecName, val);
153 sp->put("}\n");
155 pc = next;
157 #endif
159 jit::IonScriptCounts* ionCounts = script->getIonCounts();
160 while (ionCounts) {
161 if (!DumpIonScriptCounts(sp, script, ionCounts)) {
162 return false;
165 ionCounts = ionCounts->previous();
168 return true;
171 bool js::DumpRealmPCCounts(JSContext* cx) {
172 Rooted<GCVector<JSScript*>> scripts(cx, GCVector<JSScript*>(cx));
173 for (auto base = cx->zone()->cellIter<BaseScript>(); !base.done();
174 base.next()) {
175 if (base->realm() != cx->realm()) {
176 continue;
178 MOZ_ASSERT_IF(base->hasScriptCounts(), base->hasBytecode());
179 if (base->hasScriptCounts()) {
180 if (!scripts.append(base->asJSScript())) {
181 return false;
186 for (uint32_t i = 0; i < scripts.length(); i++) {
187 HandleScript script = scripts[i];
188 Sprinter sprinter(cx);
189 if (!sprinter.init()) {
190 return false;
193 const char* filename = script->filename();
194 if (!filename) {
195 filename = "(unknown)";
197 fprintf(stdout, "--- SCRIPT %s:%u ---\n", filename, script->lineno());
198 if (!DumpPCCounts(cx, script, &sprinter)) {
199 return false;
201 JS::UniqueChars out = sprinter.release();
202 if (!out) {
203 return false;
205 fputs(out.get(), stdout);
206 fprintf(stdout, "--- END SCRIPT %s:%u ---\n", filename, script->lineno());
209 return true;
212 /////////////////////////////////////////////////////////////////////
213 // Bytecode Parser
214 /////////////////////////////////////////////////////////////////////
216 // Stores the information about the stack slot, where the value comes from.
217 // Elements of BytecodeParser::Bytecode.{offsetStack,offsetStackAfter} arrays.
218 class OffsetAndDefIndex {
219 // The offset of the PC that pushed the value for this slot.
220 uint32_t offset_;
222 // The index in `ndefs` for the PC (0-origin)
223 uint8_t defIndex_;
225 enum : uint8_t {
226 Normal = 0,
228 // Ignored this value in the expression decompilation.
229 // Used by JSOp::NopDestructuring. See BytecodeParser::simulateOp.
230 Ignored,
232 // The value in this slot comes from 2 or more paths.
233 // offset_ and defIndex_ holds the information for the path that
234 // reaches here first.
235 Merged,
236 } type_;
238 public:
239 uint32_t offset() const {
240 MOZ_ASSERT(!isSpecial());
241 return offset_;
243 uint32_t specialOffset() const {
244 MOZ_ASSERT(isSpecial());
245 return offset_;
248 uint8_t defIndex() const {
249 MOZ_ASSERT(!isSpecial());
250 return defIndex_;
252 uint8_t specialDefIndex() const {
253 MOZ_ASSERT(isSpecial());
254 return defIndex_;
257 bool isSpecial() const { return type_ != Normal; }
258 bool isMerged() const { return type_ == Merged; }
259 bool isIgnored() const { return type_ == Ignored; }
261 void set(uint32_t aOffset, uint8_t aDefIndex) {
262 offset_ = aOffset;
263 defIndex_ = aDefIndex;
264 type_ = Normal;
267 // Keep offset_ and defIndex_ values for stack dump.
268 void setMerged() { type_ = Merged; }
269 void setIgnored() { type_ = Ignored; }
271 bool operator==(const OffsetAndDefIndex& rhs) const {
272 return offset_ == rhs.offset_ && defIndex_ == rhs.defIndex_;
275 bool operator!=(const OffsetAndDefIndex& rhs) const {
276 return !(*this == rhs);
280 namespace {
282 class BytecodeParser {
283 public:
284 enum class JumpKind {
285 Simple,
286 SwitchCase,
287 SwitchDefault,
288 TryCatch,
289 TryFinally
292 private:
293 class Bytecode {
294 public:
295 explicit Bytecode(const LifoAllocPolicy<Fallible>& alloc)
296 : parsed(false),
297 stackDepth(0),
298 offsetStack(nullptr)
299 #if defined(DEBUG) || defined(JS_JITSPEW)
301 stackDepthAfter(0),
302 offsetStackAfter(nullptr),
303 jumpOrigins(alloc)
304 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
308 // Whether this instruction has been analyzed to get its output defines
309 // and stack.
310 bool parsed;
312 // Stack depth before this opcode.
313 uint32_t stackDepth;
315 // Pointer to array of |stackDepth| offsets. An element at position N
316 // in the array is the offset of the opcode that defined the
317 // corresponding stack slot. The top of the stack is at position
318 // |stackDepth - 1|.
319 OffsetAndDefIndex* offsetStack;
321 #if defined(DEBUG) || defined(JS_JITSPEW)
322 // stack depth after this opcode.
323 uint32_t stackDepthAfter;
325 // Pointer to array of |stackDepthAfter| offsets.
326 OffsetAndDefIndex* offsetStackAfter;
328 struct JumpInfo {
329 uint32_t from;
330 JumpKind kind;
332 JumpInfo(uint32_t from_, JumpKind kind_) : from(from_), kind(kind_) {}
335 // A list of offsets of the bytecode that jumps to this bytecode,
336 // exclusing previous bytecode.
337 Vector<JumpInfo, 0, LifoAllocPolicy<Fallible>> jumpOrigins;
338 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
340 bool captureOffsetStack(LifoAlloc& alloc, const OffsetAndDefIndex* stack,
341 uint32_t depth) {
342 stackDepth = depth;
343 if (stackDepth) {
344 offsetStack = alloc.newArray<OffsetAndDefIndex>(stackDepth);
345 if (!offsetStack) {
346 return false;
348 for (uint32_t n = 0; n < stackDepth; n++) {
349 offsetStack[n] = stack[n];
352 return true;
355 #if defined(DEBUG) || defined(JS_JITSPEW)
356 bool captureOffsetStackAfter(LifoAlloc& alloc,
357 const OffsetAndDefIndex* stack,
358 uint32_t depth) {
359 stackDepthAfter = depth;
360 if (stackDepthAfter) {
361 offsetStackAfter = alloc.newArray<OffsetAndDefIndex>(stackDepthAfter);
362 if (!offsetStackAfter) {
363 return false;
365 for (uint32_t n = 0; n < stackDepthAfter; n++) {
366 offsetStackAfter[n] = stack[n];
369 return true;
372 bool addJump(uint32_t from, JumpKind kind) {
373 return jumpOrigins.append(JumpInfo(from, kind));
375 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
377 // When control-flow merges, intersect the stacks, marking slots that
378 // are defined by different offsets and/or defIndices merged.
379 // This is sufficient for forward control-flow. It doesn't grok loops
380 // -- for that you would have to iterate to a fixed point -- but there
381 // shouldn't be operands on the stack at a loop back-edge anyway.
382 void mergeOffsetStack(const OffsetAndDefIndex* stack, uint32_t depth) {
383 MOZ_ASSERT(depth == stackDepth);
384 for (uint32_t n = 0; n < stackDepth; n++) {
385 if (stack[n].isIgnored()) {
386 continue;
388 if (offsetStack[n].isIgnored()) {
389 offsetStack[n] = stack[n];
391 if (offsetStack[n] != stack[n]) {
392 offsetStack[n].setMerged();
398 JSContext* cx_;
399 LifoAlloc& alloc_;
400 RootedScript script_;
402 Bytecode** codeArray_;
404 #if defined(DEBUG) || defined(JS_JITSPEW)
405 // Dedicated mode for stack dump.
406 // Capture stack after each opcode, and also enable special handling for
407 // some opcodes to make stack transition clearer.
408 bool isStackDump;
409 #endif
411 public:
412 BytecodeParser(JSContext* cx, LifoAlloc& alloc, JSScript* script)
413 : cx_(cx),
414 alloc_(alloc),
415 script_(cx, script),
416 codeArray_(nullptr)
417 #ifdef DEBUG
419 isStackDump(false)
420 #endif
424 bool parse();
426 #if defined(DEBUG) || defined(JS_JITSPEW)
427 bool isReachable(const jsbytecode* pc) const { return maybeCode(pc); }
428 #endif
430 uint32_t stackDepthAtPC(uint32_t offset) const {
431 // Sometimes the code generator in debug mode asks about the stack depth
432 // of unreachable code (bug 932180 comment 22). Assume that unreachable
433 // code has no operands on the stack.
434 return getCode(offset).stackDepth;
436 uint32_t stackDepthAtPC(const jsbytecode* pc) const {
437 return stackDepthAtPC(script_->pcToOffset(pc));
440 #if defined(DEBUG) || defined(JS_JITSPEW)
441 uint32_t stackDepthAfterPC(uint32_t offset) const {
442 return getCode(offset).stackDepthAfter;
444 uint32_t stackDepthAfterPC(const jsbytecode* pc) const {
445 return stackDepthAfterPC(script_->pcToOffset(pc));
447 #endif
449 const OffsetAndDefIndex& offsetForStackOperand(uint32_t offset,
450 int operand) const {
451 Bytecode& code = getCode(offset);
452 if (operand < 0) {
453 operand += code.stackDepth;
454 MOZ_ASSERT(operand >= 0);
456 MOZ_ASSERT(uint32_t(operand) < code.stackDepth);
457 return code.offsetStack[operand];
459 jsbytecode* pcForStackOperand(jsbytecode* pc, int operand,
460 uint8_t* defIndex) const {
461 size_t offset = script_->pcToOffset(pc);
462 const OffsetAndDefIndex& offsetAndDefIndex =
463 offsetForStackOperand(offset, operand);
464 if (offsetAndDefIndex.isSpecial()) {
465 return nullptr;
467 *defIndex = offsetAndDefIndex.defIndex();
468 return script_->offsetToPC(offsetAndDefIndex.offset());
471 #if defined(DEBUG) || defined(JS_JITSPEW)
472 const OffsetAndDefIndex& offsetForStackOperandAfterPC(uint32_t offset,
473 int operand) const {
474 Bytecode& code = getCode(offset);
475 if (operand < 0) {
476 operand += code.stackDepthAfter;
477 MOZ_ASSERT(operand >= 0);
479 MOZ_ASSERT(uint32_t(operand) < code.stackDepthAfter);
480 return code.offsetStackAfter[operand];
483 template <typename Callback>
484 bool forEachJumpOrigins(jsbytecode* pc, Callback callback) const {
485 Bytecode& code = getCode(script_->pcToOffset(pc));
487 for (Bytecode::JumpInfo& info : code.jumpOrigins) {
488 if (!callback(script_->offsetToPC(info.from), info.kind)) {
489 return false;
493 return true;
496 void setStackDump() { isStackDump = true; }
497 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
499 private:
500 LifoAlloc& alloc() { return alloc_; }
502 void reportOOM() { ReportOutOfMemory(cx_); }
504 uint32_t maximumStackDepth() const {
505 return script_->nslots() - script_->nfixed();
508 Bytecode& getCode(uint32_t offset) const {
509 MOZ_ASSERT(offset < script_->length());
510 MOZ_ASSERT(codeArray_[offset]);
511 return *codeArray_[offset];
514 Bytecode* maybeCode(uint32_t offset) const {
515 MOZ_ASSERT(offset < script_->length());
516 return codeArray_[offset];
519 #if defined(DEBUG) || defined(JS_JITSPEW)
520 Bytecode* maybeCode(const jsbytecode* pc) const {
521 return maybeCode(script_->pcToOffset(pc));
523 #endif
525 uint32_t simulateOp(JSOp op, uint32_t offset, OffsetAndDefIndex* offsetStack,
526 uint32_t stackDepth);
528 inline bool recordBytecode(uint32_t offset,
529 const OffsetAndDefIndex* offsetStack,
530 uint32_t stackDepth);
532 inline bool addJump(uint32_t offset, uint32_t stackDepth,
533 const OffsetAndDefIndex* offsetStack, jsbytecode* pc,
534 JumpKind kind);
537 } // anonymous namespace
539 uint32_t BytecodeParser::simulateOp(JSOp op, uint32_t offset,
540 OffsetAndDefIndex* offsetStack,
541 uint32_t stackDepth) {
542 jsbytecode* pc = script_->offsetToPC(offset);
543 uint32_t nuses = GetUseCount(pc);
544 uint32_t ndefs = GetDefCount(pc);
546 MOZ_RELEASE_ASSERT(stackDepth >= nuses);
547 stackDepth -= nuses;
548 MOZ_RELEASE_ASSERT(stackDepth + ndefs <= maximumStackDepth());
550 #ifdef DEBUG
551 if (isStackDump) {
552 // Opcodes that modifies the object but keeps it on the stack while
553 // initialization should be listed here instead of switch below.
554 // For error message, they shouldn't be shown as the original object
555 // after adding properties.
556 // For stack dump, keeping the input is better.
557 switch (op) {
558 case JSOp::InitHiddenProp:
559 case JSOp::InitHiddenPropGetter:
560 case JSOp::InitHiddenPropSetter:
561 case JSOp::InitLockedProp:
562 case JSOp::InitProp:
563 case JSOp::InitPropGetter:
564 case JSOp::InitPropSetter:
565 case JSOp::MutateProto:
566 case JSOp::SetFunName:
567 // Keep the second value.
568 MOZ_ASSERT(nuses == 2);
569 MOZ_ASSERT(ndefs == 1);
570 goto end;
572 case JSOp::InitElem:
573 case JSOp::InitElemGetter:
574 case JSOp::InitElemSetter:
575 case JSOp::InitHiddenElem:
576 case JSOp::InitHiddenElemGetter:
577 case JSOp::InitHiddenElemSetter:
578 case JSOp::InitLockedElem:
579 // Keep the third value.
580 MOZ_ASSERT(nuses == 3);
581 MOZ_ASSERT(ndefs == 1);
582 goto end;
584 default:
585 break;
588 #endif /* DEBUG */
590 // Mark the current offset as defining its values on the offset stack,
591 // unless it just reshuffles the stack. In that case we want to preserve
592 // the opcode that generated the original value.
593 switch (op) {
594 default:
595 for (uint32_t n = 0; n != ndefs; ++n) {
596 offsetStack[stackDepth + n].set(offset, n);
598 break;
600 case JSOp::NopDestructuring:
601 // Poison the last offset to not obfuscate the error message.
602 offsetStack[stackDepth - 1].setIgnored();
603 break;
605 case JSOp::Case:
606 // Keep the switch value.
607 MOZ_ASSERT(ndefs == 1);
608 break;
610 case JSOp::Dup:
611 MOZ_ASSERT(ndefs == 2);
612 offsetStack[stackDepth + 1] = offsetStack[stackDepth];
613 break;
615 case JSOp::Dup2:
616 MOZ_ASSERT(ndefs == 4);
617 offsetStack[stackDepth + 2] = offsetStack[stackDepth];
618 offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1];
619 break;
621 case JSOp::DupAt: {
622 MOZ_ASSERT(ndefs == 1);
623 unsigned n = GET_UINT24(pc);
624 MOZ_ASSERT(n < stackDepth);
625 offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n];
626 break;
629 case JSOp::Swap: {
630 MOZ_ASSERT(ndefs == 2);
631 OffsetAndDefIndex tmp = offsetStack[stackDepth + 1];
632 offsetStack[stackDepth + 1] = offsetStack[stackDepth];
633 offsetStack[stackDepth] = tmp;
634 break;
637 case JSOp::Pick: {
638 unsigned n = GET_UINT8(pc);
639 MOZ_ASSERT(ndefs == n + 1);
640 uint32_t top = stackDepth + n;
641 OffsetAndDefIndex tmp = offsetStack[stackDepth];
642 for (uint32_t i = stackDepth; i < top; i++) {
643 offsetStack[i] = offsetStack[i + 1];
645 offsetStack[top] = tmp;
646 break;
649 case JSOp::Unpick: {
650 unsigned n = GET_UINT8(pc);
651 MOZ_ASSERT(ndefs == n + 1);
652 uint32_t top = stackDepth + n;
653 OffsetAndDefIndex tmp = offsetStack[top];
654 for (uint32_t i = top; i > stackDepth; i--) {
655 offsetStack[i] = offsetStack[i - 1];
657 offsetStack[stackDepth] = tmp;
658 break;
661 case JSOp::And:
662 case JSOp::CheckIsObj:
663 case JSOp::CheckObjCoercible:
664 case JSOp::CheckThis:
665 case JSOp::CheckThisReinit:
666 case JSOp::CheckClassHeritage:
667 case JSOp::DebugCheckSelfHosted:
668 case JSOp::InitGLexical:
669 case JSOp::InitLexical:
670 case JSOp::Or:
671 case JSOp::Coalesce:
672 case JSOp::SetAliasedVar:
673 case JSOp::SetArg:
674 case JSOp::SetIntrinsic:
675 case JSOp::SetLocal:
676 case JSOp::InitAliasedLexical:
677 case JSOp::CheckLexical:
678 case JSOp::CheckAliasedLexical:
679 // Keep the top value.
680 MOZ_ASSERT(nuses == 1);
681 MOZ_ASSERT(ndefs == 1);
682 break;
684 case JSOp::InitHomeObject:
685 // Pop the top value, keep the other value.
686 MOZ_ASSERT(nuses == 2);
687 MOZ_ASSERT(ndefs == 1);
688 break;
690 case JSOp::CheckResumeKind:
691 // Pop the top two values, keep the other value.
692 MOZ_ASSERT(nuses == 3);
693 MOZ_ASSERT(ndefs == 1);
694 break;
696 case JSOp::SetGName:
697 case JSOp::SetName:
698 case JSOp::SetProp:
699 case JSOp::StrictSetGName:
700 case JSOp::StrictSetName:
701 case JSOp::StrictSetProp:
702 // Keep the top value, removing other 1 value.
703 MOZ_ASSERT(nuses == 2);
704 MOZ_ASSERT(ndefs == 1);
705 offsetStack[stackDepth] = offsetStack[stackDepth + 1];
706 break;
708 case JSOp::SetPropSuper:
709 case JSOp::StrictSetPropSuper:
710 // Keep the top value, removing other 2 values.
711 MOZ_ASSERT(nuses == 3);
712 MOZ_ASSERT(ndefs == 1);
713 offsetStack[stackDepth] = offsetStack[stackDepth + 2];
714 break;
716 case JSOp::SetElemSuper:
717 case JSOp::StrictSetElemSuper:
718 // Keep the top value, removing other 3 values.
719 MOZ_ASSERT(nuses == 4);
720 MOZ_ASSERT(ndefs == 1);
721 offsetStack[stackDepth] = offsetStack[stackDepth + 3];
722 break;
724 case JSOp::IsGenClosing:
725 case JSOp::IsNoIter:
726 case JSOp::IsNullOrUndefined:
727 case JSOp::MoreIter:
728 case JSOp::CanSkipAwait:
729 // Keep the top value and push one more value.
730 MOZ_ASSERT(nuses == 1);
731 MOZ_ASSERT(ndefs == 2);
732 offsetStack[stackDepth + 1].set(offset, 1);
733 break;
735 case JSOp::MaybeExtractAwaitValue:
736 // Keep the top value and replace the second to top value.
737 MOZ_ASSERT(nuses == 2);
738 MOZ_ASSERT(ndefs == 2);
739 offsetStack[stackDepth].set(offset, 0);
740 break;
742 case JSOp::CheckPrivateField:
743 // Keep the top two values, and push one new value.
744 MOZ_ASSERT(nuses == 2);
745 MOZ_ASSERT(ndefs == 3);
746 offsetStack[stackDepth + 2].set(offset, 2);
747 break;
750 #ifdef DEBUG
751 end:
752 #endif /* DEBUG */
754 stackDepth += ndefs;
755 return stackDepth;
758 bool BytecodeParser::recordBytecode(uint32_t offset,
759 const OffsetAndDefIndex* offsetStack,
760 uint32_t stackDepth) {
761 MOZ_RELEASE_ASSERT(offset < script_->length());
762 MOZ_RELEASE_ASSERT(stackDepth <= maximumStackDepth());
764 Bytecode*& code = codeArray_[offset];
765 if (!code) {
766 code = alloc().new_<Bytecode>(alloc());
767 if (!code || !code->captureOffsetStack(alloc(), offsetStack, stackDepth)) {
768 reportOOM();
769 return false;
771 } else {
772 code->mergeOffsetStack(offsetStack, stackDepth);
775 return true;
778 bool BytecodeParser::addJump(uint32_t offset, uint32_t stackDepth,
779 const OffsetAndDefIndex* offsetStack,
780 jsbytecode* pc, JumpKind kind) {
781 if (!recordBytecode(offset, offsetStack, stackDepth)) {
782 return false;
785 #ifdef DEBUG
786 uint32_t currentOffset = script_->pcToOffset(pc);
787 if (isStackDump) {
788 if (!codeArray_[offset]->addJump(currentOffset, kind)) {
789 reportOOM();
790 return false;
794 // If this is a backedge, assert we parsed the target JSOp::LoopHead.
795 MOZ_ASSERT_IF(offset < currentOffset, codeArray_[offset]->parsed);
796 #endif /* DEBUG */
798 return true;
801 bool BytecodeParser::parse() {
802 MOZ_ASSERT(!codeArray_);
804 uint32_t length = script_->length();
805 codeArray_ = alloc().newArray<Bytecode*>(length);
807 if (!codeArray_) {
808 reportOOM();
809 return false;
812 mozilla::PodZero(codeArray_, length);
814 // Fill in stack depth and definitions at initial bytecode.
815 Bytecode* startcode = alloc().new_<Bytecode>(alloc());
816 if (!startcode) {
817 reportOOM();
818 return false;
821 // Fill in stack depth and definitions at initial bytecode.
822 OffsetAndDefIndex* offsetStack =
823 alloc().newArray<OffsetAndDefIndex>(maximumStackDepth());
824 if (maximumStackDepth() && !offsetStack) {
825 reportOOM();
826 return false;
829 startcode->stackDepth = 0;
830 codeArray_[0] = startcode;
832 for (uint32_t offset = 0, nextOffset = 0; offset < length;
833 offset = nextOffset) {
834 Bytecode* code = maybeCode(offset);
835 jsbytecode* pc = script_->offsetToPC(offset);
837 // Next bytecode to analyze.
838 nextOffset = offset + GetBytecodeLength(pc);
840 MOZ_RELEASE_ASSERT(*pc < JSOP_LIMIT);
841 JSOp op = JSOp(*pc);
843 if (!code) {
844 // Haven't found a path by which this bytecode is reachable.
845 continue;
848 // On a jump target, we reload the offsetStack saved for the current
849 // bytecode, as it contains either the original offset stack, or the
850 // merged offset stack.
851 if (BytecodeIsJumpTarget(op)) {
852 for (uint32_t n = 0; n < code->stackDepth; ++n) {
853 offsetStack[n] = code->offsetStack[n];
857 if (code->parsed) {
858 // No need to reparse.
859 continue;
862 code->parsed = true;
864 uint32_t stackDepth = simulateOp(op, offset, offsetStack, code->stackDepth);
866 #if defined(DEBUG) || defined(JS_JITSPEW)
867 if (isStackDump) {
868 if (!code->captureOffsetStackAfter(alloc(), offsetStack, stackDepth)) {
869 reportOOM();
870 return false;
873 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
875 switch (op) {
876 case JSOp::TableSwitch: {
877 uint32_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
878 jsbytecode* pc2 = pc + JUMP_OFFSET_LEN;
879 int32_t low = GET_JUMP_OFFSET(pc2);
880 pc2 += JUMP_OFFSET_LEN;
881 int32_t high = GET_JUMP_OFFSET(pc2);
882 pc2 += JUMP_OFFSET_LEN;
884 if (!addJump(defaultOffset, stackDepth, offsetStack, pc,
885 JumpKind::SwitchDefault)) {
886 return false;
889 uint32_t ncases = high - low + 1;
891 for (uint32_t i = 0; i < ncases; i++) {
892 uint32_t targetOffset = script_->tableSwitchCaseOffset(pc, i);
893 if (targetOffset != defaultOffset) {
894 if (!addJump(targetOffset, stackDepth, offsetStack, pc,
895 JumpKind::SwitchCase)) {
896 return false;
900 break;
903 case JSOp::Try: {
904 // Everything between a try and corresponding catch or finally is
905 // conditional. Note that there is no problem with code which is skipped
906 // by a thrown exception but is not caught by a later handler in the
907 // same function: no more code will execute, and it does not matter what
908 // is defined.
909 for (const TryNote& tn : script_->trynotes()) {
910 if (tn.start == offset + JSOpLength_Try) {
911 uint32_t catchOffset = tn.start + tn.length;
912 if (tn.kind() == TryNoteKind::Catch) {
913 if (!addJump(catchOffset, stackDepth, offsetStack, pc,
914 JumpKind::TryCatch)) {
915 return false;
917 } else if (tn.kind() == TryNoteKind::Finally) {
918 // Three additional values will be on the stack at the beginning
919 // of the finally block: the exception/resume index, the exception
920 // stack, and the |throwing| value. For the benefit of the
921 // decompiler, point them at this Try.
922 offsetStack[stackDepth].set(offset, 0);
923 offsetStack[stackDepth + 1].set(offset, 1);
924 offsetStack[stackDepth + 2].set(offset, 2);
925 if (!addJump(catchOffset, stackDepth + 3, offsetStack, pc,
926 JumpKind::TryFinally)) {
927 return false;
932 break;
935 default:
936 break;
939 // Check basic jump opcodes, which may or may not have a fallthrough.
940 if (IsJumpOpcode(op)) {
941 // Case instructions do not push the lvalue back when branching.
942 uint32_t newStackDepth = stackDepth;
943 if (op == JSOp::Case) {
944 newStackDepth--;
947 uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc);
948 if (!addJump(targetOffset, newStackDepth, offsetStack, pc,
949 JumpKind::Simple)) {
950 return false;
954 // Handle any fallthrough from this opcode.
955 if (BytecodeFallsThrough(op)) {
956 if (!recordBytecode(nextOffset, offsetStack, stackDepth)) {
957 return false;
962 return true;
965 #if defined(DEBUG) || defined(JS_JITSPEW)
967 bool js::ReconstructStackDepth(JSContext* cx, JSScript* script, jsbytecode* pc,
968 uint32_t* depth, bool* reachablePC) {
969 LifoAllocScope allocScope(&cx->tempLifoAlloc());
970 BytecodeParser parser(cx, allocScope.alloc(), script);
971 if (!parser.parse()) {
972 return false;
975 *reachablePC = parser.isReachable(pc);
977 if (*reachablePC) {
978 *depth = parser.stackDepthAtPC(pc);
981 return true;
984 static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
985 unsigned loc, bool lines,
986 const BytecodeParser* parser, StringPrinter* sp);
989 * If pc != nullptr, include a prefix indicating whether the PC is at the
990 * current line. If showAll is true, include the entry stack depth.
992 [[nodiscard]] static bool DisassembleAtPC(
993 JSContext* cx, JSScript* scriptArg, bool lines, const jsbytecode* pc,
994 bool showAll, StringPrinter* sp,
995 DisassembleSkeptically skeptically = DisassembleSkeptically::No) {
996 LifoAllocScope allocScope(&cx->tempLifoAlloc());
997 RootedScript script(cx, scriptArg);
998 mozilla::Maybe<BytecodeParser> parser;
1000 if (skeptically == DisassembleSkeptically::No) {
1001 parser.emplace(cx, allocScope.alloc(), script);
1002 parser->setStackDump();
1003 if (!parser->parse()) {
1004 return false;
1008 if (showAll) {
1009 sp->printf("%s:%u\n", script->filename(), unsigned(script->lineno()));
1012 if (pc != nullptr) {
1013 sp->put(" ");
1015 if (showAll) {
1016 sp->put("sn stack ");
1018 sp->put("loc ");
1019 if (lines) {
1020 sp->put("line");
1022 sp->put(" op\n");
1024 if (pc != nullptr) {
1025 sp->put(" ");
1027 if (showAll) {
1028 sp->put("-- ----- ");
1030 sp->put("----- ");
1031 if (lines) {
1032 sp->put("----");
1034 sp->put(" --\n");
1036 jsbytecode* next = script->code();
1037 jsbytecode* end = script->codeEnd();
1038 while (next < end) {
1039 if (next == script->main()) {
1040 sp->put("main:\n");
1042 if (pc != nullptr) {
1043 sp->put(pc == next ? "--> " : " ");
1045 if (showAll) {
1046 if (parser && parser->isReachable(next)) {
1047 sp->printf("%05u ", parser->stackDepthAtPC(next));
1048 } else {
1049 sp->put(" ");
1052 unsigned len = Disassemble1(cx, script, next, script->pcToOffset(next),
1053 lines, parser.ptrOr(nullptr), sp);
1054 if (!len) {
1055 return false;
1058 next += len;
1061 return true;
1064 bool js::Disassemble(JSContext* cx, HandleScript script, bool lines,
1065 StringPrinter* sp, DisassembleSkeptically skeptically) {
1066 return DisassembleAtPC(cx, script, lines, nullptr, false, sp, skeptically);
1069 JS_PUBLIC_API bool js::DumpPC(JSContext* cx, FILE* fp) {
1070 gc::AutoSuppressGC suppressGC(cx);
1071 Sprinter sprinter(cx);
1072 if (!sprinter.init()) {
1073 return false;
1075 ScriptFrameIter iter(cx);
1076 if (iter.done()) {
1077 fprintf(fp, "Empty stack.\n");
1078 return true;
1080 RootedScript script(cx, iter.script());
1081 bool ok = DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter);
1082 JS::UniqueChars out = sprinter.release();
1083 if (!out) {
1084 return false;
1086 fprintf(fp, "%s", out.get());
1087 return ok;
1090 JS_PUBLIC_API bool js::DumpScript(JSContext* cx, JSScript* scriptArg,
1091 FILE* fp) {
1092 gc::AutoSuppressGC suppressGC(cx);
1093 Sprinter sprinter(cx);
1094 if (!sprinter.init()) {
1095 return false;
1097 RootedScript script(cx, scriptArg);
1098 bool ok = Disassemble(cx, script, true, &sprinter);
1099 JS::UniqueChars out = sprinter.release();
1100 if (!out) {
1101 return false;
1103 fprintf(fp, "%s", out.get());
1104 return ok;
1107 static UniqueChars ToDisassemblySource(JSContext* cx, HandleValue v) {
1108 if (v.isString()) {
1109 return QuoteString(cx, v.toString(), '"');
1112 if (JS::RuntimeHeapIsBusy()) {
1113 return DuplicateString(cx, "<value>");
1116 if (v.isObject()) {
1117 JSObject& obj = v.toObject();
1119 if (obj.is<JSFunction>()) {
1120 RootedFunction fun(cx, &obj.as<JSFunction>());
1121 JSString* str = JS_DecompileFunction(cx, fun);
1122 if (!str) {
1123 return nullptr;
1125 return QuoteString(cx, str);
1128 if (obj.is<RegExpObject>()) {
1129 Rooted<RegExpObject*> reobj(cx, &obj.as<RegExpObject>());
1130 JSString* source = RegExpObject::toString(cx, reobj);
1131 if (!source) {
1132 return nullptr;
1134 return QuoteString(cx, source);
1138 JSString* str = ValueToSource(cx, v);
1139 if (!str) {
1140 return nullptr;
1142 return QuoteString(cx, str);
1145 static bool ToDisassemblySource(JSContext* cx, Handle<Scope*> scope,
1146 UniqueChars* bytes) {
1147 UniqueChars source = JS_smprintf("%s {", ScopeKindString(scope->kind()));
1148 if (!source) {
1149 ReportOutOfMemory(cx);
1150 return false;
1153 for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
1154 UniqueChars nameBytes = AtomToPrintableString(cx, bi.name());
1155 if (!nameBytes) {
1156 return false;
1159 source = JS_sprintf_append(std::move(source), "%s: ", nameBytes.get());
1160 if (!source) {
1161 ReportOutOfMemory(cx);
1162 return false;
1165 BindingLocation loc = bi.location();
1166 switch (loc.kind()) {
1167 case BindingLocation::Kind::Global:
1168 source = JS_sprintf_append(std::move(source), "global");
1169 break;
1171 case BindingLocation::Kind::Frame:
1172 source =
1173 JS_sprintf_append(std::move(source), "frame slot %u", loc.slot());
1174 break;
1176 case BindingLocation::Kind::Environment:
1177 source =
1178 JS_sprintf_append(std::move(source), "env slot %u", loc.slot());
1179 break;
1181 case BindingLocation::Kind::Argument:
1182 source =
1183 JS_sprintf_append(std::move(source), "arg slot %u", loc.slot());
1184 break;
1186 case BindingLocation::Kind::NamedLambdaCallee:
1187 source = JS_sprintf_append(std::move(source), "named lambda callee");
1188 break;
1190 case BindingLocation::Kind::Import:
1191 source = JS_sprintf_append(std::move(source), "import");
1192 break;
1195 if (!source) {
1196 ReportOutOfMemory(cx);
1197 return false;
1200 if (!bi.isLast()) {
1201 source = JS_sprintf_append(std::move(source), ", ");
1202 if (!source) {
1203 ReportOutOfMemory(cx);
1204 return false;
1209 source = JS_sprintf_append(std::move(source), "}");
1210 if (!source) {
1211 ReportOutOfMemory(cx);
1212 return false;
1215 *bytes = std::move(source);
1216 return true;
1219 static bool DumpJumpOrigins(HandleScript script, jsbytecode* pc,
1220 const BytecodeParser* parser, StringPrinter* sp) {
1221 bool called = false;
1222 auto callback = [&script, &sp, &called](jsbytecode* pc,
1223 BytecodeParser::JumpKind kind) {
1224 if (!called) {
1225 called = true;
1226 sp->put("\n# ");
1227 } else {
1228 sp->put(", ");
1231 switch (kind) {
1232 case BytecodeParser::JumpKind::Simple:
1233 break;
1235 case BytecodeParser::JumpKind::SwitchCase:
1236 sp->put("switch-case ");
1237 break;
1239 case BytecodeParser::JumpKind::SwitchDefault:
1240 sp->put("switch-default ");
1241 break;
1243 case BytecodeParser::JumpKind::TryCatch:
1244 sp->put("try-catch ");
1245 break;
1247 case BytecodeParser::JumpKind::TryFinally:
1248 sp->put("try-finally ");
1249 break;
1252 sp->printf("from %s @ %05u", CodeName(JSOp(*pc)),
1253 unsigned(script->pcToOffset(pc)));
1255 return true;
1257 if (!parser->forEachJumpOrigins(pc, callback)) {
1258 return false;
1260 if (called) {
1261 sp->put("\n");
1264 return true;
1267 static bool DecompileAtPCForStackDump(
1268 JSContext* cx, HandleScript script,
1269 const OffsetAndDefIndex& offsetAndDefIndex, StringPrinter* sp);
1271 static bool PrintShapeProperties(JSContext* cx, StringPrinter* sp,
1272 SharedShape* shape) {
1273 // Add all property keys to a vector to allow printing them in property
1274 // definition order.
1275 Vector<PropertyKey> props(cx);
1276 for (SharedShapePropertyIter<NoGC> iter(shape); !iter.done(); iter++) {
1277 if (!props.append(iter->key())) {
1278 return false;
1282 sp->put("{");
1284 for (size_t i = props.length(); i > 0; i--) {
1285 PropertyKey key = props[i - 1];
1286 RootedValue keyv(cx, IdToValue(key));
1287 JSString* str = ToString<NoGC>(cx, keyv);
1288 if (!str) {
1289 ReportOutOfMemory(cx);
1290 return false;
1292 sp->putString(cx, str);
1293 if (i > 1) {
1294 sp->put(", ");
1298 sp->put("}");
1299 return true;
1302 static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
1303 unsigned loc, bool lines,
1304 const BytecodeParser* parser, StringPrinter* sp) {
1305 if (parser && parser->isReachable(pc)) {
1306 if (!DumpJumpOrigins(script, pc, parser, sp)) {
1307 return 0;
1311 size_t before = sp->length();
1312 bool stackDumped = false;
1313 auto dumpStack = [&cx, &script, &pc, &parser, &sp, &before, &stackDumped]() {
1314 if (!parser) {
1315 return true;
1317 if (stackDumped) {
1318 return true;
1320 stackDumped = true;
1322 size_t after = sp->length();
1323 MOZ_ASSERT(after >= before);
1325 static const size_t stack_column = 40;
1326 for (size_t i = after - before; i < stack_column - 1; i++) {
1327 sp->put(" ");
1330 sp->put(" # ");
1332 if (!parser->isReachable(pc)) {
1333 sp->put("!!! UNREACHABLE !!!");
1334 } else {
1335 uint32_t depth = parser->stackDepthAfterPC(pc);
1337 for (uint32_t i = 0; i < depth; i++) {
1338 if (i) {
1339 sp->put(" ");
1342 const OffsetAndDefIndex& offsetAndDefIndex =
1343 parser->offsetForStackOperandAfterPC(script->pcToOffset(pc), i);
1344 // This will decompile the stack for the same PC many times.
1345 // We'll avoid optimizing it since this is a testing function
1346 // and it won't be worth managing cached expression here.
1347 if (!DecompileAtPCForStackDump(cx, script, offsetAndDefIndex, sp)) {
1348 return false;
1353 return true;
1356 if (*pc >= JSOP_LIMIT) {
1357 char numBuf1[12], numBuf2[12];
1358 SprintfLiteral(numBuf1, "%d", int(*pc));
1359 SprintfLiteral(numBuf2, "%d", JSOP_LIMIT);
1360 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1361 JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2);
1362 return 0;
1364 JSOp op = JSOp(*pc);
1365 const JSCodeSpec& cs = CodeSpec(op);
1366 const unsigned len = cs.length;
1367 sp->printf("%05u:", loc);
1368 if (lines) {
1369 sp->printf("%4u", PCToLineNumber(script, pc));
1371 sp->printf(" %s", CodeName(op));
1373 int i;
1374 switch (JOF_TYPE(cs.format)) {
1375 case JOF_BYTE:
1376 break;
1378 case JOF_JUMP: {
1379 ptrdiff_t off = GET_JUMP_OFFSET(pc);
1380 sp->printf(" %u (%+d)", unsigned(loc + int(off)), int(off));
1381 break;
1384 case JOF_SCOPE: {
1385 Rooted<Scope*> scope(cx, script->getScope(pc));
1386 UniqueChars bytes;
1387 if (!ToDisassemblySource(cx, scope, &bytes)) {
1388 return 0;
1390 sp->printf(" %s", bytes.get());
1391 break;
1394 case JOF_ENVCOORD: {
1395 RootedValue v(cx, StringValue(EnvironmentCoordinateNameSlow(script, pc)));
1396 UniqueChars bytes = ToDisassemblySource(cx, v);
1397 if (!bytes) {
1398 return 0;
1400 EnvironmentCoordinate ec(pc);
1401 sp->printf(" %s (hops = %u, slot = %u)", bytes.get(), ec.hops(),
1402 ec.slot());
1403 break;
1405 case JOF_DEBUGCOORD: {
1406 EnvironmentCoordinate ec(pc);
1407 sp->printf("(hops = %u, slot = %u)", ec.hops(), ec.slot());
1408 break;
1410 case JOF_ATOM: {
1411 RootedValue v(cx, StringValue(script->getAtom(pc)));
1412 UniqueChars bytes = ToDisassemblySource(cx, v);
1413 if (!bytes) {
1414 return 0;
1416 sp->printf(" %s", bytes.get());
1417 break;
1419 case JOF_STRING: {
1420 RootedValue v(cx, StringValue(script->getString(pc)));
1421 UniqueChars bytes = ToDisassemblySource(cx, v);
1422 if (!bytes) {
1423 return 0;
1425 sp->printf(" %s", bytes.get());
1426 break;
1429 case JOF_DOUBLE: {
1430 double d = GET_INLINE_VALUE(pc).toDouble();
1431 sp->printf(" %lf", d);
1432 break;
1435 case JOF_BIGINT: {
1436 RootedValue v(cx, BigIntValue(script->getBigInt(pc)));
1437 UniqueChars bytes = ToDisassemblySource(cx, v);
1438 if (!bytes) {
1439 return 0;
1441 sp->printf(" %s", bytes.get());
1442 break;
1445 case JOF_OBJECT: {
1446 JSObject* obj = script->getObject(pc);
1448 RootedValue v(cx, ObjectValue(*obj));
1449 UniqueChars bytes = ToDisassemblySource(cx, v);
1450 if (!bytes) {
1451 return 0;
1453 sp->printf(" %s", bytes.get());
1455 break;
1458 case JOF_SHAPE: {
1459 SharedShape* shape = script->getShape(pc);
1460 sp->put(" ");
1461 if (!PrintShapeProperties(cx, sp, shape)) {
1462 return 0;
1464 break;
1467 case JOF_REGEXP: {
1468 js::RegExpObject* obj = script->getRegExp(pc);
1469 RootedValue v(cx, ObjectValue(*obj));
1470 UniqueChars bytes = ToDisassemblySource(cx, v);
1471 if (!bytes) {
1472 return 0;
1474 sp->printf(" %s", bytes.get());
1475 break;
1478 case JOF_TABLESWITCH: {
1479 int32_t i, low, high;
1481 ptrdiff_t off = GET_JUMP_OFFSET(pc);
1482 jsbytecode* pc2 = pc + JUMP_OFFSET_LEN;
1483 low = GET_JUMP_OFFSET(pc2);
1484 pc2 += JUMP_OFFSET_LEN;
1485 high = GET_JUMP_OFFSET(pc2);
1486 pc2 += JUMP_OFFSET_LEN;
1487 sp->printf(" defaultOffset %d low %d high %d", int(off), low, high);
1489 // Display stack dump before diplaying the offsets for each case.
1490 if (!dumpStack()) {
1491 return 0;
1494 for (i = low; i <= high; i++) {
1495 off =
1496 script->tableSwitchCaseOffset(pc, i - low) - script->pcToOffset(pc);
1497 sp->printf("\n\t%d: %d", i, int(off));
1499 break;
1502 case JOF_QARG:
1503 sp->printf(" %u", GET_ARGNO(pc));
1504 break;
1506 case JOF_LOCAL:
1507 sp->printf(" %u", GET_LOCALNO(pc));
1508 break;
1510 case JOF_GCTHING:
1511 sp->printf(" %u", unsigned(GET_GCTHING_INDEX(pc)));
1512 break;
1514 case JOF_UINT32:
1515 sp->printf(" %u", GET_UINT32(pc));
1516 break;
1518 case JOF_ICINDEX:
1519 sp->printf(" (ic: %u)", GET_ICINDEX(pc));
1520 break;
1522 case JOF_LOOPHEAD:
1523 sp->printf(" (ic: %u, depthHint: %u)", GET_ICINDEX(pc),
1524 LoopHeadDepthHint(pc));
1525 break;
1527 case JOF_TWO_UINT8: {
1528 int one = (int)GET_UINT8(pc);
1529 int two = (int)GET_UINT8(pc + 1);
1531 sp->printf(" %d", one);
1532 sp->printf(" %d", two);
1533 break;
1536 case JOF_ARGC:
1537 case JOF_UINT16:
1538 i = (int)GET_UINT16(pc);
1539 goto print_int;
1541 case JOF_RESUMEINDEX:
1542 case JOF_UINT24:
1543 MOZ_ASSERT(len == 4);
1544 i = (int)GET_UINT24(pc);
1545 goto print_int;
1547 case JOF_UINT8:
1548 i = GET_UINT8(pc);
1549 goto print_int;
1551 case JOF_INT8:
1552 i = GET_INT8(pc);
1553 goto print_int;
1555 case JOF_INT32:
1556 MOZ_ASSERT(op == JSOp::Int32);
1557 i = GET_INT32(pc);
1558 print_int:
1559 sp->printf(" %d", i);
1560 break;
1562 default: {
1563 char numBuf[12];
1564 SprintfLiteral(numBuf, "%x", cs.format);
1565 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1566 JSMSG_UNKNOWN_FORMAT, numBuf);
1567 return 0;
1571 if (!dumpStack()) {
1572 return 0;
1575 sp->put("\n");
1576 return len;
1579 unsigned js::Disassemble1(JSContext* cx, JS::Handle<JSScript*> script,
1580 jsbytecode* pc, unsigned loc, bool lines,
1581 StringPrinter* sp) {
1582 return Disassemble1(cx, script, pc, loc, lines, nullptr, sp);
1585 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
1587 namespace {
1589 * The expression decompiler is invoked by error handling code to produce a
1590 * string representation of the erroring expression. As it's only a debugging
1591 * tool, it only supports basic expressions. For anything complicated, it simply
1592 * puts "(intermediate value)" into the error result.
1594 * Here's the basic algorithm:
1596 * 1. Find the stack location of the value whose expression we wish to
1597 * decompile. The error handler can explicitly pass this as an
1598 * argument. Otherwise, we search backwards down the stack for the offending
1599 * value.
1601 * 2. Instantiate and run a BytecodeParser for the current frame. This creates a
1602 * stack of pcs parallel to the interpreter stack; given an interpreter stack
1603 * location, the corresponding pc stack location contains the opcode that pushed
1604 * the value in the interpreter. Now, with the result of step 1, we have the
1605 * opcode responsible for pushing the value we want to decompile.
1607 * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler
1608 * routine, responsible for a string representation of the expression that
1609 * generated a certain stack location. decompilePC looks at one opcode and
1610 * returns the JS source equivalent of that opcode.
1612 * 4. Expressions can, of course, contain subexpressions. For example, the
1613 * literals "4" and "5" are subexpressions of the addition operator in "4 +
1614 * 5". If we need to decompile a subexpression, we call decompilePC (step 2)
1615 * recursively on the operands' pcs. The result is a depth-first traversal of
1616 * the expression tree.
1619 struct ExpressionDecompiler {
1620 JSContext* cx;
1621 RootedScript script;
1622 const BytecodeParser& parser;
1623 Sprinter sprinter;
1625 #if defined(DEBUG) || defined(JS_JITSPEW)
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
1632 ExpressionDecompiler(JSContext* cx, JSScript* script,
1633 const BytecodeParser& parser)
1634 : cx(cx),
1635 script(cx, script),
1636 parser(parser),
1637 sprinter(cx)
1638 #if defined(DEBUG) || defined(JS_JITSPEW)
1640 isStackDump(false)
1641 #endif
1644 bool init();
1645 bool decompilePCForStackOperand(jsbytecode* pc, int i);
1646 bool decompilePC(jsbytecode* pc, uint8_t defIndex);
1647 bool decompilePC(const OffsetAndDefIndex& offsetAndDefIndex);
1648 JSAtom* getArg(unsigned slot);
1649 JSAtom* loadAtom(jsbytecode* pc);
1650 JSString* loadString(jsbytecode* pc);
1651 bool quote(JSString* s, char quote);
1652 bool write(const char* s);
1653 bool write(JSString* str);
1654 UniqueChars getOutput();
1655 #if defined(DEBUG) || defined(JS_JITSPEW)
1656 void setStackDump() { isStackDump = true; }
1657 #endif
1660 bool ExpressionDecompiler::decompilePCForStackOperand(jsbytecode* pc, int i) {
1661 return decompilePC(parser.offsetForStackOperand(script->pcToOffset(pc), i));
1664 bool ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) {
1665 MOZ_ASSERT(script->containsPC(pc));
1667 JSOp op = (JSOp)*pc;
1669 if (const char* token = CodeToken[uint8_t(op)]) {
1670 MOZ_ASSERT(defIndex == 0);
1671 MOZ_ASSERT(CodeSpec(op).ndefs == 1);
1673 // Handle simple cases of binary and unary operators.
1674 switch (CodeSpec(op).nuses) {
1675 case 2: {
1676 const char* extra = "";
1678 MOZ_ASSERT(pc + 1 < script->codeEnd(),
1679 "binary opcode shouldn't be the last opcode in the script");
1680 if (CodeSpec(op).length == 1 &&
1681 (JSOp)(*(pc + 1)) == JSOp::NopIsAssignOp) {
1682 extra = "=";
1685 return write("(") && decompilePCForStackOperand(pc, -2) && write(" ") &&
1686 write(token) && write(extra) && write(" ") &&
1687 decompilePCForStackOperand(pc, -1) && write(")");
1688 break;
1690 case 1:
1691 return write("(") && write(token) &&
1692 decompilePCForStackOperand(pc, -1) && write(")");
1693 default:
1694 break;
1698 switch (op) {
1699 case JSOp::DelName:
1700 return write("(delete ") && write(loadAtom(pc)) && write(")");
1702 case JSOp::GetGName:
1703 case JSOp::GetName:
1704 case JSOp::GetIntrinsic:
1705 return write(loadAtom(pc));
1706 case JSOp::GetArg: {
1707 unsigned slot = GET_ARGNO(pc);
1709 // For self-hosted scripts that are called from non-self-hosted code,
1710 // decompiling the parameter name in the self-hosted script is
1711 // unhelpful. Decompile the argument name instead.
1712 if (script->selfHosted()
1713 #ifdef DEBUG
1714 // For stack dump, argument name is not necessary.
1715 && !isStackDump
1716 #endif /* DEBUG */
1718 UniqueChars result;
1719 if (!DecompileArgumentFromStack(cx, slot, &result)) {
1720 return false;
1723 // Note that decompiling the argument in the parent frame might
1724 // not succeed.
1725 if (result) {
1726 return write(result.get());
1729 // If it fails, do not return parameter name and let the caller
1730 // fallback.
1731 return write("(intermediate value)");
1734 JSAtom* atom = getArg(slot);
1735 if (!atom) {
1736 return false;
1738 return write(atom);
1740 case JSOp::GetLocal: {
1741 JSAtom* atom = FrameSlotName(script, pc);
1742 MOZ_ASSERT(atom);
1743 return write(atom);
1745 case JSOp::GetAliasedVar: {
1746 JSAtom* atom = EnvironmentCoordinateNameSlow(script, pc);
1747 MOZ_ASSERT(atom);
1748 return write(atom);
1751 case JSOp::DelProp:
1752 case JSOp::StrictDelProp:
1753 case JSOp::GetProp:
1754 case JSOp::GetBoundName: {
1755 bool hasDelete = op == JSOp::DelProp || op == JSOp::StrictDelProp;
1756 Rooted<JSAtom*> prop(cx, loadAtom(pc));
1757 MOZ_ASSERT(prop);
1758 return (hasDelete ? write("(delete ") : true) &&
1759 decompilePCForStackOperand(pc, -1) &&
1760 (IsIdentifier(prop)
1761 ? write(".") && quote(prop, '\0')
1762 : write("[") && quote(prop, '\'') && write("]")) &&
1763 (hasDelete ? write(")") : true);
1765 case JSOp::GetPropSuper: {
1766 Rooted<JSAtom*> prop(cx, loadAtom(pc));
1767 return write("super.") && quote(prop, '\0');
1769 case JSOp::SetElem:
1770 case JSOp::StrictSetElem:
1771 // NOTE: We don't show the right hand side of the operation because
1772 // it's used in error messages like: "a[0] is not readable".
1774 // We could though.
1775 return decompilePCForStackOperand(pc, -3) && write("[") &&
1776 decompilePCForStackOperand(pc, -2) && write("]");
1778 case JSOp::DelElem:
1779 case JSOp::StrictDelElem:
1780 case JSOp::GetElem: {
1781 bool hasDelete = (op == JSOp::DelElem || op == JSOp::StrictDelElem);
1782 return (hasDelete ? write("(delete ") : true) &&
1783 decompilePCForStackOperand(pc, -2) && write("[") &&
1784 decompilePCForStackOperand(pc, -1) && write("]") &&
1785 (hasDelete ? write(")") : true);
1788 case JSOp::GetElemSuper:
1789 return write("super[") && decompilePCForStackOperand(pc, -2) &&
1790 write("]");
1791 case JSOp::Null:
1792 return write("null");
1793 case JSOp::True:
1794 return write("true");
1795 case JSOp::False:
1796 return write("false");
1797 case JSOp::Zero:
1798 case JSOp::One:
1799 case JSOp::Int8:
1800 case JSOp::Uint16:
1801 case JSOp::Uint24:
1802 case JSOp::Int32:
1803 sprinter.printf("%d", GetBytecodeInteger(pc));
1804 return true;
1805 case JSOp::String:
1806 return quote(loadString(pc), '"');
1807 case JSOp::Symbol: {
1808 unsigned i = uint8_t(pc[1]);
1809 MOZ_ASSERT(i < JS::WellKnownSymbolLimit);
1810 if (i < JS::WellKnownSymbolLimit) {
1811 return write(cx->names().wellKnownSymbolDescriptions()[i]);
1813 break;
1815 case JSOp::Undefined:
1816 return write("undefined");
1817 case JSOp::GlobalThis:
1818 case JSOp::NonSyntacticGlobalThis:
1819 // |this| could convert to a very long object initialiser, so cite it by
1820 // its keyword name.
1821 return write("this");
1822 case JSOp::NewTarget:
1823 return write("new.target");
1824 case JSOp::ImportMeta:
1825 return write("import.meta");
1826 case JSOp::Call:
1827 case JSOp::CallContent:
1828 case JSOp::CallIgnoresRv:
1829 case JSOp::CallIter:
1830 case JSOp::CallContentIter: {
1831 uint16_t argc = GET_ARGC(pc);
1832 return decompilePCForStackOperand(pc, -int32_t(argc + 2)) &&
1833 write(argc ? "(...)" : "()");
1835 case JSOp::SpreadCall:
1836 return decompilePCForStackOperand(pc, -3) && write("(...)");
1837 case JSOp::NewArray:
1838 return write("[]");
1839 case JSOp::RegExp: {
1840 Rooted<RegExpObject*> obj(cx, &script->getObject(pc)->as<RegExpObject>());
1841 JSString* str = RegExpObject::toString(cx, obj);
1842 if (!str) {
1843 return false;
1845 return write(str);
1847 case JSOp::Object: {
1848 JSObject* obj = script->getObject(pc);
1849 RootedValue objv(cx, ObjectValue(*obj));
1850 JSString* str = ValueToSource(cx, objv);
1851 if (!str) {
1852 return false;
1854 return write(str);
1856 case JSOp::Void:
1857 return write("(void ") && decompilePCForStackOperand(pc, -1) &&
1858 write(")");
1860 case JSOp::SuperCall:
1861 if (GET_ARGC(pc) == 0) {
1862 return write("super()");
1864 [[fallthrough]];
1865 case JSOp::SpreadSuperCall:
1866 return write("super(...)");
1867 case JSOp::SuperFun:
1868 return write("super");
1870 case JSOp::Eval:
1871 case JSOp::SpreadEval:
1872 case JSOp::StrictEval:
1873 case JSOp::StrictSpreadEval:
1874 return write("eval(...)");
1876 case JSOp::New:
1877 case JSOp::NewContent: {
1878 uint16_t argc = GET_ARGC(pc);
1879 return write("(new ") &&
1880 decompilePCForStackOperand(pc, -int32_t(argc + 3)) &&
1881 write(argc ? "(...))" : "())");
1884 case JSOp::SpreadNew:
1885 return write("(new ") && decompilePCForStackOperand(pc, -4) &&
1886 write("(...))");
1888 case JSOp::DynamicImport:
1889 return write("import(...)");
1891 case JSOp::Typeof:
1892 case JSOp::TypeofExpr:
1893 return write("(typeof ") && decompilePCForStackOperand(pc, -1) &&
1894 write(")");
1896 case JSOp::InitElemArray:
1897 return write("[...]");
1899 case JSOp::InitElemInc:
1900 if (defIndex == 0) {
1901 return write("[...]");
1903 MOZ_ASSERT(defIndex == 1);
1904 #ifdef DEBUG
1905 // INDEX won't be be exposed to error message.
1906 if (isStackDump) {
1907 return write("INDEX");
1909 #endif
1910 break;
1912 case JSOp::ToNumeric:
1913 return write("(tonumeric ") && decompilePCForStackOperand(pc, -1) &&
1914 write(")");
1916 case JSOp::Inc:
1917 return write("(inc ") && decompilePCForStackOperand(pc, -1) && write(")");
1919 case JSOp::Dec:
1920 return write("(dec ") && decompilePCForStackOperand(pc, -1) && write(")");
1922 case JSOp::BigInt:
1923 #if defined(DEBUG) || defined(JS_JITSPEW)
1924 // BigInt::dumpLiteral() only available in this configuration.
1925 script->getBigInt(pc)->dumpLiteral(sprinter);
1926 return !sprinter.hadOutOfMemory();
1927 #else
1928 return write("[bigint]");
1929 #endif
1931 case JSOp::BuiltinObject: {
1932 auto kind = BuiltinObjectKind(GET_UINT8(pc));
1933 return write(BuiltinObjectName(kind));
1936 #ifdef ENABLE_RECORD_TUPLE
1937 case JSOp::InitTuple:
1938 return write("#[]");
1940 case JSOp::AddTupleElement:
1941 case JSOp::FinishTuple:
1942 return write("#[...]");
1943 #endif
1945 default:
1946 break;
1949 #ifdef DEBUG
1950 if (isStackDump) {
1951 // Special decompilation for stack dump.
1952 switch (op) {
1953 case JSOp::Arguments:
1954 return write("arguments");
1956 case JSOp::ArgumentsLength:
1957 return write("arguments.length");
1959 case JSOp::GetFrameArg:
1960 sprinter.printf("arguments[%u]", GET_ARGNO(pc));
1961 return true;
1963 case JSOp::GetActualArg:
1964 return write("arguments[") && decompilePCForStackOperand(pc, -1) &&
1965 write("]");
1967 case JSOp::BindGName:
1968 return write("GLOBAL");
1970 case JSOp::BindName:
1971 case JSOp::BindVar:
1972 return write("ENV");
1974 case JSOp::Callee:
1975 return write("CALLEE");
1977 case JSOp::EnvCallee:
1978 return write("ENVCALLEE");
1980 case JSOp::CallSiteObj:
1981 return write("OBJ");
1983 case JSOp::Double:
1984 sprinter.printf("%lf", GET_INLINE_VALUE(pc).toDouble());
1985 return true;
1987 case JSOp::Exception:
1988 return write("EXCEPTION");
1990 case JSOp::ExceptionAndStack:
1991 if (defIndex == 0) {
1992 return write("EXCEPTION");
1994 MOZ_ASSERT(defIndex == 1);
1995 return write("STACK");
1997 case JSOp::Try:
1998 // Used for the values live on entry to the finally block.
1999 // See TryNoteKind::Finally above.
2000 if (defIndex == 0) {
2001 return write("PC");
2003 if (defIndex == 1) {
2004 return write("STACK");
2006 MOZ_ASSERT(defIndex == 2);
2007 return write("THROWING");
2009 case JSOp::FunctionThis:
2010 case JSOp::ImplicitThis:
2011 return write("THIS");
2013 case JSOp::FunWithProto:
2014 return write("FUN");
2016 case JSOp::Generator:
2017 return write("GENERATOR");
2019 case JSOp::GetImport:
2020 return write("VAL");
2022 case JSOp::GetRval:
2023 return write("RVAL");
2025 case JSOp::Hole:
2026 return write("HOLE");
2028 case JSOp::IsGenClosing:
2029 // For stack dump, defIndex == 0 is not used.
2030 MOZ_ASSERT(defIndex == 1);
2031 return write("ISGENCLOSING");
2033 case JSOp::IsNoIter:
2034 // For stack dump, defIndex == 0 is not used.
2035 MOZ_ASSERT(defIndex == 1);
2036 return write("ISNOITER");
2038 case JSOp::IsConstructing:
2039 return write("JS_IS_CONSTRUCTING");
2041 case JSOp::IsNullOrUndefined:
2042 return write("IS_NULL_OR_UNDEF");
2044 case JSOp::Iter:
2045 return write("ITER");
2047 case JSOp::Lambda:
2048 return write("FUN");
2050 case JSOp::ToAsyncIter:
2051 return write("ASYNCITER");
2053 case JSOp::MoreIter:
2054 // For stack dump, defIndex == 0 is not used.
2055 MOZ_ASSERT(defIndex == 1);
2056 return write("MOREITER");
2058 case JSOp::NewInit:
2059 case JSOp::NewObject:
2060 case JSOp::ObjWithProto:
2061 return write("OBJ");
2063 case JSOp::OptimizeGetIterator:
2064 case JSOp::OptimizeSpreadCall:
2065 return write("OPTIMIZED");
2067 case JSOp::Rest:
2068 return write("REST");
2070 case JSOp::Resume:
2071 return write("RVAL");
2073 case JSOp::SuperBase:
2074 return write("HOMEOBJECTPROTO");
2076 case JSOp::ToPropertyKey:
2077 return write("TOPROPERTYKEY(") && decompilePCForStackOperand(pc, -1) &&
2078 write(")");
2079 case JSOp::ToString:
2080 return write("TOSTRING(") && decompilePCForStackOperand(pc, -1) &&
2081 write(")");
2083 case JSOp::Uninitialized:
2084 return write("UNINITIALIZED");
2086 case JSOp::InitialYield:
2087 case JSOp::Await:
2088 case JSOp::Yield:
2089 // Printing "yield SOMETHING" is confusing since the operand doesn't
2090 // match to the syntax, since the stack operand for "yield 10" is
2091 // the result object, not 10.
2092 if (defIndex == 0) {
2093 return write("RVAL");
2095 if (defIndex == 1) {
2096 return write("GENERATOR");
2098 MOZ_ASSERT(defIndex == 2);
2099 return write("RESUMEKIND");
2101 case JSOp::ResumeKind:
2102 return write("RESUMEKIND");
2104 case JSOp::AsyncAwait:
2105 case JSOp::AsyncResolve:
2106 case JSOp::AsyncReject:
2107 return write("PROMISE");
2109 case JSOp::CanSkipAwait:
2110 // For stack dump, defIndex == 0 is not used.
2111 MOZ_ASSERT(defIndex == 1);
2112 return write("CAN_SKIP_AWAIT");
2114 case JSOp::MaybeExtractAwaitValue:
2115 // For stack dump, defIndex == 1 is not used.
2116 MOZ_ASSERT(defIndex == 0);
2117 return write("MAYBE_RESOLVED(") && decompilePCForStackOperand(pc, -2) &&
2118 write(")");
2120 case JSOp::CheckPrivateField:
2121 return write("HasPrivateField");
2123 case JSOp::NewPrivateName:
2124 return write("PRIVATENAME");
2126 case JSOp::CheckReturn:
2127 return write("RVAL");
2129 case JSOp::HasOwn:
2130 return write("HasOwn(") && decompilePCForStackOperand(pc, -2) &&
2131 write(", ") && decompilePCForStackOperand(pc, -1) && write(")");
2133 default:
2134 break;
2136 return write("<unknown>");
2138 #endif /* DEBUG */
2140 return write("(intermediate value)");
2143 bool ExpressionDecompiler::decompilePC(
2144 const OffsetAndDefIndex& offsetAndDefIndex) {
2145 if (offsetAndDefIndex.isSpecial()) {
2146 #ifdef DEBUG
2147 if (isStackDump) {
2148 if (offsetAndDefIndex.isMerged()) {
2149 if (!write("merged<")) {
2150 return false;
2152 } else if (offsetAndDefIndex.isIgnored()) {
2153 if (!write("ignored<")) {
2154 return false;
2158 if (!decompilePC(script->offsetToPC(offsetAndDefIndex.specialOffset()),
2159 offsetAndDefIndex.specialDefIndex())) {
2160 return false;
2163 if (!write(">")) {
2164 return false;
2167 return true;
2169 #endif /* DEBUG */
2170 return write("(intermediate value)");
2173 return decompilePC(script->offsetToPC(offsetAndDefIndex.offset()),
2174 offsetAndDefIndex.defIndex());
2177 bool ExpressionDecompiler::init() {
2178 cx->check(script);
2179 return sprinter.init();
2182 bool ExpressionDecompiler::write(const char* s) {
2183 sprinter.put(s);
2184 return true;
2187 bool ExpressionDecompiler::write(JSString* str) {
2188 if (str == cx->names().dot_this_) {
2189 return write("this");
2191 if (str == cx->names().dot_newTarget_) {
2192 return write("new.target");
2194 sprinter.putString(cx, str);
2195 return true;
2198 bool ExpressionDecompiler::quote(JSString* s, char quote) {
2199 QuoteString(&sprinter, s, quote);
2200 return true;
2203 JSAtom* ExpressionDecompiler::loadAtom(jsbytecode* pc) {
2204 return script->getAtom(pc);
2207 JSString* ExpressionDecompiler::loadString(jsbytecode* pc) {
2208 return script->getString(pc);
2211 JSAtom* ExpressionDecompiler::getArg(unsigned slot) {
2212 MOZ_ASSERT(script->isFunction());
2213 MOZ_ASSERT(slot < script->numArgs());
2215 for (PositionalFormalParameterIter fi(script); fi; fi++) {
2216 if (fi.argumentSlot() == slot) {
2217 if (!fi.isDestructured()) {
2218 return fi.name();
2221 // Destructured arguments have no single binding name.
2222 static const char destructuredParam[] = "(destructured parameter)";
2223 return Atomize(cx, destructuredParam, strlen(destructuredParam));
2227 MOZ_CRASH("No binding");
2230 UniqueChars ExpressionDecompiler::getOutput() { return sprinter.release(); }
2232 } // anonymous namespace
2234 #if defined(DEBUG) || defined(JS_JITSPEW)
2235 static bool DecompileAtPCForStackDump(
2236 JSContext* cx, HandleScript script,
2237 const OffsetAndDefIndex& offsetAndDefIndex, StringPrinter* sp) {
2238 // The expression decompiler asserts the script is in the current realm.
2239 AutoRealm ar(cx, script);
2241 LifoAllocScope allocScope(&cx->tempLifoAlloc());
2242 BytecodeParser parser(cx, allocScope.alloc(), script);
2243 parser.setStackDump();
2244 if (!parser.parse()) {
2245 return false;
2248 ExpressionDecompiler ed(cx, script, parser);
2249 ed.setStackDump();
2250 if (!ed.init()) {
2251 return false;
2254 if (!ed.decompilePC(offsetAndDefIndex)) {
2255 return false;
2258 UniqueChars result = ed.getOutput();
2259 if (!result) {
2260 return false;
2263 sp->put(result.get());
2264 return true;
2266 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
2268 static bool FindStartPC(JSContext* cx, const FrameIter& iter,
2269 const BytecodeParser& parser, int spindex,
2270 int skipStackHits, const Value& v, jsbytecode** valuepc,
2271 uint8_t* defIndex) {
2272 jsbytecode* current = *valuepc;
2273 *valuepc = nullptr;
2274 *defIndex = 0;
2276 if (spindex < 0 && spindex + int(parser.stackDepthAtPC(current)) < 0) {
2277 spindex = JSDVG_SEARCH_STACK;
2280 if (spindex == JSDVG_SEARCH_STACK) {
2281 size_t index = iter.numFrameSlots();
2283 // The decompiler may be called from inside functions that are not
2284 // called from script, but via the C++ API directly, such as
2285 // Invoke. In that case, the youngest script frame may have a
2286 // completely unrelated pc and stack depth, so we give up.
2287 if (index < size_t(parser.stackDepthAtPC(current))) {
2288 return true;
2291 // We search from fp->sp to base to find the most recently calculated
2292 // value matching v under assumption that it is the value that caused
2293 // the exception.
2294 int stackHits = 0;
2295 Value s;
2296 do {
2297 if (!index) {
2298 return true;
2300 s = iter.frameSlotValue(--index);
2301 } while (s != v || stackHits++ != skipStackHits);
2303 // If the current PC has fewer values on the stack than the index we are
2304 // looking for, the blamed value must be one pushed by the current
2305 // bytecode (e.g. JSOp::MoreIter), so restore *valuepc.
2306 if (index < size_t(parser.stackDepthAtPC(current))) {
2307 *valuepc = parser.pcForStackOperand(current, index, defIndex);
2308 } else {
2309 *valuepc = current;
2310 *defIndex = index - size_t(parser.stackDepthAtPC(current));
2312 } else {
2313 *valuepc = parser.pcForStackOperand(current, spindex, defIndex);
2315 return true;
2318 static bool DecompileExpressionFromStack(JSContext* cx, int spindex,
2319 int skipStackHits, HandleValue v,
2320 UniqueChars* res) {
2321 MOZ_ASSERT(spindex < 0 || spindex == JSDVG_IGNORE_STACK ||
2322 spindex == JSDVG_SEARCH_STACK);
2324 *res = nullptr;
2327 * Give up if we need deterministic behavior for differential testing.
2328 * IonMonkey doesn't use InterpreterFrames and this ensures we get the same
2329 * error messages.
2331 if (js::SupportDifferentialTesting()) {
2332 return true;
2335 if (spindex == JSDVG_IGNORE_STACK) {
2336 return true;
2339 FrameIter frameIter(cx);
2341 if (frameIter.done() || !frameIter.hasScript() ||
2342 frameIter.realm() != cx->realm() || frameIter.inPrologue()) {
2343 return true;
2347 * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the
2348 * previous pc (see bug 831120).
2350 if (frameIter.isIon()) {
2351 return true;
2354 RootedScript script(cx, frameIter.script());
2355 jsbytecode* valuepc = frameIter.pc();
2357 MOZ_ASSERT(script->containsPC(valuepc));
2359 LifoAllocScope allocScope(&cx->tempLifoAlloc());
2360 BytecodeParser parser(cx, allocScope.alloc(), frameIter.script());
2361 if (!parser.parse()) {
2362 return false;
2365 uint8_t defIndex;
2366 if (!FindStartPC(cx, frameIter, parser, spindex, skipStackHits, v, &valuepc,
2367 &defIndex)) {
2368 return false;
2370 if (!valuepc) {
2371 return true;
2374 ExpressionDecompiler ed(cx, script, parser);
2375 if (!ed.init()) {
2376 return false;
2378 if (!ed.decompilePC(valuepc, defIndex)) {
2379 return false;
2382 *res = ed.getOutput();
2383 return *res != nullptr;
2386 UniqueChars js::DecompileValueGenerator(JSContext* cx, int spindex,
2387 HandleValue v, HandleString fallbackArg,
2388 int skipStackHits) {
2389 RootedString fallback(cx, fallbackArg);
2391 UniqueChars result;
2392 if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result)) {
2393 return nullptr;
2395 if (result && strcmp(result.get(), "(intermediate value)")) {
2396 return result;
2399 if (!fallback) {
2400 if (v.isUndefined()) {
2401 return DuplicateString(cx, "undefined"); // Prevent users from seeing
2402 // "(void 0)"
2404 fallback = ValueToSource(cx, v);
2405 if (!fallback) {
2406 return nullptr;
2410 return StringToNewUTF8CharsZ(cx, *fallback);
2413 static bool DecompileArgumentFromStack(JSContext* cx, int formalIndex,
2414 UniqueChars* res) {
2415 MOZ_ASSERT(formalIndex >= 0);
2417 *res = nullptr;
2419 /* See note in DecompileExpressionFromStack. */
2420 if (js::SupportDifferentialTesting()) {
2421 return true;
2425 * Settle on the nearest script frame, which should be the builtin that
2426 * called the intrinsic.
2428 FrameIter frameIter(cx);
2429 MOZ_ASSERT(!frameIter.done());
2430 MOZ_ASSERT(frameIter.script()->selfHosted());
2433 * Get the second-to-top frame, the non-self-hosted caller of the builtin
2434 * that called the intrinsic.
2436 ++frameIter;
2437 if (frameIter.done() || !frameIter.hasScript() ||
2438 frameIter.script()->selfHosted() || frameIter.realm() != cx->realm()) {
2439 return true;
2442 RootedScript script(cx, frameIter.script());
2443 jsbytecode* current = frameIter.pc();
2445 MOZ_ASSERT(script->containsPC(current));
2447 if (current < script->main()) {
2448 return true;
2451 /* Don't handle getters, setters or calls from fun.call/fun.apply. */
2452 JSOp op = JSOp(*current);
2453 if (op != JSOp::Call && op != JSOp::CallContent &&
2454 op != JSOp::CallIgnoresRv && op != JSOp::New && op != JSOp::NewContent) {
2455 return true;
2458 if (static_cast<unsigned>(formalIndex) >= GET_ARGC(current)) {
2459 return true;
2462 LifoAllocScope allocScope(&cx->tempLifoAlloc());
2463 BytecodeParser parser(cx, allocScope.alloc(), script);
2464 if (!parser.parse()) {
2465 return false;
2468 bool pushedNewTarget = op == JSOp::New || op == JSOp::NewContent;
2469 int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) -
2470 pushedNewTarget + formalIndex;
2471 MOZ_ASSERT(formalStackIndex >= 0);
2472 if (uint32_t(formalStackIndex) >= parser.stackDepthAtPC(current)) {
2473 return true;
2476 ExpressionDecompiler ed(cx, script, parser);
2477 if (!ed.init()) {
2478 return false;
2480 if (!ed.decompilePCForStackOperand(current, formalStackIndex)) {
2481 return false;
2484 *res = ed.getOutput();
2485 return *res != nullptr;
2488 JSString* js::DecompileArgument(JSContext* cx, int formalIndex, HandleValue v) {
2490 UniqueChars result;
2491 if (!DecompileArgumentFromStack(cx, formalIndex, &result)) {
2492 return nullptr;
2494 if (result && strcmp(result.get(), "(intermediate value)")) {
2495 JS::ConstUTF8CharsZ utf8chars(result.get(), strlen(result.get()));
2496 return NewStringCopyUTF8Z(cx, utf8chars);
2499 if (v.isUndefined()) {
2500 return cx->names().undefined; // Prevent users from seeing "(void 0)"
2503 return ValueToSource(cx, v);
2506 extern bool js::IsValidBytecodeOffset(JSContext* cx, JSScript* script,
2507 size_t offset) {
2508 // This could be faster (by following jump instructions if the target
2509 // is <= offset).
2510 for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) {
2511 size_t here = r.frontOffset();
2512 if (here >= offset) {
2513 return here == offset;
2516 return false;
2520 * There are three possible PCCount profiling states:
2522 * 1. None: Neither scripts nor the runtime have count information.
2523 * 2. Profile: Active scripts have count information, the runtime does not.
2524 * 3. Query: Scripts do not have count information, the runtime does.
2526 * When starting to profile scripts, counting begins immediately, with all JIT
2527 * code discarded and recompiled with counts as necessary. Active interpreter
2528 * frames will not begin profiling until they begin executing another script
2529 * (via a call or return).
2531 * The below API functions manage transitions to new states, according
2532 * to the table below.
2534 * Old State
2535 * -------------------------
2536 * Function None Profile Query
2537 * --------
2538 * StartPCCountProfiling Profile Profile Profile
2539 * StopPCCountProfiling None Query Query
2540 * PurgePCCounts None None None
2543 static void ReleaseScriptCounts(JSRuntime* rt) {
2544 MOZ_ASSERT(rt->scriptAndCountsVector);
2546 js_delete(rt->scriptAndCountsVector.ref());
2547 rt->scriptAndCountsVector = nullptr;
2550 void JS::StartPCCountProfiling(JSContext* cx) {
2551 JSRuntime* rt = cx->runtime();
2553 if (rt->profilingScripts) {
2554 return;
2557 if (rt->scriptAndCountsVector) {
2558 ReleaseScriptCounts(rt);
2561 ReleaseAllJITCode(rt->gcContext());
2563 rt->profilingScripts = true;
2566 void JS::StopPCCountProfiling(JSContext* cx) {
2567 JSRuntime* rt = cx->runtime();
2569 if (!rt->profilingScripts) {
2570 return;
2572 MOZ_ASSERT(!rt->scriptAndCountsVector);
2574 ReleaseAllJITCode(rt->gcContext());
2576 auto* vec = cx->new_<PersistentRooted<ScriptAndCountsVector>>(
2577 cx, ScriptAndCountsVector());
2578 if (!vec) {
2579 return;
2582 for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
2583 for (auto base = zone->cellIter<BaseScript>(); !base.done(); base.next()) {
2584 if (base->hasScriptCounts() && base->hasJitScript()) {
2585 if (!vec->append(base->asJSScript())) {
2586 return;
2592 rt->profilingScripts = false;
2593 rt->scriptAndCountsVector = vec;
2596 void JS::PurgePCCounts(JSContext* cx) {
2597 JSRuntime* rt = cx->runtime();
2599 if (!rt->scriptAndCountsVector) {
2600 return;
2602 MOZ_ASSERT(!rt->profilingScripts);
2604 ReleaseScriptCounts(rt);
2607 size_t JS::GetPCCountScriptCount(JSContext* cx) {
2608 JSRuntime* rt = cx->runtime();
2610 if (!rt->scriptAndCountsVector) {
2611 return 0;
2614 return rt->scriptAndCountsVector->length();
2617 [[nodiscard]] static bool JSONStringProperty(StringPrinter& sp,
2618 JSONPrinter& json,
2619 const char* name, JSString* str) {
2620 json.beginStringProperty(name);
2621 JSONQuoteString(&sp, str);
2622 json.endStringProperty();
2623 return true;
2626 JSString* JS::GetPCCountScriptSummary(JSContext* cx, size_t index) {
2627 JSRuntime* rt = cx->runtime();
2629 if (!rt->scriptAndCountsVector ||
2630 index >= rt->scriptAndCountsVector->length()) {
2631 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2632 JSMSG_BUFFER_TOO_SMALL);
2633 return nullptr;
2636 const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index];
2637 RootedScript script(cx, sac.script);
2639 JSSprinter sp(cx);
2640 if (!sp.init()) {
2641 return nullptr;
2644 JSONPrinter json(sp, false);
2646 json.beginObject();
2648 Rooted<JSString*> filenameStr(cx);
2649 if (const char* filename = script->filename()) {
2650 filenameStr =
2651 JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(filename, strlen(filename)));
2652 } else {
2653 filenameStr = JS_GetEmptyString(cx);
2655 if (!filenameStr) {
2656 return nullptr;
2658 if (!JSONStringProperty(sp, json, "file", filenameStr)) {
2659 return nullptr;
2661 json.property("line", script->lineno());
2663 if (JSFunction* fun = script->function()) {
2664 if (JSAtom* atom = fun->fullDisplayAtom()) {
2665 if (!JSONStringProperty(sp, json, "name", atom)) {
2666 return nullptr;
2671 uint64_t total = 0;
2673 AllBytecodesIterable iter(script);
2674 for (BytecodeLocation loc : iter) {
2675 if (const PCCounts* counts = sac.maybeGetPCCounts(loc.toRawBytecode())) {
2676 total += counts->numExec();
2680 json.beginObjectProperty("totals");
2682 json.property(PCCounts::numExecName, total);
2684 uint64_t ionActivity = 0;
2685 jit::IonScriptCounts* ionCounts = sac.getIonCounts();
2686 while (ionCounts) {
2687 for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
2688 ionActivity += ionCounts->block(i).hitCount();
2690 ionCounts = ionCounts->previous();
2692 if (ionActivity) {
2693 json.property("ion", ionActivity);
2696 json.endObject();
2698 json.endObject();
2700 return sp.release(cx);
2703 static bool GetPCCountJSON(JSContext* cx, const ScriptAndCounts& sac,
2704 StringPrinter& sp) {
2705 JSONPrinter json(sp, false);
2707 RootedScript script(cx, sac.script);
2709 LifoAllocScope allocScope(&cx->tempLifoAlloc());
2710 BytecodeParser parser(cx, allocScope.alloc(), script);
2711 if (!parser.parse()) {
2712 return false;
2715 json.beginObject();
2717 JSString* str = JS_DecompileScript(cx, script);
2718 if (!str) {
2719 return false;
2722 if (!JSONStringProperty(sp, json, "text", str)) {
2723 return false;
2726 json.property("line", script->lineno());
2728 json.beginListProperty("opcodes");
2730 uint64_t hits = 0;
2731 for (BytecodeRangeWithPosition range(cx, script); !range.empty();
2732 range.popFront()) {
2733 jsbytecode* pc = range.frontPC();
2734 size_t offset = script->pcToOffset(pc);
2735 JSOp op = JSOp(*pc);
2737 // If the current instruction is a jump target,
2738 // then update the number of hits.
2739 if (const PCCounts* counts = sac.maybeGetPCCounts(pc)) {
2740 hits = counts->numExec();
2743 json.beginObject();
2745 json.property("id", offset);
2746 json.property("line", range.frontLineNumber());
2747 json.property("name", CodeName(op));
2750 ExpressionDecompiler ed(cx, script, parser);
2751 if (!ed.init()) {
2752 return false;
2754 // defIndex passed here is not used.
2755 if (!ed.decompilePC(pc, /* defIndex = */ 0)) {
2756 return false;
2758 UniqueChars text = ed.getOutput();
2759 if (!text) {
2760 return false;
2763 JS::ConstUTF8CharsZ utf8chars(text.get(), strlen(text.get()));
2764 JSString* str = NewStringCopyUTF8Z(cx, utf8chars);
2765 if (!str) {
2766 return false;
2769 if (!JSONStringProperty(sp, json, "text", str)) {
2770 return false;
2774 json.beginObjectProperty("counts");
2775 if (hits > 0) {
2776 json.property(PCCounts::numExecName, hits);
2778 json.endObject();
2780 json.endObject();
2782 // If the current instruction has thrown,
2783 // then decrement the hit counts with the number of throws.
2784 if (const PCCounts* counts = sac.maybeGetThrowCounts(pc)) {
2785 hits -= counts->numExec();
2789 json.endList();
2791 if (jit::IonScriptCounts* ionCounts = sac.getIonCounts()) {
2792 json.beginListProperty("ion");
2794 while (ionCounts) {
2795 json.beginList();
2796 for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
2797 const jit::IonBlockCounts& block = ionCounts->block(i);
2799 json.beginObject();
2800 json.property("id", block.id());
2801 json.property("offset", block.offset());
2803 json.beginListProperty("successors");
2804 for (size_t j = 0; j < block.numSuccessors(); j++) {
2805 json.value(block.successor(j));
2807 json.endList();
2809 json.property("hits", block.hitCount());
2811 JSString* str = NewStringCopyZ<CanGC>(cx, block.code());
2812 if (!str) {
2813 return false;
2816 if (!JSONStringProperty(sp, json, "code", str)) {
2817 return false;
2820 json.endObject();
2822 json.endList();
2824 ionCounts = ionCounts->previous();
2827 json.endList();
2830 json.endObject();
2832 if (sp.hadOutOfMemory()) {
2833 sp.reportOutOfMemory();
2834 return false;
2837 return true;
2840 JSString* JS::GetPCCountScriptContents(JSContext* cx, size_t index) {
2841 JSRuntime* rt = cx->runtime();
2843 if (!rt->scriptAndCountsVector ||
2844 index >= rt->scriptAndCountsVector->length()) {
2845 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2846 JSMSG_BUFFER_TOO_SMALL);
2847 return nullptr;
2850 const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index];
2851 JSScript* script = sac.script;
2853 JSSprinter sp(cx);
2854 if (!sp.init()) {
2855 return nullptr;
2859 AutoRealm ar(cx, &script->global());
2860 if (!GetPCCountJSON(cx, sac, sp)) {
2861 return nullptr;
2865 return sp.release(cx);
2868 struct CollectedScripts {
2869 MutableHandle<ScriptVector> scripts;
2870 bool ok = true;
2872 explicit CollectedScripts(MutableHandle<ScriptVector> scripts)
2873 : scripts(scripts) {}
2875 static void consider(JSRuntime* rt, void* data, BaseScript* script,
2876 const JS::AutoRequireNoGC& nogc) {
2877 auto self = static_cast<CollectedScripts*>(data);
2878 if (!script->filename()) {
2879 return;
2881 if (!self->scripts.append(script->asJSScript())) {
2882 self->ok = false;
2887 static bool GenerateLcovInfo(JSContext* cx, JS::Realm* realm,
2888 GenericPrinter& out) {
2889 AutoRealmUnchecked ar(cx, realm);
2891 // Collect the list of scripts which are part of the current realm.
2893 MOZ_RELEASE_ASSERT(
2894 coverage::IsLCovEnabled(),
2895 "Coverage must be enabled for process before generating LCov info");
2897 // Hold the scripts that we have already flushed, to avoid flushing them
2898 // twice.
2899 using JSScriptSet = GCHashSet<JSScript*>;
2900 Rooted<JSScriptSet> scriptsDone(cx, JSScriptSet(cx));
2902 Rooted<ScriptVector> queue(cx, ScriptVector(cx));
2905 CollectedScripts result(&queue);
2906 IterateScripts(cx, realm, &result, &CollectedScripts::consider);
2907 if (!result.ok) {
2908 ReportOutOfMemory(cx);
2909 return false;
2913 if (queue.length() == 0) {
2914 return true;
2917 // Ensure the LCovRealm exists to collect info into.
2918 coverage::LCovRealm* lcovRealm = realm->lcovRealm();
2919 if (!lcovRealm) {
2920 return false;
2923 // Collect code coverage info for one realm.
2924 do {
2925 RootedScript script(cx, queue.popCopy());
2926 RootedFunction fun(cx);
2928 JSScriptSet::AddPtr entry = scriptsDone.lookupForAdd(script);
2929 if (entry) {
2930 continue;
2933 if (!coverage::CollectScriptCoverage(script, false)) {
2934 ReportOutOfMemory(cx);
2935 return false;
2938 script->resetScriptCounts();
2940 if (!scriptsDone.add(entry, script)) {
2941 return false;
2944 if (!script->isTopLevel()) {
2945 continue;
2948 // Iterate from the last to the first object in order to have
2949 // the functions them visited in the opposite order when popping
2950 // elements from the stack of remaining scripts, such that the
2951 // functions are more-less listed with increasing line numbers.
2952 auto gcthings = script->gcthings();
2953 for (JS::GCCellPtr gcThing : mozilla::Reversed(gcthings)) {
2954 if (!gcThing.is<JSObject>()) {
2955 continue;
2957 JSObject* obj = &gcThing.as<JSObject>();
2959 if (!obj->is<JSFunction>()) {
2960 continue;
2962 fun = &obj->as<JSFunction>();
2964 // Ignore asm.js functions
2965 if (!fun->isInterpreted()) {
2966 continue;
2969 // Queue the script in the list of script associated to the
2970 // current source.
2971 JSScript* childScript = JSFunction::getOrCreateScript(cx, fun);
2972 if (!childScript || !queue.append(childScript)) {
2973 return false;
2976 } while (!queue.empty());
2978 bool isEmpty = true;
2979 lcovRealm->exportInto(out, &isEmpty);
2980 return true;
2983 JS_PUBLIC_API UniqueChars js::GetCodeCoverageSummaryAll(JSContext* cx,
2984 size_t* length) {
2985 Sprinter out(cx);
2986 if (!out.init()) {
2987 return nullptr;
2990 for (RealmsIter realm(cx->runtime()); !realm.done(); realm.next()) {
2991 if (!GenerateLcovInfo(cx, realm, out)) {
2992 return nullptr;
2996 *length = out.length();
2997 return out.release();
3000 JS_PUBLIC_API UniqueChars js::GetCodeCoverageSummary(JSContext* cx,
3001 size_t* length) {
3002 Sprinter out(cx);
3003 if (!out.init()) {
3004 return nullptr;
3007 if (!GenerateLcovInfo(cx, cx->realm(), out)) {
3008 return nullptr;
3011 *length = out.length();
3012 return out.release();