js/src/jsopcode.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     2  * vim: set ts=8 sts=4 et sw=4 tw=99:
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 /*
     8  * JS bytecode descriptors, disassemblers, and (expression) decompilers.
     9  */
    11 #include "jsopcodeinlines.h"
    13 #include <ctype.h>
    14 #include <stdarg.h>
    15 #include <stdio.h>
    16 #include <string.h>
    18 #include "jsanalyze.h"
    19 #include "jsapi.h"
    20 #include "jsatom.h"
    21 #include "jscntxt.h"
    22 #include "jscompartment.h"
    23 #include "jsfun.h"
    24 #include "jsnum.h"
    25 #include "jsobj.h"
    26 #include "jsprf.h"
    27 #include "jsscript.h"
    28 #include "jsstr.h"
    29 #include "jstypes.h"
    30 #include "jsutil.h"
    32 #include "frontend/BytecodeCompiler.h"
    33 #include "frontend/SourceNotes.h"
    34 #include "js/CharacterEncoding.h"
    35 #include "vm/Opcodes.h"
    36 #include "vm/ScopeObject.h"
    37 #include "vm/Shape.h"
    38 #include "vm/StringBuffer.h"
    40 #include "jscntxtinlines.h"
    41 #include "jscompartmentinlines.h"
    42 #include "jsinferinlines.h"
    43 #include "jsobjinlines.h"
    44 #include "jsscriptinlines.h"
    46 using namespace js;
    47 using namespace js::gc;
    49 using js::frontend::IsIdentifier;
    51 /*
    52  * Index limit must stay within 32 bits.
    53  */
    54 JS_STATIC_ASSERT(sizeof(uint32_t) * JS_BITS_PER_BYTE >= INDEX_LIMIT_LOG2 + 1);
    56 const JSCodeSpec js_CodeSpec[] = {
    57 #define MAKE_CODESPEC(op,val,name,token,length,nuses,ndefs,format)  {length,nuses,ndefs,format},
    58     FOR_EACH_OPCODE(MAKE_CODESPEC)
    59 #undef MAKE_CODESPEC
    60 };
    62 const unsigned js_NumCodeSpecs = JS_ARRAY_LENGTH(js_CodeSpec);
    64 /*
    65  * Each element of the array is either a source literal associated with JS
    66  * bytecode or null.
    67  */
    68 static const char * const CodeToken[] = {
    69 #define TOKEN(op, val, name, token, ...)  token,
    70     FOR_EACH_OPCODE(TOKEN)
    71 #undef TOKEN
    72 };
    74 /*
    75  * Array of JS bytecode names used by PC count JSON, DEBUG-only js_Disassemble
    76  * and JIT debug spew.
    77  */
    78 const char * const js_CodeName[] = {
    79 #define OPNAME(op, val, name, ...)  name,
    80     FOR_EACH_OPCODE(OPNAME)
    81 #undef OPNAME
    82 };
    84 /************************************************************************/
    86 #define COUNTS_LEN 16
    88 size_t
    89 js_GetVariableBytecodeLength(jsbytecode *pc)
    90 {
    91     JSOp op = JSOp(*pc);
    92     JS_ASSERT(js_CodeSpec[op].length == -1);
    93     switch (op) {
    94       case JSOP_TABLESWITCH: {
    95         /* Structure: default-jump case-low case-high case1-jump ... */
    96         pc += JUMP_OFFSET_LEN;
    97         int32_t low = GET_JUMP_OFFSET(pc);
    98         pc += JUMP_OFFSET_LEN;
    99         int32_t high = GET_JUMP_OFFSET(pc);
   100         unsigned ncases = unsigned(high - low + 1);
   101         return 1 + 3 * JUMP_OFFSET_LEN + ncases * JUMP_OFFSET_LEN;
   102       }
   103       default:
   104         MOZ_ASSUME_UNREACHABLE("Unexpected op");
   105     }
   106 }
   108 unsigned
   109 js::StackUses(JSScript *script, jsbytecode *pc)
   110 {
   111     JSOp op = (JSOp) *pc;
   112     const JSCodeSpec &cs = js_CodeSpec[op];
   113     if (cs.nuses >= 0)
   114         return cs.nuses;
   116     JS_ASSERT(js_CodeSpec[op].nuses == -1);
   117     switch (op) {
   118       case JSOP_POPN:
   119         return GET_UINT16(pc);
   120       default:
   121         /* stack: fun, this, [argc arguments] */
   122         JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL ||
   123                   op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
   124         return 2 + GET_ARGC(pc);
   125     }
   126 }
   128 unsigned
   129 js::StackDefs(JSScript *script, jsbytecode *pc)
   130 {
   131     JSOp op = (JSOp) *pc;
   132     const JSCodeSpec &cs = js_CodeSpec[op];
   133     JS_ASSERT (cs.ndefs >= 0);
   134     return cs.ndefs;
   135 }
   137 static const char * const countBaseNames[] = {
   138     "interp"
   139 };
   141 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) == PCCounts::BASE_LIMIT);
   143 static const char * const countAccessNames[] = {
   144     "infer_mono",
   145     "infer_di",
   146     "infer_poly",
   147     "infer_barrier",
   148     "infer_nobarrier",
   149     "observe_undefined",
   150     "observe_null",
   151     "observe_boolean",
   152     "observe_int32",
   153     "observe_double",
   154     "observe_string",
   155     "observe_object"
   156 };
   158 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) +
   159                  JS_ARRAY_LENGTH(countAccessNames) == PCCounts::ACCESS_LIMIT);
   161 static const char * const countElementNames[] = {
   162     "id_int",
   163     "id_double",
   164     "id_other",
   165     "id_unknown",
   166     "elem_typed",
   167     "elem_packed",
   168     "elem_dense",
   169     "elem_other"
   170 };
   172 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) +
   173                  JS_ARRAY_LENGTH(countAccessNames) +
   174                  JS_ARRAY_LENGTH(countElementNames) == PCCounts::ELEM_LIMIT);
   176 static const char * const countPropertyNames[] = {
   177     "prop_static",
   178     "prop_definite",
   179     "prop_other"
   180 };
   182 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) +
   183                  JS_ARRAY_LENGTH(countAccessNames) +
   184                  JS_ARRAY_LENGTH(countPropertyNames) == PCCounts::PROP_LIMIT);
   186 static const char * const countArithNames[] = {
   187     "arith_int",
   188     "arith_double",
   189     "arith_other",
   190     "arith_unknown",
   191 };
   193 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) +
   194                  JS_ARRAY_LENGTH(countArithNames) == PCCounts::ARITH_LIMIT);
   196 /* static */ const char *
   197 PCCounts::countName(JSOp op, size_t which)
   198 {
   199     JS_ASSERT(which < numCounts(op));
   201     if (which < BASE_LIMIT)
   202         return countBaseNames[which];
   204     if (accessOp(op)) {
   205         if (which < ACCESS_LIMIT)
   206             return countAccessNames[which - BASE_LIMIT];
   207         if (elementOp(op))
   208             return countElementNames[which - ACCESS_LIMIT];
   209         if (propertyOp(op))
   210             return countPropertyNames[which - ACCESS_LIMIT];
   211         MOZ_ASSUME_UNREACHABLE("bad op");
   212     }
   214     if (arithOp(op))
   215         return countArithNames[which - BASE_LIMIT];
   217     MOZ_ASSUME_UNREACHABLE("bad op");
   218 }
   220 #ifdef JS_ION
   221 void
   222 js::DumpIonScriptCounts(Sprinter *sp, jit::IonScriptCounts *ionCounts)
   223 {
   224     Sprint(sp, "IonScript [%lu blocks]:\n", ionCounts->numBlocks());
   225     for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
   226         const jit::IonBlockCounts &block = ionCounts->block(i);
   227         if (block.hitCount() < 10)
   228             continue;
   229         Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset());
   230         for (size_t j = 0; j < block.numSuccessors(); j++)
   231             Sprint(sp, " -> #%lu", block.successor(j));
   232         Sprint(sp, " :: %llu hits\n", block.hitCount());
   233         Sprint(sp, "%s\n", block.code());
   234     }
   235 }
   236 #endif
   238 void
   239 js_DumpPCCounts(JSContext *cx, HandleScript script, js::Sprinter *sp)
   240 {
   241     JS_ASSERT(script->hasScriptCounts());
   243 #ifdef DEBUG
   244     jsbytecode *pc = script->code();
   245     while (pc < script->codeEnd()) {
   246         JSOp op = JSOp(*pc);
   247         jsbytecode *next = GetNextPc(pc);
   249         if (!js_Disassemble1(cx, script, pc, script->pcToOffset(pc), true, sp))
   250             return;
   252         size_t total = PCCounts::numCounts(op);
   253         double *raw = script->getPCCounts(pc).rawCounts();
   255         Sprint(sp, "                  {");
   256         bool printed = false;
   257         for (size_t i = 0; i < total; i++) {
   258             double val = raw[i];
   259             if (val) {
   260                 if (printed)
   261                     Sprint(sp, ", ");
   262                 Sprint(sp, "\"%s\": %.0f", PCCounts::countName(op, i), val);
   263                 printed = true;
   264             }
   265         }
   266         Sprint(sp, "}\n");
   268         pc = next;
   269     }
   270 #endif
   272 #ifdef JS_ION
   273     jit::IonScriptCounts *ionCounts = script->getIonCounts();
   275     while (ionCounts) {
   276         DumpIonScriptCounts(sp, ionCounts);
   277         ionCounts = ionCounts->previous();
   278     }
   279 #endif
   280 }
   282 /////////////////////////////////////////////////////////////////////
   283 // Bytecode Parser
   284 /////////////////////////////////////////////////////////////////////
   286 namespace {
   288 class BytecodeParser
   289 {
   290     class Bytecode
   291     {
   292       public:
   293         Bytecode() { mozilla::PodZero(this); }
   295         // Whether this instruction has been analyzed to get its output defines
   296         // and stack.
   297         bool parsed : 1;
   299         // Stack depth before this opcode.
   300         uint32_t stackDepth;
   302         // Pointer to array of |stackDepth| offsets.  An element at position N
   303         // in the array is the offset of the opcode that defined the
   304         // corresponding stack slot.  The top of the stack is at position
   305         // |stackDepth - 1|.
   306         uint32_t *offsetStack;
   308         bool captureOffsetStack(LifoAlloc &alloc, const uint32_t *stack, uint32_t depth) {
   309             stackDepth = depth;
   310             offsetStack = alloc.newArray<uint32_t>(stackDepth);
   311             if (stackDepth) {
   312                 if (!offsetStack)
   313                     return false;
   314                 for (uint32_t n = 0; n < stackDepth; n++)
   315                     offsetStack[n] = stack[n];
   316             }
   317             return true;
   318         }
   320         // When control-flow merges, intersect the stacks, marking slots that
   321         // are defined by different offsets with the UINT32_MAX sentinel.
   322         // This is sufficient for forward control-flow.  It doesn't grok loops
   323         // -- for that you would have to iterate to a fixed point -- but there
   324         // shouldn't be operands on the stack at a loop back-edge anyway.
   325         void mergeOffsetStack(const uint32_t *stack, uint32_t depth) {
   326             JS_ASSERT(depth == stackDepth);
   327             for (uint32_t n = 0; n < stackDepth; n++)
   328                 if (offsetStack[n] != stack[n])
   329                     offsetStack[n] = UINT32_MAX;
   330         }
   331     };
   333     JSContext *cx_;
   334     LifoAllocScope allocScope_;
   335     RootedScript script_;
   337     Bytecode **codeArray_;
   339   public:
   340     BytecodeParser(JSContext *cx, JSScript *script)
   341       : cx_(cx),
   342         allocScope_(&cx->tempLifoAlloc()),
   343         script_(cx, script),
   344         codeArray_(nullptr) { }
   346     bool parse();
   348 #ifdef DEBUG
   349     bool isReachable(uint32_t offset) { return maybeCode(offset); }
   350     bool isReachable(const jsbytecode *pc) { return maybeCode(pc); }
   351 #endif
   353     uint32_t stackDepthAtPC(uint32_t offset) {
   354         // Sometimes the code generator in debug mode asks about the stack depth
   355         // of unreachable code (bug 932180 comment 22).  Assume that unreachable
   356         // code has no operands on the stack.
   357         return getCode(offset).stackDepth;
   358     }
   359     uint32_t stackDepthAtPC(const jsbytecode *pc) { return stackDepthAtPC(script_->pcToOffset(pc)); }
   361     uint32_t offsetForStackOperand(uint32_t offset, int operand) {
   362         Bytecode &code = getCode(offset);
   363         if (operand < 0) {
   364             operand += code.stackDepth;
   365             JS_ASSERT(operand >= 0);
   366         }
   367         JS_ASSERT(uint32_t(operand) < code.stackDepth);
   368         return code.offsetStack[operand];
   369     }
   370     jsbytecode *pcForStackOperand(jsbytecode *pc, int operand) {
   371         uint32_t offset = offsetForStackOperand(script_->pcToOffset(pc), operand);
   372         if (offset == UINT32_MAX)
   373             return nullptr;
   374         return script_->offsetToPC(offsetForStackOperand(script_->pcToOffset(pc), operand));
   375     }
   377   private:
   378     LifoAlloc &alloc() {
   379         return allocScope_.alloc();
   380     }
   382     void reportOOM() {
   383         allocScope_.releaseEarly();
   384         js_ReportOutOfMemory(cx_);
   385     }
   387     uint32_t numSlots() {
   388         return 1 + script_->nfixed() +
   389                (script_->functionNonDelazifying() ? script_->functionNonDelazifying()->nargs() : 0);
   390     }
   392     uint32_t maximumStackDepth() {
   393         return script_->nslots() - script_->nfixed();
   394     }
   396     Bytecode& getCode(uint32_t offset) {
   397         JS_ASSERT(offset < script_->length());
   398         JS_ASSERT(codeArray_[offset]);
   399         return *codeArray_[offset];
   400     }
   401     Bytecode& getCode(const jsbytecode *pc) { return getCode(script_->pcToOffset(pc)); }
   403     Bytecode* maybeCode(uint32_t offset) {
   404         JS_ASSERT(offset < script_->length());
   405         return codeArray_[offset];
   406     }
   407     Bytecode* maybeCode(const jsbytecode *pc) { return maybeCode(script_->pcToOffset(pc)); }
   409     uint32_t simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint32_t stackDepth);
   411     inline bool addJump(uint32_t offset, uint32_t *currentOffset,
   412                         uint32_t stackDepth, const uint32_t *offsetStack);
   413 };
   415 }  // anonymous namespace
   417 uint32_t
   418 BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint32_t stackDepth)
   419 {
   420     uint32_t nuses = GetUseCount(script_, offset);
   421     uint32_t ndefs = GetDefCount(script_, offset);
   423     JS_ASSERT(stackDepth >= nuses);
   424     stackDepth -= nuses;
   425     JS_ASSERT(stackDepth + ndefs <= maximumStackDepth());
   427     // Mark the current offset as defining its values on the offset stack,
   428     // unless it just reshuffles the stack.  In that case we want to preserve
   429     // the opcode that generated the original value.
   430     switch (op) {
   431       default:
   432         for (uint32_t n = 0; n != ndefs; ++n)
   433             offsetStack[stackDepth + n] = offset;
   434         break;
   436       case JSOP_CASE:
   437         /* Keep the switch value. */
   438         JS_ASSERT(ndefs == 1);
   439         break;
   441       case JSOP_DUP:
   442         JS_ASSERT(ndefs == 2);
   443         if (offsetStack)
   444             offsetStack[stackDepth + 1] = offsetStack[stackDepth];
   445         break;
   447       case JSOP_DUP2:
   448         JS_ASSERT(ndefs == 4);
   449         if (offsetStack) {
   450             offsetStack[stackDepth + 2] = offsetStack[stackDepth];
   451             offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1];
   452         }
   453         break;
   455       case JSOP_DUPAT: {
   456         JS_ASSERT(ndefs == 1);
   457         jsbytecode *pc = script_->offsetToPC(offset);
   458         unsigned n = GET_UINT24(pc);
   459         JS_ASSERT(n < stackDepth);
   460         if (offsetStack)
   461             offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n];
   462         break;
   463       }
   465       case JSOP_SWAP:
   466         JS_ASSERT(ndefs == 2);
   467         if (offsetStack) {
   468             uint32_t tmp = offsetStack[stackDepth + 1];
   469             offsetStack[stackDepth + 1] = offsetStack[stackDepth];
   470             offsetStack[stackDepth] = tmp;
   471         }
   472         break;
   473     }
   474     stackDepth += ndefs;
   475     return stackDepth;
   476 }
   478 bool
   479 BytecodeParser::addJump(uint32_t offset, uint32_t *currentOffset,
   480                         uint32_t stackDepth, const uint32_t *offsetStack)
   481 {
   482     JS_ASSERT(offset < script_->length());
   484     Bytecode *&code = codeArray_[offset];
   485     if (!code) {
   486         code = alloc().new_<Bytecode>();
   487         if (!code)
   488             return false;
   489         if (!code->captureOffsetStack(alloc(), offsetStack, stackDepth)) {
   490             reportOOM();
   491             return false;
   492         }
   493     } else {
   494         code->mergeOffsetStack(offsetStack, stackDepth);
   495     }
   497     if (offset < *currentOffset && !code->parsed) {
   498         // Backedge in a while/for loop, whose body has not been parsed due
   499         // to a lack of fallthrough at the loop head. Roll back the offset
   500         // to analyze the body.
   501         *currentOffset = offset;
   502     }
   504     return true;
   505 }
   507 bool
   508 BytecodeParser::parse()
   509 {
   510     JS_ASSERT(!codeArray_);
   512     uint32_t length = script_->length();
   513     codeArray_ = alloc().newArray<Bytecode*>(length);
   515     if (!codeArray_) {
   516         reportOOM();
   517         return false;
   518     }
   520     mozilla::PodZero(codeArray_, length);
   522     // Fill in stack depth and definitions at initial bytecode.
   523     Bytecode *startcode = alloc().new_<Bytecode>();
   524     if (!startcode) {
   525         reportOOM();
   526         return false;
   527     }
   529     // Fill in stack depth and definitions at initial bytecode.
   530     uint32_t *offsetStack = alloc().newArray<uint32_t>(maximumStackDepth());
   531     if (maximumStackDepth() && !offsetStack) {
   532         reportOOM();
   533         return false;
   534     }
   536     startcode->stackDepth = 0;
   537     codeArray_[0] = startcode;
   539     uint32_t offset, nextOffset = 0;
   540     while (nextOffset < length) {
   541         offset = nextOffset;
   543         Bytecode *code = maybeCode(offset);
   544         jsbytecode *pc = script_->offsetToPC(offset);
   546         JSOp op = (JSOp)*pc;
   547         JS_ASSERT(op < JSOP_LIMIT);
   549         // Immediate successor of this bytecode.
   550         uint32_t successorOffset = offset + GetBytecodeLength(pc);
   552         // Next bytecode to analyze.  This is either the successor, or is an
   553         // earlier bytecode if this bytecode has a loop backedge.
   554         nextOffset = successorOffset;
   556         if (!code) {
   557             // Haven't found a path by which this bytecode is reachable.
   558             continue;
   559         }
   561         if (code->parsed) {
   562             // No need to reparse.
   563             continue;
   564         }
   566         code->parsed = true;
   568         uint32_t stackDepth = simulateOp(op, offset, offsetStack, code->stackDepth);
   570         switch (op) {
   571           case JSOP_TABLESWITCH: {
   572             uint32_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
   573             jsbytecode *pc2 = pc + JUMP_OFFSET_LEN;
   574             int32_t low = GET_JUMP_OFFSET(pc2);
   575             pc2 += JUMP_OFFSET_LEN;
   576             int32_t high = GET_JUMP_OFFSET(pc2);
   577             pc2 += JUMP_OFFSET_LEN;
   579             if (!addJump(defaultOffset, &nextOffset, stackDepth, offsetStack))
   580                 return false;
   582             for (int32_t i = low; i <= high; i++) {
   583                 uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc2);
   584                 if (targetOffset != offset) {
   585                     if (!addJump(targetOffset, &nextOffset, stackDepth, offsetStack))
   586                         return false;
   587                 }
   588                 pc2 += JUMP_OFFSET_LEN;
   589             }
   590             break;
   591           }
   593           case JSOP_TRY: {
   594             // Everything between a try and corresponding catch or finally is conditional.
   595             // Note that there is no problem with code which is skipped by a thrown
   596             // exception but is not caught by a later handler in the same function:
   597             // no more code will execute, and it does not matter what is defined.
   598             JSTryNote *tn = script_->trynotes()->vector;
   599             JSTryNote *tnlimit = tn + script_->trynotes()->length;
   600             for (; tn < tnlimit; tn++) {
   601                 uint32_t startOffset = script_->mainOffset() + tn->start;
   602                 if (startOffset == offset + 1) {
   603                     uint32_t catchOffset = startOffset + tn->length;
   604                     if (tn->kind != JSTRY_ITER && tn->kind != JSTRY_LOOP) {
   605                         if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack))
   606                             return false;
   607                     }
   608                 }
   609             }
   610             break;
   611           }
   613           default:
   614             break;
   615         }
   617         // Check basic jump opcodes, which may or may not have a fallthrough.
   618         if (IsJumpOpcode(op)) {
   619             // Case instructions do not push the lvalue back when branching.
   620             uint32_t newStackDepth = stackDepth;
   621             if (op == JSOP_CASE)
   622                 newStackDepth--;
   624             uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc);
   625             if (!addJump(targetOffset, &nextOffset, newStackDepth, offsetStack))
   626                 return false;
   627         }
   629         // Handle any fallthrough from this opcode.
   630         if (BytecodeFallsThrough(op)) {
   631             JS_ASSERT(successorOffset < script_->length());
   633             Bytecode *&nextcode = codeArray_[successorOffset];
   635             if (!nextcode) {
   636                 nextcode = alloc().new_<Bytecode>();
   637                 if (!nextcode) {
   638                     reportOOM();
   639                     return false;
   640                 }
   641                 if (!nextcode->captureOffsetStack(alloc(), offsetStack, stackDepth)) {
   642                     reportOOM();
   643                     return false;
   644                 }
   645             } else {
   646                 nextcode->mergeOffsetStack(offsetStack, stackDepth);
   647             }
   648         }
   649     }
   651     return true;
   652 }
   654 #ifdef DEBUG
   656 bool
   657 js::ReconstructStackDepth(JSContext *cx, JSScript *script, jsbytecode *pc, uint32_t *depth, bool *reachablePC)
   658 {
   659     BytecodeParser parser(cx, script);
   660     if (!parser.parse())
   661         return false;
   663     *reachablePC = parser.isReachable(pc);
   665     if (*reachablePC)
   666         *depth = parser.stackDepthAtPC(pc);
   668     return true;
   669 }
   671 /*
   672  * If pc != nullptr, include a prefix indicating whether the PC is at the
   673  * current line. If showAll is true, include the source note type and the
   674  * entry stack depth.
   675  */
   676 JS_FRIEND_API(bool)
   677 js_DisassembleAtPC(JSContext *cx, JSScript *scriptArg, bool lines,
   678                    jsbytecode *pc, bool showAll, Sprinter *sp)
   679 {
   680     RootedScript script(cx, scriptArg);
   681     BytecodeParser parser(cx, script);
   683     jsbytecode *next, *end;
   684     unsigned len;
   686     if (showAll && !parser.parse())
   687         return false;
   689     if (showAll)
   690         Sprint(sp, "%s:%u\n", script->filename(), script->lineno());
   692     if (pc != nullptr)
   693         sp->put("    ");
   694     if (showAll)
   695         sp->put("sn stack ");
   696     sp->put("loc   ");
   697     if (lines)
   698         sp->put("line");
   699     sp->put("  op\n");
   701     if (pc != nullptr)
   702         sp->put("    ");
   703     if (showAll)
   704         sp->put("-- ----- ");
   705     sp->put("----- ");
   706     if (lines)
   707         sp->put("----");
   708     sp->put("  --\n");
   710     next = script->code();
   711     end = script->codeEnd();
   712     while (next < end) {
   713         if (next == script->main())
   714             sp->put("main:\n");
   715         if (pc != nullptr) {
   716             if (pc == next)
   717                 sp->put("--> ");
   718             else
   719                 sp->put("    ");
   720         }
   721         if (showAll) {
   722             jssrcnote *sn = js_GetSrcNote(cx, script, next);
   723             if (sn) {
   724                 JS_ASSERT(!SN_IS_TERMINATOR(sn));
   725                 jssrcnote *next = SN_NEXT(sn);
   726                 while (!SN_IS_TERMINATOR(next) && SN_DELTA(next) == 0) {
   727                     Sprint(sp, "%02u\n    ", SN_TYPE(sn));
   728                     sn = next;
   729                     next = SN_NEXT(sn);
   730                 }
   731                 Sprint(sp, "%02u ", SN_TYPE(sn));
   732             }
   733             else
   734                 sp->put("   ");
   735             if (parser.isReachable(next))
   736                 Sprint(sp, "%05u ", parser.stackDepthAtPC(next));
   737             else
   738                 Sprint(sp, "      ");
   739         }
   740         len = js_Disassemble1(cx, script, next, script->pcToOffset(next), lines, sp);
   741         if (!len)
   742             return false;
   743         next += len;
   744     }
   745     return true;
   746 }
   748 bool
   749 js_Disassemble(JSContext *cx, HandleScript script, bool lines, Sprinter *sp)
   750 {
   751     return js_DisassembleAtPC(cx, script, lines, nullptr, false, sp);
   752 }
   754 JS_FRIEND_API(bool)
   755 js_DumpPC(JSContext *cx)
   756 {
   757     js::gc::AutoSuppressGC suppressGC(cx);
   758     Sprinter sprinter(cx);
   759     if (!sprinter.init())
   760         return false;
   761     ScriptFrameIter iter(cx);
   762     RootedScript script(cx, iter.script());
   763     bool ok = js_DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter);
   764     fprintf(stdout, "%s", sprinter.string());
   765     return ok;
   766 }
   768 JS_FRIEND_API(bool)
   769 js_DumpScript(JSContext *cx, JSScript *scriptArg)
   770 {
   771     js::gc::AutoSuppressGC suppressGC(cx);
   772     Sprinter sprinter(cx);
   773     if (!sprinter.init())
   774         return false;
   775     RootedScript script(cx, scriptArg);
   776     bool ok = js_Disassemble(cx, script, true, &sprinter);
   777     fprintf(stdout, "%s", sprinter.string());
   778     return ok;
   779 }
   781 /*
   782  * Useful to debug ReconstructPCStack.
   783  */
   784 JS_FRIEND_API(bool)
   785 js_DumpScriptDepth(JSContext *cx, JSScript *scriptArg, jsbytecode *pc)
   786 {
   787     js::gc::AutoSuppressGC suppressGC(cx);
   788     Sprinter sprinter(cx);
   789     if (!sprinter.init())
   790         return false;
   791     RootedScript script(cx, scriptArg);
   792     bool ok = js_DisassembleAtPC(cx, script, true, pc, true, &sprinter);
   793     fprintf(stdout, "%s", sprinter.string());
   794     return ok;
   795 }
   797 static char *
   798 QuoteString(Sprinter *sp, JSString *str, uint32_t quote);
   800 static bool
   801 ToDisassemblySource(JSContext *cx, HandleValue v, JSAutoByteString *bytes)
   802 {
   803     if (JSVAL_IS_STRING(v)) {
   804         Sprinter sprinter(cx);
   805         if (!sprinter.init())
   806             return false;
   807         char *nbytes = QuoteString(&sprinter, JSVAL_TO_STRING(v), '"');
   808         if (!nbytes)
   809             return false;
   810         nbytes = JS_sprintf_append(nullptr, "%s", nbytes);
   811         if (!nbytes)
   812             return false;
   813         bytes->initBytes(nbytes);
   814         return true;
   815     }
   817     if (cx->runtime()->isHeapBusy() || cx->runtime()->noGCOrAllocationCheck) {
   818         char *source = JS_sprintf_append(nullptr, "<value>");
   819         if (!source)
   820             return false;
   821         bytes->initBytes(source);
   822         return true;
   823     }
   825     if (!JSVAL_IS_PRIMITIVE(v)) {
   826         JSObject *obj = JSVAL_TO_OBJECT(v);
   827         if (obj->is<StaticBlockObject>()) {
   828             Rooted<StaticBlockObject*> block(cx, &obj->as<StaticBlockObject>());
   829             char *source = JS_sprintf_append(nullptr, "depth %d {", block->localOffset());
   830             if (!source)
   831                 return false;
   833             Shape::Range<CanGC> r(cx, block->lastProperty());
   835             while (!r.empty()) {
   836                 Rooted<Shape*> shape(cx, &r.front());
   837                 JSAtom *atom = JSID_IS_INT(shape->propid())
   838                                ? cx->names().empty
   839                                : JSID_TO_ATOM(shape->propid());
   841                 JSAutoByteString bytes;
   842                 if (!AtomToPrintableString(cx, atom, &bytes))
   843                     return false;
   845                 r.popFront();
   846                 source = JS_sprintf_append(source, "%s: %d%s",
   847                                            bytes.ptr(),
   848                                            block->shapeToIndex(*shape),
   849                                            !r.empty() ? ", " : "");
   850                 if (!source)
   851                     return false;
   852             }
   854             source = JS_sprintf_append(source, "}");
   855             if (!source)
   856                 return false;
   857             bytes->initBytes(source);
   858             return true;
   859         }
   861         if (obj->is<JSFunction>()) {
   862             RootedFunction fun(cx, &obj->as<JSFunction>());
   863             JSString *str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT);
   864             if (!str)
   865                 return false;
   866             return bytes->encodeLatin1(cx, str);
   867         }
   869         if (obj->is<RegExpObject>()) {
   870             JSString *source = obj->as<RegExpObject>().toString(cx);
   871             if (!source)
   872                 return false;
   873             JS::Anchor<JSString *> anchor(source);
   874             return bytes->encodeLatin1(cx, source);
   875         }
   876     }
   878     return !!js_ValueToPrintable(cx, v, bytes, true);
   879 }
   881 unsigned
   882 js_Disassemble1(JSContext *cx, HandleScript script, jsbytecode *pc,
   883                 unsigned loc, bool lines, Sprinter *sp)
   884 {
   885     JSOp op = (JSOp)*pc;
   886     if (op >= JSOP_LIMIT) {
   887         char numBuf1[12], numBuf2[12];
   888         JS_snprintf(numBuf1, sizeof numBuf1, "%d", op);
   889         JS_snprintf(numBuf2, sizeof numBuf2, "%d", JSOP_LIMIT);
   890         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
   891                              JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2);
   892         return 0;
   893     }
   894     const JSCodeSpec *cs = &js_CodeSpec[op];
   895     ptrdiff_t len = (ptrdiff_t) cs->length;
   896     Sprint(sp, "%05u:", loc);
   897     if (lines)
   898         Sprint(sp, "%4u", JS_PCToLineNumber(cx, script, pc));
   899     Sprint(sp, "  %s", js_CodeName[op]);
   901     switch (JOF_TYPE(cs->format)) {
   902       case JOF_BYTE:
   903           // Scan the trynotes to find the associated catch block
   904           // and make the try opcode look like a jump instruction
   905           // with an offset. This simplifies code coverage analysis
   906           // based on this disassembled output.
   907           if (op == JSOP_TRY) {
   908               TryNoteArray *trynotes = script->trynotes();
   909               uint32_t i;
   910               for(i = 0; i < trynotes->length; i++) {
   911                   JSTryNote note = trynotes->vector[i];
   912                   if (note.kind == JSTRY_CATCH && note.start == loc + 1) {
   913                       Sprint(sp, " %u (%+d)",
   914                              (unsigned int) (loc+note.length+1),
   915                              (int) (note.length+1));
   916                       break;
   917                   }
   918               }
   919           }
   920         break;
   922       case JOF_JUMP: {
   923         ptrdiff_t off = GET_JUMP_OFFSET(pc);
   924         Sprint(sp, " %u (%+d)", loc + (int) off, (int) off);
   925         break;
   926       }
   928       case JOF_SCOPECOORD: {
   929         RootedValue v(cx,
   930             StringValue(ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc)));
   931         JSAutoByteString bytes;
   932         if (!ToDisassemblySource(cx, v, &bytes))
   933             return 0;
   934         ScopeCoordinate sc(pc);
   935         Sprint(sp, " %s (hops = %u, slot = %u)", bytes.ptr(), sc.hops(), sc.slot());
   936         break;
   937       }
   939       case JOF_ATOM: {
   940         RootedValue v(cx, StringValue(script->getAtom(GET_UINT32_INDEX(pc))));
   941         JSAutoByteString bytes;
   942         if (!ToDisassemblySource(cx, v, &bytes))
   943             return 0;
   944         Sprint(sp, " %s", bytes.ptr());
   945         break;
   946       }
   948       case JOF_DOUBLE: {
   949         RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc)));
   950         JSAutoByteString bytes;
   951         if (!ToDisassemblySource(cx, v, &bytes))
   952             return 0;
   953         Sprint(sp, " %s", bytes.ptr());
   954         break;
   955       }
   957       case JOF_OBJECT: {
   958         /* Don't call obj.toSource if analysis/inference is active. */
   959         if (script->compartment()->activeAnalysis) {
   960             Sprint(sp, " object");
   961             break;
   962         }
   964         JSObject *obj = script->getObject(GET_UINT32_INDEX(pc));
   965         {
   966             JSAutoByteString bytes;
   967             RootedValue v(cx, ObjectValue(*obj));
   968             if (!ToDisassemblySource(cx, v, &bytes))
   969                 return 0;
   970             Sprint(sp, " %s", bytes.ptr());
   971         }
   972         break;
   973       }
   975       case JOF_REGEXP: {
   976         JSObject *obj = script->getRegExp(GET_UINT32_INDEX(pc));
   977         JSAutoByteString bytes;
   978         RootedValue v(cx, ObjectValue(*obj));
   979         if (!ToDisassemblySource(cx, v, &bytes))
   980             return 0;
   981         Sprint(sp, " %s", bytes.ptr());
   982         break;
   983       }
   985       case JOF_TABLESWITCH:
   986       {
   987         int32_t i, low, high;
   989         ptrdiff_t off = GET_JUMP_OFFSET(pc);
   990         jsbytecode *pc2 = pc + JUMP_OFFSET_LEN;
   991         low = GET_JUMP_OFFSET(pc2);
   992         pc2 += JUMP_OFFSET_LEN;
   993         high = GET_JUMP_OFFSET(pc2);
   994         pc2 += JUMP_OFFSET_LEN;
   995         Sprint(sp, " defaultOffset %d low %d high %d", int(off), low, high);
   996         for (i = low; i <= high; i++) {
   997             off = GET_JUMP_OFFSET(pc2);
   998             Sprint(sp, "\n\t%d: %d", i, int(off));
   999             pc2 += JUMP_OFFSET_LEN;
  1001         len = 1 + pc2 - pc;
  1002         break;
  1005       case JOF_QARG:
  1006         Sprint(sp, " %u", GET_ARGNO(pc));
  1007         break;
  1009       case JOF_LOCAL:
  1010         Sprint(sp, " %u", GET_LOCALNO(pc));
  1011         break;
  1014         int i;
  1016       case JOF_UINT16:
  1017         i = (int)GET_UINT16(pc);
  1018         goto print_int;
  1020       case JOF_UINT24:
  1021         JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY ||
  1022                   op == JSOP_DUPAT);
  1023         i = (int)GET_UINT24(pc);
  1024         goto print_int;
  1026       case JOF_UINT8:
  1027         i = GET_UINT8(pc);
  1028         goto print_int;
  1030       case JOF_INT8:
  1031         i = GET_INT8(pc);
  1032         goto print_int;
  1034       case JOF_INT32:
  1035         JS_ASSERT(op == JSOP_INT32);
  1036         i = GET_INT32(pc);
  1037       print_int:
  1038         Sprint(sp, " %d", i);
  1039         break;
  1042       default: {
  1043         char numBuf[12];
  1044         JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) cs->format);
  1045         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
  1046                              JSMSG_UNKNOWN_FORMAT, numBuf);
  1047         return 0;
  1050     sp->put("\n");
  1051     return len;
  1054 #endif /* DEBUG */
  1056 /************************************************************************/
  1058 const size_t Sprinter::DefaultSize = 64;
  1060 bool
  1061 Sprinter::realloc_(size_t newSize)
  1063     JS_ASSERT(newSize > (size_t) offset);
  1064     char *newBuf = (char *) js_realloc(base, newSize);
  1065     if (!newBuf) {
  1066         reportOutOfMemory();
  1067         return false;
  1069     base = newBuf;
  1070     size = newSize;
  1071     base[size - 1] = 0;
  1072     return true;
  1075 Sprinter::Sprinter(ExclusiveContext *cx)
  1076   : context(cx),
  1077 #ifdef DEBUG
  1078     initialized(false),
  1079 #endif
  1080     base(nullptr), size(0), offset(0), reportedOOM(false)
  1081 { }
  1083 Sprinter::~Sprinter()
  1085 #ifdef DEBUG
  1086     if (initialized)
  1087         checkInvariants();
  1088 #endif
  1089     js_free(base);
  1092 bool
  1093 Sprinter::init()
  1095     JS_ASSERT(!initialized);
  1096     base = (char *) js_malloc(DefaultSize);
  1097     if (!base) {
  1098         reportOutOfMemory();
  1099         return false;
  1101 #ifdef DEBUG
  1102     initialized = true;
  1103 #endif
  1104     *base = 0;
  1105     size = DefaultSize;
  1106     base[size - 1] = 0;
  1107     return true;
  1110 void
  1111 Sprinter::checkInvariants() const
  1113     JS_ASSERT(initialized);
  1114     JS_ASSERT((size_t) offset < size);
  1115     JS_ASSERT(base[size - 1] == 0);
  1118 const char *
  1119 Sprinter::string() const
  1121     return base;
  1124 const char *
  1125 Sprinter::stringEnd() const
  1127     return base + offset;
  1130 char *
  1131 Sprinter::stringAt(ptrdiff_t off) const
  1133     JS_ASSERT(off >= 0 && (size_t) off < size);
  1134     return base + off;
  1137 char &
  1138 Sprinter::operator[](size_t off)
  1140     JS_ASSERT(off < size);
  1141     return *(base + off);
  1144 char *
  1145 Sprinter::reserve(size_t len)
  1147     InvariantChecker ic(this);
  1149     while (len + 1 > size - offset) { /* Include trailing \0 */
  1150         if (!realloc_(size * 2))
  1151             return nullptr;
  1154     char *sb = base + offset;
  1155     offset += len;
  1156     return sb;
  1159 ptrdiff_t
  1160 Sprinter::put(const char *s, size_t len)
  1162     InvariantChecker ic(this);
  1164     const char *oldBase = base;
  1165     const char *oldEnd = base + size;
  1167     ptrdiff_t oldOffset = offset;
  1168     char *bp = reserve(len);
  1169     if (!bp)
  1170         return -1;
  1172     /* s is within the buffer already */
  1173     if (s >= oldBase && s < oldEnd) {
  1174         /* buffer was realloc'ed */
  1175         if (base != oldBase)
  1176             s = stringAt(s - oldBase);  /* this is where it lives now */
  1177         memmove(bp, s, len);
  1178     } else {
  1179         js_memcpy(bp, s, len);
  1182     bp[len] = 0;
  1183     return oldOffset;
  1186 ptrdiff_t
  1187 Sprinter::put(const char *s)
  1189     return put(s, strlen(s));
  1192 ptrdiff_t
  1193 Sprinter::putString(JSString *s)
  1195     InvariantChecker ic(this);
  1197     size_t length = s->length();
  1198     const jschar *chars = s->getChars(context);
  1199     if (!chars)
  1200         return -1;
  1202     size_t size = length;
  1203     if (size == (size_t) -1)
  1204         return -1;
  1206     ptrdiff_t oldOffset = offset;
  1207     char *buffer = reserve(size);
  1208     if (!buffer)
  1209         return -1;
  1210     DeflateStringToBuffer(nullptr, chars, length, buffer, &size);
  1211     buffer[size] = 0;
  1213     return oldOffset;
  1216 int
  1217 Sprinter::printf(const char *fmt, ...)
  1219     InvariantChecker ic(this);
  1221     do {
  1222         va_list va;
  1223         va_start(va, fmt);
  1224         int i = vsnprintf(base + offset, size - offset, fmt, va);
  1225         va_end(va);
  1227         if (i > -1 && (size_t) i < size - offset) {
  1228             offset += i;
  1229             return i;
  1231     } while (realloc_(size * 2));
  1233     return -1;
  1236 ptrdiff_t
  1237 Sprinter::getOffset() const
  1239     return offset;
  1242 void
  1243 Sprinter::reportOutOfMemory()
  1245     if (reportedOOM)
  1246         return;
  1247     if (context)
  1248         js_ReportOutOfMemory(context);
  1249     reportedOOM = true;
  1252 bool
  1253 Sprinter::hadOutOfMemory() const
  1255     return reportedOOM;
  1258 ptrdiff_t
  1259 js::Sprint(Sprinter *sp, const char *format, ...)
  1261     va_list ap;
  1262     char *bp;
  1263     ptrdiff_t offset;
  1265     va_start(ap, format);
  1266     bp = JS_vsmprintf(format, ap);      /* XXX vsaprintf */
  1267     va_end(ap);
  1268     if (!bp) {
  1269         sp->reportOutOfMemory();
  1270         return -1;
  1272     offset = sp->put(bp);
  1273     js_free(bp);
  1274     return offset;
  1277 const char js_EscapeMap[] = {
  1278     '\b', 'b',
  1279     '\f', 'f',
  1280     '\n', 'n',
  1281     '\r', 'r',
  1282     '\t', 't',
  1283     '\v', 'v',
  1284     '"',  '"',
  1285     '\'', '\'',
  1286     '\\', '\\',
  1287     '\0'
  1288 };
  1290 #define DONT_ESCAPE     0x10000
  1292 static char *
  1293 QuoteString(Sprinter *sp, JSString *str, uint32_t quote)
  1295     /* Sample off first for later return value pointer computation. */
  1296     bool dontEscape = (quote & DONT_ESCAPE) != 0;
  1297     jschar qc = (jschar) quote;
  1298     ptrdiff_t offset = sp->getOffset();
  1299     if (qc && Sprint(sp, "%c", (char)qc) < 0)
  1300         return nullptr;
  1302     const jschar *s = str->getChars(sp->context);
  1303     if (!s)
  1304         return nullptr;
  1305     const jschar *z = s + str->length();
  1307     /* Loop control variables: z points at end of string sentinel. */
  1308     for (const jschar *t = s; t < z; s = ++t) {
  1309         /* Move t forward from s past un-quote-worthy characters. */
  1310         jschar c = *t;
  1311         while (c < 127 && isprint(c) && c != qc && c != '\\' && c != '\t') {
  1312             c = *++t;
  1313             if (t == z)
  1314                 break;
  1318             ptrdiff_t len = t - s;
  1319             ptrdiff_t base = sp->getOffset();
  1320             char *bp = sp->reserve(len);
  1321             if (!bp)
  1322                 return nullptr;
  1324             for (ptrdiff_t i = 0; i < len; ++i)
  1325                 (*sp)[base + i] = (char) *s++;
  1326             (*sp)[base + len] = 0;
  1329         if (t == z)
  1330             break;
  1332         /* Use js_EscapeMap, \u, or \x only if necessary. */
  1333         bool ok;
  1334         const char *e;
  1335         if (!(c >> 8) && c != 0 && (e = strchr(js_EscapeMap, (int)c)) != nullptr) {
  1336             ok = dontEscape
  1337                  ? Sprint(sp, "%c", (char)c) >= 0
  1338                  : Sprint(sp, "\\%c", e[1]) >= 0;
  1339         } else {
  1340             /*
  1341              * Use \x only if the high byte is 0 and we're in a quoted string,
  1342              * because ECMA-262 allows only \u, not \x, in Unicode identifiers
  1343              * (see bug 621814).
  1344              */
  1345             ok = Sprint(sp, (qc && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c) >= 0;
  1347         if (!ok)
  1348             return nullptr;
  1351     /* Sprint the closing quote and return the quoted string. */
  1352     if (qc && Sprint(sp, "%c", (char)qc) < 0)
  1353         return nullptr;
  1355     /*
  1356      * If we haven't Sprint'd anything yet, Sprint an empty string so that
  1357      * the return below gives a valid result.
  1358      */
  1359     if (offset == sp->getOffset() && Sprint(sp, "") < 0)
  1360         return nullptr;
  1362     return sp->stringAt(offset);
  1365 JSString *
  1366 js_QuoteString(ExclusiveContext *cx, JSString *str, jschar quote)
  1368     Sprinter sprinter(cx);
  1369     if (!sprinter.init())
  1370         return nullptr;
  1371     char *bytes = QuoteString(&sprinter, str, quote);
  1372     if (!bytes)
  1373         return nullptr;
  1374     return js_NewStringCopyZ<CanGC>(cx, bytes);
  1377 /************************************************************************/
  1379 namespace {
  1380 /*
  1381  * The expression decompiler is invoked by error handling code to produce a
  1382  * string representation of the erroring expression. As it's only a debugging
  1383  * tool, it only supports basic expressions. For anything complicated, it simply
  1384  * puts "(intermediate value)" into the error result.
  1386  * Here's the basic algorithm:
  1388  * 1. Find the stack location of the value whose expression we wish to
  1389  * decompile. The error handler can explicitly pass this as an
  1390  * argument. Otherwise, we search backwards down the stack for the offending
  1391  * value.
  1393  * 2. Instantiate and run a BytecodeParser for the current frame. This creates a
  1394  * stack of pcs parallel to the interpreter stack; given an interpreter stack
  1395  * location, the corresponding pc stack location contains the opcode that pushed
  1396  * the value in the interpreter. Now, with the result of step 1, we have the
  1397  * opcode responsible for pushing the value we want to decompile.
  1399  * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler
  1400  * routine, responsible for a string representation of the expression that
  1401  * generated a certain stack location. decompilePC looks at one opcode and
  1402  * returns the JS source equivalent of that opcode.
  1404  * 4. Expressions can, of course, contain subexpressions. For example, the
  1405  * literals "4" and "5" are subexpressions of the addition operator in "4 +
  1406  * 5". If we need to decompile a subexpression, we call decompilePC (step 2)
  1407  * recursively on the operands' pcs. The result is a depth-first traversal of
  1408  * the expression tree.
  1410  */
  1411 struct ExpressionDecompiler
  1413     JSContext *cx;
  1414     InterpreterFrame *fp;
  1415     RootedScript script;
  1416     RootedFunction fun;
  1417     BindingVector *localNames;
  1418     BytecodeParser parser;
  1419     Sprinter sprinter;
  1421     ExpressionDecompiler(JSContext *cx, JSScript *script, JSFunction *fun)
  1422         : cx(cx),
  1423           script(cx, script),
  1424           fun(cx, fun),
  1425           localNames(nullptr),
  1426           parser(cx, script),
  1427           sprinter(cx)
  1428     {}
  1429     ~ExpressionDecompiler();
  1430     bool init();
  1431     bool decompilePCForStackOperand(jsbytecode *pc, int i);
  1432     bool decompilePC(jsbytecode *pc);
  1433     JSAtom *getLocal(uint32_t local, jsbytecode *pc);
  1434     JSAtom *getArg(unsigned slot);
  1435     JSAtom *loadAtom(jsbytecode *pc);
  1436     bool quote(JSString *s, uint32_t quote);
  1437     bool write(const char *s);
  1438     bool write(JSString *str);
  1439     bool getOutput(char **out);
  1440 };
  1442 bool
  1443 ExpressionDecompiler::decompilePCForStackOperand(jsbytecode *pc, int i)
  1445     pc = parser.pcForStackOperand(pc, i);
  1446     if (!pc)
  1447         return write("(intermediate value)");
  1448     return decompilePC(pc);
  1451 bool
  1452 ExpressionDecompiler::decompilePC(jsbytecode *pc)
  1454     JS_ASSERT(script->containsPC(pc));
  1456     JSOp op = (JSOp)*pc;
  1458     if (const char *token = CodeToken[op]) {
  1459         // Handle simple cases of binary and unary operators.
  1460         switch (js_CodeSpec[op].nuses) {
  1461           case 2: {
  1462             jssrcnote *sn = js_GetSrcNote(cx, script, pc);
  1463             if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP)
  1464                 return write("(") &&
  1465                        decompilePCForStackOperand(pc, -2) &&
  1466                        write(" ") &&
  1467                        write(token) &&
  1468                        write(" ") &&
  1469                        decompilePCForStackOperand(pc, -1) &&
  1470                        write(")");
  1471             break;
  1473           case 1:
  1474             return write(token) &&
  1475                    write("(") &&
  1476                    decompilePCForStackOperand(pc, -1) &&
  1477                    write(")");
  1478           default:
  1479             break;
  1483     switch (op) {
  1484       case JSOP_GETGNAME:
  1485       case JSOP_NAME:
  1486       case JSOP_GETINTRINSIC:
  1487         return write(loadAtom(pc));
  1488       case JSOP_GETARG: {
  1489         unsigned slot = GET_ARGNO(pc);
  1490         JSAtom *atom = getArg(slot);
  1491         return write(atom);
  1493       case JSOP_GETLOCAL: {
  1494         uint32_t i = GET_LOCALNO(pc);
  1495         if (JSAtom *atom = getLocal(i, pc))
  1496             return write(atom);
  1497         return write("(intermediate value)");
  1499       case JSOP_GETALIASEDVAR: {
  1500         JSAtom *atom = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc);
  1501         JS_ASSERT(atom);
  1502         return write(atom);
  1504       case JSOP_LENGTH:
  1505       case JSOP_GETPROP:
  1506       case JSOP_CALLPROP: {
  1507         RootedAtom prop(cx, (op == JSOP_LENGTH) ? cx->names().length : loadAtom(pc));
  1508         if (!decompilePCForStackOperand(pc, -1))
  1509             return false;
  1510         if (IsIdentifier(prop)) {
  1511             return write(".") &&
  1512                    quote(prop, '\0');
  1514         return write("[") &&
  1515                quote(prop, '\'') &&
  1516                write("]");
  1518       case JSOP_GETELEM:
  1519       case JSOP_CALLELEM:
  1520         return decompilePCForStackOperand(pc, -2) &&
  1521                write("[") &&
  1522                decompilePCForStackOperand(pc, -1) &&
  1523                write("]");
  1524       case JSOP_NULL:
  1525         return write(js_null_str);
  1526       case JSOP_TRUE:
  1527         return write(js_true_str);
  1528       case JSOP_FALSE:
  1529         return write(js_false_str);
  1530       case JSOP_ZERO:
  1531       case JSOP_ONE:
  1532       case JSOP_INT8:
  1533       case JSOP_UINT16:
  1534       case JSOP_UINT24:
  1535       case JSOP_INT32:
  1536         return sprinter.printf("%d", GetBytecodeInteger(pc)) >= 0;
  1537       case JSOP_STRING:
  1538         return quote(loadAtom(pc), '"');
  1539       case JSOP_UNDEFINED:
  1540         return write(js_undefined_str);
  1541       case JSOP_THIS:
  1542         // |this| could convert to a very long object initialiser, so cite it by
  1543         // its keyword name.
  1544         return write(js_this_str);
  1545       case JSOP_CALL:
  1546       case JSOP_FUNCALL:
  1547         return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) &&
  1548                write("(...)");
  1549       case JSOP_SPREADCALL:
  1550         return decompilePCForStackOperand(pc, -int32_t(3)) &&
  1551                write("(...)");
  1552       case JSOP_NEWARRAY:
  1553         return write("[]");
  1554       case JSOP_REGEXP:
  1555       case JSOP_OBJECT: {
  1556         JSObject *obj = (op == JSOP_REGEXP)
  1557                         ? script->getRegExp(GET_UINT32_INDEX(pc))
  1558                         : script->getObject(GET_UINT32_INDEX(pc));
  1559         RootedValue objv(cx, ObjectValue(*obj));
  1560         JSString *str = ValueToSource(cx, objv);
  1561         if (!str)
  1562             return false;
  1563         return write(str);
  1565       default:
  1566         break;
  1568     return write("(intermediate value)");
  1571 ExpressionDecompiler::~ExpressionDecompiler()
  1573     js_delete<BindingVector>(localNames);
  1576 bool
  1577 ExpressionDecompiler::init()
  1579     assertSameCompartment(cx, script);
  1581     if (!sprinter.init())
  1582         return false;
  1584     localNames = cx->new_<BindingVector>(cx);
  1585     if (!localNames)
  1586         return false;
  1587     RootedScript script_(cx, script);
  1588     if (!FillBindingVector(script_, localNames))
  1589         return false;
  1591     if (!parser.parse())
  1592         return false;
  1594     return true;
  1597 bool
  1598 ExpressionDecompiler::write(const char *s)
  1600     return sprinter.put(s) >= 0;
  1603 bool
  1604 ExpressionDecompiler::write(JSString *str)
  1606     return sprinter.putString(str) >= 0;
  1609 bool
  1610 ExpressionDecompiler::quote(JSString *s, uint32_t quote)
  1612     return QuoteString(&sprinter, s, quote) >= 0;
  1615 JSAtom *
  1616 ExpressionDecompiler::loadAtom(jsbytecode *pc)
  1618     return script->getAtom(GET_UINT32_INDEX(pc));
  1621 JSAtom *
  1622 ExpressionDecompiler::getArg(unsigned slot)
  1624     JS_ASSERT(fun);
  1625     JS_ASSERT(slot < script->bindings.count());
  1626     return (*localNames)[slot].name();
  1629 JSAtom *
  1630 ExpressionDecompiler::getLocal(uint32_t local, jsbytecode *pc)
  1632     JS_ASSERT(local < script->nfixed());
  1633     if (local < script->nfixedvars()) {
  1634         JS_ASSERT(fun);
  1635         uint32_t slot = local + fun->nargs();
  1636         JS_ASSERT(slot < script->bindings.count());
  1637         return (*localNames)[slot].name();
  1639     for (NestedScopeObject *chain = script->getStaticScope(pc);
  1640          chain;
  1641          chain = chain->enclosingNestedScope()) {
  1642         if (!chain->is<StaticBlockObject>())
  1643             continue;
  1644         StaticBlockObject &block = chain->as<StaticBlockObject>();
  1645         if (local < block.localOffset())
  1646             continue;
  1647         local -= block.localOffset();
  1648         if (local >= block.numVariables())
  1649             return nullptr;
  1650         for (Shape::Range<NoGC> r(block.lastProperty()); !r.empty(); r.popFront()) {
  1651             const Shape &shape = r.front();
  1652             if (block.shapeToIndex(shape) == local)
  1653                 return JSID_TO_ATOM(shape.propid());
  1655         break;
  1657     return nullptr;
  1660 bool
  1661 ExpressionDecompiler::getOutput(char **res)
  1663     ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0);
  1664     *res = cx->pod_malloc<char>(len + 1);
  1665     if (!*res)
  1666         return false;
  1667     js_memcpy(*res, sprinter.stringAt(0), len);
  1668     (*res)[len] = 0;
  1669     return true;
  1672 }  // anonymous namespace
  1674 static bool
  1675 FindStartPC(JSContext *cx, const FrameIter &iter, int spindex, int skipStackHits, Value v,
  1676             jsbytecode **valuepc)
  1678     jsbytecode *current = *valuepc;
  1680     if (spindex == JSDVG_IGNORE_STACK)
  1681         return true;
  1683     /*
  1684      * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the
  1685      * previous pc (see bug 831120).
  1686      */
  1687     if (iter.isIon())
  1688         return true;
  1690     *valuepc = nullptr;
  1692     BytecodeParser parser(cx, iter.script());
  1693     if (!parser.parse())
  1694         return false;
  1696     if (spindex < 0 && spindex + int(parser.stackDepthAtPC(current)) < 0)
  1697         spindex = JSDVG_SEARCH_STACK;
  1699     if (spindex == JSDVG_SEARCH_STACK) {
  1700         size_t index = iter.numFrameSlots();
  1701         JS_ASSERT(index >= size_t(parser.stackDepthAtPC(current)));
  1703         // We search from fp->sp to base to find the most recently calculated
  1704         // value matching v under assumption that it is the value that caused
  1705         // the exception.
  1706         int stackHits = 0;
  1707         Value s;
  1708         do {
  1709             if (!index)
  1710                 return true;
  1711             s = iter.frameSlotValue(--index);
  1712         } while (s != v || stackHits++ != skipStackHits);
  1714         // If the current PC has fewer values on the stack than the index we are
  1715         // looking for, the blamed value must be one pushed by the current
  1716         // bytecode, so restore *valuepc.
  1717         jsbytecode *pc = nullptr;
  1718         if (index < size_t(parser.stackDepthAtPC(current)))
  1719             pc = parser.pcForStackOperand(current, index);
  1720         *valuepc = pc ? pc : current;
  1721     } else {
  1722         jsbytecode *pc = parser.pcForStackOperand(current, spindex);
  1723         *valuepc = pc ? pc : current;
  1725     return true;
  1728 static bool
  1729 DecompileExpressionFromStack(JSContext *cx, int spindex, int skipStackHits, HandleValue v, char **res)
  1731     JS_ASSERT(spindex < 0 ||
  1732               spindex == JSDVG_IGNORE_STACK ||
  1733               spindex == JSDVG_SEARCH_STACK);
  1735     *res = nullptr;
  1737 #ifdef JS_MORE_DETERMINISTIC
  1738     /*
  1739      * Give up if we need deterministic behavior for differential testing.
  1740      * IonMonkey doesn't use InterpreterFrames and this ensures we get the same
  1741      * error messages.
  1742      */
  1743     return true;
  1744 #endif
  1746     FrameIter frameIter(cx);
  1748     if (frameIter.done() || !frameIter.hasScript())
  1749         return true;
  1751     RootedScript script(cx, frameIter.script());
  1752     AutoCompartment ac(cx, &script->global());
  1753     jsbytecode *valuepc = frameIter.pc();
  1754     RootedFunction fun(cx, frameIter.isFunctionFrame()
  1755                            ? frameIter.callee()
  1756                            : nullptr);
  1758     JS_ASSERT(script->containsPC(valuepc));
  1760     // Give up if in prologue.
  1761     if (valuepc < script->main())
  1762         return true;
  1764     if (!FindStartPC(cx, frameIter, spindex, skipStackHits, v, &valuepc))
  1765         return false;
  1766     if (!valuepc)
  1767         return true;
  1769     ExpressionDecompiler ed(cx, script, fun);
  1770     if (!ed.init())
  1771         return false;
  1772     if (!ed.decompilePC(valuepc))
  1773         return false;
  1775     return ed.getOutput(res);
  1778 char *
  1779 js::DecompileValueGenerator(JSContext *cx, int spindex, HandleValue v,
  1780                             HandleString fallbackArg, int skipStackHits)
  1782     RootedString fallback(cx, fallbackArg);
  1784         char *result;
  1785         if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result))
  1786             return nullptr;
  1787         if (result) {
  1788             if (strcmp(result, "(intermediate value)"))
  1789                 return result;
  1790             js_free(result);
  1793     if (!fallback) {
  1794         if (v.isUndefined())
  1795             return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)"
  1796         fallback = ValueToSource(cx, v);
  1797         if (!fallback)
  1798             return nullptr;
  1801     Rooted<JSLinearString *> linear(cx, fallback->ensureLinear(cx));
  1802     if (!linear)
  1803         return nullptr;
  1804     TwoByteChars tbchars(linear->chars(), linear->length());
  1805     return LossyTwoByteCharsToNewLatin1CharsZ(cx, tbchars).c_str();
  1808 static bool
  1809 DecompileArgumentFromStack(JSContext *cx, int formalIndex, char **res)
  1811     JS_ASSERT(formalIndex >= 0);
  1813     *res = nullptr;
  1815 #ifdef JS_MORE_DETERMINISTIC
  1816     /* See note in DecompileExpressionFromStack. */
  1817     return true;
  1818 #endif
  1820     /*
  1821      * Settle on the nearest script frame, which should be the builtin that
  1822      * called the intrinsic.
  1823      */
  1824     FrameIter frameIter(cx);
  1825     JS_ASSERT(!frameIter.done());
  1827     /*
  1828      * Get the second-to-top frame, the caller of the builtin that called the
  1829      * intrinsic.
  1830      */
  1831     ++frameIter;
  1832     if (frameIter.done() || !frameIter.hasScript())
  1833         return true;
  1835     RootedScript script(cx, frameIter.script());
  1836     AutoCompartment ac(cx, &script->global());
  1837     jsbytecode *current = frameIter.pc();
  1838     RootedFunction fun(cx, frameIter.isFunctionFrame()
  1839                        ? frameIter.callee()
  1840                        : nullptr);
  1842     JS_ASSERT(script->containsPC(current));
  1844     if (current < script->main())
  1845         return true;
  1847     /* Don't handle getters, setters or calls from fun.call/fun.apply. */
  1848     if (JSOp(*current) != JSOP_CALL || static_cast<unsigned>(formalIndex) >= GET_ARGC(current))
  1849         return true;
  1851     BytecodeParser parser(cx, script);
  1852     if (!parser.parse())
  1853         return false;
  1855     int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) + formalIndex;
  1856     JS_ASSERT(formalStackIndex >= 0);
  1857     if (uint32_t(formalStackIndex) >= parser.stackDepthAtPC(current))
  1858         return true;
  1860     ExpressionDecompiler ed(cx, script, fun);
  1861     if (!ed.init())
  1862         return false;
  1863     if (!ed.decompilePCForStackOperand(current, formalStackIndex))
  1864         return false;
  1866     return ed.getOutput(res);
  1869 char *
  1870 js::DecompileArgument(JSContext *cx, int formalIndex, HandleValue v)
  1873         char *result;
  1874         if (!DecompileArgumentFromStack(cx, formalIndex, &result))
  1875             return nullptr;
  1876         if (result) {
  1877             if (strcmp(result, "(intermediate value)"))
  1878                 return result;
  1879             js_free(result);
  1882     if (v.isUndefined())
  1883         return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)"
  1884     RootedString fallback(cx, ValueToSource(cx, v));
  1885     if (!fallback)
  1886         return nullptr;
  1888     Rooted<JSLinearString *> linear(cx, fallback->ensureLinear(cx));
  1889     if (!linear)
  1890         return nullptr;
  1891     return LossyTwoByteCharsToNewLatin1CharsZ(cx, linear->range()).c_str();
  1894 bool
  1895 js::CallResultEscapes(jsbytecode *pc)
  1897     /*
  1898      * If we see any of these sequences, the result is unused:
  1899      * - call / pop
  1901      * If we see any of these sequences, the result is only tested for nullness:
  1902      * - call / ifeq
  1903      * - call / not / ifeq
  1904      */
  1906     if (*pc == JSOP_CALL)
  1907         pc += JSOP_CALL_LENGTH;
  1908     else if (*pc == JSOP_SPREADCALL)
  1909         pc += JSOP_SPREADCALL_LENGTH;
  1910     else
  1911         return true;
  1913     if (*pc == JSOP_POP)
  1914         return false;
  1916     if (*pc == JSOP_NOT)
  1917         pc += JSOP_NOT_LENGTH;
  1919     return *pc != JSOP_IFEQ;
  1922 extern bool
  1923 js::IsValidBytecodeOffset(JSContext *cx, JSScript *script, size_t offset)
  1925     // This could be faster (by following jump instructions if the target is <= offset).
  1926     for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) {
  1927         size_t here = r.frontOffset();
  1928         if (here >= offset)
  1929             return here == offset;
  1931     return false;
  1934 JS_FRIEND_API(size_t)
  1935 js::GetPCCountScriptCount(JSContext *cx)
  1937     JSRuntime *rt = cx->runtime();
  1939     if (!rt->scriptAndCountsVector)
  1940         return 0;
  1942     return rt->scriptAndCountsVector->length();
  1945 enum MaybeComma {NO_COMMA, COMMA};
  1947 static void
  1948 AppendJSONProperty(StringBuffer &buf, const char *name, MaybeComma comma = COMMA)
  1950     if (comma)
  1951         buf.append(',');
  1953     buf.append('\"');
  1954     buf.appendInflated(name, strlen(name));
  1955     buf.appendInflated("\":", 2);
  1958 static void
  1959 AppendArrayJSONProperties(JSContext *cx, StringBuffer &buf,
  1960                           double *values, const char * const *names, unsigned count,
  1961                           MaybeComma &comma)
  1963     for (unsigned i = 0; i < count; i++) {
  1964         if (values[i]) {
  1965             AppendJSONProperty(buf, names[i], comma);
  1966             comma = COMMA;
  1967             NumberValueToStringBuffer(cx, DoubleValue(values[i]), buf);
  1972 JS_FRIEND_API(JSString *)
  1973 js::GetPCCountScriptSummary(JSContext *cx, size_t index)
  1975     JSRuntime *rt = cx->runtime();
  1977     if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
  1978         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
  1979         return nullptr;
  1982     const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index];
  1983     RootedScript script(cx, sac.script);
  1985     /*
  1986      * OOM on buffer appends here will not be caught immediately, but since
  1987      * StringBuffer uses a ContextAllocPolicy will trigger an exception on the
  1988      * context if they occur, which we'll catch before returning.
  1989      */
  1990     StringBuffer buf(cx);
  1992     buf.append('{');
  1994     AppendJSONProperty(buf, "file", NO_COMMA);
  1995     JSString *str = JS_NewStringCopyZ(cx, script->filename());
  1996     if (!str || !(str = StringToSource(cx, str)))
  1997         return nullptr;
  1998     buf.append(str);
  2000     AppendJSONProperty(buf, "line");
  2001     NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf);
  2003     if (script->functionNonDelazifying()) {
  2004         JSAtom *atom = script->functionNonDelazifying()->displayAtom();
  2005         if (atom) {
  2006             AppendJSONProperty(buf, "name");
  2007             if (!(str = StringToSource(cx, atom)))
  2008                 return nullptr;
  2009             buf.append(str);
  2013     double baseTotals[PCCounts::BASE_LIMIT] = {0.0};
  2014     double accessTotals[PCCounts::ACCESS_LIMIT - PCCounts::BASE_LIMIT] = {0.0};
  2015     double elementTotals[PCCounts::ELEM_LIMIT - PCCounts::ACCESS_LIMIT] = {0.0};
  2016     double propertyTotals[PCCounts::PROP_LIMIT - PCCounts::ACCESS_LIMIT] = {0.0};
  2017     double arithTotals[PCCounts::ARITH_LIMIT - PCCounts::BASE_LIMIT] = {0.0};
  2019     for (unsigned i = 0; i < script->length(); i++) {
  2020         PCCounts &counts = sac.getPCCounts(script->offsetToPC(i));
  2021         if (!counts)
  2022             continue;
  2024         JSOp op = (JSOp)script->code()[i];
  2025         unsigned numCounts = PCCounts::numCounts(op);
  2027         for (unsigned j = 0; j < numCounts; j++) {
  2028             double value = counts.get(j);
  2029             if (j < PCCounts::BASE_LIMIT) {
  2030                 baseTotals[j] += value;
  2031             } else if (PCCounts::accessOp(op)) {
  2032                 if (j < PCCounts::ACCESS_LIMIT)
  2033                     accessTotals[j - PCCounts::BASE_LIMIT] += value;
  2034                 else if (PCCounts::elementOp(op))
  2035                     elementTotals[j - PCCounts::ACCESS_LIMIT] += value;
  2036                 else if (PCCounts::propertyOp(op))
  2037                     propertyTotals[j - PCCounts::ACCESS_LIMIT] += value;
  2038                 else
  2039                     MOZ_ASSUME_UNREACHABLE("Bad opcode");
  2040             } else if (PCCounts::arithOp(op)) {
  2041                 arithTotals[j - PCCounts::BASE_LIMIT] += value;
  2042             } else {
  2043                 MOZ_ASSUME_UNREACHABLE("Bad opcode");
  2048     AppendJSONProperty(buf, "totals");
  2049     buf.append('{');
  2051     MaybeComma comma = NO_COMMA;
  2053     AppendArrayJSONProperties(cx, buf, baseTotals, countBaseNames,
  2054                               JS_ARRAY_LENGTH(baseTotals), comma);
  2055     AppendArrayJSONProperties(cx, buf, accessTotals, countAccessNames,
  2056                               JS_ARRAY_LENGTH(accessTotals), comma);
  2057     AppendArrayJSONProperties(cx, buf, elementTotals, countElementNames,
  2058                               JS_ARRAY_LENGTH(elementTotals), comma);
  2059     AppendArrayJSONProperties(cx, buf, propertyTotals, countPropertyNames,
  2060                               JS_ARRAY_LENGTH(propertyTotals), comma);
  2061     AppendArrayJSONProperties(cx, buf, arithTotals, countArithNames,
  2062                               JS_ARRAY_LENGTH(arithTotals), comma);
  2064     uint64_t ionActivity = 0;
  2065     jit::IonScriptCounts *ionCounts = sac.getIonCounts();
  2066     while (ionCounts) {
  2067         for (size_t i = 0; i < ionCounts->numBlocks(); i++)
  2068             ionActivity += ionCounts->block(i).hitCount();
  2069         ionCounts = ionCounts->previous();
  2071     if (ionActivity) {
  2072         AppendJSONProperty(buf, "ion", comma);
  2073         NumberValueToStringBuffer(cx, DoubleValue(ionActivity), buf);
  2076     buf.append('}');
  2077     buf.append('}');
  2079     if (cx->isExceptionPending())
  2080         return nullptr;
  2082     return buf.finishString();
  2085 static bool
  2086 GetPCCountJSON(JSContext *cx, const ScriptAndCounts &sac, StringBuffer &buf)
  2088     RootedScript script(cx, sac.script);
  2090     buf.append('{');
  2091     AppendJSONProperty(buf, "text", NO_COMMA);
  2093     JSString *str = JS_DecompileScript(cx, script, nullptr, 0);
  2094     if (!str || !(str = StringToSource(cx, str)))
  2095         return false;
  2097     buf.append(str);
  2099     AppendJSONProperty(buf, "line");
  2100     NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf);
  2102     AppendJSONProperty(buf, "opcodes");
  2103     buf.append('[');
  2104     bool comma = false;
  2106     SrcNoteLineScanner scanner(script->notes(), script->lineno());
  2108     for (jsbytecode *pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) {
  2109         size_t offset = script->pcToOffset(pc);
  2111         JSOp op = (JSOp) *pc;
  2113         if (comma)
  2114             buf.append(',');
  2115         comma = true;
  2117         buf.append('{');
  2119         AppendJSONProperty(buf, "id", NO_COMMA);
  2120         NumberValueToStringBuffer(cx, Int32Value(offset), buf);
  2122         scanner.advanceTo(offset);
  2124         AppendJSONProperty(buf, "line");
  2125         NumberValueToStringBuffer(cx, Int32Value(scanner.getLine()), buf);
  2128             const char *name = js_CodeName[op];
  2129             AppendJSONProperty(buf, "name");
  2130             buf.append('\"');
  2131             buf.appendInflated(name, strlen(name));
  2132             buf.append('\"');
  2136             ExpressionDecompiler ed(cx, script, script->functionDelazifying());
  2137             if (!ed.init())
  2138                 return false;
  2139             if (!ed.decompilePC(pc))
  2140                 return false;
  2141             char *text;
  2142             if (!ed.getOutput(&text))
  2143                 return false;
  2144             AppendJSONProperty(buf, "text");
  2145             JSString *str = JS_NewStringCopyZ(cx, text);
  2146             js_free(text);
  2147             if (!str || !(str = StringToSource(cx, str)))
  2148                 return false;
  2149             buf.append(str);
  2152         PCCounts &counts = sac.getPCCounts(pc);
  2153         unsigned numCounts = PCCounts::numCounts(op);
  2155         AppendJSONProperty(buf, "counts");
  2156         buf.append('{');
  2158         MaybeComma comma = NO_COMMA;
  2159         for (unsigned i = 0; i < numCounts; i++) {
  2160             double value = counts.get(i);
  2161             if (value > 0) {
  2162                 AppendJSONProperty(buf, PCCounts::countName(op, i), comma);
  2163                 comma = COMMA;
  2164                 NumberValueToStringBuffer(cx, DoubleValue(value), buf);
  2168         buf.append('}');
  2169         buf.append('}');
  2172     buf.append(']');
  2174     jit::IonScriptCounts *ionCounts = sac.getIonCounts();
  2175     if (ionCounts) {
  2176         AppendJSONProperty(buf, "ion");
  2177         buf.append('[');
  2178         bool comma = false;
  2179         while (ionCounts) {
  2180             if (comma)
  2181                 buf.append(',');
  2182             comma = true;
  2184             buf.append('[');
  2185             for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
  2186                 if (i)
  2187                     buf.append(',');
  2188                 const jit::IonBlockCounts &block = ionCounts->block(i);
  2190                 buf.append('{');
  2191                 AppendJSONProperty(buf, "id", NO_COMMA);
  2192                 NumberValueToStringBuffer(cx, Int32Value(block.id()), buf);
  2193                 AppendJSONProperty(buf, "offset");
  2194                 NumberValueToStringBuffer(cx, Int32Value(block.offset()), buf);
  2195                 AppendJSONProperty(buf, "successors");
  2196                 buf.append('[');
  2197                 for (size_t j = 0; j < block.numSuccessors(); j++) {
  2198                     if (j)
  2199                         buf.append(',');
  2200                     NumberValueToStringBuffer(cx, Int32Value(block.successor(j)), buf);
  2202                 buf.append(']');
  2203                 AppendJSONProperty(buf, "hits");
  2204                 NumberValueToStringBuffer(cx, DoubleValue(block.hitCount()), buf);
  2206                 AppendJSONProperty(buf, "code");
  2207                 JSString *str = JS_NewStringCopyZ(cx, block.code());
  2208                 if (!str || !(str = StringToSource(cx, str)))
  2209                     return false;
  2210                 buf.append(str);
  2211                 buf.append('}');
  2213             buf.append(']');
  2215             ionCounts = ionCounts->previous();
  2217         buf.append(']');
  2220     buf.append('}');
  2222     return !cx->isExceptionPending();
  2225 JS_FRIEND_API(JSString *)
  2226 js::GetPCCountScriptContents(JSContext *cx, size_t index)
  2228     JSRuntime *rt = cx->runtime();
  2230     if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
  2231         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
  2232         return nullptr;
  2235     const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index];
  2236     JSScript *script = sac.script;
  2238     StringBuffer buf(cx);
  2240     if (!script->functionNonDelazifying() && !script->compileAndGo())
  2241         return buf.finishString();
  2244         AutoCompartment ac(cx, &script->global());
  2245         if (!GetPCCountJSON(cx, sac, buf))
  2246             return nullptr;
  2249     return buf.finishString();

mercurial