tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

BytecodeUtil.cpp (84999B)


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