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
.oneOriginValue());
118 if (block
.description()) {
119 sp
->printf(" [inlined %s]", block
.description());
121 for (size_t j
= 0; j
< block
.numSuccessors(); j
++) {
122 sp
->printf(" -> #%" PRIu32
, block
.successor(j
));
124 sp
->printf(" :: %" PRIu64
" hits\n", block
.hitCount());
125 sp
->printf("%s\n", block
.code());
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 // Three additional values will be on the stack at the beginning
919 // of the finally block: the exception/resume index, the exception
920 // stack, and the |throwing| value. For the benefit of the
921 // decompiler, point them at this Try.
922 offsetStack
[stackDepth
].set(offset
, 0);
923 offsetStack
[stackDepth
+ 1].set(offset
, 1);
924 offsetStack
[stackDepth
+ 2].set(offset
, 2);
925 if (!addJump(catchOffset
, stackDepth
+ 3, offsetStack
, pc
,
926 JumpKind::TryFinally
)) {
939 // Check basic jump opcodes, which may or may not have a fallthrough.
940 if (IsJumpOpcode(op
)) {
941 // Case instructions do not push the lvalue back when branching.
942 uint32_t newStackDepth
= stackDepth
;
943 if (op
== JSOp::Case
) {
947 uint32_t targetOffset
= offset
+ GET_JUMP_OFFSET(pc
);
948 if (!addJump(targetOffset
, newStackDepth
, offsetStack
, pc
,
954 // Handle any fallthrough from this opcode.
955 if (BytecodeFallsThrough(op
)) {
956 if (!recordBytecode(nextOffset
, offsetStack
, stackDepth
)) {
965 #if defined(DEBUG) || defined(JS_JITSPEW)
967 bool js::ReconstructStackDepth(JSContext
* cx
, JSScript
* script
, jsbytecode
* pc
,
968 uint32_t* depth
, bool* reachablePC
) {
969 LifoAllocScope
allocScope(&cx
->tempLifoAlloc());
970 BytecodeParser
parser(cx
, allocScope
.alloc(), script
);
971 if (!parser
.parse()) {
975 *reachablePC
= parser
.isReachable(pc
);
978 *depth
= parser
.stackDepthAtPC(pc
);
984 static unsigned Disassemble1(JSContext
* cx
, HandleScript script
, jsbytecode
* pc
,
985 unsigned loc
, bool lines
,
986 const BytecodeParser
* parser
, StringPrinter
* sp
);
989 * If pc != nullptr, include a prefix indicating whether the PC is at the
990 * current line. If showAll is true, include the entry stack depth.
992 [[nodiscard
]] static bool DisassembleAtPC(
993 JSContext
* cx
, JSScript
* scriptArg
, bool lines
, const jsbytecode
* pc
,
994 bool showAll
, StringPrinter
* sp
,
995 DisassembleSkeptically skeptically
= DisassembleSkeptically::No
) {
996 LifoAllocScope
allocScope(&cx
->tempLifoAlloc());
997 RootedScript
script(cx
, scriptArg
);
998 mozilla::Maybe
<BytecodeParser
> parser
;
1000 if (skeptically
== DisassembleSkeptically::No
) {
1001 parser
.emplace(cx
, allocScope
.alloc(), script
);
1002 parser
->setStackDump();
1003 if (!parser
->parse()) {
1009 sp
->printf("%s:%u\n", script
->filename(), unsigned(script
->lineno()));
1012 if (pc
!= nullptr) {
1016 sp
->put("sn stack ");
1024 if (pc
!= nullptr) {
1028 sp
->put("-- ----- ");
1036 jsbytecode
* next
= script
->code();
1037 jsbytecode
* end
= script
->codeEnd();
1038 while (next
< end
) {
1039 if (next
== script
->main()) {
1042 if (pc
!= nullptr) {
1043 sp
->put(pc
== next
? "--> " : " ");
1046 if (parser
&& parser
->isReachable(next
)) {
1047 sp
->printf("%05u ", parser
->stackDepthAtPC(next
));
1052 unsigned len
= Disassemble1(cx
, script
, next
, script
->pcToOffset(next
),
1053 lines
, parser
.ptrOr(nullptr), sp
);
1064 bool js::Disassemble(JSContext
* cx
, HandleScript script
, bool lines
,
1065 StringPrinter
* sp
, DisassembleSkeptically skeptically
) {
1066 return DisassembleAtPC(cx
, script
, lines
, nullptr, false, sp
, skeptically
);
1069 JS_PUBLIC_API
bool js::DumpPC(JSContext
* cx
, FILE* fp
) {
1070 gc::AutoSuppressGC
suppressGC(cx
);
1071 Sprinter
sprinter(cx
);
1072 if (!sprinter
.init()) {
1075 ScriptFrameIter
iter(cx
);
1077 fprintf(fp
, "Empty stack.\n");
1080 RootedScript
script(cx
, iter
.script());
1081 bool ok
= DisassembleAtPC(cx
, script
, true, iter
.pc(), false, &sprinter
);
1082 JS::UniqueChars out
= sprinter
.release();
1086 fprintf(fp
, "%s", out
.get());
1090 JS_PUBLIC_API
bool js::DumpScript(JSContext
* cx
, JSScript
* scriptArg
,
1092 gc::AutoSuppressGC
suppressGC(cx
);
1093 Sprinter
sprinter(cx
);
1094 if (!sprinter
.init()) {
1097 RootedScript
script(cx
, scriptArg
);
1098 bool ok
= Disassemble(cx
, script
, true, &sprinter
);
1099 JS::UniqueChars out
= sprinter
.release();
1103 fprintf(fp
, "%s", out
.get());
1107 static UniqueChars
ToDisassemblySource(JSContext
* cx
, HandleValue v
) {
1109 return QuoteString(cx
, v
.toString(), '"');
1112 if (JS::RuntimeHeapIsBusy()) {
1113 return DuplicateString(cx
, "<value>");
1117 JSObject
& obj
= v
.toObject();
1119 if (obj
.is
<JSFunction
>()) {
1120 RootedFunction
fun(cx
, &obj
.as
<JSFunction
>());
1121 JSString
* str
= JS_DecompileFunction(cx
, fun
);
1125 return QuoteString(cx
, str
);
1128 if (obj
.is
<RegExpObject
>()) {
1129 Rooted
<RegExpObject
*> reobj(cx
, &obj
.as
<RegExpObject
>());
1130 JSString
* source
= RegExpObject::toString(cx
, reobj
);
1134 return QuoteString(cx
, source
);
1138 JSString
* str
= ValueToSource(cx
, v
);
1142 return QuoteString(cx
, str
);
1145 static bool ToDisassemblySource(JSContext
* cx
, Handle
<Scope
*> scope
,
1146 UniqueChars
* bytes
) {
1147 UniqueChars source
= JS_smprintf("%s {", ScopeKindString(scope
->kind()));
1149 ReportOutOfMemory(cx
);
1153 for (Rooted
<BindingIter
> bi(cx
, BindingIter(scope
)); bi
; bi
++) {
1154 UniqueChars nameBytes
= AtomToPrintableString(cx
, bi
.name());
1159 source
= JS_sprintf_append(std::move(source
), "%s: ", nameBytes
.get());
1161 ReportOutOfMemory(cx
);
1165 BindingLocation loc
= bi
.location();
1166 switch (loc
.kind()) {
1167 case BindingLocation::Kind::Global
:
1168 source
= JS_sprintf_append(std::move(source
), "global");
1171 case BindingLocation::Kind::Frame
:
1173 JS_sprintf_append(std::move(source
), "frame slot %u", loc
.slot());
1176 case BindingLocation::Kind::Environment
:
1178 JS_sprintf_append(std::move(source
), "env slot %u", loc
.slot());
1181 case BindingLocation::Kind::Argument
:
1183 JS_sprintf_append(std::move(source
), "arg slot %u", loc
.slot());
1186 case BindingLocation::Kind::NamedLambdaCallee
:
1187 source
= JS_sprintf_append(std::move(source
), "named lambda callee");
1190 case BindingLocation::Kind::Import
:
1191 source
= JS_sprintf_append(std::move(source
), "import");
1196 ReportOutOfMemory(cx
);
1201 source
= JS_sprintf_append(std::move(source
), ", ");
1203 ReportOutOfMemory(cx
);
1209 source
= JS_sprintf_append(std::move(source
), "}");
1211 ReportOutOfMemory(cx
);
1215 *bytes
= std::move(source
);
1219 static bool DumpJumpOrigins(HandleScript script
, jsbytecode
* pc
,
1220 const BytecodeParser
* parser
, StringPrinter
* sp
) {
1221 bool called
= false;
1222 auto callback
= [&script
, &sp
, &called
](jsbytecode
* pc
,
1223 BytecodeParser::JumpKind kind
) {
1232 case BytecodeParser::JumpKind::Simple
:
1235 case BytecodeParser::JumpKind::SwitchCase
:
1236 sp
->put("switch-case ");
1239 case BytecodeParser::JumpKind::SwitchDefault
:
1240 sp
->put("switch-default ");
1243 case BytecodeParser::JumpKind::TryCatch
:
1244 sp
->put("try-catch ");
1247 case BytecodeParser::JumpKind::TryFinally
:
1248 sp
->put("try-finally ");
1252 sp
->printf("from %s @ %05u", CodeName(JSOp(*pc
)),
1253 unsigned(script
->pcToOffset(pc
)));
1257 if (!parser
->forEachJumpOrigins(pc
, callback
)) {
1267 static bool DecompileAtPCForStackDump(
1268 JSContext
* cx
, HandleScript script
,
1269 const OffsetAndDefIndex
& offsetAndDefIndex
, StringPrinter
* sp
);
1271 static bool PrintShapeProperties(JSContext
* cx
, StringPrinter
* sp
,
1272 SharedShape
* shape
) {
1273 // Add all property keys to a vector to allow printing them in property
1274 // definition order.
1275 Vector
<PropertyKey
> props(cx
);
1276 for (SharedShapePropertyIter
<NoGC
> iter(shape
); !iter
.done(); iter
++) {
1277 if (!props
.append(iter
->key())) {
1284 for (size_t i
= props
.length(); i
> 0; i
--) {
1285 PropertyKey key
= props
[i
- 1];
1286 RootedValue
keyv(cx
, IdToValue(key
));
1287 JSString
* str
= ToString
<NoGC
>(cx
, keyv
);
1289 ReportOutOfMemory(cx
);
1292 sp
->putString(cx
, str
);
1302 static unsigned Disassemble1(JSContext
* cx
, HandleScript script
, jsbytecode
* pc
,
1303 unsigned loc
, bool lines
,
1304 const BytecodeParser
* parser
, StringPrinter
* sp
) {
1305 if (parser
&& parser
->isReachable(pc
)) {
1306 if (!DumpJumpOrigins(script
, pc
, parser
, sp
)) {
1311 size_t before
= sp
->length();
1312 bool stackDumped
= false;
1313 auto dumpStack
= [&cx
, &script
, &pc
, &parser
, &sp
, &before
, &stackDumped
]() {
1322 size_t after
= sp
->length();
1323 MOZ_ASSERT(after
>= before
);
1325 static const size_t stack_column
= 40;
1326 for (size_t i
= after
- before
; i
< stack_column
- 1; i
++) {
1332 if (!parser
->isReachable(pc
)) {
1333 sp
->put("!!! UNREACHABLE !!!");
1335 uint32_t depth
= parser
->stackDepthAfterPC(pc
);
1337 for (uint32_t i
= 0; i
< depth
; i
++) {
1342 const OffsetAndDefIndex
& offsetAndDefIndex
=
1343 parser
->offsetForStackOperandAfterPC(script
->pcToOffset(pc
), i
);
1344 // This will decompile the stack for the same PC many times.
1345 // We'll avoid optimizing it since this is a testing function
1346 // and it won't be worth managing cached expression here.
1347 if (!DecompileAtPCForStackDump(cx
, script
, offsetAndDefIndex
, sp
)) {
1356 if (*pc
>= JSOP_LIMIT
) {
1357 char numBuf1
[12], numBuf2
[12];
1358 SprintfLiteral(numBuf1
, "%d", int(*pc
));
1359 SprintfLiteral(numBuf2
, "%d", JSOP_LIMIT
);
1360 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1361 JSMSG_BYTECODE_TOO_BIG
, numBuf1
, numBuf2
);
1364 JSOp op
= JSOp(*pc
);
1365 const JSCodeSpec
& cs
= CodeSpec(op
);
1366 const unsigned len
= cs
.length
;
1367 sp
->printf("%05u:", loc
);
1369 sp
->printf("%4u", PCToLineNumber(script
, pc
));
1371 sp
->printf(" %s", CodeName(op
));
1374 switch (JOF_TYPE(cs
.format
)) {
1379 ptrdiff_t off
= GET_JUMP_OFFSET(pc
);
1380 sp
->printf(" %u (%+d)", unsigned(loc
+ int(off
)), int(off
));
1385 Rooted
<Scope
*> scope(cx
, script
->getScope(pc
));
1387 if (!ToDisassemblySource(cx
, scope
, &bytes
)) {
1390 sp
->printf(" %s", bytes
.get());
1394 case JOF_ENVCOORD
: {
1395 RootedValue
v(cx
, StringValue(EnvironmentCoordinateNameSlow(script
, pc
)));
1396 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1400 EnvironmentCoordinate
ec(pc
);
1401 sp
->printf(" %s (hops = %u, slot = %u)", bytes
.get(), ec
.hops(),
1405 case JOF_DEBUGCOORD
: {
1406 EnvironmentCoordinate
ec(pc
);
1407 sp
->printf("(hops = %u, slot = %u)", ec
.hops(), ec
.slot());
1411 RootedValue
v(cx
, StringValue(script
->getAtom(pc
)));
1412 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1416 sp
->printf(" %s", bytes
.get());
1420 RootedValue
v(cx
, StringValue(script
->getString(pc
)));
1421 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1425 sp
->printf(" %s", bytes
.get());
1430 double d
= GET_INLINE_VALUE(pc
).toDouble();
1431 sp
->printf(" %lf", d
);
1436 RootedValue
v(cx
, BigIntValue(script
->getBigInt(pc
)));
1437 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1441 sp
->printf(" %s", bytes
.get());
1446 JSObject
* obj
= script
->getObject(pc
);
1448 RootedValue
v(cx
, ObjectValue(*obj
));
1449 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1453 sp
->printf(" %s", bytes
.get());
1459 SharedShape
* shape
= script
->getShape(pc
);
1461 if (!PrintShapeProperties(cx
, sp
, shape
)) {
1468 js::RegExpObject
* obj
= script
->getRegExp(pc
);
1469 RootedValue
v(cx
, ObjectValue(*obj
));
1470 UniqueChars bytes
= ToDisassemblySource(cx
, v
);
1474 sp
->printf(" %s", bytes
.get());
1478 case JOF_TABLESWITCH
: {
1479 int32_t i
, low
, high
;
1481 ptrdiff_t off
= GET_JUMP_OFFSET(pc
);
1482 jsbytecode
* pc2
= pc
+ JUMP_OFFSET_LEN
;
1483 low
= GET_JUMP_OFFSET(pc2
);
1484 pc2
+= JUMP_OFFSET_LEN
;
1485 high
= GET_JUMP_OFFSET(pc2
);
1486 pc2
+= JUMP_OFFSET_LEN
;
1487 sp
->printf(" defaultOffset %d low %d high %d", int(off
), low
, high
);
1489 // Display stack dump before diplaying the offsets for each case.
1494 for (i
= low
; i
<= high
; i
++) {
1496 script
->tableSwitchCaseOffset(pc
, i
- low
) - script
->pcToOffset(pc
);
1497 sp
->printf("\n\t%d: %d", i
, int(off
));
1503 sp
->printf(" %u", GET_ARGNO(pc
));
1507 sp
->printf(" %u", GET_LOCALNO(pc
));
1511 sp
->printf(" %u", unsigned(GET_GCTHING_INDEX(pc
)));
1515 sp
->printf(" %u", GET_UINT32(pc
));
1519 sp
->printf(" (ic: %u)", GET_ICINDEX(pc
));
1523 sp
->printf(" (ic: %u, depthHint: %u)", GET_ICINDEX(pc
),
1524 LoopHeadDepthHint(pc
));
1527 case JOF_TWO_UINT8
: {
1528 int one
= (int)GET_UINT8(pc
);
1529 int two
= (int)GET_UINT8(pc
+ 1);
1531 sp
->printf(" %d", one
);
1532 sp
->printf(" %d", two
);
1538 i
= (int)GET_UINT16(pc
);
1541 case JOF_RESUMEINDEX
:
1543 MOZ_ASSERT(len
== 4);
1544 i
= (int)GET_UINT24(pc
);
1556 MOZ_ASSERT(op
== JSOp::Int32
);
1559 sp
->printf(" %d", i
);
1564 SprintfLiteral(numBuf
, "%x", cs
.format
);
1565 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
1566 JSMSG_UNKNOWN_FORMAT
, numBuf
);
1579 unsigned js::Disassemble1(JSContext
* cx
, JS::Handle
<JSScript
*> script
,
1580 jsbytecode
* pc
, unsigned loc
, bool lines
,
1581 StringPrinter
* sp
) {
1582 return Disassemble1(cx
, script
, pc
, loc
, lines
, nullptr, sp
);
1585 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
1589 * The expression decompiler is invoked by error handling code to produce a
1590 * string representation of the erroring expression. As it's only a debugging
1591 * tool, it only supports basic expressions. For anything complicated, it simply
1592 * puts "(intermediate value)" into the error result.
1594 * Here's the basic algorithm:
1596 * 1. Find the stack location of the value whose expression we wish to
1597 * decompile. The error handler can explicitly pass this as an
1598 * argument. Otherwise, we search backwards down the stack for the offending
1601 * 2. Instantiate and run a BytecodeParser for the current frame. This creates a
1602 * stack of pcs parallel to the interpreter stack; given an interpreter stack
1603 * location, the corresponding pc stack location contains the opcode that pushed
1604 * the value in the interpreter. Now, with the result of step 1, we have the
1605 * opcode responsible for pushing the value we want to decompile.
1607 * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler
1608 * routine, responsible for a string representation of the expression that
1609 * generated a certain stack location. decompilePC looks at one opcode and
1610 * returns the JS source equivalent of that opcode.
1612 * 4. Expressions can, of course, contain subexpressions. For example, the
1613 * literals "4" and "5" are subexpressions of the addition operator in "4 +
1614 * 5". If we need to decompile a subexpression, we call decompilePC (step 2)
1615 * recursively on the operands' pcs. The result is a depth-first traversal of
1616 * the expression tree.
1619 struct ExpressionDecompiler
{
1621 RootedScript script
;
1622 const BytecodeParser
& parser
;
1625 #if defined(DEBUG) || defined(JS_JITSPEW)
1626 // Dedicated mode for stack dump.
1627 // Generates an expression for stack dump, including internal state,
1628 // and also disables special handling for self-hosted code.
1632 ExpressionDecompiler(JSContext
* cx
, JSScript
* script
,
1633 const BytecodeParser
& parser
)
1638 #if defined(DEBUG) || defined(JS_JITSPEW)
1645 bool decompilePCForStackOperand(jsbytecode
* pc
, int i
);
1646 bool decompilePC(jsbytecode
* pc
, uint8_t defIndex
);
1647 bool decompilePC(const OffsetAndDefIndex
& offsetAndDefIndex
);
1648 JSAtom
* getArg(unsigned slot
);
1649 JSAtom
* loadAtom(jsbytecode
* pc
);
1650 JSString
* loadString(jsbytecode
* pc
);
1651 bool quote(JSString
* s
, char quote
);
1652 bool write(const char* s
);
1653 bool write(JSString
* str
);
1654 UniqueChars
getOutput();
1655 #if defined(DEBUG) || defined(JS_JITSPEW)
1656 void setStackDump() { isStackDump
= true; }
1660 bool ExpressionDecompiler::decompilePCForStackOperand(jsbytecode
* pc
, int i
) {
1661 return decompilePC(parser
.offsetForStackOperand(script
->pcToOffset(pc
), i
));
1664 bool ExpressionDecompiler::decompilePC(jsbytecode
* pc
, uint8_t defIndex
) {
1665 MOZ_ASSERT(script
->containsPC(pc
));
1667 JSOp op
= (JSOp
)*pc
;
1669 if (const char* token
= CodeToken
[uint8_t(op
)]) {
1670 MOZ_ASSERT(defIndex
== 0);
1671 MOZ_ASSERT(CodeSpec(op
).ndefs
== 1);
1673 // Handle simple cases of binary and unary operators.
1674 switch (CodeSpec(op
).nuses
) {
1676 const char* extra
= "";
1678 MOZ_ASSERT(pc
+ 1 < script
->codeEnd(),
1679 "binary opcode shouldn't be the last opcode in the script");
1680 if (CodeSpec(op
).length
== 1 &&
1681 (JSOp
)(*(pc
+ 1)) == JSOp::NopIsAssignOp
) {
1685 return write("(") && decompilePCForStackOperand(pc
, -2) && write(" ") &&
1686 write(token
) && write(extra
) && write(" ") &&
1687 decompilePCForStackOperand(pc
, -1) && write(")");
1691 return write("(") && write(token
) &&
1692 decompilePCForStackOperand(pc
, -1) && write(")");
1700 return write("(delete ") && write(loadAtom(pc
)) && write(")");
1702 case JSOp::GetGName
:
1704 case JSOp::GetIntrinsic
:
1705 return write(loadAtom(pc
));
1706 case JSOp::GetArg
: {
1707 unsigned slot
= GET_ARGNO(pc
);
1709 // For self-hosted scripts that are called from non-self-hosted code,
1710 // decompiling the parameter name in the self-hosted script is
1711 // unhelpful. Decompile the argument name instead.
1712 if (script
->selfHosted()
1714 // For stack dump, argument name is not necessary.
1719 if (!DecompileArgumentFromStack(cx
, slot
, &result
)) {
1723 // Note that decompiling the argument in the parent frame might
1726 return write(result
.get());
1729 // If it fails, do not return parameter name and let the caller
1731 return write("(intermediate value)");
1734 JSAtom
* atom
= getArg(slot
);
1740 case JSOp::GetLocal
: {
1741 JSAtom
* atom
= FrameSlotName(script
, pc
);
1745 case JSOp::GetAliasedVar
: {
1746 JSAtom
* atom
= EnvironmentCoordinateNameSlow(script
, pc
);
1752 case JSOp::StrictDelProp
:
1754 case JSOp::GetBoundName
: {
1755 bool hasDelete
= op
== JSOp::DelProp
|| op
== JSOp::StrictDelProp
;
1756 Rooted
<JSAtom
*> prop(cx
, loadAtom(pc
));
1758 return (hasDelete
? write("(delete ") : true) &&
1759 decompilePCForStackOperand(pc
, -1) &&
1761 ? write(".") && quote(prop
, '\0')
1762 : write("[") && quote(prop
, '\'') && write("]")) &&
1763 (hasDelete
? write(")") : true);
1765 case JSOp::GetPropSuper
: {
1766 Rooted
<JSAtom
*> prop(cx
, loadAtom(pc
));
1767 return write("super.") && quote(prop
, '\0');
1770 case JSOp::StrictSetElem
:
1771 // NOTE: We don't show the right hand side of the operation because
1772 // it's used in error messages like: "a[0] is not readable".
1775 return decompilePCForStackOperand(pc
, -3) && write("[") &&
1776 decompilePCForStackOperand(pc
, -2) && write("]");
1779 case JSOp::StrictDelElem
:
1780 case JSOp::GetElem
: {
1781 bool hasDelete
= (op
== JSOp::DelElem
|| op
== JSOp::StrictDelElem
);
1782 return (hasDelete
? write("(delete ") : true) &&
1783 decompilePCForStackOperand(pc
, -2) && write("[") &&
1784 decompilePCForStackOperand(pc
, -1) && write("]") &&
1785 (hasDelete
? write(")") : true);
1788 case JSOp::GetElemSuper
:
1789 return write("super[") && decompilePCForStackOperand(pc
, -2) &&
1792 return write("null");
1794 return write("true");
1796 return write("false");
1803 sprinter
.printf("%d", GetBytecodeInteger(pc
));
1806 return quote(loadString(pc
), '"');
1807 case JSOp::Symbol
: {
1808 unsigned i
= uint8_t(pc
[1]);
1809 MOZ_ASSERT(i
< JS::WellKnownSymbolLimit
);
1810 if (i
< JS::WellKnownSymbolLimit
) {
1811 return write(cx
->names().wellKnownSymbolDescriptions()[i
]);
1815 case JSOp::Undefined
:
1816 return write("undefined");
1817 case JSOp::GlobalThis
:
1818 case JSOp::NonSyntacticGlobalThis
:
1819 // |this| could convert to a very long object initialiser, so cite it by
1820 // its keyword name.
1821 return write("this");
1822 case JSOp::NewTarget
:
1823 return write("new.target");
1824 case JSOp::ImportMeta
:
1825 return write("import.meta");
1827 case JSOp::CallContent
:
1828 case JSOp::CallIgnoresRv
:
1829 case JSOp::CallIter
:
1830 case JSOp::CallContentIter
: {
1831 uint16_t argc
= GET_ARGC(pc
);
1832 return decompilePCForStackOperand(pc
, -int32_t(argc
+ 2)) &&
1833 write(argc
? "(...)" : "()");
1835 case JSOp::SpreadCall
:
1836 return decompilePCForStackOperand(pc
, -3) && write("(...)");
1837 case JSOp::NewArray
:
1839 case JSOp::RegExp
: {
1840 Rooted
<RegExpObject
*> obj(cx
, &script
->getObject(pc
)->as
<RegExpObject
>());
1841 JSString
* str
= RegExpObject::toString(cx
, obj
);
1847 case JSOp::Object
: {
1848 JSObject
* obj
= script
->getObject(pc
);
1849 RootedValue
objv(cx
, ObjectValue(*obj
));
1850 JSString
* str
= ValueToSource(cx
, objv
);
1857 return write("(void ") && decompilePCForStackOperand(pc
, -1) &&
1860 case JSOp::SuperCall
:
1861 if (GET_ARGC(pc
) == 0) {
1862 return write("super()");
1865 case JSOp::SpreadSuperCall
:
1866 return write("super(...)");
1867 case JSOp::SuperFun
:
1868 return write("super");
1871 case JSOp::SpreadEval
:
1872 case JSOp::StrictEval
:
1873 case JSOp::StrictSpreadEval
:
1874 return write("eval(...)");
1877 case JSOp::NewContent
: {
1878 uint16_t argc
= GET_ARGC(pc
);
1879 return write("(new ") &&
1880 decompilePCForStackOperand(pc
, -int32_t(argc
+ 3)) &&
1881 write(argc
? "(...))" : "())");
1884 case JSOp::SpreadNew
:
1885 return write("(new ") && decompilePCForStackOperand(pc
, -4) &&
1888 case JSOp::DynamicImport
:
1889 return write("import(...)");
1892 case JSOp::TypeofExpr
:
1893 return write("(typeof ") && decompilePCForStackOperand(pc
, -1) &&
1896 case JSOp::InitElemArray
:
1897 return write("[...]");
1899 case JSOp::InitElemInc
:
1900 if (defIndex
== 0) {
1901 return write("[...]");
1903 MOZ_ASSERT(defIndex
== 1);
1905 // INDEX won't be be exposed to error message.
1907 return write("INDEX");
1912 case JSOp::ToNumeric
:
1913 return write("(tonumeric ") && decompilePCForStackOperand(pc
, -1) &&
1917 return write("(inc ") && decompilePCForStackOperand(pc
, -1) && write(")");
1920 return write("(dec ") && decompilePCForStackOperand(pc
, -1) && write(")");
1923 #if defined(DEBUG) || defined(JS_JITSPEW)
1924 // BigInt::dumpLiteral() only available in this configuration.
1925 script
->getBigInt(pc
)->dumpLiteral(sprinter
);
1926 return !sprinter
.hadOutOfMemory();
1928 return write("[bigint]");
1931 case JSOp::BuiltinObject
: {
1932 auto kind
= BuiltinObjectKind(GET_UINT8(pc
));
1933 return write(BuiltinObjectName(kind
));
1936 #ifdef ENABLE_RECORD_TUPLE
1937 case JSOp::InitTuple
:
1938 return write("#[]");
1940 case JSOp::AddTupleElement
:
1941 case JSOp::FinishTuple
:
1942 return write("#[...]");
1951 // Special decompilation for stack dump.
1953 case JSOp::Arguments
:
1954 return write("arguments");
1956 case JSOp::ArgumentsLength
:
1957 return write("arguments.length");
1959 case JSOp::GetFrameArg
:
1960 sprinter
.printf("arguments[%u]", GET_ARGNO(pc
));
1963 case JSOp::GetActualArg
:
1964 return write("arguments[") && decompilePCForStackOperand(pc
, -1) &&
1967 case JSOp::BindGName
:
1968 return write("GLOBAL");
1970 case JSOp::BindName
:
1972 return write("ENV");
1975 return write("CALLEE");
1977 case JSOp::EnvCallee
:
1978 return write("ENVCALLEE");
1980 case JSOp::CallSiteObj
:
1981 return write("OBJ");
1984 sprinter
.printf("%lf", GET_INLINE_VALUE(pc
).toDouble());
1987 case JSOp::Exception
:
1988 return write("EXCEPTION");
1990 case JSOp::ExceptionAndStack
:
1991 if (defIndex
== 0) {
1992 return write("EXCEPTION");
1994 MOZ_ASSERT(defIndex
== 1);
1995 return write("STACK");
1998 // Used for the values live on entry to the finally block.
1999 // See TryNoteKind::Finally above.
2000 if (defIndex
== 0) {
2003 if (defIndex
== 1) {
2004 return write("STACK");
2006 MOZ_ASSERT(defIndex
== 2);
2007 return write("THROWING");
2009 case JSOp::FunctionThis
:
2010 case JSOp::ImplicitThis
:
2011 return write("THIS");
2013 case JSOp::FunWithProto
:
2014 return write("FUN");
2016 case JSOp::Generator
:
2017 return write("GENERATOR");
2019 case JSOp::GetImport
:
2020 return write("VAL");
2023 return write("RVAL");
2026 return write("HOLE");
2028 case JSOp::IsGenClosing
:
2029 // For stack dump, defIndex == 0 is not used.
2030 MOZ_ASSERT(defIndex
== 1);
2031 return write("ISGENCLOSING");
2033 case JSOp::IsNoIter
:
2034 // For stack dump, defIndex == 0 is not used.
2035 MOZ_ASSERT(defIndex
== 1);
2036 return write("ISNOITER");
2038 case JSOp::IsConstructing
:
2039 return write("JS_IS_CONSTRUCTING");
2041 case JSOp::IsNullOrUndefined
:
2042 return write("IS_NULL_OR_UNDEF");
2045 return write("ITER");
2048 return write("FUN");
2050 case JSOp::ToAsyncIter
:
2051 return write("ASYNCITER");
2053 case JSOp::MoreIter
:
2054 // For stack dump, defIndex == 0 is not used.
2055 MOZ_ASSERT(defIndex
== 1);
2056 return write("MOREITER");
2059 case JSOp::NewObject
:
2060 case JSOp::ObjWithProto
:
2061 return write("OBJ");
2063 case JSOp::OptimizeGetIterator
:
2064 case JSOp::OptimizeSpreadCall
:
2065 return write("OPTIMIZED");
2068 return write("REST");
2071 return write("RVAL");
2073 case JSOp::SuperBase
:
2074 return write("HOMEOBJECTPROTO");
2076 case JSOp::ToPropertyKey
:
2077 return write("TOPROPERTYKEY(") && decompilePCForStackOperand(pc
, -1) &&
2079 case JSOp::ToString
:
2080 return write("TOSTRING(") && decompilePCForStackOperand(pc
, -1) &&
2083 case JSOp::Uninitialized
:
2084 return write("UNINITIALIZED");
2086 case JSOp::InitialYield
:
2089 // Printing "yield SOMETHING" is confusing since the operand doesn't
2090 // match to the syntax, since the stack operand for "yield 10" is
2091 // the result object, not 10.
2092 if (defIndex
== 0) {
2093 return write("RVAL");
2095 if (defIndex
== 1) {
2096 return write("GENERATOR");
2098 MOZ_ASSERT(defIndex
== 2);
2099 return write("RESUMEKIND");
2101 case JSOp::ResumeKind
:
2102 return write("RESUMEKIND");
2104 case JSOp::AsyncAwait
:
2105 case JSOp::AsyncResolve
:
2106 case JSOp::AsyncReject
:
2107 return write("PROMISE");
2109 case JSOp::CanSkipAwait
:
2110 // For stack dump, defIndex == 0 is not used.
2111 MOZ_ASSERT(defIndex
== 1);
2112 return write("CAN_SKIP_AWAIT");
2114 case JSOp::MaybeExtractAwaitValue
:
2115 // For stack dump, defIndex == 1 is not used.
2116 MOZ_ASSERT(defIndex
== 0);
2117 return write("MAYBE_RESOLVED(") && decompilePCForStackOperand(pc
, -2) &&
2120 case JSOp::CheckPrivateField
:
2121 return write("HasPrivateField");
2123 case JSOp::NewPrivateName
:
2124 return write("PRIVATENAME");
2126 case JSOp::CheckReturn
:
2127 return write("RVAL");
2130 return write("HasOwn(") && decompilePCForStackOperand(pc
, -2) &&
2131 write(", ") && decompilePCForStackOperand(pc
, -1) && write(")");
2136 return write("<unknown>");
2140 return write("(intermediate value)");
2143 bool ExpressionDecompiler::decompilePC(
2144 const OffsetAndDefIndex
& offsetAndDefIndex
) {
2145 if (offsetAndDefIndex
.isSpecial()) {
2148 if (offsetAndDefIndex
.isMerged()) {
2149 if (!write("merged<")) {
2152 } else if (offsetAndDefIndex
.isIgnored()) {
2153 if (!write("ignored<")) {
2158 if (!decompilePC(script
->offsetToPC(offsetAndDefIndex
.specialOffset()),
2159 offsetAndDefIndex
.specialDefIndex())) {
2170 return write("(intermediate value)");
2173 return decompilePC(script
->offsetToPC(offsetAndDefIndex
.offset()),
2174 offsetAndDefIndex
.defIndex());
2177 bool ExpressionDecompiler::init() {
2179 return sprinter
.init();
2182 bool ExpressionDecompiler::write(const char* s
) {
2187 bool ExpressionDecompiler::write(JSString
* str
) {
2188 if (str
== cx
->names().dot_this_
) {
2189 return write("this");
2191 if (str
== cx
->names().dot_newTarget_
) {
2192 return write("new.target");
2194 sprinter
.putString(cx
, str
);
2198 bool ExpressionDecompiler::quote(JSString
* s
, char quote
) {
2199 QuoteString(&sprinter
, s
, quote
);
2203 JSAtom
* ExpressionDecompiler::loadAtom(jsbytecode
* pc
) {
2204 return script
->getAtom(pc
);
2207 JSString
* ExpressionDecompiler::loadString(jsbytecode
* pc
) {
2208 return script
->getString(pc
);
2211 JSAtom
* ExpressionDecompiler::getArg(unsigned slot
) {
2212 MOZ_ASSERT(script
->isFunction());
2213 MOZ_ASSERT(slot
< script
->numArgs());
2215 for (PositionalFormalParameterIter
fi(script
); fi
; fi
++) {
2216 if (fi
.argumentSlot() == slot
) {
2217 if (!fi
.isDestructured()) {
2221 // Destructured arguments have no single binding name.
2222 static const char destructuredParam
[] = "(destructured parameter)";
2223 return Atomize(cx
, destructuredParam
, strlen(destructuredParam
));
2227 MOZ_CRASH("No binding");
2230 UniqueChars
ExpressionDecompiler::getOutput() { return sprinter
.release(); }
2232 } // anonymous namespace
2234 #if defined(DEBUG) || defined(JS_JITSPEW)
2235 static bool DecompileAtPCForStackDump(
2236 JSContext
* cx
, HandleScript script
,
2237 const OffsetAndDefIndex
& offsetAndDefIndex
, StringPrinter
* sp
) {
2238 // The expression decompiler asserts the script is in the current realm.
2239 AutoRealm
ar(cx
, script
);
2241 LifoAllocScope
allocScope(&cx
->tempLifoAlloc());
2242 BytecodeParser
parser(cx
, allocScope
.alloc(), script
);
2243 parser
.setStackDump();
2244 if (!parser
.parse()) {
2248 ExpressionDecompiler
ed(cx
, script
, parser
);
2254 if (!ed
.decompilePC(offsetAndDefIndex
)) {
2258 UniqueChars result
= ed
.getOutput();
2263 sp
->put(result
.get());
2266 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
2268 static bool FindStartPC(JSContext
* cx
, const FrameIter
& iter
,
2269 const BytecodeParser
& parser
, int spindex
,
2270 int skipStackHits
, const Value
& v
, jsbytecode
** valuepc
,
2271 uint8_t* defIndex
) {
2272 jsbytecode
* current
= *valuepc
;
2276 if (spindex
< 0 && spindex
+ int(parser
.stackDepthAtPC(current
)) < 0) {
2277 spindex
= JSDVG_SEARCH_STACK
;
2280 if (spindex
== JSDVG_SEARCH_STACK
) {
2281 size_t index
= iter
.numFrameSlots();
2283 // The decompiler may be called from inside functions that are not
2284 // called from script, but via the C++ API directly, such as
2285 // Invoke. In that case, the youngest script frame may have a
2286 // completely unrelated pc and stack depth, so we give up.
2287 if (index
< size_t(parser
.stackDepthAtPC(current
))) {
2291 // We search from fp->sp to base to find the most recently calculated
2292 // value matching v under assumption that it is the value that caused
2300 s
= iter
.frameSlotValue(--index
);
2301 } while (s
!= v
|| stackHits
++ != skipStackHits
);
2303 // If the current PC has fewer values on the stack than the index we are
2304 // looking for, the blamed value must be one pushed by the current
2305 // bytecode (e.g. JSOp::MoreIter), so restore *valuepc.
2306 if (index
< size_t(parser
.stackDepthAtPC(current
))) {
2307 *valuepc
= parser
.pcForStackOperand(current
, index
, defIndex
);
2310 *defIndex
= index
- size_t(parser
.stackDepthAtPC(current
));
2313 *valuepc
= parser
.pcForStackOperand(current
, spindex
, defIndex
);
2318 static bool DecompileExpressionFromStack(JSContext
* cx
, int spindex
,
2319 int skipStackHits
, HandleValue v
,
2321 MOZ_ASSERT(spindex
< 0 || spindex
== JSDVG_IGNORE_STACK
||
2322 spindex
== JSDVG_SEARCH_STACK
);
2327 * Give up if we need deterministic behavior for differential testing.
2328 * IonMonkey doesn't use InterpreterFrames and this ensures we get the same
2331 if (js::SupportDifferentialTesting()) {
2335 if (spindex
== JSDVG_IGNORE_STACK
) {
2339 FrameIter
frameIter(cx
);
2341 if (frameIter
.done() || !frameIter
.hasScript() ||
2342 frameIter
.realm() != cx
->realm() || frameIter
.inPrologue()) {
2347 * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the
2348 * previous pc (see bug 831120).
2350 if (frameIter
.isIon()) {
2354 RootedScript
script(cx
, frameIter
.script());
2355 jsbytecode
* valuepc
= frameIter
.pc();
2357 MOZ_ASSERT(script
->containsPC(valuepc
));
2359 LifoAllocScope
allocScope(&cx
->tempLifoAlloc());
2360 BytecodeParser
parser(cx
, allocScope
.alloc(), frameIter
.script());
2361 if (!parser
.parse()) {
2366 if (!FindStartPC(cx
, frameIter
, parser
, spindex
, skipStackHits
, v
, &valuepc
,
2374 ExpressionDecompiler
ed(cx
, script
, parser
);
2378 if (!ed
.decompilePC(valuepc
, defIndex
)) {
2382 *res
= ed
.getOutput();
2383 return *res
!= nullptr;
2386 UniqueChars
js::DecompileValueGenerator(JSContext
* cx
, int spindex
,
2387 HandleValue v
, HandleString fallbackArg
,
2388 int skipStackHits
) {
2389 RootedString
fallback(cx
, fallbackArg
);
2392 if (!DecompileExpressionFromStack(cx
, spindex
, skipStackHits
, v
, &result
)) {
2395 if (result
&& strcmp(result
.get(), "(intermediate value)")) {
2400 if (v
.isUndefined()) {
2401 return DuplicateString(cx
, "undefined"); // Prevent users from seeing
2404 fallback
= ValueToSource(cx
, v
);
2410 return StringToNewUTF8CharsZ(cx
, *fallback
);
2413 static bool DecompileArgumentFromStack(JSContext
* cx
, int formalIndex
,
2415 MOZ_ASSERT(formalIndex
>= 0);
2419 /* See note in DecompileExpressionFromStack. */
2420 if (js::SupportDifferentialTesting()) {
2425 * Settle on the nearest script frame, which should be the builtin that
2426 * called the intrinsic.
2428 FrameIter
frameIter(cx
);
2429 MOZ_ASSERT(!frameIter
.done());
2430 MOZ_ASSERT(frameIter
.script()->selfHosted());
2433 * Get the second-to-top frame, the non-self-hosted caller of the builtin
2434 * that called the intrinsic.
2437 if (frameIter
.done() || !frameIter
.hasScript() ||
2438 frameIter
.script()->selfHosted() || frameIter
.realm() != cx
->realm()) {
2442 RootedScript
script(cx
, frameIter
.script());
2443 jsbytecode
* current
= frameIter
.pc();
2445 MOZ_ASSERT(script
->containsPC(current
));
2447 if (current
< script
->main()) {
2451 /* Don't handle getters, setters or calls from fun.call/fun.apply. */
2452 JSOp op
= JSOp(*current
);
2453 if (op
!= JSOp::Call
&& op
!= JSOp::CallContent
&&
2454 op
!= JSOp::CallIgnoresRv
&& op
!= JSOp::New
&& op
!= JSOp::NewContent
) {
2458 if (static_cast<unsigned>(formalIndex
) >= GET_ARGC(current
)) {
2462 LifoAllocScope
allocScope(&cx
->tempLifoAlloc());
2463 BytecodeParser
parser(cx
, allocScope
.alloc(), script
);
2464 if (!parser
.parse()) {
2468 bool pushedNewTarget
= op
== JSOp::New
|| op
== JSOp::NewContent
;
2469 int formalStackIndex
= parser
.stackDepthAtPC(current
) - GET_ARGC(current
) -
2470 pushedNewTarget
+ formalIndex
;
2471 MOZ_ASSERT(formalStackIndex
>= 0);
2472 if (uint32_t(formalStackIndex
) >= parser
.stackDepthAtPC(current
)) {
2476 ExpressionDecompiler
ed(cx
, script
, parser
);
2480 if (!ed
.decompilePCForStackOperand(current
, formalStackIndex
)) {
2484 *res
= ed
.getOutput();
2485 return *res
!= nullptr;
2488 JSString
* js::DecompileArgument(JSContext
* cx
, int formalIndex
, HandleValue v
) {
2491 if (!DecompileArgumentFromStack(cx
, formalIndex
, &result
)) {
2494 if (result
&& strcmp(result
.get(), "(intermediate value)")) {
2495 JS::ConstUTF8CharsZ
utf8chars(result
.get(), strlen(result
.get()));
2496 return NewStringCopyUTF8Z(cx
, utf8chars
);
2499 if (v
.isUndefined()) {
2500 return cx
->names().undefined
; // Prevent users from seeing "(void 0)"
2503 return ValueToSource(cx
, v
);
2506 extern bool js::IsValidBytecodeOffset(JSContext
* cx
, JSScript
* script
,
2508 // This could be faster (by following jump instructions if the target
2510 for (BytecodeRange
r(cx
, script
); !r
.empty(); r
.popFront()) {
2511 size_t here
= r
.frontOffset();
2512 if (here
>= offset
) {
2513 return here
== offset
;
2520 * There are three possible PCCount profiling states:
2522 * 1. None: Neither scripts nor the runtime have count information.
2523 * 2. Profile: Active scripts have count information, the runtime does not.
2524 * 3. Query: Scripts do not have count information, the runtime does.
2526 * When starting to profile scripts, counting begins immediately, with all JIT
2527 * code discarded and recompiled with counts as necessary. Active interpreter
2528 * frames will not begin profiling until they begin executing another script
2529 * (via a call or return).
2531 * The below API functions manage transitions to new states, according
2532 * to the table below.
2535 * -------------------------
2536 * Function None Profile Query
2538 * StartPCCountProfiling Profile Profile Profile
2539 * StopPCCountProfiling None Query Query
2540 * PurgePCCounts None None None
2543 static void ReleaseScriptCounts(JSRuntime
* rt
) {
2544 MOZ_ASSERT(rt
->scriptAndCountsVector
);
2546 js_delete(rt
->scriptAndCountsVector
.ref());
2547 rt
->scriptAndCountsVector
= nullptr;
2550 void JS::StartPCCountProfiling(JSContext
* cx
) {
2551 JSRuntime
* rt
= cx
->runtime();
2553 if (rt
->profilingScripts
) {
2557 if (rt
->scriptAndCountsVector
) {
2558 ReleaseScriptCounts(rt
);
2561 ReleaseAllJITCode(rt
->gcContext());
2563 rt
->profilingScripts
= true;
2566 void JS::StopPCCountProfiling(JSContext
* cx
) {
2567 JSRuntime
* rt
= cx
->runtime();
2569 if (!rt
->profilingScripts
) {
2572 MOZ_ASSERT(!rt
->scriptAndCountsVector
);
2574 ReleaseAllJITCode(rt
->gcContext());
2576 auto* vec
= cx
->new_
<PersistentRooted
<ScriptAndCountsVector
>>(
2577 cx
, ScriptAndCountsVector());
2582 for (ZonesIter
zone(rt
, SkipAtoms
); !zone
.done(); zone
.next()) {
2583 for (auto base
= zone
->cellIter
<BaseScript
>(); !base
.done(); base
.next()) {
2584 if (base
->hasScriptCounts() && base
->hasJitScript()) {
2585 if (!vec
->append(base
->asJSScript())) {
2592 rt
->profilingScripts
= false;
2593 rt
->scriptAndCountsVector
= vec
;
2596 void JS::PurgePCCounts(JSContext
* cx
) {
2597 JSRuntime
* rt
= cx
->runtime();
2599 if (!rt
->scriptAndCountsVector
) {
2602 MOZ_ASSERT(!rt
->profilingScripts
);
2604 ReleaseScriptCounts(rt
);
2607 size_t JS::GetPCCountScriptCount(JSContext
* cx
) {
2608 JSRuntime
* rt
= cx
->runtime();
2610 if (!rt
->scriptAndCountsVector
) {
2614 return rt
->scriptAndCountsVector
->length();
2617 [[nodiscard
]] static bool JSONStringProperty(StringPrinter
& sp
,
2619 const char* name
, JSString
* str
) {
2620 json
.beginStringProperty(name
);
2621 JSONQuoteString(&sp
, str
);
2622 json
.endStringProperty();
2626 JSString
* JS::GetPCCountScriptSummary(JSContext
* cx
, size_t index
) {
2627 JSRuntime
* rt
= cx
->runtime();
2629 if (!rt
->scriptAndCountsVector
||
2630 index
>= rt
->scriptAndCountsVector
->length()) {
2631 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2632 JSMSG_BUFFER_TOO_SMALL
);
2636 const ScriptAndCounts
& sac
= (*rt
->scriptAndCountsVector
)[index
];
2637 RootedScript
script(cx
, sac
.script
);
2644 JSONPrinter
json(sp
, false);
2648 Rooted
<JSString
*> filenameStr(cx
);
2649 if (const char* filename
= script
->filename()) {
2651 JS_NewStringCopyUTF8N(cx
, JS::UTF8Chars(filename
, strlen(filename
)));
2653 filenameStr
= JS_GetEmptyString(cx
);
2658 if (!JSONStringProperty(sp
, json
, "file", filenameStr
)) {
2661 json
.property("line", script
->lineno());
2663 if (JSFunction
* fun
= script
->function()) {
2664 if (JSAtom
* atom
= fun
->fullDisplayAtom()) {
2665 if (!JSONStringProperty(sp
, json
, "name", atom
)) {
2673 AllBytecodesIterable
iter(script
);
2674 for (BytecodeLocation loc
: iter
) {
2675 if (const PCCounts
* counts
= sac
.maybeGetPCCounts(loc
.toRawBytecode())) {
2676 total
+= counts
->numExec();
2680 json
.beginObjectProperty("totals");
2682 json
.property(PCCounts::numExecName
, total
);
2684 uint64_t ionActivity
= 0;
2685 jit::IonScriptCounts
* ionCounts
= sac
.getIonCounts();
2687 for (size_t i
= 0; i
< ionCounts
->numBlocks(); i
++) {
2688 ionActivity
+= ionCounts
->block(i
).hitCount();
2690 ionCounts
= ionCounts
->previous();
2693 json
.property("ion", ionActivity
);
2700 return sp
.release(cx
);
2703 static bool GetPCCountJSON(JSContext
* cx
, const ScriptAndCounts
& sac
,
2704 StringPrinter
& sp
) {
2705 JSONPrinter
json(sp
, false);
2707 RootedScript
script(cx
, sac
.script
);
2709 LifoAllocScope
allocScope(&cx
->tempLifoAlloc());
2710 BytecodeParser
parser(cx
, allocScope
.alloc(), script
);
2711 if (!parser
.parse()) {
2717 JSString
* str
= JS_DecompileScript(cx
, script
);
2722 if (!JSONStringProperty(sp
, json
, "text", str
)) {
2726 json
.property("line", script
->lineno());
2728 json
.beginListProperty("opcodes");
2731 for (BytecodeRangeWithPosition
range(cx
, script
); !range
.empty();
2733 jsbytecode
* pc
= range
.frontPC();
2734 size_t offset
= script
->pcToOffset(pc
);
2735 JSOp op
= JSOp(*pc
);
2737 // If the current instruction is a jump target,
2738 // then update the number of hits.
2739 if (const PCCounts
* counts
= sac
.maybeGetPCCounts(pc
)) {
2740 hits
= counts
->numExec();
2745 json
.property("id", offset
);
2746 json
.property("line", range
.frontLineNumber());
2747 json
.property("name", CodeName(op
));
2750 ExpressionDecompiler
ed(cx
, script
, parser
);
2754 // defIndex passed here is not used.
2755 if (!ed
.decompilePC(pc
, /* defIndex = */ 0)) {
2758 UniqueChars text
= ed
.getOutput();
2763 JS::ConstUTF8CharsZ
utf8chars(text
.get(), strlen(text
.get()));
2764 JSString
* str
= NewStringCopyUTF8Z(cx
, utf8chars
);
2769 if (!JSONStringProperty(sp
, json
, "text", str
)) {
2774 json
.beginObjectProperty("counts");
2776 json
.property(PCCounts::numExecName
, hits
);
2782 // If the current instruction has thrown,
2783 // then decrement the hit counts with the number of throws.
2784 if (const PCCounts
* counts
= sac
.maybeGetThrowCounts(pc
)) {
2785 hits
-= counts
->numExec();
2791 if (jit::IonScriptCounts
* ionCounts
= sac
.getIonCounts()) {
2792 json
.beginListProperty("ion");
2796 for (size_t i
= 0; i
< ionCounts
->numBlocks(); i
++) {
2797 const jit::IonBlockCounts
& block
= ionCounts
->block(i
);
2800 json
.property("id", block
.id());
2801 json
.property("offset", block
.offset());
2803 json
.beginListProperty("successors");
2804 for (size_t j
= 0; j
< block
.numSuccessors(); j
++) {
2805 json
.value(block
.successor(j
));
2809 json
.property("hits", block
.hitCount());
2811 JSString
* str
= NewStringCopyZ
<CanGC
>(cx
, block
.code());
2816 if (!JSONStringProperty(sp
, json
, "code", str
)) {
2824 ionCounts
= ionCounts
->previous();
2832 if (sp
.hadOutOfMemory()) {
2833 sp
.reportOutOfMemory();
2840 JSString
* JS::GetPCCountScriptContents(JSContext
* cx
, size_t index
) {
2841 JSRuntime
* rt
= cx
->runtime();
2843 if (!rt
->scriptAndCountsVector
||
2844 index
>= rt
->scriptAndCountsVector
->length()) {
2845 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
2846 JSMSG_BUFFER_TOO_SMALL
);
2850 const ScriptAndCounts
& sac
= (*rt
->scriptAndCountsVector
)[index
];
2851 JSScript
* script
= sac
.script
;
2859 AutoRealm
ar(cx
, &script
->global());
2860 if (!GetPCCountJSON(cx
, sac
, sp
)) {
2865 return sp
.release(cx
);
2868 struct CollectedScripts
{
2869 MutableHandle
<ScriptVector
> scripts
;
2872 explicit CollectedScripts(MutableHandle
<ScriptVector
> scripts
)
2873 : scripts(scripts
) {}
2875 static void consider(JSRuntime
* rt
, void* data
, BaseScript
* script
,
2876 const JS::AutoRequireNoGC
& nogc
) {
2877 auto self
= static_cast<CollectedScripts
*>(data
);
2878 if (!script
->filename()) {
2881 if (!self
->scripts
.append(script
->asJSScript())) {
2887 static bool GenerateLcovInfo(JSContext
* cx
, JS::Realm
* realm
,
2888 GenericPrinter
& out
) {
2889 AutoRealmUnchecked
ar(cx
, realm
);
2891 // Collect the list of scripts which are part of the current realm.
2894 coverage::IsLCovEnabled(),
2895 "Coverage must be enabled for process before generating LCov info");
2897 // Hold the scripts that we have already flushed, to avoid flushing them
2899 using JSScriptSet
= GCHashSet
<JSScript
*>;
2900 Rooted
<JSScriptSet
> scriptsDone(cx
, JSScriptSet(cx
));
2902 Rooted
<ScriptVector
> queue(cx
, ScriptVector(cx
));
2905 CollectedScripts
result(&queue
);
2906 IterateScripts(cx
, realm
, &result
, &CollectedScripts::consider
);
2908 ReportOutOfMemory(cx
);
2913 if (queue
.length() == 0) {
2917 // Ensure the LCovRealm exists to collect info into.
2918 coverage::LCovRealm
* lcovRealm
= realm
->lcovRealm();
2923 // Collect code coverage info for one realm.
2925 RootedScript
script(cx
, queue
.popCopy());
2926 RootedFunction
fun(cx
);
2928 JSScriptSet::AddPtr entry
= scriptsDone
.lookupForAdd(script
);
2933 if (!coverage::CollectScriptCoverage(script
, false)) {
2934 ReportOutOfMemory(cx
);
2938 script
->resetScriptCounts();
2940 if (!scriptsDone
.add(entry
, script
)) {
2944 if (!script
->isTopLevel()) {
2948 // Iterate from the last to the first object in order to have
2949 // the functions them visited in the opposite order when popping
2950 // elements from the stack of remaining scripts, such that the
2951 // functions are more-less listed with increasing line numbers.
2952 auto gcthings
= script
->gcthings();
2953 for (JS::GCCellPtr gcThing
: mozilla::Reversed(gcthings
)) {
2954 if (!gcThing
.is
<JSObject
>()) {
2957 JSObject
* obj
= &gcThing
.as
<JSObject
>();
2959 if (!obj
->is
<JSFunction
>()) {
2962 fun
= &obj
->as
<JSFunction
>();
2964 // Ignore asm.js functions
2965 if (!fun
->isInterpreted()) {
2969 // Queue the script in the list of script associated to the
2971 JSScript
* childScript
= JSFunction::getOrCreateScript(cx
, fun
);
2972 if (!childScript
|| !queue
.append(childScript
)) {
2976 } while (!queue
.empty());
2978 bool isEmpty
= true;
2979 lcovRealm
->exportInto(out
, &isEmpty
);
2983 JS_PUBLIC_API UniqueChars
js::GetCodeCoverageSummaryAll(JSContext
* cx
,
2990 for (RealmsIter
realm(cx
->runtime()); !realm
.done(); realm
.next()) {
2991 if (!GenerateLcovInfo(cx
, realm
, out
)) {
2996 *length
= out
.length();
2997 return out
.release();
3000 JS_PUBLIC_API UniqueChars
js::GetCodeCoverageSummary(JSContext
* cx
,
3007 if (!GenerateLcovInfo(cx
, cx
->realm(), out
)) {
3011 *length
= out
.length();
3012 return out
.release();