Bug 1865597 - Add error checking when initializing parallel marking and disable on...
[gecko.git] / js / src / vm / BytecodeUtil.cpp
blobf7e7ea2daa1033e796f2b6f207ef3f3da75de596
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.zeroOriginValue());
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 // Two additional values will be on the stack at the beginning
919 // of the finally block: the exception/resume index, and the
920 // |throwing| value. For the benefit of the decompiler, point
921 // them at this Try.
922 offsetStack[stackDepth].set(offset, 0);
923 offsetStack[stackDepth + 1].set(offset, 1);
924 if (!addJump(catchOffset, stackDepth + 2, offsetStack, pc,
925 JumpKind::TryFinally)) {
926 return false;
931 break;
934 default:
935 break;
938 // Check basic jump opcodes, which may or may not have a fallthrough.
939 if (IsJumpOpcode(op)) {
940 // Case instructions do not push the lvalue back when branching.
941 uint32_t newStackDepth = stackDepth;
942 if (op == JSOp::Case) {
943 newStackDepth--;
946 uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc);
947 if (!addJump(targetOffset, newStackDepth, offsetStack, pc,
948 JumpKind::Simple)) {
949 return false;
953 // Handle any fallthrough from this opcode.
954 if (BytecodeFallsThrough(op)) {
955 if (!recordBytecode(nextOffset, offsetStack, stackDepth)) {
956 return false;
961 return true;
964 #if defined(DEBUG) || defined(JS_JITSPEW)
966 bool js::ReconstructStackDepth(JSContext* cx, JSScript* script, jsbytecode* pc,
967 uint32_t* depth, bool* reachablePC) {
968 LifoAllocScope allocScope(&cx->tempLifoAlloc());
969 BytecodeParser parser(cx, allocScope.alloc(), script);
970 if (!parser.parse()) {
971 return false;
974 *reachablePC = parser.isReachable(pc);
976 if (*reachablePC) {
977 *depth = parser.stackDepthAtPC(pc);
980 return true;
983 static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
984 unsigned loc, bool lines,
985 const BytecodeParser* parser, StringPrinter* sp);
988 * If pc != nullptr, include a prefix indicating whether the PC is at the
989 * current line. If showAll is true, include the entry stack depth.
991 [[nodiscard]] static bool DisassembleAtPC(
992 JSContext* cx, JSScript* scriptArg, bool lines, const jsbytecode* pc,
993 bool showAll, StringPrinter* sp,
994 DisassembleSkeptically skeptically = DisassembleSkeptically::No) {
995 LifoAllocScope allocScope(&cx->tempLifoAlloc());
996 RootedScript script(cx, scriptArg);
997 mozilla::Maybe<BytecodeParser> parser;
999 if (skeptically == DisassembleSkeptically::No) {
1000 parser.emplace(cx, allocScope.alloc(), script);
1001 parser->setStackDump();
1002 if (!parser->parse()) {
1003 return false;
1007 if (showAll) {
1008 sp->printf("%s:%u\n", script->filename(), unsigned(script->lineno()));
1011 if (pc != nullptr) {
1012 sp->put(" ");
1014 if (showAll) {
1015 sp->put("sn stack ");
1017 sp->put("loc ");
1018 if (lines) {
1019 sp->put("line");
1021 sp->put(" op\n");
1023 if (pc != nullptr) {
1024 sp->put(" ");
1026 if (showAll) {
1027 sp->put("-- ----- ");
1029 sp->put("----- ");
1030 if (lines) {
1031 sp->put("----");
1033 sp->put(" --\n");
1035 jsbytecode* next = script->code();
1036 jsbytecode* end = script->codeEnd();
1037 while (next < end) {
1038 if (next == script->main()) {
1039 sp->put("main:\n");
1041 if (pc != nullptr) {
1042 sp->put(pc == next ? "--> " : " ");
1044 if (showAll) {
1045 if (parser && parser->isReachable(next)) {
1046 sp->printf("%05u ", parser->stackDepthAtPC(next));
1047 } else {
1048 sp->put(" ");
1051 unsigned len = Disassemble1(cx, script, next, script->pcToOffset(next),
1052 lines, parser.ptrOr(nullptr), sp);
1053 if (!len) {
1054 return false;
1057 next += len;
1060 return true;
1063 bool js::Disassemble(JSContext* cx, HandleScript script, bool lines,
1064 StringPrinter* sp, DisassembleSkeptically skeptically) {
1065 return DisassembleAtPC(cx, script, lines, nullptr, false, sp, skeptically);
1068 JS_PUBLIC_API bool js::DumpPC(JSContext* cx, FILE* fp) {
1069 gc::AutoSuppressGC suppressGC(cx);
1070 Sprinter sprinter(cx);
1071 if (!sprinter.init()) {
1072 return false;
1074 ScriptFrameIter iter(cx);
1075 if (iter.done()) {
1076 fprintf(fp, "Empty stack.\n");
1077 return true;
1079 RootedScript script(cx, iter.script());
1080 bool ok = DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter);
1081 JS::UniqueChars out = sprinter.release();
1082 if (!out) {
1083 return false;
1085 fprintf(fp, "%s", out.get());
1086 return ok;
1089 JS_PUBLIC_API bool js::DumpScript(JSContext* cx, JSScript* scriptArg,
1090 FILE* fp) {
1091 gc::AutoSuppressGC suppressGC(cx);
1092 Sprinter sprinter(cx);
1093 if (!sprinter.init()) {
1094 return false;
1096 RootedScript script(cx, scriptArg);
1097 bool ok = Disassemble(cx, script, true, &sprinter);
1098 JS::UniqueChars out = sprinter.release();
1099 if (!out) {
1100 return false;
1102 fprintf(fp, "%s", out.get());
1103 return ok;
1106 static UniqueChars ToDisassemblySource(JSContext* cx, HandleValue v) {
1107 if (v.isString()) {
1108 return QuoteString(cx, v.toString(), '"');
1111 if (JS::RuntimeHeapIsBusy()) {
1112 return DuplicateString(cx, "<value>");
1115 if (v.isObject()) {
1116 JSObject& obj = v.toObject();
1118 if (obj.is<JSFunction>()) {
1119 RootedFunction fun(cx, &obj.as<JSFunction>());
1120 JSString* str = JS_DecompileFunction(cx, fun);
1121 if (!str) {
1122 return nullptr;
1124 return QuoteString(cx, str);
1127 if (obj.is<RegExpObject>()) {
1128 Rooted<RegExpObject*> reobj(cx, &obj.as<RegExpObject>());
1129 JSString* source = RegExpObject::toString(cx, reobj);
1130 if (!source) {
1131 return nullptr;
1133 return QuoteString(cx, source);
1137 JSString* str = ValueToSource(cx, v);
1138 if (!str) {
1139 return nullptr;
1141 return QuoteString(cx, str);
1144 static bool ToDisassemblySource(JSContext* cx, Handle<Scope*> scope,
1145 UniqueChars* bytes) {
1146 UniqueChars source = JS_smprintf("%s {", ScopeKindString(scope->kind()));
1147 if (!source) {
1148 ReportOutOfMemory(cx);
1149 return false;
1152 for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
1153 UniqueChars nameBytes = AtomToPrintableString(cx, bi.name());
1154 if (!nameBytes) {
1155 return false;
1158 source = JS_sprintf_append(std::move(source), "%s: ", nameBytes.get());
1159 if (!source) {
1160 ReportOutOfMemory(cx);
1161 return false;
1164 BindingLocation loc = bi.location();
1165 switch (loc.kind()) {
1166 case BindingLocation::Kind::Global:
1167 source = JS_sprintf_append(std::move(source), "global");
1168 break;
1170 case BindingLocation::Kind::Frame:
1171 source =
1172 JS_sprintf_append(std::move(source), "frame slot %u", loc.slot());
1173 break;
1175 case BindingLocation::Kind::Environment:
1176 source =
1177 JS_sprintf_append(std::move(source), "env slot %u", loc.slot());
1178 break;
1180 case BindingLocation::Kind::Argument:
1181 source =
1182 JS_sprintf_append(std::move(source), "arg slot %u", loc.slot());
1183 break;
1185 case BindingLocation::Kind::NamedLambdaCallee:
1186 source = JS_sprintf_append(std::move(source), "named lambda callee");
1187 break;
1189 case BindingLocation::Kind::Import:
1190 source = JS_sprintf_append(std::move(source), "import");
1191 break;
1194 if (!source) {
1195 ReportOutOfMemory(cx);
1196 return false;
1199 if (!bi.isLast()) {
1200 source = JS_sprintf_append(std::move(source), ", ");
1201 if (!source) {
1202 ReportOutOfMemory(cx);
1203 return false;
1208 source = JS_sprintf_append(std::move(source), "}");
1209 if (!source) {
1210 ReportOutOfMemory(cx);
1211 return false;
1214 *bytes = std::move(source);
1215 return true;
1218 static bool DumpJumpOrigins(HandleScript script, jsbytecode* pc,
1219 const BytecodeParser* parser, StringPrinter* sp) {
1220 bool called = false;
1221 auto callback = [&script, &sp, &called](jsbytecode* pc,
1222 BytecodeParser::JumpKind kind) {
1223 if (!called) {
1224 called = true;
1225 sp->put("\n# ");
1226 } else {
1227 sp->put(", ");
1230 switch (kind) {
1231 case BytecodeParser::JumpKind::Simple:
1232 break;
1234 case BytecodeParser::JumpKind::SwitchCase:
1235 sp->put("switch-case ");
1236 break;
1238 case BytecodeParser::JumpKind::SwitchDefault:
1239 sp->put("switch-default ");
1240 break;
1242 case BytecodeParser::JumpKind::TryCatch:
1243 sp->put("try-catch ");
1244 break;
1246 case BytecodeParser::JumpKind::TryFinally:
1247 sp->put("try-finally ");
1248 break;
1251 sp->printf("from %s @ %05u", CodeName(JSOp(*pc)),
1252 unsigned(script->pcToOffset(pc)));
1254 return true;
1256 if (!parser->forEachJumpOrigins(pc, callback)) {
1257 return false;
1259 if (called) {
1260 sp->put("\n");
1263 return true;
1266 static bool DecompileAtPCForStackDump(
1267 JSContext* cx, HandleScript script,
1268 const OffsetAndDefIndex& offsetAndDefIndex, StringPrinter* sp);
1270 static bool PrintShapeProperties(JSContext* cx, StringPrinter* sp,
1271 SharedShape* shape) {
1272 // Add all property keys to a vector to allow printing them in property
1273 // definition order.
1274 Vector<PropertyKey> props(cx);
1275 for (SharedShapePropertyIter<NoGC> iter(shape); !iter.done(); iter++) {
1276 if (!props.append(iter->key())) {
1277 return false;
1281 sp->put("{");
1283 for (size_t i = props.length(); i > 0; i--) {
1284 PropertyKey key = props[i - 1];
1285 RootedValue keyv(cx, IdToValue(key));
1286 JSString* str = ToString<NoGC>(cx, keyv);
1287 if (!str) {
1288 ReportOutOfMemory(cx);
1289 return false;
1291 sp->putString(cx, str);
1292 if (i > 1) {
1293 sp->put(", ");
1297 sp->put("}");
1298 return true;
1301 static unsigned Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
1302 unsigned loc, bool lines,
1303 const BytecodeParser* parser, StringPrinter* sp) {
1304 if (parser && parser->isReachable(pc)) {
1305 if (!DumpJumpOrigins(script, pc, parser, sp)) {
1306 return 0;
1310 size_t before = sp->length();
1311 bool stackDumped = false;
1312 auto dumpStack = [&cx, &script, &pc, &parser, &sp, &before, &stackDumped]() {
1313 if (!parser) {
1314 return true;
1316 if (stackDumped) {
1317 return true;
1319 stackDumped = true;
1321 size_t after = sp->length();
1322 MOZ_ASSERT(after >= before);
1324 static const size_t stack_column = 40;
1325 for (size_t i = after - before; i < stack_column - 1; i++) {
1326 sp->put(" ");
1329 sp->put(" # ");
1331 if (!parser->isReachable(pc)) {
1332 sp->put("!!! UNREACHABLE !!!");
1333 } else {
1334 uint32_t depth = parser->stackDepthAfterPC(pc);
1336 for (uint32_t i = 0; i < depth; i++) {
1337 if (i) {
1338 sp->put(" ");
1341 const OffsetAndDefIndex& offsetAndDefIndex =
1342 parser->offsetForStackOperandAfterPC(script->pcToOffset(pc), i);
1343 // This will decompile the stack for the same PC many times.
1344 // We'll avoid optimizing it since this is a testing function
1345 // and it won't be worth managing cached expression here.
1346 if (!DecompileAtPCForStackDump(cx, script, offsetAndDefIndex, sp)) {
1347 return false;
1352 return true;
1355 if (*pc >= JSOP_LIMIT) {
1356 char numBuf1[12], numBuf2[12];
1357 SprintfLiteral(numBuf1, "%d", int(*pc));
1358 SprintfLiteral(numBuf2, "%d", JSOP_LIMIT);
1359 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1360 JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2);
1361 return 0;
1363 JSOp op = JSOp(*pc);
1364 const JSCodeSpec& cs = CodeSpec(op);
1365 const unsigned len = cs.length;
1366 sp->printf("%05u:", loc);
1367 if (lines) {
1368 sp->printf("%4u", PCToLineNumber(script, pc));
1370 sp->printf(" %s", CodeName(op));
1372 int i;
1373 switch (JOF_TYPE(cs.format)) {
1374 case JOF_BYTE:
1375 break;
1377 case JOF_JUMP: {
1378 ptrdiff_t off = GET_JUMP_OFFSET(pc);
1379 sp->printf(" %u (%+d)", unsigned(loc + int(off)), int(off));
1380 break;
1383 case JOF_SCOPE: {
1384 Rooted<Scope*> scope(cx, script->getScope(pc));
1385 UniqueChars bytes;
1386 if (!ToDisassemblySource(cx, scope, &bytes)) {
1387 return 0;
1389 sp->printf(" %s", bytes.get());
1390 break;
1393 case JOF_ENVCOORD: {
1394 RootedValue v(cx, StringValue(EnvironmentCoordinateNameSlow(script, pc)));
1395 UniqueChars bytes = ToDisassemblySource(cx, v);
1396 if (!bytes) {
1397 return 0;
1399 EnvironmentCoordinate ec(pc);
1400 sp->printf(" %s (hops = %u, slot = %u)", bytes.get(), ec.hops(),
1401 ec.slot());
1402 break;
1404 case JOF_DEBUGCOORD: {
1405 EnvironmentCoordinate ec(pc);
1406 sp->printf("(hops = %u, slot = %u)", ec.hops(), ec.slot());
1407 break;
1409 case JOF_ATOM: {
1410 RootedValue v(cx, StringValue(script->getAtom(pc)));
1411 UniqueChars bytes = ToDisassemblySource(cx, v);
1412 if (!bytes) {
1413 return 0;
1415 sp->printf(" %s", bytes.get());
1416 break;
1418 case JOF_STRING: {
1419 RootedValue v(cx, StringValue(script->getString(pc)));
1420 UniqueChars bytes = ToDisassemblySource(cx, v);
1421 if (!bytes) {
1422 return 0;
1424 sp->printf(" %s", bytes.get());
1425 break;
1428 case JOF_DOUBLE: {
1429 double d = GET_INLINE_VALUE(pc).toDouble();
1430 sp->printf(" %lf", d);
1431 break;
1434 case JOF_BIGINT: {
1435 RootedValue v(cx, BigIntValue(script->getBigInt(pc)));
1436 UniqueChars bytes = ToDisassemblySource(cx, v);
1437 if (!bytes) {
1438 return 0;
1440 sp->printf(" %s", bytes.get());
1441 break;
1444 case JOF_OBJECT: {
1445 JSObject* obj = script->getObject(pc);
1447 RootedValue v(cx, ObjectValue(*obj));
1448 UniqueChars bytes = ToDisassemblySource(cx, v);
1449 if (!bytes) {
1450 return 0;
1452 sp->printf(" %s", bytes.get());
1454 break;
1457 case JOF_SHAPE: {
1458 SharedShape* shape = script->getShape(pc);
1459 sp->put(" ");
1460 if (!PrintShapeProperties(cx, sp, shape)) {
1461 return 0;
1463 break;
1466 case JOF_REGEXP: {
1467 js::RegExpObject* obj = script->getRegExp(pc);
1468 RootedValue v(cx, ObjectValue(*obj));
1469 UniqueChars bytes = ToDisassemblySource(cx, v);
1470 if (!bytes) {
1471 return 0;
1473 sp->printf(" %s", bytes.get());
1474 break;
1477 case JOF_TABLESWITCH: {
1478 int32_t i, low, high;
1480 ptrdiff_t off = GET_JUMP_OFFSET(pc);
1481 jsbytecode* pc2 = pc + JUMP_OFFSET_LEN;
1482 low = GET_JUMP_OFFSET(pc2);
1483 pc2 += JUMP_OFFSET_LEN;
1484 high = GET_JUMP_OFFSET(pc2);
1485 pc2 += JUMP_OFFSET_LEN;
1486 sp->printf(" defaultOffset %d low %d high %d", int(off), low, high);
1488 // Display stack dump before diplaying the offsets for each case.
1489 if (!dumpStack()) {
1490 return 0;
1493 for (i = low; i <= high; i++) {
1494 off =
1495 script->tableSwitchCaseOffset(pc, i - low) - script->pcToOffset(pc);
1496 sp->printf("\n\t%d: %d", i, int(off));
1498 break;
1501 case JOF_QARG:
1502 sp->printf(" %u", GET_ARGNO(pc));
1503 break;
1505 case JOF_LOCAL:
1506 sp->printf(" %u", GET_LOCALNO(pc));
1507 break;
1509 case JOF_GCTHING:
1510 sp->printf(" %u", unsigned(GET_GCTHING_INDEX(pc)));
1511 break;
1513 case JOF_UINT32:
1514 sp->printf(" %u", GET_UINT32(pc));
1515 break;
1517 case JOF_ICINDEX:
1518 sp->printf(" (ic: %u)", GET_ICINDEX(pc));
1519 break;
1521 case JOF_LOOPHEAD:
1522 sp->printf(" (ic: %u, depthHint: %u)", GET_ICINDEX(pc),
1523 LoopHeadDepthHint(pc));
1524 break;
1526 case JOF_TWO_UINT8: {
1527 int one = (int)GET_UINT8(pc);
1528 int two = (int)GET_UINT8(pc + 1);
1530 sp->printf(" %d", one);
1531 sp->printf(" %d", two);
1532 break;
1535 case JOF_ARGC:
1536 case JOF_UINT16:
1537 i = (int)GET_UINT16(pc);
1538 goto print_int;
1540 case JOF_RESUMEINDEX:
1541 case JOF_UINT24:
1542 MOZ_ASSERT(len == 4);
1543 i = (int)GET_UINT24(pc);
1544 goto print_int;
1546 case JOF_UINT8:
1547 i = GET_UINT8(pc);
1548 goto print_int;
1550 case JOF_INT8:
1551 i = GET_INT8(pc);
1552 goto print_int;
1554 case JOF_INT32:
1555 MOZ_ASSERT(op == JSOp::Int32);
1556 i = GET_INT32(pc);
1557 print_int:
1558 sp->printf(" %d", i);
1559 break;
1561 default: {
1562 char numBuf[12];
1563 SprintfLiteral(numBuf, "%x", cs.format);
1564 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1565 JSMSG_UNKNOWN_FORMAT, numBuf);
1566 return 0;
1570 if (!dumpStack()) {
1571 return 0;
1574 sp->put("\n");
1575 return len;
1578 unsigned js::Disassemble1(JSContext* cx, JS::Handle<JSScript*> script,
1579 jsbytecode* pc, unsigned loc, bool lines,
1580 StringPrinter* sp) {
1581 return Disassemble1(cx, script, pc, loc, lines, nullptr, sp);
1584 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
1586 namespace {
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.
1593 * Here's the basic algorithm:
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.
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.
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.
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.
1618 struct ExpressionDecompiler {
1619 JSContext* cx;
1620 RootedScript script;
1621 const BytecodeParser& parser;
1622 Sprinter sprinter;
1624 #if defined(DEBUG) || defined(JS_JITSPEW)
1625 // Dedicated mode for stack dump.
1626 // Generates an expression for stack dump, including internal state,
1627 // and also disables special handling for self-hosted code.
1628 bool isStackDump;
1629 #endif
1631 ExpressionDecompiler(JSContext* cx, JSScript* script,
1632 const BytecodeParser& parser)
1633 : cx(cx),
1634 script(cx, script),
1635 parser(parser),
1636 sprinter(cx)
1637 #if defined(DEBUG) || defined(JS_JITSPEW)
1639 isStackDump(false)
1640 #endif
1643 bool init();
1644 bool decompilePCForStackOperand(jsbytecode* pc, int i);
1645 bool decompilePC(jsbytecode* pc, uint8_t defIndex);
1646 bool decompilePC(const OffsetAndDefIndex& offsetAndDefIndex);
1647 JSAtom* getArg(unsigned slot);
1648 JSAtom* loadAtom(jsbytecode* pc);
1649 JSString* loadString(jsbytecode* pc);
1650 bool quote(JSString* s, char quote);
1651 bool write(const char* s);
1652 bool write(JSString* str);
1653 UniqueChars getOutput();
1654 #if defined(DEBUG) || defined(JS_JITSPEW)
1655 void setStackDump() { isStackDump = true; }
1656 #endif
1659 bool ExpressionDecompiler::decompilePCForStackOperand(jsbytecode* pc, int i) {
1660 return decompilePC(parser.offsetForStackOperand(script->pcToOffset(pc), i));
1663 bool ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex) {
1664 MOZ_ASSERT(script->containsPC(pc));
1666 JSOp op = (JSOp)*pc;
1668 if (const char* token = CodeToken[uint8_t(op)]) {
1669 MOZ_ASSERT(defIndex == 0);
1670 MOZ_ASSERT(CodeSpec(op).ndefs == 1);
1672 // Handle simple cases of binary and unary operators.
1673 switch (CodeSpec(op).nuses) {
1674 case 2: {
1675 const char* extra = "";
1677 MOZ_ASSERT(pc + 1 < script->codeEnd(),
1678 "binary opcode shouldn't be the last opcode in the script");
1679 if (CodeSpec(op).length == 1 &&
1680 (JSOp)(*(pc + 1)) == JSOp::NopIsAssignOp) {
1681 extra = "=";
1684 return write("(") && decompilePCForStackOperand(pc, -2) && write(" ") &&
1685 write(token) && write(extra) && write(" ") &&
1686 decompilePCForStackOperand(pc, -1) && write(")");
1687 break;
1689 case 1:
1690 return write("(") && write(token) &&
1691 decompilePCForStackOperand(pc, -1) && write(")");
1692 default:
1693 break;
1697 switch (op) {
1698 case JSOp::DelName:
1699 return write("(delete ") && write(loadAtom(pc)) && write(")");
1701 case JSOp::GetGName:
1702 case JSOp::GetName:
1703 case JSOp::GetIntrinsic:
1704 return write(loadAtom(pc));
1705 case JSOp::GetArg: {
1706 unsigned slot = GET_ARGNO(pc);
1708 // For self-hosted scripts that are called from non-self-hosted code,
1709 // decompiling the parameter name in the self-hosted script is
1710 // unhelpful. Decompile the argument name instead.
1711 if (script->selfHosted()
1712 #ifdef DEBUG
1713 // For stack dump, argument name is not necessary.
1714 && !isStackDump
1715 #endif /* DEBUG */
1717 UniqueChars result;
1718 if (!DecompileArgumentFromStack(cx, slot, &result)) {
1719 return false;
1722 // Note that decompiling the argument in the parent frame might
1723 // not succeed.
1724 if (result) {
1725 return write(result.get());
1728 // If it fails, do not return parameter name and let the caller
1729 // fallback.
1730 return write("(intermediate value)");
1733 JSAtom* atom = getArg(slot);
1734 if (!atom) {
1735 return false;
1737 return write(atom);
1739 case JSOp::GetLocal: {
1740 JSAtom* atom = FrameSlotName(script, pc);
1741 MOZ_ASSERT(atom);
1742 return write(atom);
1744 case JSOp::GetAliasedVar: {
1745 JSAtom* atom = EnvironmentCoordinateNameSlow(script, pc);
1746 MOZ_ASSERT(atom);
1747 return write(atom);
1750 case JSOp::DelProp:
1751 case JSOp::StrictDelProp:
1752 case JSOp::GetProp:
1753 case JSOp::GetBoundName: {
1754 bool hasDelete = op == JSOp::DelProp || op == JSOp::StrictDelProp;
1755 Rooted<JSAtom*> prop(cx, loadAtom(pc));
1756 MOZ_ASSERT(prop);
1757 return (hasDelete ? write("(delete ") : true) &&
1758 decompilePCForStackOperand(pc, -1) &&
1759 (IsIdentifier(prop)
1760 ? write(".") && quote(prop, '\0')
1761 : write("[") && quote(prop, '\'') && write("]")) &&
1762 (hasDelete ? write(")") : true);
1764 case JSOp::GetPropSuper: {
1765 Rooted<JSAtom*> prop(cx, loadAtom(pc));
1766 return write("super.") && quote(prop, '\0');
1768 case JSOp::SetElem:
1769 case JSOp::StrictSetElem:
1770 // NOTE: We don't show the right hand side of the operation because
1771 // it's used in error messages like: "a[0] is not readable".
1773 // We could though.
1774 return decompilePCForStackOperand(pc, -3) && write("[") &&
1775 decompilePCForStackOperand(pc, -2) && write("]");
1777 case JSOp::DelElem:
1778 case JSOp::StrictDelElem:
1779 case JSOp::GetElem: {
1780 bool hasDelete = (op == JSOp::DelElem || op == JSOp::StrictDelElem);
1781 return (hasDelete ? write("(delete ") : true) &&
1782 decompilePCForStackOperand(pc, -2) && write("[") &&
1783 decompilePCForStackOperand(pc, -1) && write("]") &&
1784 (hasDelete ? write(")") : true);
1787 case JSOp::GetElemSuper:
1788 return write("super[") && decompilePCForStackOperand(pc, -2) &&
1789 write("]");
1790 case JSOp::Null:
1791 return write("null");
1792 case JSOp::True:
1793 return write("true");
1794 case JSOp::False:
1795 return write("false");
1796 case JSOp::Zero:
1797 case JSOp::One:
1798 case JSOp::Int8:
1799 case JSOp::Uint16:
1800 case JSOp::Uint24:
1801 case JSOp::Int32:
1802 sprinter.printf("%d", GetBytecodeInteger(pc));
1803 return true;
1804 case JSOp::String:
1805 return quote(loadString(pc), '"');
1806 case JSOp::Symbol: {
1807 unsigned i = uint8_t(pc[1]);
1808 MOZ_ASSERT(i < JS::WellKnownSymbolLimit);
1809 if (i < JS::WellKnownSymbolLimit) {
1810 return write(cx->names().wellKnownSymbolDescriptions()[i]);
1812 break;
1814 case JSOp::Undefined:
1815 return write("undefined");
1816 case JSOp::GlobalThis:
1817 case JSOp::NonSyntacticGlobalThis:
1818 // |this| could convert to a very long object initialiser, so cite it by
1819 // its keyword name.
1820 return write("this");
1821 case JSOp::NewTarget:
1822 return write("new.target");
1823 case JSOp::ImportMeta:
1824 return write("import.meta");
1825 case JSOp::Call:
1826 case JSOp::CallContent:
1827 case JSOp::CallIgnoresRv:
1828 case JSOp::CallIter:
1829 case JSOp::CallContentIter: {
1830 uint16_t argc = GET_ARGC(pc);
1831 return decompilePCForStackOperand(pc, -int32_t(argc + 2)) &&
1832 write(argc ? "(...)" : "()");
1834 case JSOp::SpreadCall:
1835 return decompilePCForStackOperand(pc, -3) && write("(...)");
1836 case JSOp::NewArray:
1837 return write("[]");
1838 case JSOp::RegExp: {
1839 Rooted<RegExpObject*> obj(cx, &script->getObject(pc)->as<RegExpObject>());
1840 JSString* str = RegExpObject::toString(cx, obj);
1841 if (!str) {
1842 return false;
1844 return write(str);
1846 case JSOp::Object: {
1847 JSObject* obj = script->getObject(pc);
1848 RootedValue objv(cx, ObjectValue(*obj));
1849 JSString* str = ValueToSource(cx, objv);
1850 if (!str) {
1851 return false;
1853 return write(str);
1855 case JSOp::Void:
1856 return write("(void ") && decompilePCForStackOperand(pc, -1) &&
1857 write(")");
1859 case JSOp::SuperCall:
1860 if (GET_ARGC(pc) == 0) {
1861 return write("super()");
1863 [[fallthrough]];
1864 case JSOp::SpreadSuperCall:
1865 return write("super(...)");
1866 case JSOp::SuperFun:
1867 return write("super");
1869 case JSOp::Eval:
1870 case JSOp::SpreadEval:
1871 case JSOp::StrictEval:
1872 case JSOp::StrictSpreadEval:
1873 return write("eval(...)");
1875 case JSOp::New:
1876 case JSOp::NewContent: {
1877 uint16_t argc = GET_ARGC(pc);
1878 return write("(new ") &&
1879 decompilePCForStackOperand(pc, -int32_t(argc + 3)) &&
1880 write(argc ? "(...))" : "())");
1883 case JSOp::SpreadNew:
1884 return write("(new ") && decompilePCForStackOperand(pc, -4) &&
1885 write("(...))");
1887 case JSOp::DynamicImport:
1888 return write("import(...)");
1890 case JSOp::Typeof:
1891 case JSOp::TypeofExpr:
1892 return write("(typeof ") && decompilePCForStackOperand(pc, -1) &&
1893 write(")");
1895 case JSOp::InitElemArray:
1896 return write("[...]");
1898 case JSOp::InitElemInc:
1899 if (defIndex == 0) {
1900 return write("[...]");
1902 MOZ_ASSERT(defIndex == 1);
1903 #ifdef DEBUG
1904 // INDEX won't be be exposed to error message.
1905 if (isStackDump) {
1906 return write("INDEX");
1908 #endif
1909 break;
1911 case JSOp::ToNumeric:
1912 return write("(tonumeric ") && decompilePCForStackOperand(pc, -1) &&
1913 write(")");
1915 case JSOp::Inc:
1916 return write("(inc ") && decompilePCForStackOperand(pc, -1) && write(")");
1918 case JSOp::Dec:
1919 return write("(dec ") && decompilePCForStackOperand(pc, -1) && write(")");
1921 case JSOp::BigInt:
1922 #if defined(DEBUG) || defined(JS_JITSPEW)
1923 // BigInt::dump() only available in this configuration.
1924 script->getBigInt(pc)->dump(sprinter);
1925 return !sprinter.hadOutOfMemory();
1926 #else
1927 return write("[bigint]");
1928 #endif
1930 case JSOp::BuiltinObject: {
1931 auto kind = BuiltinObjectKind(GET_UINT8(pc));
1932 return write(BuiltinObjectName(kind));
1935 #ifdef ENABLE_RECORD_TUPLE
1936 case JSOp::InitTuple:
1937 return write("#[]");
1939 case JSOp::AddTupleElement:
1940 case JSOp::FinishTuple:
1941 return write("#[...]");
1942 #endif
1944 default:
1945 break;
1948 #ifdef DEBUG
1949 if (isStackDump) {
1950 // Special decompilation for stack dump.
1951 switch (op) {
1952 case JSOp::Arguments:
1953 return write("arguments");
1955 case JSOp::ArgumentsLength:
1956 return write("arguments.length");
1958 case JSOp::GetFrameArg:
1959 sprinter.printf("arguments[%u]", GET_ARGNO(pc));
1960 return true;
1962 case JSOp::GetActualArg:
1963 return write("arguments[") && decompilePCForStackOperand(pc, -1) &&
1964 write("]");
1966 case JSOp::BindGName:
1967 return write("GLOBAL");
1969 case JSOp::BindName:
1970 case JSOp::BindVar:
1971 return write("ENV");
1973 case JSOp::Callee:
1974 return write("CALLEE");
1976 case JSOp::EnvCallee:
1977 return write("ENVCALLEE");
1979 case JSOp::CallSiteObj:
1980 return write("OBJ");
1982 case JSOp::Double:
1983 sprinter.printf("%lf", GET_INLINE_VALUE(pc).toDouble());
1984 return true;
1986 case JSOp::Exception:
1987 return write("EXCEPTION");
1989 case JSOp::Try:
1990 // Used for the values live on entry to the finally block.
1991 // See TryNoteKind::Finally above.
1992 if (defIndex == 0) {
1993 return write("PC");
1995 MOZ_ASSERT(defIndex == 1);
1996 return write("THROWING");
1998 case JSOp::FunctionThis:
1999 case JSOp::ImplicitThis:
2000 return write("THIS");
2002 case JSOp::FunWithProto:
2003 return write("FUN");
2005 case JSOp::Generator:
2006 return write("GENERATOR");
2008 case JSOp::GetImport:
2009 return write("VAL");
2011 case JSOp::GetRval:
2012 return write("RVAL");
2014 case JSOp::Hole:
2015 return write("HOLE");
2017 case JSOp::IsGenClosing:
2018 // For stack dump, defIndex == 0 is not used.
2019 MOZ_ASSERT(defIndex == 1);
2020 return write("ISGENCLOSING");
2022 case JSOp::IsNoIter:
2023 // For stack dump, defIndex == 0 is not used.
2024 MOZ_ASSERT(defIndex == 1);
2025 return write("ISNOITER");
2027 case JSOp::IsConstructing:
2028 return write("JS_IS_CONSTRUCTING");
2030 case JSOp::IsNullOrUndefined:
2031 return write("IS_NULL_OR_UNDEF");
2033 case JSOp::Iter:
2034 return write("ITER");
2036 case JSOp::Lambda:
2037 return write("FUN");
2039 case JSOp::ToAsyncIter:
2040 return write("ASYNCITER");
2042 case JSOp::MoreIter:
2043 // For stack dump, defIndex == 0 is not used.
2044 MOZ_ASSERT(defIndex == 1);
2045 return write("MOREITER");
2047 case JSOp::NewInit:
2048 case JSOp::NewObject:
2049 case JSOp::ObjWithProto:
2050 return write("OBJ");
2052 case JSOp::OptimizeGetIterator:
2053 case JSOp::OptimizeSpreadCall:
2054 return write("OPTIMIZED");
2056 case JSOp::Rest:
2057 return write("REST");
2059 case JSOp::Resume:
2060 return write("RVAL");
2062 case JSOp::SuperBase:
2063 return write("HOMEOBJECTPROTO");
2065 case JSOp::ToPropertyKey:
2066 return write("TOPROPERTYKEY(") && decompilePCForStackOperand(pc, -1) &&
2067 write(")");
2068 case JSOp::ToString:
2069 return write("TOSTRING(") && decompilePCForStackOperand(pc, -1) &&
2070 write(")");
2072 case JSOp::Uninitialized:
2073 return write("UNINITIALIZED");
2075 case JSOp::InitialYield:
2076 case JSOp::Await:
2077 case JSOp::Yield:
2078 // Printing "yield SOMETHING" is confusing since the operand doesn't
2079 // match to the syntax, since the stack operand for "yield 10" is
2080 // the result object, not 10.
2081 if (defIndex == 0) {
2082 return write("RVAL");
2084 if (defIndex == 1) {
2085 return write("GENERATOR");
2087 MOZ_ASSERT(defIndex == 2);
2088 return write("RESUMEKIND");
2090 case JSOp::ResumeKind:
2091 return write("RESUMEKIND");
2093 case JSOp::AsyncAwait:
2094 case JSOp::AsyncResolve:
2095 return write("PROMISE");
2097 case JSOp::CanSkipAwait:
2098 // For stack dump, defIndex == 0 is not used.
2099 MOZ_ASSERT(defIndex == 1);
2100 return write("CAN_SKIP_AWAIT");
2102 case JSOp::MaybeExtractAwaitValue:
2103 // For stack dump, defIndex == 1 is not used.
2104 MOZ_ASSERT(defIndex == 0);
2105 return write("MAYBE_RESOLVED(") && decompilePCForStackOperand(pc, -2) &&
2106 write(")");
2108 case JSOp::CheckPrivateField:
2109 return write("HasPrivateField");
2111 case JSOp::NewPrivateName:
2112 return write("PRIVATENAME");
2114 case JSOp::CheckReturn:
2115 return write("RVAL");
2117 case JSOp::HasOwn:
2118 return write("HasOwn(") && decompilePCForStackOperand(pc, -2) &&
2119 write(", ") && decompilePCForStackOperand(pc, -1) && write(")");
2121 default:
2122 break;
2124 return write("<unknown>");
2126 #endif /* DEBUG */
2128 return write("(intermediate value)");
2131 bool ExpressionDecompiler::decompilePC(
2132 const OffsetAndDefIndex& offsetAndDefIndex) {
2133 if (offsetAndDefIndex.isSpecial()) {
2134 #ifdef DEBUG
2135 if (isStackDump) {
2136 if (offsetAndDefIndex.isMerged()) {
2137 if (!write("merged<")) {
2138 return false;
2140 } else if (offsetAndDefIndex.isIgnored()) {
2141 if (!write("ignored<")) {
2142 return false;
2146 if (!decompilePC(script->offsetToPC(offsetAndDefIndex.specialOffset()),
2147 offsetAndDefIndex.specialDefIndex())) {
2148 return false;
2151 if (!write(">")) {
2152 return false;
2155 return true;
2157 #endif /* DEBUG */
2158 return write("(intermediate value)");
2161 return decompilePC(script->offsetToPC(offsetAndDefIndex.offset()),
2162 offsetAndDefIndex.defIndex());
2165 bool ExpressionDecompiler::init() {
2166 cx->check(script);
2167 return sprinter.init();
2170 bool ExpressionDecompiler::write(const char* s) {
2171 sprinter.put(s);
2172 return true;
2175 bool ExpressionDecompiler::write(JSString* str) {
2176 if (str == cx->names().dot_this_) {
2177 return write("this");
2179 if (str == cx->names().dot_newTarget_) {
2180 return write("new.target");
2182 sprinter.putString(cx, str);
2183 return true;
2186 bool ExpressionDecompiler::quote(JSString* s, char quote) {
2187 QuoteString(&sprinter, s, quote);
2188 return true;
2191 JSAtom* ExpressionDecompiler::loadAtom(jsbytecode* pc) {
2192 return script->getAtom(pc);
2195 JSString* ExpressionDecompiler::loadString(jsbytecode* pc) {
2196 return script->getString(pc);
2199 JSAtom* ExpressionDecompiler::getArg(unsigned slot) {
2200 MOZ_ASSERT(script->isFunction());
2201 MOZ_ASSERT(slot < script->numArgs());
2203 for (PositionalFormalParameterIter fi(script); fi; fi++) {
2204 if (fi.argumentSlot() == slot) {
2205 if (!fi.isDestructured()) {
2206 return fi.name();
2209 // Destructured arguments have no single binding name.
2210 static const char destructuredParam[] = "(destructured parameter)";
2211 return Atomize(cx, destructuredParam, strlen(destructuredParam));
2215 MOZ_CRASH("No binding");
2218 UniqueChars ExpressionDecompiler::getOutput() { return sprinter.release(); }
2220 } // anonymous namespace
2222 #if defined(DEBUG) || defined(JS_JITSPEW)
2223 static bool DecompileAtPCForStackDump(
2224 JSContext* cx, HandleScript script,
2225 const OffsetAndDefIndex& offsetAndDefIndex, StringPrinter* sp) {
2226 // The expression decompiler asserts the script is in the current realm.
2227 AutoRealm ar(cx, script);
2229 LifoAllocScope allocScope(&cx->tempLifoAlloc());
2230 BytecodeParser parser(cx, allocScope.alloc(), script);
2231 parser.setStackDump();
2232 if (!parser.parse()) {
2233 return false;
2236 ExpressionDecompiler ed(cx, script, parser);
2237 ed.setStackDump();
2238 if (!ed.init()) {
2239 return false;
2242 if (!ed.decompilePC(offsetAndDefIndex)) {
2243 return false;
2246 UniqueChars result = ed.getOutput();
2247 if (!result) {
2248 return false;
2251 sp->put(result.get());
2252 return true;
2254 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
2256 static bool FindStartPC(JSContext* cx, const FrameIter& iter,
2257 const BytecodeParser& parser, int spindex,
2258 int skipStackHits, const Value& v, jsbytecode** valuepc,
2259 uint8_t* defIndex) {
2260 jsbytecode* current = *valuepc;
2261 *valuepc = nullptr;
2262 *defIndex = 0;
2264 if (spindex < 0 && spindex + int(parser.stackDepthAtPC(current)) < 0) {
2265 spindex = JSDVG_SEARCH_STACK;
2268 if (spindex == JSDVG_SEARCH_STACK) {
2269 size_t index = iter.numFrameSlots();
2271 // The decompiler may be called from inside functions that are not
2272 // called from script, but via the C++ API directly, such as
2273 // Invoke. In that case, the youngest script frame may have a
2274 // completely unrelated pc and stack depth, so we give up.
2275 if (index < size_t(parser.stackDepthAtPC(current))) {
2276 return true;
2279 // We search from fp->sp to base to find the most recently calculated
2280 // value matching v under assumption that it is the value that caused
2281 // the exception.
2282 int stackHits = 0;
2283 Value s;
2284 do {
2285 if (!index) {
2286 return true;
2288 s = iter.frameSlotValue(--index);
2289 } while (s != v || stackHits++ != skipStackHits);
2291 // If the current PC has fewer values on the stack than the index we are
2292 // looking for, the blamed value must be one pushed by the current
2293 // bytecode (e.g. JSOp::MoreIter), so restore *valuepc.
2294 if (index < size_t(parser.stackDepthAtPC(current))) {
2295 *valuepc = parser.pcForStackOperand(current, index, defIndex);
2296 } else {
2297 *valuepc = current;
2298 *defIndex = index - size_t(parser.stackDepthAtPC(current));
2300 } else {
2301 *valuepc = parser.pcForStackOperand(current, spindex, defIndex);
2303 return true;
2306 static bool DecompileExpressionFromStack(JSContext* cx, int spindex,
2307 int skipStackHits, HandleValue v,
2308 UniqueChars* res) {
2309 MOZ_ASSERT(spindex < 0 || spindex == JSDVG_IGNORE_STACK ||
2310 spindex == JSDVG_SEARCH_STACK);
2312 *res = nullptr;
2315 * Give up if we need deterministic behavior for differential testing.
2316 * IonMonkey doesn't use InterpreterFrames and this ensures we get the same
2317 * error messages.
2319 if (js::SupportDifferentialTesting()) {
2320 return true;
2323 if (spindex == JSDVG_IGNORE_STACK) {
2324 return true;
2327 FrameIter frameIter(cx);
2329 if (frameIter.done() || !frameIter.hasScript() ||
2330 frameIter.realm() != cx->realm() || frameIter.inPrologue()) {
2331 return true;
2335 * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the
2336 * previous pc (see bug 831120).
2338 if (frameIter.isIon()) {
2339 return true;
2342 RootedScript script(cx, frameIter.script());
2343 jsbytecode* valuepc = frameIter.pc();
2345 MOZ_ASSERT(script->containsPC(valuepc));
2347 LifoAllocScope allocScope(&cx->tempLifoAlloc());
2348 BytecodeParser parser(cx, allocScope.alloc(), frameIter.script());
2349 if (!parser.parse()) {
2350 return false;
2353 uint8_t defIndex;
2354 if (!FindStartPC(cx, frameIter, parser, spindex, skipStackHits, v, &valuepc,
2355 &defIndex)) {
2356 return false;
2358 if (!valuepc) {
2359 return true;
2362 ExpressionDecompiler ed(cx, script, parser);
2363 if (!ed.init()) {
2364 return false;
2366 if (!ed.decompilePC(valuepc, defIndex)) {
2367 return false;
2370 *res = ed.getOutput();
2371 return *res != nullptr;
2374 UniqueChars js::DecompileValueGenerator(JSContext* cx, int spindex,
2375 HandleValue v, HandleString fallbackArg,
2376 int skipStackHits) {
2377 RootedString fallback(cx, fallbackArg);
2379 UniqueChars result;
2380 if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result)) {
2381 return nullptr;
2383 if (result && strcmp(result.get(), "(intermediate value)")) {
2384 return result;
2387 if (!fallback) {
2388 if (v.isUndefined()) {
2389 return DuplicateString(cx, "undefined"); // Prevent users from seeing
2390 // "(void 0)"
2392 fallback = ValueToSource(cx, v);
2393 if (!fallback) {
2394 return nullptr;
2398 return StringToNewUTF8CharsZ(cx, *fallback);
2401 static bool DecompileArgumentFromStack(JSContext* cx, int formalIndex,
2402 UniqueChars* res) {
2403 MOZ_ASSERT(formalIndex >= 0);
2405 *res = nullptr;
2407 /* See note in DecompileExpressionFromStack. */
2408 if (js::SupportDifferentialTesting()) {
2409 return true;
2413 * Settle on the nearest script frame, which should be the builtin that
2414 * called the intrinsic.
2416 FrameIter frameIter(cx);
2417 MOZ_ASSERT(!frameIter.done());
2418 MOZ_ASSERT(frameIter.script()->selfHosted());
2421 * Get the second-to-top frame, the non-self-hosted caller of the builtin
2422 * that called the intrinsic.
2424 ++frameIter;
2425 if (frameIter.done() || !frameIter.hasScript() ||
2426 frameIter.script()->selfHosted() || frameIter.realm() != cx->realm()) {
2427 return true;
2430 RootedScript script(cx, frameIter.script());
2431 jsbytecode* current = frameIter.pc();
2433 MOZ_ASSERT(script->containsPC(current));
2435 if (current < script->main()) {
2436 return true;
2439 /* Don't handle getters, setters or calls from fun.call/fun.apply. */
2440 JSOp op = JSOp(*current);
2441 if (op != JSOp::Call && op != JSOp::CallContent &&
2442 op != JSOp::CallIgnoresRv && op != JSOp::New && op != JSOp::NewContent) {
2443 return true;
2446 if (static_cast<unsigned>(formalIndex) >= GET_ARGC(current)) {
2447 return true;
2450 LifoAllocScope allocScope(&cx->tempLifoAlloc());
2451 BytecodeParser parser(cx, allocScope.alloc(), script);
2452 if (!parser.parse()) {
2453 return false;
2456 bool pushedNewTarget = op == JSOp::New || op == JSOp::NewContent;
2457 int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) -
2458 pushedNewTarget + formalIndex;
2459 MOZ_ASSERT(formalStackIndex >= 0);
2460 if (uint32_t(formalStackIndex) >= parser.stackDepthAtPC(current)) {
2461 return true;
2464 ExpressionDecompiler ed(cx, script, parser);
2465 if (!ed.init()) {
2466 return false;
2468 if (!ed.decompilePCForStackOperand(current, formalStackIndex)) {
2469 return false;
2472 *res = ed.getOutput();
2473 return *res != nullptr;
2476 JSString* js::DecompileArgument(JSContext* cx, int formalIndex, HandleValue v) {
2478 UniqueChars result;
2479 if (!DecompileArgumentFromStack(cx, formalIndex, &result)) {
2480 return nullptr;
2482 if (result && strcmp(result.get(), "(intermediate value)")) {
2483 JS::ConstUTF8CharsZ utf8chars(result.get(), strlen(result.get()));
2484 return NewStringCopyUTF8Z(cx, utf8chars);
2487 if (v.isUndefined()) {
2488 return cx->names().undefined; // Prevent users from seeing "(void 0)"
2491 return ValueToSource(cx, v);
2494 extern bool js::IsValidBytecodeOffset(JSContext* cx, JSScript* script,
2495 size_t offset) {
2496 // This could be faster (by following jump instructions if the target
2497 // is <= offset).
2498 for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) {
2499 size_t here = r.frontOffset();
2500 if (here >= offset) {
2501 return here == offset;
2504 return false;
2508 * There are three possible PCCount profiling states:
2510 * 1. None: Neither scripts nor the runtime have count information.
2511 * 2. Profile: Active scripts have count information, the runtime does not.
2512 * 3. Query: Scripts do not have count information, the runtime does.
2514 * When starting to profile scripts, counting begins immediately, with all JIT
2515 * code discarded and recompiled with counts as necessary. Active interpreter
2516 * frames will not begin profiling until they begin executing another script
2517 * (via a call or return).
2519 * The below API functions manage transitions to new states, according
2520 * to the table below.
2522 * Old State
2523 * -------------------------
2524 * Function None Profile Query
2525 * --------
2526 * StartPCCountProfiling Profile Profile Profile
2527 * StopPCCountProfiling None Query Query
2528 * PurgePCCounts None None None
2531 static void ReleaseScriptCounts(JSRuntime* rt) {
2532 MOZ_ASSERT(rt->scriptAndCountsVector);
2534 js_delete(rt->scriptAndCountsVector.ref());
2535 rt->scriptAndCountsVector = nullptr;
2538 void JS::StartPCCountProfiling(JSContext* cx) {
2539 JSRuntime* rt = cx->runtime();
2541 if (rt->profilingScripts) {
2542 return;
2545 if (rt->scriptAndCountsVector) {
2546 ReleaseScriptCounts(rt);
2549 ReleaseAllJITCode(rt->gcContext());
2551 rt->profilingScripts = true;
2554 void JS::StopPCCountProfiling(JSContext* cx) {
2555 JSRuntime* rt = cx->runtime();
2557 if (!rt->profilingScripts) {
2558 return;
2560 MOZ_ASSERT(!rt->scriptAndCountsVector);
2562 ReleaseAllJITCode(rt->gcContext());
2564 auto* vec = cx->new_<PersistentRooted<ScriptAndCountsVector>>(
2565 cx, ScriptAndCountsVector());
2566 if (!vec) {
2567 return;
2570 for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
2571 for (auto base = zone->cellIter<BaseScript>(); !base.done(); base.next()) {
2572 if (base->hasScriptCounts() && base->hasJitScript()) {
2573 if (!vec->append(base->asJSScript())) {
2574 return;
2580 rt->profilingScripts = false;
2581 rt->scriptAndCountsVector = vec;
2584 void JS::PurgePCCounts(JSContext* cx) {
2585 JSRuntime* rt = cx->runtime();
2587 if (!rt->scriptAndCountsVector) {
2588 return;
2590 MOZ_ASSERT(!rt->profilingScripts);
2592 ReleaseScriptCounts(rt);
2595 size_t JS::GetPCCountScriptCount(JSContext* cx) {
2596 JSRuntime* rt = cx->runtime();
2598 if (!rt->scriptAndCountsVector) {
2599 return 0;
2602 return rt->scriptAndCountsVector->length();
2605 [[nodiscard]] static bool JSONStringProperty(StringPrinter& sp,
2606 JSONPrinter& json,
2607 const char* name, JSString* str) {
2608 json.beginStringProperty(name);
2609 JSONQuoteString(&sp, str);
2610 json.endStringProperty();
2611 return true;
2614 JSString* JS::GetPCCountScriptSummary(JSContext* cx, size_t index) {
2615 JSRuntime* rt = cx->runtime();
2617 if (!rt->scriptAndCountsVector ||
2618 index >= rt->scriptAndCountsVector->length()) {
2619 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2620 JSMSG_BUFFER_TOO_SMALL);
2621 return nullptr;
2624 const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index];
2625 RootedScript script(cx, sac.script);
2627 JSSprinter sp(cx);
2628 if (!sp.init()) {
2629 return nullptr;
2632 JSONPrinter json(sp, false);
2634 json.beginObject();
2636 Rooted<JSString*> filenameStr(cx);
2637 if (const char* filename = script->filename()) {
2638 filenameStr =
2639 JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(filename, strlen(filename)));
2640 } else {
2641 filenameStr = JS_GetEmptyString(cx);
2643 if (!filenameStr) {
2644 return nullptr;
2646 if (!JSONStringProperty(sp, json, "file", filenameStr)) {
2647 return nullptr;
2649 json.property("line", script->lineno());
2651 if (JSFunction* fun = script->function()) {
2652 if (JSAtom* atom = fun->fullDisplayAtom()) {
2653 if (!JSONStringProperty(sp, json, "name", atom)) {
2654 return nullptr;
2659 uint64_t total = 0;
2661 AllBytecodesIterable iter(script);
2662 for (BytecodeLocation loc : iter) {
2663 if (const PCCounts* counts = sac.maybeGetPCCounts(loc.toRawBytecode())) {
2664 total += counts->numExec();
2668 json.beginObjectProperty("totals");
2670 json.property(PCCounts::numExecName, total);
2672 uint64_t ionActivity = 0;
2673 jit::IonScriptCounts* ionCounts = sac.getIonCounts();
2674 while (ionCounts) {
2675 for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
2676 ionActivity += ionCounts->block(i).hitCount();
2678 ionCounts = ionCounts->previous();
2680 if (ionActivity) {
2681 json.property("ion", ionActivity);
2684 json.endObject();
2686 json.endObject();
2688 return sp.release(cx);
2691 static bool GetPCCountJSON(JSContext* cx, const ScriptAndCounts& sac,
2692 StringPrinter& sp) {
2693 JSONPrinter json(sp, false);
2695 RootedScript script(cx, sac.script);
2697 LifoAllocScope allocScope(&cx->tempLifoAlloc());
2698 BytecodeParser parser(cx, allocScope.alloc(), script);
2699 if (!parser.parse()) {
2700 return false;
2703 json.beginObject();
2705 JSString* str = JS_DecompileScript(cx, script);
2706 if (!str) {
2707 return false;
2710 if (!JSONStringProperty(sp, json, "text", str)) {
2711 return false;
2714 json.property("line", script->lineno());
2716 json.beginListProperty("opcodes");
2718 uint64_t hits = 0;
2719 for (BytecodeRangeWithPosition range(cx, script); !range.empty();
2720 range.popFront()) {
2721 jsbytecode* pc = range.frontPC();
2722 size_t offset = script->pcToOffset(pc);
2723 JSOp op = JSOp(*pc);
2725 // If the current instruction is a jump target,
2726 // then update the number of hits.
2727 if (const PCCounts* counts = sac.maybeGetPCCounts(pc)) {
2728 hits = counts->numExec();
2731 json.beginObject();
2733 json.property("id", offset);
2734 json.property("line", range.frontLineNumber());
2735 json.property("name", CodeName(op));
2738 ExpressionDecompiler ed(cx, script, parser);
2739 if (!ed.init()) {
2740 return false;
2742 // defIndex passed here is not used.
2743 if (!ed.decompilePC(pc, /* defIndex = */ 0)) {
2744 return false;
2746 UniqueChars text = ed.getOutput();
2747 if (!text) {
2748 return false;
2751 JS::ConstUTF8CharsZ utf8chars(text.get(), strlen(text.get()));
2752 JSString* str = NewStringCopyUTF8Z(cx, utf8chars);
2753 if (!str) {
2754 return false;
2757 if (!JSONStringProperty(sp, json, "text", str)) {
2758 return false;
2762 json.beginObjectProperty("counts");
2763 if (hits > 0) {
2764 json.property(PCCounts::numExecName, hits);
2766 json.endObject();
2768 json.endObject();
2770 // If the current instruction has thrown,
2771 // then decrement the hit counts with the number of throws.
2772 if (const PCCounts* counts = sac.maybeGetThrowCounts(pc)) {
2773 hits -= counts->numExec();
2777 json.endList();
2779 if (jit::IonScriptCounts* ionCounts = sac.getIonCounts()) {
2780 json.beginListProperty("ion");
2782 while (ionCounts) {
2783 json.beginList();
2784 for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
2785 const jit::IonBlockCounts& block = ionCounts->block(i);
2787 json.beginObject();
2788 json.property("id", block.id());
2789 json.property("offset", block.offset());
2791 json.beginListProperty("successors");
2792 for (size_t j = 0; j < block.numSuccessors(); j++) {
2793 json.value(block.successor(j));
2795 json.endList();
2797 json.property("hits", block.hitCount());
2799 JSString* str = NewStringCopyZ<CanGC>(cx, block.code());
2800 if (!str) {
2801 return false;
2804 if (!JSONStringProperty(sp, json, "code", str)) {
2805 return false;
2808 json.endObject();
2810 json.endList();
2812 ionCounts = ionCounts->previous();
2815 json.endList();
2818 json.endObject();
2820 if (sp.hadOutOfMemory()) {
2821 sp.reportOutOfMemory();
2822 return false;
2825 return true;
2828 JSString* JS::GetPCCountScriptContents(JSContext* cx, size_t index) {
2829 JSRuntime* rt = cx->runtime();
2831 if (!rt->scriptAndCountsVector ||
2832 index >= rt->scriptAndCountsVector->length()) {
2833 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2834 JSMSG_BUFFER_TOO_SMALL);
2835 return nullptr;
2838 const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index];
2839 JSScript* script = sac.script;
2841 JSSprinter sp(cx);
2842 if (!sp.init()) {
2843 return nullptr;
2847 AutoRealm ar(cx, &script->global());
2848 if (!GetPCCountJSON(cx, sac, sp)) {
2849 return nullptr;
2853 return sp.release(cx);
2856 struct CollectedScripts {
2857 MutableHandle<ScriptVector> scripts;
2858 bool ok = true;
2860 explicit CollectedScripts(MutableHandle<ScriptVector> scripts)
2861 : scripts(scripts) {}
2863 static void consider(JSRuntime* rt, void* data, BaseScript* script,
2864 const JS::AutoRequireNoGC& nogc) {
2865 auto self = static_cast<CollectedScripts*>(data);
2866 if (!script->filename()) {
2867 return;
2869 if (!self->scripts.append(script->asJSScript())) {
2870 self->ok = false;
2875 static bool GenerateLcovInfo(JSContext* cx, JS::Realm* realm,
2876 GenericPrinter& out) {
2877 AutoRealmUnchecked ar(cx, realm);
2879 // Collect the list of scripts which are part of the current realm.
2881 MOZ_RELEASE_ASSERT(
2882 coverage::IsLCovEnabled(),
2883 "Coverage must be enabled for process before generating LCov info");
2885 // Hold the scripts that we have already flushed, to avoid flushing them
2886 // twice.
2887 using JSScriptSet = GCHashSet<JSScript*>;
2888 Rooted<JSScriptSet> scriptsDone(cx, JSScriptSet(cx));
2890 Rooted<ScriptVector> queue(cx, ScriptVector(cx));
2893 CollectedScripts result(&queue);
2894 IterateScripts(cx, realm, &result, &CollectedScripts::consider);
2895 if (!result.ok) {
2896 ReportOutOfMemory(cx);
2897 return false;
2901 if (queue.length() == 0) {
2902 return true;
2905 // Ensure the LCovRealm exists to collect info into.
2906 coverage::LCovRealm* lcovRealm = realm->lcovRealm();
2907 if (!lcovRealm) {
2908 return false;
2911 // Collect code coverage info for one realm.
2912 do {
2913 RootedScript script(cx, queue.popCopy());
2914 RootedFunction fun(cx);
2916 JSScriptSet::AddPtr entry = scriptsDone.lookupForAdd(script);
2917 if (entry) {
2918 continue;
2921 if (!coverage::CollectScriptCoverage(script, false)) {
2922 ReportOutOfMemory(cx);
2923 return false;
2926 script->resetScriptCounts();
2928 if (!scriptsDone.add(entry, script)) {
2929 return false;
2932 if (!script->isTopLevel()) {
2933 continue;
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 auto gcthings = script->gcthings();
2941 for (JS::GCCellPtr gcThing : mozilla::Reversed(gcthings)) {
2942 if (!gcThing.is<JSObject>()) {
2943 continue;
2945 JSObject* obj = &gcThing.as<JSObject>();
2947 if (!obj->is<JSFunction>()) {
2948 continue;
2950 fun = &obj->as<JSFunction>();
2952 // Ignore asm.js functions
2953 if (!fun->isInterpreted()) {
2954 continue;
2957 // Queue the script in the list of script associated to the
2958 // current source.
2959 JSScript* childScript = JSFunction::getOrCreateScript(cx, fun);
2960 if (!childScript || !queue.append(childScript)) {
2961 return false;
2964 } while (!queue.empty());
2966 bool isEmpty = true;
2967 lcovRealm->exportInto(out, &isEmpty);
2968 return true;
2971 JS_PUBLIC_API UniqueChars js::GetCodeCoverageSummaryAll(JSContext* cx,
2972 size_t* length) {
2973 Sprinter out(cx);
2974 if (!out.init()) {
2975 return nullptr;
2978 for (RealmsIter realm(cx->runtime()); !realm.done(); realm.next()) {
2979 if (!GenerateLcovInfo(cx, realm, out)) {
2980 return nullptr;
2984 *length = out.length();
2985 return out.release();
2988 JS_PUBLIC_API UniqueChars js::GetCodeCoverageSummary(JSContext* cx,
2989 size_t* length) {
2990 Sprinter out(cx);
2991 if (!out.init()) {
2992 return nullptr;
2995 if (!GenerateLcovInfo(cx, cx->realm(), out)) {
2996 return nullptr;
2999 *length = out.length();
3000 return out.release();