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/. */
8 * JS bytecode descriptors, disassemblers, and (expression) decompilers.
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"
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"
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"
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
)
79 * Each element of the array is either a source literal associated with JS
82 static const char* const CodeToken
[] = {
83 #define TOKEN(op, op_snake, token, ...) token,
84 FOR_EACH_OPCODE(TOKEN
)
89 * Array of JS bytecode names used by PC count JSON, DEBUG-only Disassemble
92 const char* const js::CodeNameTable
[] = {
93 #define OPNAME(op, ...) #op,
94 FOR_EACH_OPCODE(OPNAME
)
98 /************************************************************************/
100 static bool DecompileArgumentFromStack(JSContext
* cx
, int formalIndex
,
103 /* static */ const char PCCounts::numExecName
[] = "interp";
105 [[nodiscard
]] static bool DumpIonScriptCounts(StringPrinter
* sp
,
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()),
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());
131 [[nodiscard
]] static bool DumpPCCounts(JSContext
* cx
, HandleScript script
,
133 MOZ_ASSERT(script
->hasScriptCounts());
135 // Ensure the Disassemble1 call below does not discard the script counts.
136 gc::AutoSuppressGC
suppress(cx
);
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
)) {
149 PCCounts
* counts
= script
->maybeGetPCCounts(pc
);
150 if (double val
= counts
? counts
->numExec() : 0.0) {
151 sp
->printf("\"%s\": %.0f", PCCounts::numExecName
, val
);
159 jit::IonScriptCounts
* ionCounts
= script
->getIonCounts();
161 if (!DumpIonScriptCounts(sp
, script
, ionCounts
)) {
165 ionCounts
= ionCounts
->previous();
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();
175 if (base
->realm() != cx
->realm()) {
178 MOZ_ASSERT_IF(base
->hasScriptCounts(), base
->hasBytecode());
179 if (base
->hasScriptCounts()) {
180 if (!scripts
.append(base
->asJSScript())) {
186 for (uint32_t i
= 0; i
< scripts
.length(); i
++) {
187 HandleScript script
= scripts
[i
];
188 Sprinter
sprinter(cx
);
189 if (!sprinter
.init()) {
193 const char* filename
= script
->filename();
195 filename
= "(unknown)";
197 fprintf(stdout
, "--- SCRIPT %s:%u ---\n", filename
, script
->lineno());
198 if (!DumpPCCounts(cx
, script
, &sprinter
)) {
201 JS::UniqueChars out
= sprinter
.release();
205 fputs(out
.get(), stdout
);
206 fprintf(stdout
, "--- END SCRIPT %s:%u ---\n", filename
, script
->lineno());
212 /////////////////////////////////////////////////////////////////////
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.
222 // The index in `ndefs` for the PC (0-origin)
228 // Ignored this value in the expression decompilation.
229 // Used by JSOp::NopDestructuring. See BytecodeParser::simulateOp.
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.
239 uint32_t offset() const {
240 MOZ_ASSERT(!isSpecial());
243 uint32_t specialOffset() const {
244 MOZ_ASSERT(isSpecial());
248 uint8_t defIndex() const {
249 MOZ_ASSERT(!isSpecial());
252 uint8_t specialDefIndex() const {
253 MOZ_ASSERT(isSpecial());
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
) {
263 defIndex_
= aDefIndex
;
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
);
282 class BytecodeParser
{
284 enum class JumpKind
{
295 explicit Bytecode(const LifoAllocPolicy
<Fallible
>& alloc
)
299 #if defined(DEBUG) || defined(JS_JITSPEW)
302 offsetStackAfter(nullptr),
304 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
308 // Whether this instruction has been analyzed to get its output defines
312 // Stack depth before this opcode.
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
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
;
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
,
344 offsetStack
= alloc
.newArray
<OffsetAndDefIndex
>(stackDepth
);
348 for (uint32_t n
= 0; n
< stackDepth
; n
++) {
349 offsetStack
[n
] = stack
[n
];
355 #if defined(DEBUG) || defined(JS_JITSPEW)
356 bool captureOffsetStackAfter(LifoAlloc
& alloc
,
357 const OffsetAndDefIndex
* stack
,
359 stackDepthAfter
= depth
;
360 if (stackDepthAfter
) {
361 offsetStackAfter
= alloc
.newArray
<OffsetAndDefIndex
>(stackDepthAfter
);
362 if (!offsetStackAfter
) {
365 for (uint32_t n
= 0; n
< stackDepthAfter
; n
++) {
366 offsetStackAfter
[n
] = stack
[n
];
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()) {
388 if (offsetStack
[n
].isIgnored()) {
389 offsetStack
[n
] = stack
[n
];
391 if (offsetStack
[n
] != stack
[n
]) {
392 offsetStack
[n
].setMerged();
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.
412 BytecodeParser(JSContext
* cx
, LifoAlloc
& alloc
, JSScript
* script
)
426 #if defined(DEBUG) || defined(JS_JITSPEW)
427 bool isReachable(const jsbytecode
* pc
) const { return maybeCode(pc
); }
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
));
449 const OffsetAndDefIndex
& offsetForStackOperand(uint32_t offset
,
451 Bytecode
& code
= getCode(offset
);
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()) {
467 *defIndex
= offsetAndDefIndex
.defIndex();
468 return script_
->offsetToPC(offsetAndDefIndex
.offset());
471 #if defined(DEBUG) || defined(JS_JITSPEW)
472 const OffsetAndDefIndex
& offsetForStackOperandAfterPC(uint32_t offset
,
474 Bytecode
& code
= getCode(offset
);
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
)) {
496 void setStackDump() { isStackDump
= true; }
497 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
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
));
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
,
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
);
548 MOZ_RELEASE_ASSERT(stackDepth
+ ndefs
<= maximumStackDepth());
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.
558 case JSOp::InitHiddenProp
:
559 case JSOp::InitHiddenPropGetter
:
560 case JSOp::InitHiddenPropSetter
:
561 case JSOp::InitLockedProp
:
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);
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);
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.
595 for (uint32_t n
= 0; n
!= ndefs
; ++n
) {
596 offsetStack
[stackDepth
+ n
].set(offset
, n
);
600 case JSOp::NopDestructuring
:
601 // Poison the last offset to not obfuscate the error message.
602 offsetStack
[stackDepth
- 1].setIgnored();
606 // Keep the switch value.
607 MOZ_ASSERT(ndefs
== 1);
611 MOZ_ASSERT(ndefs
== 2);
612 offsetStack
[stackDepth
+ 1] = offsetStack
[stackDepth
];
616 MOZ_ASSERT(ndefs
== 4);
617 offsetStack
[stackDepth
+ 2] = offsetStack
[stackDepth
];
618 offsetStack
[stackDepth
+ 3] = offsetStack
[stackDepth
+ 1];
622 MOZ_ASSERT(ndefs
== 1);
623 unsigned n
= GET_UINT24(pc
);
624 MOZ_ASSERT(n
< stackDepth
);
625 offsetStack
[stackDepth
] = offsetStack
[stackDepth
- 1 - n
];
630 MOZ_ASSERT(ndefs
== 2);
631 OffsetAndDefIndex tmp
= offsetStack
[stackDepth
+ 1];
632 offsetStack
[stackDepth
+ 1] = offsetStack
[stackDepth
];
633 offsetStack
[stackDepth
] = tmp
;
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
;
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
;
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
:
672 case JSOp::SetAliasedVar
:
674 case JSOp::SetIntrinsic
:
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);
684 case JSOp::InitHomeObject
:
685 // Pop the top value, keep the other value.
686 MOZ_ASSERT(nuses
== 2);
687 MOZ_ASSERT(ndefs
== 1);
690 case JSOp::CheckResumeKind
:
691 // Pop the top two values, keep the other value.
692 MOZ_ASSERT(nuses
== 3);
693 MOZ_ASSERT(ndefs
== 1);
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];
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];
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];
724 case JSOp::IsGenClosing
:
726 case JSOp::IsNullOrUndefined
:
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);
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);
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);
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
];
766 code
= alloc().new_
<Bytecode
>(alloc());
767 if (!code
|| !code
->captureOffsetStack(alloc(), offsetStack
, stackDepth
)) {
772 code
->mergeOffsetStack(offsetStack
, stackDepth
);
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
)) {
786 uint32_t currentOffset
= script_
->pcToOffset(pc
);
788 if (!codeArray_
[offset
]->addJump(currentOffset
, kind
)) {
794 // If this is a backedge, assert we parsed the target JSOp::LoopHead.
795 MOZ_ASSERT_IF(offset
< currentOffset
, codeArray_
[offset
]->parsed
);
801 bool BytecodeParser::parse() {
802 MOZ_ASSERT(!codeArray_
);
804 uint32_t length
= script_
->length();
805 codeArray_
= alloc().newArray
<Bytecode
*>(length
);
812 mozilla::PodZero(codeArray_
, length
);
814 // Fill in stack depth and definitions at initial bytecode.
815 Bytecode
* startcode
= alloc().new_
<Bytecode
>(alloc());
821 // Fill in stack depth and definitions at initial bytecode.
822 OffsetAndDefIndex
* offsetStack
=
823 alloc().newArray
<OffsetAndDefIndex
>(maximumStackDepth());
824 if (maximumStackDepth() && !offsetStack
) {
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
);
844 // Haven't found a path by which this bytecode is reachable.
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
];
858 // No need to reparse.
864 uint32_t stackDepth
= simulateOp(op
, offset
, offsetStack
, code
->stackDepth
);
866 #if defined(DEBUG) || defined(JS_JITSPEW)
868 if (!code
->captureOffsetStackAfter(alloc(), offsetStack
, stackDepth
)) {
873 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
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
)) {
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
)) {
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
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
)) {
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
922 offsetStack
[stackDepth
].set(offset
, 0);
923 offsetStack
[stackDepth
+ 1].set(offset
, 1);
924 if (!addJump(catchOffset
, stackDepth
+ 2, offsetStack
, pc
,
925 JumpKind::TryFinally
)) {
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
) {
946 uint32_t targetOffset
= offset
+ GET_JUMP_OFFSET(pc
);
947 if (!addJump(targetOffset
, newStackDepth
, offsetStack
, pc
,
953 // Handle any fallthrough from this opcode.
954 if (BytecodeFallsThrough(op
)) {
955 if (!recordBytecode(nextOffset
, offsetStack
, stackDepth
)) {
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()) {
974 *reachablePC
= parser
.isReachable(pc
);
977 *depth
= parser
.stackDepthAtPC(pc
);
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()) {
1008 sp
->printf("%s:%u\n", script
->filename(), unsigned(script
->lineno()));
1011 if (pc
!= nullptr) {
1015 sp
->put("sn stack ");
1023 if (pc
!= nullptr) {
1027 sp
->put("-- ----- ");
1035 jsbytecode
* next
= script
->code();
1036 jsbytecode
* end
= script
->codeEnd();
1037 while (next
< end
) {
1038 if (next
== script
->main()) {
1041 if (pc
!= nullptr) {
1042 sp
->put(pc
== next
? "--> " : " ");
1045 if (parser
&& parser
->isReachable(next
)) {
1046 sp
->printf("%05u ", parser
->stackDepthAtPC(next
));
1051 unsigned len
= Disassemble1(cx
, script
, next
, script
->pcToOffset(next
),
1052 lines
, parser
.ptrOr(nullptr), sp
);
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()) {
1074 ScriptFrameIter
iter(cx
);
1076 fprintf(fp
, "Empty stack.\n");
1079 RootedScript
script(cx
, iter
.script());
1080 bool ok
= DisassembleAtPC(cx
, script
, true, iter
.pc(), false, &sprinter
);
1081 JS::UniqueChars out
= sprinter
.release();
1085 fprintf(fp
, "%s", out
.get());
1089 JS_PUBLIC_API
bool js::DumpScript(JSContext
* cx
, JSScript
* scriptArg
,
1091 gc::AutoSuppressGC
suppressGC(cx
);
1092 Sprinter
sprinter(cx
);
1093 if (!sprinter
.init()) {
1096 RootedScript
script(cx
, scriptArg
);
1097 bool ok
= Disassemble(cx
, script
, true, &sprinter
);
1098 JS::UniqueChars out
= sprinter
.release();
1102 fprintf(fp
, "%s", out
.get());
1106 static UniqueChars
ToDisassemblySource(JSContext
* cx
, HandleValue v
) {
1108 return QuoteString(cx
, v
.toString(), '"');
1111 if (JS::RuntimeHeapIsBusy()) {
1112 return DuplicateString(cx
, "<value>");
1116 JSObject
& obj
= v
.toObject();
1118 if (obj
.is
<JSFunction
>()) {
1119 RootedFunction
fun(cx
, &obj
.as
<JSFunction
>());
1120 JSString
* str
= JS_DecompileFunction(cx
, fun
);
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
);
1133 return QuoteString(cx
, source
);
1137 JSString
* str
= ValueToSource(cx
, v
);
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()));
1148 ReportOutOfMemory(cx
);
1152 for (Rooted
<BindingIter
> bi(cx
, BindingIter(scope
)); bi
; bi
++) {
1153 UniqueChars nameBytes
= AtomToPrintableString(cx
, bi
.name());
1158 source
= JS_sprintf_append(std::move(source
), "%s: ", nameBytes
.get());
1160 ReportOutOfMemory(cx
);
1164 BindingLocation loc
= bi
.location();
1165 switch (loc
.kind()) {
1166 case BindingLocation::Kind::Global
:
1167 source
= JS_sprintf_append(std::move(source
), "global");
1170 case BindingLocation::Kind::Frame
:
1172 JS_sprintf_append(std::move(source
), "frame slot %u", loc
.slot());
1175 case BindingLocation::Kind::Environment
:
1177 JS_sprintf_append(std::move(source
), "env slot %u", loc
.slot());
1180 case BindingLocation::Kind::Argument
:
1182 JS_sprintf_append(std::move(source
), "arg slot %u", loc
.slot());
1185 case BindingLocation::Kind::NamedLambdaCallee
:
1186 source
= JS_sprintf_append(std::move(source
), "named lambda callee");
1189 case BindingLocation::Kind::Import
:
1190 source
= JS_sprintf_append(std::move(source
), "import");
1195 ReportOutOfMemory(cx
);
1200 source
= JS_sprintf_append(std::move(source
), ", ");
1202 ReportOutOfMemory(cx
);
1208 source
= JS_sprintf_append(std::move(source
), "}");
1210 ReportOutOfMemory(cx
);
1214 *bytes
= std::move(source
);
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
) {
1231 case BytecodeParser::JumpKind::Simple
:
1234 case BytecodeParser::JumpKind::SwitchCase
:
1235 sp
->put("switch-case ");
1238 case BytecodeParser::JumpKind::SwitchDefault
:
1239 sp
->put("switch-default ");
1242 case BytecodeParser::JumpKind::TryCatch
:
1243 sp
->put("try-catch ");
1246 case BytecodeParser::JumpKind::TryFinally
:
1247 sp
->put("try-finally ");
1251 sp
->printf("from %s @ %05u", CodeName(JSOp(*pc
)),
1252 unsigned(script
->pcToOffset(pc
)));
1256 if (!parser
->forEachJumpOrigins(pc
, callback
)) {
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())) {
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
);
1288 ReportOutOfMemory(cx
);
1291 sp
->putString(cx
, str
);
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
)) {
1310 size_t before
= sp
->length();
1311 bool stackDumped
= false;
1312 auto dumpStack
= [&cx
, &script
, &pc
, &parser
, &sp
, &before
, &stackDumped
]() {
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
++) {
1331 if (!parser
->isReachable(pc
)) {
1332 sp
->put("!!! UNREACHABLE !!!");
1334 uint32_t depth
= parser
->stackDepthAfterPC(pc
);
1336 for (uint32_t i
= 0; i
< depth
; i
++) {
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
)) {
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
);
1363 JSOp op
= JSOp(*pc
);
1364 const JSCodeSpec
& cs
= CodeSpec(op
);
1365 const unsigned len
= cs
.length
;
1366 sp
->printf("%05u:", loc
);
1368 sp
->printf("%4u", PCToLineNumber(script
, pc
));
1370 sp
->printf(" %s", CodeName(op
));
1373 switch (JOF_TYPE(cs
.format
)) {
1378 ptrdiff_t off
= GET_JUMP_OFFSET(pc
);
1379 sp
->printf(" %u (%+d)", unsigned(loc
+ int(off
)), int(off
));
1384 Rooted
<Scope
*> scope(cx
, script
->getScope(pc
));
1386 if (!ToDisassemblySource(cx
, scope
, &bytes
)) {
1389 sp
->printf(" %s", bytes
.get());
1393 case JOF_ENVCOORD
: {
1394 RootedValue
v(cx
, StringValue(EnvironmentCoordinateNameSlow(script
, pc
)));
1395 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1399 EnvironmentCoordinate
ec(pc
);
1400 sp
->printf(" %s (hops = %u, slot = %u)", bytes
.get(), ec
.hops(),
1404 case JOF_DEBUGCOORD
: {
1405 EnvironmentCoordinate
ec(pc
);
1406 sp
->printf("(hops = %u, slot = %u)", ec
.hops(), ec
.slot());
1410 RootedValue
v(cx
, StringValue(script
->getAtom(pc
)));
1411 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1415 sp
->printf(" %s", bytes
.get());
1419 RootedValue
v(cx
, StringValue(script
->getString(pc
)));
1420 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1424 sp
->printf(" %s", bytes
.get());
1429 double d
= GET_INLINE_VALUE(pc
).toDouble();
1430 sp
->printf(" %lf", d
);
1435 RootedValue
v(cx
, BigIntValue(script
->getBigInt(pc
)));
1436 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1440 sp
->printf(" %s", bytes
.get());
1445 JSObject
* obj
= script
->getObject(pc
);
1447 RootedValue
v(cx
, ObjectValue(*obj
));
1448 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1452 sp
->printf(" %s", bytes
.get());
1458 SharedShape
* shape
= script
->getShape(pc
);
1460 if (!PrintShapeProperties(cx
, sp
, shape
)) {
1467 js::RegExpObject
* obj
= script
->getRegExp(pc
);
1468 RootedValue
v(cx
, ObjectValue(*obj
));
1469 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1473 sp
->printf(" %s", bytes
.get());
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.
1493 for (i
= low
; i
<= high
; i
++) {
1495 script
->tableSwitchCaseOffset(pc
, i
- low
) - script
->pcToOffset(pc
);
1496 sp
->printf("\n\t%d: %d", i
, int(off
));
1502 sp
->printf(" %u", GET_ARGNO(pc
));
1506 sp
->printf(" %u", GET_LOCALNO(pc
));
1510 sp
->printf(" %u", unsigned(GET_GCTHING_INDEX(pc
)));
1514 sp
->printf(" %u", GET_UINT32(pc
));
1518 sp
->printf(" (ic: %u)", GET_ICINDEX(pc
));
1522 sp
->printf(" (ic: %u, depthHint: %u)", GET_ICINDEX(pc
),
1523 LoopHeadDepthHint(pc
));
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
);
1537 i
= (int)GET_UINT16(pc
);
1540 case JOF_RESUMEINDEX
:
1542 MOZ_ASSERT(len
== 4);
1543 i
= (int)GET_UINT24(pc
);
1555 MOZ_ASSERT(op
== JSOp::Int32
);
1558 sp
->printf(" %d", i
);
1563 SprintfLiteral(numBuf
, "%x", cs
.format
);
1564 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1565 JSMSG_UNKNOWN_FORMAT
, numBuf
);
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) */
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
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
{
1620 RootedScript script
;
1621 const BytecodeParser
& parser
;
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.
1631 ExpressionDecompiler(JSContext
* cx
, JSScript
* script
,
1632 const BytecodeParser
& parser
)
1637 #if defined(DEBUG) || defined(JS_JITSPEW)
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; }
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
) {
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
) {
1684 return write("(") && decompilePCForStackOperand(pc
, -2) && write(" ") &&
1685 write(token
) && write(extra
) && write(" ") &&
1686 decompilePCForStackOperand(pc
, -1) && write(")");
1690 return write("(") && write(token
) &&
1691 decompilePCForStackOperand(pc
, -1) && write(")");
1699 return write("(delete ") && write(loadAtom(pc
)) && write(")");
1701 case JSOp::GetGName
:
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()
1713 // For stack dump, argument name is not necessary.
1718 if (!DecompileArgumentFromStack(cx
, slot
, &result
)) {
1722 // Note that decompiling the argument in the parent frame might
1725 return write(result
.get());
1728 // If it fails, do not return parameter name and let the caller
1730 return write("(intermediate value)");
1733 JSAtom
* atom
= getArg(slot
);
1739 case JSOp::GetLocal
: {
1740 JSAtom
* atom
= FrameSlotName(script
, pc
);
1744 case JSOp::GetAliasedVar
: {
1745 JSAtom
* atom
= EnvironmentCoordinateNameSlow(script
, pc
);
1751 case JSOp::StrictDelProp
:
1753 case JSOp::GetBoundName
: {
1754 bool hasDelete
= op
== JSOp::DelProp
|| op
== JSOp::StrictDelProp
;
1755 Rooted
<JSAtom
*> prop(cx
, loadAtom(pc
));
1757 return (hasDelete
? write("(delete ") : true) &&
1758 decompilePCForStackOperand(pc
, -1) &&
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');
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".
1774 return decompilePCForStackOperand(pc
, -3) && write("[") &&
1775 decompilePCForStackOperand(pc
, -2) && write("]");
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) &&
1791 return write("null");
1793 return write("true");
1795 return write("false");
1802 sprinter
.printf("%d", GetBytecodeInteger(pc
));
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
]);
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");
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
:
1838 case JSOp::RegExp
: {
1839 Rooted
<RegExpObject
*> obj(cx
, &script
->getObject(pc
)->as
<RegExpObject
>());
1840 JSString
* str
= RegExpObject::toString(cx
, obj
);
1846 case JSOp::Object
: {
1847 JSObject
* obj
= script
->getObject(pc
);
1848 RootedValue
objv(cx
, ObjectValue(*obj
));
1849 JSString
* str
= ValueToSource(cx
, objv
);
1856 return write("(void ") && decompilePCForStackOperand(pc
, -1) &&
1859 case JSOp::SuperCall
:
1860 if (GET_ARGC(pc
) == 0) {
1861 return write("super()");
1864 case JSOp::SpreadSuperCall
:
1865 return write("super(...)");
1866 case JSOp::SuperFun
:
1867 return write("super");
1870 case JSOp::SpreadEval
:
1871 case JSOp::StrictEval
:
1872 case JSOp::StrictSpreadEval
:
1873 return write("eval(...)");
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) &&
1887 case JSOp::DynamicImport
:
1888 return write("import(...)");
1891 case JSOp::TypeofExpr
:
1892 return write("(typeof ") && decompilePCForStackOperand(pc
, -1) &&
1895 case JSOp::InitElemArray
:
1896 return write("[...]");
1898 case JSOp::InitElemInc
:
1899 if (defIndex
== 0) {
1900 return write("[...]");
1902 MOZ_ASSERT(defIndex
== 1);
1904 // INDEX won't be be exposed to error message.
1906 return write("INDEX");
1911 case JSOp::ToNumeric
:
1912 return write("(tonumeric ") && decompilePCForStackOperand(pc
, -1) &&
1916 return write("(inc ") && decompilePCForStackOperand(pc
, -1) && write(")");
1919 return write("(dec ") && decompilePCForStackOperand(pc
, -1) && write(")");
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();
1927 return write("[bigint]");
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("#[...]");
1950 // Special decompilation for stack dump.
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
));
1962 case JSOp::GetActualArg
:
1963 return write("arguments[") && decompilePCForStackOperand(pc
, -1) &&
1966 case JSOp::BindGName
:
1967 return write("GLOBAL");
1969 case JSOp::BindName
:
1971 return write("ENV");
1974 return write("CALLEE");
1976 case JSOp::EnvCallee
:
1977 return write("ENVCALLEE");
1979 case JSOp::CallSiteObj
:
1980 return write("OBJ");
1983 sprinter
.printf("%lf", GET_INLINE_VALUE(pc
).toDouble());
1986 case JSOp::Exception
:
1987 return write("EXCEPTION");
1990 // Used for the values live on entry to the finally block.
1991 // See TryNoteKind::Finally above.
1992 if (defIndex
== 0) {
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");
2012 return write("RVAL");
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");
2034 return write("ITER");
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");
2048 case JSOp::NewObject
:
2049 case JSOp::ObjWithProto
:
2050 return write("OBJ");
2052 case JSOp::OptimizeGetIterator
:
2053 case JSOp::OptimizeSpreadCall
:
2054 return write("OPTIMIZED");
2057 return write("REST");
2060 return write("RVAL");
2062 case JSOp::SuperBase
:
2063 return write("HOMEOBJECTPROTO");
2065 case JSOp::ToPropertyKey
:
2066 return write("TOPROPERTYKEY(") && decompilePCForStackOperand(pc
, -1) &&
2068 case JSOp::ToString
:
2069 return write("TOSTRING(") && decompilePCForStackOperand(pc
, -1) &&
2072 case JSOp::Uninitialized
:
2073 return write("UNINITIALIZED");
2075 case JSOp::InitialYield
:
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) &&
2108 case JSOp::CheckPrivateField
:
2109 return write("HasPrivateField");
2111 case JSOp::NewPrivateName
:
2112 return write("PRIVATENAME");
2114 case JSOp::CheckReturn
:
2115 return write("RVAL");
2118 return write("HasOwn(") && decompilePCForStackOperand(pc
, -2) &&
2119 write(", ") && decompilePCForStackOperand(pc
, -1) && write(")");
2124 return write("<unknown>");
2128 return write("(intermediate value)");
2131 bool ExpressionDecompiler::decompilePC(
2132 const OffsetAndDefIndex
& offsetAndDefIndex
) {
2133 if (offsetAndDefIndex
.isSpecial()) {
2136 if (offsetAndDefIndex
.isMerged()) {
2137 if (!write("merged<")) {
2140 } else if (offsetAndDefIndex
.isIgnored()) {
2141 if (!write("ignored<")) {
2146 if (!decompilePC(script
->offsetToPC(offsetAndDefIndex
.specialOffset()),
2147 offsetAndDefIndex
.specialDefIndex())) {
2158 return write("(intermediate value)");
2161 return decompilePC(script
->offsetToPC(offsetAndDefIndex
.offset()),
2162 offsetAndDefIndex
.defIndex());
2165 bool ExpressionDecompiler::init() {
2167 return sprinter
.init();
2170 bool ExpressionDecompiler::write(const char* s
) {
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
);
2186 bool ExpressionDecompiler::quote(JSString
* s
, char quote
) {
2187 QuoteString(&sprinter
, s
, quote
);
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()) {
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()) {
2236 ExpressionDecompiler
ed(cx
, script
, parser
);
2242 if (!ed
.decompilePC(offsetAndDefIndex
)) {
2246 UniqueChars result
= ed
.getOutput();
2251 sp
->put(result
.get());
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
;
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
))) {
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
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
);
2298 *defIndex
= index
- size_t(parser
.stackDepthAtPC(current
));
2301 *valuepc
= parser
.pcForStackOperand(current
, spindex
, defIndex
);
2306 static bool DecompileExpressionFromStack(JSContext
* cx
, int spindex
,
2307 int skipStackHits
, HandleValue v
,
2309 MOZ_ASSERT(spindex
< 0 || spindex
== JSDVG_IGNORE_STACK
||
2310 spindex
== JSDVG_SEARCH_STACK
);
2315 * Give up if we need deterministic behavior for differential testing.
2316 * IonMonkey doesn't use InterpreterFrames and this ensures we get the same
2319 if (js::SupportDifferentialTesting()) {
2323 if (spindex
== JSDVG_IGNORE_STACK
) {
2327 FrameIter
frameIter(cx
);
2329 if (frameIter
.done() || !frameIter
.hasScript() ||
2330 frameIter
.realm() != cx
->realm() || frameIter
.inPrologue()) {
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()) {
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()) {
2354 if (!FindStartPC(cx
, frameIter
, parser
, spindex
, skipStackHits
, v
, &valuepc
,
2362 ExpressionDecompiler
ed(cx
, script
, parser
);
2366 if (!ed
.decompilePC(valuepc
, defIndex
)) {
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
);
2380 if (!DecompileExpressionFromStack(cx
, spindex
, skipStackHits
, v
, &result
)) {
2383 if (result
&& strcmp(result
.get(), "(intermediate value)")) {
2388 if (v
.isUndefined()) {
2389 return DuplicateString(cx
, "undefined"); // Prevent users from seeing
2392 fallback
= ValueToSource(cx
, v
);
2398 return StringToNewUTF8CharsZ(cx
, *fallback
);
2401 static bool DecompileArgumentFromStack(JSContext
* cx
, int formalIndex
,
2403 MOZ_ASSERT(formalIndex
>= 0);
2407 /* See note in DecompileExpressionFromStack. */
2408 if (js::SupportDifferentialTesting()) {
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.
2425 if (frameIter
.done() || !frameIter
.hasScript() ||
2426 frameIter
.script()->selfHosted() || frameIter
.realm() != cx
->realm()) {
2430 RootedScript
script(cx
, frameIter
.script());
2431 jsbytecode
* current
= frameIter
.pc();
2433 MOZ_ASSERT(script
->containsPC(current
));
2435 if (current
< script
->main()) {
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
) {
2446 if (static_cast<unsigned>(formalIndex
) >= GET_ARGC(current
)) {
2450 LifoAllocScope
allocScope(&cx
->tempLifoAlloc());
2451 BytecodeParser
parser(cx
, allocScope
.alloc(), script
);
2452 if (!parser
.parse()) {
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
)) {
2464 ExpressionDecompiler
ed(cx
, script
, parser
);
2468 if (!ed
.decompilePCForStackOperand(current
, formalStackIndex
)) {
2472 *res
= ed
.getOutput();
2473 return *res
!= nullptr;
2476 JSString
* js::DecompileArgument(JSContext
* cx
, int formalIndex
, HandleValue v
) {
2479 if (!DecompileArgumentFromStack(cx
, formalIndex
, &result
)) {
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
,
2496 // This could be faster (by following jump instructions if the target
2498 for (BytecodeRange
r(cx
, script
); !r
.empty(); r
.popFront()) {
2499 size_t here
= r
.frontOffset();
2500 if (here
>= offset
) {
2501 return here
== offset
;
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.
2523 * -------------------------
2524 * Function None Profile Query
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
) {
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
) {
2560 MOZ_ASSERT(!rt
->scriptAndCountsVector
);
2562 ReleaseAllJITCode(rt
->gcContext());
2564 auto* vec
= cx
->new_
<PersistentRooted
<ScriptAndCountsVector
>>(
2565 cx
, ScriptAndCountsVector());
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())) {
2580 rt
->profilingScripts
= false;
2581 rt
->scriptAndCountsVector
= vec
;
2584 void JS::PurgePCCounts(JSContext
* cx
) {
2585 JSRuntime
* rt
= cx
->runtime();
2587 if (!rt
->scriptAndCountsVector
) {
2590 MOZ_ASSERT(!rt
->profilingScripts
);
2592 ReleaseScriptCounts(rt
);
2595 size_t JS::GetPCCountScriptCount(JSContext
* cx
) {
2596 JSRuntime
* rt
= cx
->runtime();
2598 if (!rt
->scriptAndCountsVector
) {
2602 return rt
->scriptAndCountsVector
->length();
2605 [[nodiscard
]] static bool JSONStringProperty(StringPrinter
& sp
,
2607 const char* name
, JSString
* str
) {
2608 json
.beginStringProperty(name
);
2609 JSONQuoteString(&sp
, str
);
2610 json
.endStringProperty();
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
);
2624 const ScriptAndCounts
& sac
= (*rt
->scriptAndCountsVector
)[index
];
2625 RootedScript
script(cx
, sac
.script
);
2632 JSONPrinter
json(sp
, false);
2636 Rooted
<JSString
*> filenameStr(cx
);
2637 if (const char* filename
= script
->filename()) {
2639 JS_NewStringCopyUTF8N(cx
, JS::UTF8Chars(filename
, strlen(filename
)));
2641 filenameStr
= JS_GetEmptyString(cx
);
2646 if (!JSONStringProperty(sp
, json
, "file", filenameStr
)) {
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
)) {
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();
2675 for (size_t i
= 0; i
< ionCounts
->numBlocks(); i
++) {
2676 ionActivity
+= ionCounts
->block(i
).hitCount();
2678 ionCounts
= ionCounts
->previous();
2681 json
.property("ion", ionActivity
);
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()) {
2705 JSString
* str
= JS_DecompileScript(cx
, script
);
2710 if (!JSONStringProperty(sp
, json
, "text", str
)) {
2714 json
.property("line", script
->lineno());
2716 json
.beginListProperty("opcodes");
2719 for (BytecodeRangeWithPosition
range(cx
, script
); !range
.empty();
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();
2733 json
.property("id", offset
);
2734 json
.property("line", range
.frontLineNumber());
2735 json
.property("name", CodeName(op
));
2738 ExpressionDecompiler
ed(cx
, script
, parser
);
2742 // defIndex passed here is not used.
2743 if (!ed
.decompilePC(pc
, /* defIndex = */ 0)) {
2746 UniqueChars text
= ed
.getOutput();
2751 JS::ConstUTF8CharsZ
utf8chars(text
.get(), strlen(text
.get()));
2752 JSString
* str
= NewStringCopyUTF8Z(cx
, utf8chars
);
2757 if (!JSONStringProperty(sp
, json
, "text", str
)) {
2762 json
.beginObjectProperty("counts");
2764 json
.property(PCCounts::numExecName
, hits
);
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();
2779 if (jit::IonScriptCounts
* ionCounts
= sac
.getIonCounts()) {
2780 json
.beginListProperty("ion");
2784 for (size_t i
= 0; i
< ionCounts
->numBlocks(); i
++) {
2785 const jit::IonBlockCounts
& block
= ionCounts
->block(i
);
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
));
2797 json
.property("hits", block
.hitCount());
2799 JSString
* str
= NewStringCopyZ
<CanGC
>(cx
, block
.code());
2804 if (!JSONStringProperty(sp
, json
, "code", str
)) {
2812 ionCounts
= ionCounts
->previous();
2820 if (sp
.hadOutOfMemory()) {
2821 sp
.reportOutOfMemory();
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
);
2838 const ScriptAndCounts
& sac
= (*rt
->scriptAndCountsVector
)[index
];
2839 JSScript
* script
= sac
.script
;
2847 AutoRealm
ar(cx
, &script
->global());
2848 if (!GetPCCountJSON(cx
, sac
, sp
)) {
2853 return sp
.release(cx
);
2856 struct CollectedScripts
{
2857 MutableHandle
<ScriptVector
> scripts
;
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()) {
2869 if (!self
->scripts
.append(script
->asJSScript())) {
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.
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
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
);
2896 ReportOutOfMemory(cx
);
2901 if (queue
.length() == 0) {
2905 // Ensure the LCovRealm exists to collect info into.
2906 coverage::LCovRealm
* lcovRealm
= realm
->lcovRealm();
2911 // Collect code coverage info for one realm.
2913 RootedScript
script(cx
, queue
.popCopy());
2914 RootedFunction
fun(cx
);
2916 JSScriptSet::AddPtr entry
= scriptsDone
.lookupForAdd(script
);
2921 if (!coverage::CollectScriptCoverage(script
, false)) {
2922 ReportOutOfMemory(cx
);
2926 script
->resetScriptCounts();
2928 if (!scriptsDone
.add(entry
, script
)) {
2932 if (!script
->isTopLevel()) {
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
>()) {
2945 JSObject
* obj
= &gcThing
.as
<JSObject
>();
2947 if (!obj
->is
<JSFunction
>()) {
2950 fun
= &obj
->as
<JSFunction
>();
2952 // Ignore asm.js functions
2953 if (!fun
->isInterpreted()) {
2957 // Queue the script in the list of script associated to the
2959 JSScript
* childScript
= JSFunction::getOrCreateScript(cx
, fun
);
2960 if (!childScript
|| !queue
.append(childScript
)) {
2964 } while (!queue
.empty());
2966 bool isEmpty
= true;
2967 lcovRealm
->exportInto(out
, &isEmpty
);
2971 JS_PUBLIC_API UniqueChars
js::GetCodeCoverageSummaryAll(JSContext
* cx
,
2978 for (RealmsIter
realm(cx
->runtime()); !realm
.done(); realm
.next()) {
2979 if (!GenerateLcovInfo(cx
, realm
, out
)) {
2984 *length
= out
.length();
2985 return out
.release();
2988 JS_PUBLIC_API UniqueChars
js::GetCodeCoverageSummary(JSContext
* cx
,
2995 if (!GenerateLcovInfo(cx
, cx
->realm(), out
)) {
2999 *length
= out
.length();
3000 return out
.release();