LCOV - code coverage report
Current view: top level - js/src - jsopcode.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 285 1508 18.9 %
Date: 2017-07-14 16:53:18 Functions: 42 90 46.7 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13