js/src/jsopcode.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/jsopcode.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2250 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99:
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +/*
    1.11 + * JS bytecode descriptors, disassemblers, and (expression) decompilers.
    1.12 + */
    1.13 +
    1.14 +#include "jsopcodeinlines.h"
    1.15 +
    1.16 +#include <ctype.h>
    1.17 +#include <stdarg.h>
    1.18 +#include <stdio.h>
    1.19 +#include <string.h>
    1.20 +
    1.21 +#include "jsanalyze.h"
    1.22 +#include "jsapi.h"
    1.23 +#include "jsatom.h"
    1.24 +#include "jscntxt.h"
    1.25 +#include "jscompartment.h"
    1.26 +#include "jsfun.h"
    1.27 +#include "jsnum.h"
    1.28 +#include "jsobj.h"
    1.29 +#include "jsprf.h"
    1.30 +#include "jsscript.h"
    1.31 +#include "jsstr.h"
    1.32 +#include "jstypes.h"
    1.33 +#include "jsutil.h"
    1.34 +
    1.35 +#include "frontend/BytecodeCompiler.h"
    1.36 +#include "frontend/SourceNotes.h"
    1.37 +#include "js/CharacterEncoding.h"
    1.38 +#include "vm/Opcodes.h"
    1.39 +#include "vm/ScopeObject.h"
    1.40 +#include "vm/Shape.h"
    1.41 +#include "vm/StringBuffer.h"
    1.42 +
    1.43 +#include "jscntxtinlines.h"
    1.44 +#include "jscompartmentinlines.h"
    1.45 +#include "jsinferinlines.h"
    1.46 +#include "jsobjinlines.h"
    1.47 +#include "jsscriptinlines.h"
    1.48 +
    1.49 +using namespace js;
    1.50 +using namespace js::gc;
    1.51 +
    1.52 +using js::frontend::IsIdentifier;
    1.53 +
    1.54 +/*
    1.55 + * Index limit must stay within 32 bits.
    1.56 + */
    1.57 +JS_STATIC_ASSERT(sizeof(uint32_t) * JS_BITS_PER_BYTE >= INDEX_LIMIT_LOG2 + 1);
    1.58 +
    1.59 +const JSCodeSpec js_CodeSpec[] = {
    1.60 +#define MAKE_CODESPEC(op,val,name,token,length,nuses,ndefs,format)  {length,nuses,ndefs,format},
    1.61 +    FOR_EACH_OPCODE(MAKE_CODESPEC)
    1.62 +#undef MAKE_CODESPEC
    1.63 +};
    1.64 +
    1.65 +const unsigned js_NumCodeSpecs = JS_ARRAY_LENGTH(js_CodeSpec);
    1.66 +
    1.67 +/*
    1.68 + * Each element of the array is either a source literal associated with JS
    1.69 + * bytecode or null.
    1.70 + */
    1.71 +static const char * const CodeToken[] = {
    1.72 +#define TOKEN(op, val, name, token, ...)  token,
    1.73 +    FOR_EACH_OPCODE(TOKEN)
    1.74 +#undef TOKEN
    1.75 +};
    1.76 +
    1.77 +/*
    1.78 + * Array of JS bytecode names used by PC count JSON, DEBUG-only js_Disassemble
    1.79 + * and JIT debug spew.
    1.80 + */
    1.81 +const char * const js_CodeName[] = {
    1.82 +#define OPNAME(op, val, name, ...)  name,
    1.83 +    FOR_EACH_OPCODE(OPNAME)
    1.84 +#undef OPNAME
    1.85 +};
    1.86 +
    1.87 +/************************************************************************/
    1.88 +
    1.89 +#define COUNTS_LEN 16
    1.90 +
    1.91 +size_t
    1.92 +js_GetVariableBytecodeLength(jsbytecode *pc)
    1.93 +{
    1.94 +    JSOp op = JSOp(*pc);
    1.95 +    JS_ASSERT(js_CodeSpec[op].length == -1);
    1.96 +    switch (op) {
    1.97 +      case JSOP_TABLESWITCH: {
    1.98 +        /* Structure: default-jump case-low case-high case1-jump ... */
    1.99 +        pc += JUMP_OFFSET_LEN;
   1.100 +        int32_t low = GET_JUMP_OFFSET(pc);
   1.101 +        pc += JUMP_OFFSET_LEN;
   1.102 +        int32_t high = GET_JUMP_OFFSET(pc);
   1.103 +        unsigned ncases = unsigned(high - low + 1);
   1.104 +        return 1 + 3 * JUMP_OFFSET_LEN + ncases * JUMP_OFFSET_LEN;
   1.105 +      }
   1.106 +      default:
   1.107 +        MOZ_ASSUME_UNREACHABLE("Unexpected op");
   1.108 +    }
   1.109 +}
   1.110 +
   1.111 +unsigned
   1.112 +js::StackUses(JSScript *script, jsbytecode *pc)
   1.113 +{
   1.114 +    JSOp op = (JSOp) *pc;
   1.115 +    const JSCodeSpec &cs = js_CodeSpec[op];
   1.116 +    if (cs.nuses >= 0)
   1.117 +        return cs.nuses;
   1.118 +
   1.119 +    JS_ASSERT(js_CodeSpec[op].nuses == -1);
   1.120 +    switch (op) {
   1.121 +      case JSOP_POPN:
   1.122 +        return GET_UINT16(pc);
   1.123 +      default:
   1.124 +        /* stack: fun, this, [argc arguments] */
   1.125 +        JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL ||
   1.126 +                  op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
   1.127 +        return 2 + GET_ARGC(pc);
   1.128 +    }
   1.129 +}
   1.130 +
   1.131 +unsigned
   1.132 +js::StackDefs(JSScript *script, jsbytecode *pc)
   1.133 +{
   1.134 +    JSOp op = (JSOp) *pc;
   1.135 +    const JSCodeSpec &cs = js_CodeSpec[op];
   1.136 +    JS_ASSERT (cs.ndefs >= 0);
   1.137 +    return cs.ndefs;
   1.138 +}
   1.139 +
   1.140 +static const char * const countBaseNames[] = {
   1.141 +    "interp"
   1.142 +};
   1.143 +
   1.144 +JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) == PCCounts::BASE_LIMIT);
   1.145 +
   1.146 +static const char * const countAccessNames[] = {
   1.147 +    "infer_mono",
   1.148 +    "infer_di",
   1.149 +    "infer_poly",
   1.150 +    "infer_barrier",
   1.151 +    "infer_nobarrier",
   1.152 +    "observe_undefined",
   1.153 +    "observe_null",
   1.154 +    "observe_boolean",
   1.155 +    "observe_int32",
   1.156 +    "observe_double",
   1.157 +    "observe_string",
   1.158 +    "observe_object"
   1.159 +};
   1.160 +
   1.161 +JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) +
   1.162 +                 JS_ARRAY_LENGTH(countAccessNames) == PCCounts::ACCESS_LIMIT);
   1.163 +
   1.164 +static const char * const countElementNames[] = {
   1.165 +    "id_int",
   1.166 +    "id_double",
   1.167 +    "id_other",
   1.168 +    "id_unknown",
   1.169 +    "elem_typed",
   1.170 +    "elem_packed",
   1.171 +    "elem_dense",
   1.172 +    "elem_other"
   1.173 +};
   1.174 +
   1.175 +JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) +
   1.176 +                 JS_ARRAY_LENGTH(countAccessNames) +
   1.177 +                 JS_ARRAY_LENGTH(countElementNames) == PCCounts::ELEM_LIMIT);
   1.178 +
   1.179 +static const char * const countPropertyNames[] = {
   1.180 +    "prop_static",
   1.181 +    "prop_definite",
   1.182 +    "prop_other"
   1.183 +};
   1.184 +
   1.185 +JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) +
   1.186 +                 JS_ARRAY_LENGTH(countAccessNames) +
   1.187 +                 JS_ARRAY_LENGTH(countPropertyNames) == PCCounts::PROP_LIMIT);
   1.188 +
   1.189 +static const char * const countArithNames[] = {
   1.190 +    "arith_int",
   1.191 +    "arith_double",
   1.192 +    "arith_other",
   1.193 +    "arith_unknown",
   1.194 +};
   1.195 +
   1.196 +JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) +
   1.197 +                 JS_ARRAY_LENGTH(countArithNames) == PCCounts::ARITH_LIMIT);
   1.198 +
   1.199 +/* static */ const char *
   1.200 +PCCounts::countName(JSOp op, size_t which)
   1.201 +{
   1.202 +    JS_ASSERT(which < numCounts(op));
   1.203 +
   1.204 +    if (which < BASE_LIMIT)
   1.205 +        return countBaseNames[which];
   1.206 +
   1.207 +    if (accessOp(op)) {
   1.208 +        if (which < ACCESS_LIMIT)
   1.209 +            return countAccessNames[which - BASE_LIMIT];
   1.210 +        if (elementOp(op))
   1.211 +            return countElementNames[which - ACCESS_LIMIT];
   1.212 +        if (propertyOp(op))
   1.213 +            return countPropertyNames[which - ACCESS_LIMIT];
   1.214 +        MOZ_ASSUME_UNREACHABLE("bad op");
   1.215 +    }
   1.216 +
   1.217 +    if (arithOp(op))
   1.218 +        return countArithNames[which - BASE_LIMIT];
   1.219 +
   1.220 +    MOZ_ASSUME_UNREACHABLE("bad op");
   1.221 +}
   1.222 +
   1.223 +#ifdef JS_ION
   1.224 +void
   1.225 +js::DumpIonScriptCounts(Sprinter *sp, jit::IonScriptCounts *ionCounts)
   1.226 +{
   1.227 +    Sprint(sp, "IonScript [%lu blocks]:\n", ionCounts->numBlocks());
   1.228 +    for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
   1.229 +        const jit::IonBlockCounts &block = ionCounts->block(i);
   1.230 +        if (block.hitCount() < 10)
   1.231 +            continue;
   1.232 +        Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset());
   1.233 +        for (size_t j = 0; j < block.numSuccessors(); j++)
   1.234 +            Sprint(sp, " -> #%lu", block.successor(j));
   1.235 +        Sprint(sp, " :: %llu hits\n", block.hitCount());
   1.236 +        Sprint(sp, "%s\n", block.code());
   1.237 +    }
   1.238 +}
   1.239 +#endif
   1.240 +
   1.241 +void
   1.242 +js_DumpPCCounts(JSContext *cx, HandleScript script, js::Sprinter *sp)
   1.243 +{
   1.244 +    JS_ASSERT(script->hasScriptCounts());
   1.245 +
   1.246 +#ifdef DEBUG
   1.247 +    jsbytecode *pc = script->code();
   1.248 +    while (pc < script->codeEnd()) {
   1.249 +        JSOp op = JSOp(*pc);
   1.250 +        jsbytecode *next = GetNextPc(pc);
   1.251 +
   1.252 +        if (!js_Disassemble1(cx, script, pc, script->pcToOffset(pc), true, sp))
   1.253 +            return;
   1.254 +
   1.255 +        size_t total = PCCounts::numCounts(op);
   1.256 +        double *raw = script->getPCCounts(pc).rawCounts();
   1.257 +
   1.258 +        Sprint(sp, "                  {");
   1.259 +        bool printed = false;
   1.260 +        for (size_t i = 0; i < total; i++) {
   1.261 +            double val = raw[i];
   1.262 +            if (val) {
   1.263 +                if (printed)
   1.264 +                    Sprint(sp, ", ");
   1.265 +                Sprint(sp, "\"%s\": %.0f", PCCounts::countName(op, i), val);
   1.266 +                printed = true;
   1.267 +            }
   1.268 +        }
   1.269 +        Sprint(sp, "}\n");
   1.270 +
   1.271 +        pc = next;
   1.272 +    }
   1.273 +#endif
   1.274 +
   1.275 +#ifdef JS_ION
   1.276 +    jit::IonScriptCounts *ionCounts = script->getIonCounts();
   1.277 +
   1.278 +    while (ionCounts) {
   1.279 +        DumpIonScriptCounts(sp, ionCounts);
   1.280 +        ionCounts = ionCounts->previous();
   1.281 +    }
   1.282 +#endif
   1.283 +}
   1.284 +
   1.285 +/////////////////////////////////////////////////////////////////////
   1.286 +// Bytecode Parser
   1.287 +/////////////////////////////////////////////////////////////////////
   1.288 +
   1.289 +namespace {
   1.290 +
   1.291 +class BytecodeParser
   1.292 +{
   1.293 +    class Bytecode
   1.294 +    {
   1.295 +      public:
   1.296 +        Bytecode() { mozilla::PodZero(this); }
   1.297 +
   1.298 +        // Whether this instruction has been analyzed to get its output defines
   1.299 +        // and stack.
   1.300 +        bool parsed : 1;
   1.301 +
   1.302 +        // Stack depth before this opcode.
   1.303 +        uint32_t stackDepth;
   1.304 +
   1.305 +        // Pointer to array of |stackDepth| offsets.  An element at position N
   1.306 +        // in the array is the offset of the opcode that defined the
   1.307 +        // corresponding stack slot.  The top of the stack is at position
   1.308 +        // |stackDepth - 1|.
   1.309 +        uint32_t *offsetStack;
   1.310 +
   1.311 +        bool captureOffsetStack(LifoAlloc &alloc, const uint32_t *stack, uint32_t depth) {
   1.312 +            stackDepth = depth;
   1.313 +            offsetStack = alloc.newArray<uint32_t>(stackDepth);
   1.314 +            if (stackDepth) {
   1.315 +                if (!offsetStack)
   1.316 +                    return false;
   1.317 +                for (uint32_t n = 0; n < stackDepth; n++)
   1.318 +                    offsetStack[n] = stack[n];
   1.319 +            }
   1.320 +            return true;
   1.321 +        }
   1.322 +
   1.323 +        // When control-flow merges, intersect the stacks, marking slots that
   1.324 +        // are defined by different offsets with the UINT32_MAX sentinel.
   1.325 +        // This is sufficient for forward control-flow.  It doesn't grok loops
   1.326 +        // -- for that you would have to iterate to a fixed point -- but there
   1.327 +        // shouldn't be operands on the stack at a loop back-edge anyway.
   1.328 +        void mergeOffsetStack(const uint32_t *stack, uint32_t depth) {
   1.329 +            JS_ASSERT(depth == stackDepth);
   1.330 +            for (uint32_t n = 0; n < stackDepth; n++)
   1.331 +                if (offsetStack[n] != stack[n])
   1.332 +                    offsetStack[n] = UINT32_MAX;
   1.333 +        }
   1.334 +    };
   1.335 +
   1.336 +    JSContext *cx_;
   1.337 +    LifoAllocScope allocScope_;
   1.338 +    RootedScript script_;
   1.339 +
   1.340 +    Bytecode **codeArray_;
   1.341 +
   1.342 +  public:
   1.343 +    BytecodeParser(JSContext *cx, JSScript *script)
   1.344 +      : cx_(cx),
   1.345 +        allocScope_(&cx->tempLifoAlloc()),
   1.346 +        script_(cx, script),
   1.347 +        codeArray_(nullptr) { }
   1.348 +
   1.349 +    bool parse();
   1.350 +
   1.351 +#ifdef DEBUG
   1.352 +    bool isReachable(uint32_t offset) { return maybeCode(offset); }
   1.353 +    bool isReachable(const jsbytecode *pc) { return maybeCode(pc); }
   1.354 +#endif
   1.355 +
   1.356 +    uint32_t stackDepthAtPC(uint32_t offset) {
   1.357 +        // Sometimes the code generator in debug mode asks about the stack depth
   1.358 +        // of unreachable code (bug 932180 comment 22).  Assume that unreachable
   1.359 +        // code has no operands on the stack.
   1.360 +        return getCode(offset).stackDepth;
   1.361 +    }
   1.362 +    uint32_t stackDepthAtPC(const jsbytecode *pc) { return stackDepthAtPC(script_->pcToOffset(pc)); }
   1.363 +
   1.364 +    uint32_t offsetForStackOperand(uint32_t offset, int operand) {
   1.365 +        Bytecode &code = getCode(offset);
   1.366 +        if (operand < 0) {
   1.367 +            operand += code.stackDepth;
   1.368 +            JS_ASSERT(operand >= 0);
   1.369 +        }
   1.370 +        JS_ASSERT(uint32_t(operand) < code.stackDepth);
   1.371 +        return code.offsetStack[operand];
   1.372 +    }
   1.373 +    jsbytecode *pcForStackOperand(jsbytecode *pc, int operand) {
   1.374 +        uint32_t offset = offsetForStackOperand(script_->pcToOffset(pc), operand);
   1.375 +        if (offset == UINT32_MAX)
   1.376 +            return nullptr;
   1.377 +        return script_->offsetToPC(offsetForStackOperand(script_->pcToOffset(pc), operand));
   1.378 +    }
   1.379 +
   1.380 +  private:
   1.381 +    LifoAlloc &alloc() {
   1.382 +        return allocScope_.alloc();
   1.383 +    }
   1.384 +
   1.385 +    void reportOOM() {
   1.386 +        allocScope_.releaseEarly();
   1.387 +        js_ReportOutOfMemory(cx_);
   1.388 +    }
   1.389 +
   1.390 +    uint32_t numSlots() {
   1.391 +        return 1 + script_->nfixed() +
   1.392 +               (script_->functionNonDelazifying() ? script_->functionNonDelazifying()->nargs() : 0);
   1.393 +    }
   1.394 +
   1.395 +    uint32_t maximumStackDepth() {
   1.396 +        return script_->nslots() - script_->nfixed();
   1.397 +    }
   1.398 +
   1.399 +    Bytecode& getCode(uint32_t offset) {
   1.400 +        JS_ASSERT(offset < script_->length());
   1.401 +        JS_ASSERT(codeArray_[offset]);
   1.402 +        return *codeArray_[offset];
   1.403 +    }
   1.404 +    Bytecode& getCode(const jsbytecode *pc) { return getCode(script_->pcToOffset(pc)); }
   1.405 +
   1.406 +    Bytecode* maybeCode(uint32_t offset) {
   1.407 +        JS_ASSERT(offset < script_->length());
   1.408 +        return codeArray_[offset];
   1.409 +    }
   1.410 +    Bytecode* maybeCode(const jsbytecode *pc) { return maybeCode(script_->pcToOffset(pc)); }
   1.411 +
   1.412 +    uint32_t simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint32_t stackDepth);
   1.413 +
   1.414 +    inline bool addJump(uint32_t offset, uint32_t *currentOffset,
   1.415 +                        uint32_t stackDepth, const uint32_t *offsetStack);
   1.416 +};
   1.417 +
   1.418 +}  // anonymous namespace
   1.419 +
   1.420 +uint32_t
   1.421 +BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint32_t stackDepth)
   1.422 +{
   1.423 +    uint32_t nuses = GetUseCount(script_, offset);
   1.424 +    uint32_t ndefs = GetDefCount(script_, offset);
   1.425 +
   1.426 +    JS_ASSERT(stackDepth >= nuses);
   1.427 +    stackDepth -= nuses;
   1.428 +    JS_ASSERT(stackDepth + ndefs <= maximumStackDepth());
   1.429 +
   1.430 +    // Mark the current offset as defining its values on the offset stack,
   1.431 +    // unless it just reshuffles the stack.  In that case we want to preserve
   1.432 +    // the opcode that generated the original value.
   1.433 +    switch (op) {
   1.434 +      default:
   1.435 +        for (uint32_t n = 0; n != ndefs; ++n)
   1.436 +            offsetStack[stackDepth + n] = offset;
   1.437 +        break;
   1.438 +
   1.439 +      case JSOP_CASE:
   1.440 +        /* Keep the switch value. */
   1.441 +        JS_ASSERT(ndefs == 1);
   1.442 +        break;
   1.443 +
   1.444 +      case JSOP_DUP:
   1.445 +        JS_ASSERT(ndefs == 2);
   1.446 +        if (offsetStack)
   1.447 +            offsetStack[stackDepth + 1] = offsetStack[stackDepth];
   1.448 +        break;
   1.449 +
   1.450 +      case JSOP_DUP2:
   1.451 +        JS_ASSERT(ndefs == 4);
   1.452 +        if (offsetStack) {
   1.453 +            offsetStack[stackDepth + 2] = offsetStack[stackDepth];
   1.454 +            offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1];
   1.455 +        }
   1.456 +        break;
   1.457 +
   1.458 +      case JSOP_DUPAT: {
   1.459 +        JS_ASSERT(ndefs == 1);
   1.460 +        jsbytecode *pc = script_->offsetToPC(offset);
   1.461 +        unsigned n = GET_UINT24(pc);
   1.462 +        JS_ASSERT(n < stackDepth);
   1.463 +        if (offsetStack)
   1.464 +            offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n];
   1.465 +        break;
   1.466 +      }
   1.467 +
   1.468 +      case JSOP_SWAP:
   1.469 +        JS_ASSERT(ndefs == 2);
   1.470 +        if (offsetStack) {
   1.471 +            uint32_t tmp = offsetStack[stackDepth + 1];
   1.472 +            offsetStack[stackDepth + 1] = offsetStack[stackDepth];
   1.473 +            offsetStack[stackDepth] = tmp;
   1.474 +        }
   1.475 +        break;
   1.476 +    }
   1.477 +    stackDepth += ndefs;
   1.478 +    return stackDepth;
   1.479 +}
   1.480 +
   1.481 +bool
   1.482 +BytecodeParser::addJump(uint32_t offset, uint32_t *currentOffset,
   1.483 +                        uint32_t stackDepth, const uint32_t *offsetStack)
   1.484 +{
   1.485 +    JS_ASSERT(offset < script_->length());
   1.486 +
   1.487 +    Bytecode *&code = codeArray_[offset];
   1.488 +    if (!code) {
   1.489 +        code = alloc().new_<Bytecode>();
   1.490 +        if (!code)
   1.491 +            return false;
   1.492 +        if (!code->captureOffsetStack(alloc(), offsetStack, stackDepth)) {
   1.493 +            reportOOM();
   1.494 +            return false;
   1.495 +        }
   1.496 +    } else {
   1.497 +        code->mergeOffsetStack(offsetStack, stackDepth);
   1.498 +    }
   1.499 +
   1.500 +    if (offset < *currentOffset && !code->parsed) {
   1.501 +        // Backedge in a while/for loop, whose body has not been parsed due
   1.502 +        // to a lack of fallthrough at the loop head. Roll back the offset
   1.503 +        // to analyze the body.
   1.504 +        *currentOffset = offset;
   1.505 +    }
   1.506 +
   1.507 +    return true;
   1.508 +}
   1.509 +
   1.510 +bool
   1.511 +BytecodeParser::parse()
   1.512 +{
   1.513 +    JS_ASSERT(!codeArray_);
   1.514 +
   1.515 +    uint32_t length = script_->length();
   1.516 +    codeArray_ = alloc().newArray<Bytecode*>(length);
   1.517 +
   1.518 +    if (!codeArray_) {
   1.519 +        reportOOM();
   1.520 +        return false;
   1.521 +    }
   1.522 +
   1.523 +    mozilla::PodZero(codeArray_, length);
   1.524 +
   1.525 +    // Fill in stack depth and definitions at initial bytecode.
   1.526 +    Bytecode *startcode = alloc().new_<Bytecode>();
   1.527 +    if (!startcode) {
   1.528 +        reportOOM();
   1.529 +        return false;
   1.530 +    }
   1.531 +
   1.532 +    // Fill in stack depth and definitions at initial bytecode.
   1.533 +    uint32_t *offsetStack = alloc().newArray<uint32_t>(maximumStackDepth());
   1.534 +    if (maximumStackDepth() && !offsetStack) {
   1.535 +        reportOOM();
   1.536 +        return false;
   1.537 +    }
   1.538 +
   1.539 +    startcode->stackDepth = 0;
   1.540 +    codeArray_[0] = startcode;
   1.541 +
   1.542 +    uint32_t offset, nextOffset = 0;
   1.543 +    while (nextOffset < length) {
   1.544 +        offset = nextOffset;
   1.545 +
   1.546 +        Bytecode *code = maybeCode(offset);
   1.547 +        jsbytecode *pc = script_->offsetToPC(offset);
   1.548 +
   1.549 +        JSOp op = (JSOp)*pc;
   1.550 +        JS_ASSERT(op < JSOP_LIMIT);
   1.551 +
   1.552 +        // Immediate successor of this bytecode.
   1.553 +        uint32_t successorOffset = offset + GetBytecodeLength(pc);
   1.554 +
   1.555 +        // Next bytecode to analyze.  This is either the successor, or is an
   1.556 +        // earlier bytecode if this bytecode has a loop backedge.
   1.557 +        nextOffset = successorOffset;
   1.558 +
   1.559 +        if (!code) {
   1.560 +            // Haven't found a path by which this bytecode is reachable.
   1.561 +            continue;
   1.562 +        }
   1.563 +
   1.564 +        if (code->parsed) {
   1.565 +            // No need to reparse.
   1.566 +            continue;
   1.567 +        }
   1.568 +
   1.569 +        code->parsed = true;
   1.570 +
   1.571 +        uint32_t stackDepth = simulateOp(op, offset, offsetStack, code->stackDepth);
   1.572 +
   1.573 +        switch (op) {
   1.574 +          case JSOP_TABLESWITCH: {
   1.575 +            uint32_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
   1.576 +            jsbytecode *pc2 = pc + JUMP_OFFSET_LEN;
   1.577 +            int32_t low = GET_JUMP_OFFSET(pc2);
   1.578 +            pc2 += JUMP_OFFSET_LEN;
   1.579 +            int32_t high = GET_JUMP_OFFSET(pc2);
   1.580 +            pc2 += JUMP_OFFSET_LEN;
   1.581 +
   1.582 +            if (!addJump(defaultOffset, &nextOffset, stackDepth, offsetStack))
   1.583 +                return false;
   1.584 +
   1.585 +            for (int32_t i = low; i <= high; i++) {
   1.586 +                uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc2);
   1.587 +                if (targetOffset != offset) {
   1.588 +                    if (!addJump(targetOffset, &nextOffset, stackDepth, offsetStack))
   1.589 +                        return false;
   1.590 +                }
   1.591 +                pc2 += JUMP_OFFSET_LEN;
   1.592 +            }
   1.593 +            break;
   1.594 +          }
   1.595 +
   1.596 +          case JSOP_TRY: {
   1.597 +            // Everything between a try and corresponding catch or finally is conditional.
   1.598 +            // Note that there is no problem with code which is skipped by a thrown
   1.599 +            // exception but is not caught by a later handler in the same function:
   1.600 +            // no more code will execute, and it does not matter what is defined.
   1.601 +            JSTryNote *tn = script_->trynotes()->vector;
   1.602 +            JSTryNote *tnlimit = tn + script_->trynotes()->length;
   1.603 +            for (; tn < tnlimit; tn++) {
   1.604 +                uint32_t startOffset = script_->mainOffset() + tn->start;
   1.605 +                if (startOffset == offset + 1) {
   1.606 +                    uint32_t catchOffset = startOffset + tn->length;
   1.607 +                    if (tn->kind != JSTRY_ITER && tn->kind != JSTRY_LOOP) {
   1.608 +                        if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack))
   1.609 +                            return false;
   1.610 +                    }
   1.611 +                }
   1.612 +            }
   1.613 +            break;
   1.614 +          }
   1.615 +
   1.616 +          default:
   1.617 +            break;
   1.618 +        }
   1.619 +
   1.620 +        // Check basic jump opcodes, which may or may not have a fallthrough.
   1.621 +        if (IsJumpOpcode(op)) {
   1.622 +            // Case instructions do not push the lvalue back when branching.
   1.623 +            uint32_t newStackDepth = stackDepth;
   1.624 +            if (op == JSOP_CASE)
   1.625 +                newStackDepth--;
   1.626 +
   1.627 +            uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc);
   1.628 +            if (!addJump(targetOffset, &nextOffset, newStackDepth, offsetStack))
   1.629 +                return false;
   1.630 +        }
   1.631 +
   1.632 +        // Handle any fallthrough from this opcode.
   1.633 +        if (BytecodeFallsThrough(op)) {
   1.634 +            JS_ASSERT(successorOffset < script_->length());
   1.635 +
   1.636 +            Bytecode *&nextcode = codeArray_[successorOffset];
   1.637 +
   1.638 +            if (!nextcode) {
   1.639 +                nextcode = alloc().new_<Bytecode>();
   1.640 +                if (!nextcode) {
   1.641 +                    reportOOM();
   1.642 +                    return false;
   1.643 +                }
   1.644 +                if (!nextcode->captureOffsetStack(alloc(), offsetStack, stackDepth)) {
   1.645 +                    reportOOM();
   1.646 +                    return false;
   1.647 +                }
   1.648 +            } else {
   1.649 +                nextcode->mergeOffsetStack(offsetStack, stackDepth);
   1.650 +            }
   1.651 +        }
   1.652 +    }
   1.653 +
   1.654 +    return true;
   1.655 +}
   1.656 +
   1.657 +#ifdef DEBUG
   1.658 +
   1.659 +bool
   1.660 +js::ReconstructStackDepth(JSContext *cx, JSScript *script, jsbytecode *pc, uint32_t *depth, bool *reachablePC)
   1.661 +{
   1.662 +    BytecodeParser parser(cx, script);
   1.663 +    if (!parser.parse())
   1.664 +        return false;
   1.665 +
   1.666 +    *reachablePC = parser.isReachable(pc);
   1.667 +
   1.668 +    if (*reachablePC)
   1.669 +        *depth = parser.stackDepthAtPC(pc);
   1.670 +
   1.671 +    return true;
   1.672 +}
   1.673 +
   1.674 +/*
   1.675 + * If pc != nullptr, include a prefix indicating whether the PC is at the
   1.676 + * current line. If showAll is true, include the source note type and the
   1.677 + * entry stack depth.
   1.678 + */
   1.679 +JS_FRIEND_API(bool)
   1.680 +js_DisassembleAtPC(JSContext *cx, JSScript *scriptArg, bool lines,
   1.681 +                   jsbytecode *pc, bool showAll, Sprinter *sp)
   1.682 +{
   1.683 +    RootedScript script(cx, scriptArg);
   1.684 +    BytecodeParser parser(cx, script);
   1.685 +
   1.686 +    jsbytecode *next, *end;
   1.687 +    unsigned len;
   1.688 +
   1.689 +    if (showAll && !parser.parse())
   1.690 +        return false;
   1.691 +
   1.692 +    if (showAll)
   1.693 +        Sprint(sp, "%s:%u\n", script->filename(), script->lineno());
   1.694 +
   1.695 +    if (pc != nullptr)
   1.696 +        sp->put("    ");
   1.697 +    if (showAll)
   1.698 +        sp->put("sn stack ");
   1.699 +    sp->put("loc   ");
   1.700 +    if (lines)
   1.701 +        sp->put("line");
   1.702 +    sp->put("  op\n");
   1.703 +
   1.704 +    if (pc != nullptr)
   1.705 +        sp->put("    ");
   1.706 +    if (showAll)
   1.707 +        sp->put("-- ----- ");
   1.708 +    sp->put("----- ");
   1.709 +    if (lines)
   1.710 +        sp->put("----");
   1.711 +    sp->put("  --\n");
   1.712 +
   1.713 +    next = script->code();
   1.714 +    end = script->codeEnd();
   1.715 +    while (next < end) {
   1.716 +        if (next == script->main())
   1.717 +            sp->put("main:\n");
   1.718 +        if (pc != nullptr) {
   1.719 +            if (pc == next)
   1.720 +                sp->put("--> ");
   1.721 +            else
   1.722 +                sp->put("    ");
   1.723 +        }
   1.724 +        if (showAll) {
   1.725 +            jssrcnote *sn = js_GetSrcNote(cx, script, next);
   1.726 +            if (sn) {
   1.727 +                JS_ASSERT(!SN_IS_TERMINATOR(sn));
   1.728 +                jssrcnote *next = SN_NEXT(sn);
   1.729 +                while (!SN_IS_TERMINATOR(next) && SN_DELTA(next) == 0) {
   1.730 +                    Sprint(sp, "%02u\n    ", SN_TYPE(sn));
   1.731 +                    sn = next;
   1.732 +                    next = SN_NEXT(sn);
   1.733 +                }
   1.734 +                Sprint(sp, "%02u ", SN_TYPE(sn));
   1.735 +            }
   1.736 +            else
   1.737 +                sp->put("   ");
   1.738 +            if (parser.isReachable(next))
   1.739 +                Sprint(sp, "%05u ", parser.stackDepthAtPC(next));
   1.740 +            else
   1.741 +                Sprint(sp, "      ");
   1.742 +        }
   1.743 +        len = js_Disassemble1(cx, script, next, script->pcToOffset(next), lines, sp);
   1.744 +        if (!len)
   1.745 +            return false;
   1.746 +        next += len;
   1.747 +    }
   1.748 +    return true;
   1.749 +}
   1.750 +
   1.751 +bool
   1.752 +js_Disassemble(JSContext *cx, HandleScript script, bool lines, Sprinter *sp)
   1.753 +{
   1.754 +    return js_DisassembleAtPC(cx, script, lines, nullptr, false, sp);
   1.755 +}
   1.756 +
   1.757 +JS_FRIEND_API(bool)
   1.758 +js_DumpPC(JSContext *cx)
   1.759 +{
   1.760 +    js::gc::AutoSuppressGC suppressGC(cx);
   1.761 +    Sprinter sprinter(cx);
   1.762 +    if (!sprinter.init())
   1.763 +        return false;
   1.764 +    ScriptFrameIter iter(cx);
   1.765 +    RootedScript script(cx, iter.script());
   1.766 +    bool ok = js_DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter);
   1.767 +    fprintf(stdout, "%s", sprinter.string());
   1.768 +    return ok;
   1.769 +}
   1.770 +
   1.771 +JS_FRIEND_API(bool)
   1.772 +js_DumpScript(JSContext *cx, JSScript *scriptArg)
   1.773 +{
   1.774 +    js::gc::AutoSuppressGC suppressGC(cx);
   1.775 +    Sprinter sprinter(cx);
   1.776 +    if (!sprinter.init())
   1.777 +        return false;
   1.778 +    RootedScript script(cx, scriptArg);
   1.779 +    bool ok = js_Disassemble(cx, script, true, &sprinter);
   1.780 +    fprintf(stdout, "%s", sprinter.string());
   1.781 +    return ok;
   1.782 +}
   1.783 +
   1.784 +/*
   1.785 + * Useful to debug ReconstructPCStack.
   1.786 + */
   1.787 +JS_FRIEND_API(bool)
   1.788 +js_DumpScriptDepth(JSContext *cx, JSScript *scriptArg, jsbytecode *pc)
   1.789 +{
   1.790 +    js::gc::AutoSuppressGC suppressGC(cx);
   1.791 +    Sprinter sprinter(cx);
   1.792 +    if (!sprinter.init())
   1.793 +        return false;
   1.794 +    RootedScript script(cx, scriptArg);
   1.795 +    bool ok = js_DisassembleAtPC(cx, script, true, pc, true, &sprinter);
   1.796 +    fprintf(stdout, "%s", sprinter.string());
   1.797 +    return ok;
   1.798 +}
   1.799 +
   1.800 +static char *
   1.801 +QuoteString(Sprinter *sp, JSString *str, uint32_t quote);
   1.802 +
   1.803 +static bool
   1.804 +ToDisassemblySource(JSContext *cx, HandleValue v, JSAutoByteString *bytes)
   1.805 +{
   1.806 +    if (JSVAL_IS_STRING(v)) {
   1.807 +        Sprinter sprinter(cx);
   1.808 +        if (!sprinter.init())
   1.809 +            return false;
   1.810 +        char *nbytes = QuoteString(&sprinter, JSVAL_TO_STRING(v), '"');
   1.811 +        if (!nbytes)
   1.812 +            return false;
   1.813 +        nbytes = JS_sprintf_append(nullptr, "%s", nbytes);
   1.814 +        if (!nbytes)
   1.815 +            return false;
   1.816 +        bytes->initBytes(nbytes);
   1.817 +        return true;
   1.818 +    }
   1.819 +
   1.820 +    if (cx->runtime()->isHeapBusy() || cx->runtime()->noGCOrAllocationCheck) {
   1.821 +        char *source = JS_sprintf_append(nullptr, "<value>");
   1.822 +        if (!source)
   1.823 +            return false;
   1.824 +        bytes->initBytes(source);
   1.825 +        return true;
   1.826 +    }
   1.827 +
   1.828 +    if (!JSVAL_IS_PRIMITIVE(v)) {
   1.829 +        JSObject *obj = JSVAL_TO_OBJECT(v);
   1.830 +        if (obj->is<StaticBlockObject>()) {
   1.831 +            Rooted<StaticBlockObject*> block(cx, &obj->as<StaticBlockObject>());
   1.832 +            char *source = JS_sprintf_append(nullptr, "depth %d {", block->localOffset());
   1.833 +            if (!source)
   1.834 +                return false;
   1.835 +
   1.836 +            Shape::Range<CanGC> r(cx, block->lastProperty());
   1.837 +
   1.838 +            while (!r.empty()) {
   1.839 +                Rooted<Shape*> shape(cx, &r.front());
   1.840 +                JSAtom *atom = JSID_IS_INT(shape->propid())
   1.841 +                               ? cx->names().empty
   1.842 +                               : JSID_TO_ATOM(shape->propid());
   1.843 +
   1.844 +                JSAutoByteString bytes;
   1.845 +                if (!AtomToPrintableString(cx, atom, &bytes))
   1.846 +                    return false;
   1.847 +
   1.848 +                r.popFront();
   1.849 +                source = JS_sprintf_append(source, "%s: %d%s",
   1.850 +                                           bytes.ptr(),
   1.851 +                                           block->shapeToIndex(*shape),
   1.852 +                                           !r.empty() ? ", " : "");
   1.853 +                if (!source)
   1.854 +                    return false;
   1.855 +            }
   1.856 +
   1.857 +            source = JS_sprintf_append(source, "}");
   1.858 +            if (!source)
   1.859 +                return false;
   1.860 +            bytes->initBytes(source);
   1.861 +            return true;
   1.862 +        }
   1.863 +
   1.864 +        if (obj->is<JSFunction>()) {
   1.865 +            RootedFunction fun(cx, &obj->as<JSFunction>());
   1.866 +            JSString *str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT);
   1.867 +            if (!str)
   1.868 +                return false;
   1.869 +            return bytes->encodeLatin1(cx, str);
   1.870 +        }
   1.871 +
   1.872 +        if (obj->is<RegExpObject>()) {
   1.873 +            JSString *source = obj->as<RegExpObject>().toString(cx);
   1.874 +            if (!source)
   1.875 +                return false;
   1.876 +            JS::Anchor<JSString *> anchor(source);
   1.877 +            return bytes->encodeLatin1(cx, source);
   1.878 +        }
   1.879 +    }
   1.880 +
   1.881 +    return !!js_ValueToPrintable(cx, v, bytes, true);
   1.882 +}
   1.883 +
   1.884 +unsigned
   1.885 +js_Disassemble1(JSContext *cx, HandleScript script, jsbytecode *pc,
   1.886 +                unsigned loc, bool lines, Sprinter *sp)
   1.887 +{
   1.888 +    JSOp op = (JSOp)*pc;
   1.889 +    if (op >= JSOP_LIMIT) {
   1.890 +        char numBuf1[12], numBuf2[12];
   1.891 +        JS_snprintf(numBuf1, sizeof numBuf1, "%d", op);
   1.892 +        JS_snprintf(numBuf2, sizeof numBuf2, "%d", JSOP_LIMIT);
   1.893 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
   1.894 +                             JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2);
   1.895 +        return 0;
   1.896 +    }
   1.897 +    const JSCodeSpec *cs = &js_CodeSpec[op];
   1.898 +    ptrdiff_t len = (ptrdiff_t) cs->length;
   1.899 +    Sprint(sp, "%05u:", loc);
   1.900 +    if (lines)
   1.901 +        Sprint(sp, "%4u", JS_PCToLineNumber(cx, script, pc));
   1.902 +    Sprint(sp, "  %s", js_CodeName[op]);
   1.903 +
   1.904 +    switch (JOF_TYPE(cs->format)) {
   1.905 +      case JOF_BYTE:
   1.906 +          // Scan the trynotes to find the associated catch block
   1.907 +          // and make the try opcode look like a jump instruction
   1.908 +          // with an offset. This simplifies code coverage analysis
   1.909 +          // based on this disassembled output.
   1.910 +          if (op == JSOP_TRY) {
   1.911 +              TryNoteArray *trynotes = script->trynotes();
   1.912 +              uint32_t i;
   1.913 +              for(i = 0; i < trynotes->length; i++) {
   1.914 +                  JSTryNote note = trynotes->vector[i];
   1.915 +                  if (note.kind == JSTRY_CATCH && note.start == loc + 1) {
   1.916 +                      Sprint(sp, " %u (%+d)",
   1.917 +                             (unsigned int) (loc+note.length+1),
   1.918 +                             (int) (note.length+1));
   1.919 +                      break;
   1.920 +                  }
   1.921 +              }
   1.922 +          }
   1.923 +        break;
   1.924 +
   1.925 +      case JOF_JUMP: {
   1.926 +        ptrdiff_t off = GET_JUMP_OFFSET(pc);
   1.927 +        Sprint(sp, " %u (%+d)", loc + (int) off, (int) off);
   1.928 +        break;
   1.929 +      }
   1.930 +
   1.931 +      case JOF_SCOPECOORD: {
   1.932 +        RootedValue v(cx,
   1.933 +            StringValue(ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc)));
   1.934 +        JSAutoByteString bytes;
   1.935 +        if (!ToDisassemblySource(cx, v, &bytes))
   1.936 +            return 0;
   1.937 +        ScopeCoordinate sc(pc);
   1.938 +        Sprint(sp, " %s (hops = %u, slot = %u)", bytes.ptr(), sc.hops(), sc.slot());
   1.939 +        break;
   1.940 +      }
   1.941 +
   1.942 +      case JOF_ATOM: {
   1.943 +        RootedValue v(cx, StringValue(script->getAtom(GET_UINT32_INDEX(pc))));
   1.944 +        JSAutoByteString bytes;
   1.945 +        if (!ToDisassemblySource(cx, v, &bytes))
   1.946 +            return 0;
   1.947 +        Sprint(sp, " %s", bytes.ptr());
   1.948 +        break;
   1.949 +      }
   1.950 +
   1.951 +      case JOF_DOUBLE: {
   1.952 +        RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc)));
   1.953 +        JSAutoByteString bytes;
   1.954 +        if (!ToDisassemblySource(cx, v, &bytes))
   1.955 +            return 0;
   1.956 +        Sprint(sp, " %s", bytes.ptr());
   1.957 +        break;
   1.958 +      }
   1.959 +
   1.960 +      case JOF_OBJECT: {
   1.961 +        /* Don't call obj.toSource if analysis/inference is active. */
   1.962 +        if (script->compartment()->activeAnalysis) {
   1.963 +            Sprint(sp, " object");
   1.964 +            break;
   1.965 +        }
   1.966 +
   1.967 +        JSObject *obj = script->getObject(GET_UINT32_INDEX(pc));
   1.968 +        {
   1.969 +            JSAutoByteString bytes;
   1.970 +            RootedValue v(cx, ObjectValue(*obj));
   1.971 +            if (!ToDisassemblySource(cx, v, &bytes))
   1.972 +                return 0;
   1.973 +            Sprint(sp, " %s", bytes.ptr());
   1.974 +        }
   1.975 +        break;
   1.976 +      }
   1.977 +
   1.978 +      case JOF_REGEXP: {
   1.979 +        JSObject *obj = script->getRegExp(GET_UINT32_INDEX(pc));
   1.980 +        JSAutoByteString bytes;
   1.981 +        RootedValue v(cx, ObjectValue(*obj));
   1.982 +        if (!ToDisassemblySource(cx, v, &bytes))
   1.983 +            return 0;
   1.984 +        Sprint(sp, " %s", bytes.ptr());
   1.985 +        break;
   1.986 +      }
   1.987 +
   1.988 +      case JOF_TABLESWITCH:
   1.989 +      {
   1.990 +        int32_t i, low, high;
   1.991 +
   1.992 +        ptrdiff_t off = GET_JUMP_OFFSET(pc);
   1.993 +        jsbytecode *pc2 = pc + JUMP_OFFSET_LEN;
   1.994 +        low = GET_JUMP_OFFSET(pc2);
   1.995 +        pc2 += JUMP_OFFSET_LEN;
   1.996 +        high = GET_JUMP_OFFSET(pc2);
   1.997 +        pc2 += JUMP_OFFSET_LEN;
   1.998 +        Sprint(sp, " defaultOffset %d low %d high %d", int(off), low, high);
   1.999 +        for (i = low; i <= high; i++) {
  1.1000 +            off = GET_JUMP_OFFSET(pc2);
  1.1001 +            Sprint(sp, "\n\t%d: %d", i, int(off));
  1.1002 +            pc2 += JUMP_OFFSET_LEN;
  1.1003 +        }
  1.1004 +        len = 1 + pc2 - pc;
  1.1005 +        break;
  1.1006 +      }
  1.1007 +
  1.1008 +      case JOF_QARG:
  1.1009 +        Sprint(sp, " %u", GET_ARGNO(pc));
  1.1010 +        break;
  1.1011 +
  1.1012 +      case JOF_LOCAL:
  1.1013 +        Sprint(sp, " %u", GET_LOCALNO(pc));
  1.1014 +        break;
  1.1015 +
  1.1016 +      {
  1.1017 +        int i;
  1.1018 +
  1.1019 +      case JOF_UINT16:
  1.1020 +        i = (int)GET_UINT16(pc);
  1.1021 +        goto print_int;
  1.1022 +
  1.1023 +      case JOF_UINT24:
  1.1024 +        JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY ||
  1.1025 +                  op == JSOP_DUPAT);
  1.1026 +        i = (int)GET_UINT24(pc);
  1.1027 +        goto print_int;
  1.1028 +
  1.1029 +      case JOF_UINT8:
  1.1030 +        i = GET_UINT8(pc);
  1.1031 +        goto print_int;
  1.1032 +
  1.1033 +      case JOF_INT8:
  1.1034 +        i = GET_INT8(pc);
  1.1035 +        goto print_int;
  1.1036 +
  1.1037 +      case JOF_INT32:
  1.1038 +        JS_ASSERT(op == JSOP_INT32);
  1.1039 +        i = GET_INT32(pc);
  1.1040 +      print_int:
  1.1041 +        Sprint(sp, " %d", i);
  1.1042 +        break;
  1.1043 +      }
  1.1044 +
  1.1045 +      default: {
  1.1046 +        char numBuf[12];
  1.1047 +        JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) cs->format);
  1.1048 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
  1.1049 +                             JSMSG_UNKNOWN_FORMAT, numBuf);
  1.1050 +        return 0;
  1.1051 +      }
  1.1052 +    }
  1.1053 +    sp->put("\n");
  1.1054 +    return len;
  1.1055 +}
  1.1056 +
  1.1057 +#endif /* DEBUG */
  1.1058 +
  1.1059 +/************************************************************************/
  1.1060 +
  1.1061 +const size_t Sprinter::DefaultSize = 64;
  1.1062 +
  1.1063 +bool
  1.1064 +Sprinter::realloc_(size_t newSize)
  1.1065 +{
  1.1066 +    JS_ASSERT(newSize > (size_t) offset);
  1.1067 +    char *newBuf = (char *) js_realloc(base, newSize);
  1.1068 +    if (!newBuf) {
  1.1069 +        reportOutOfMemory();
  1.1070 +        return false;
  1.1071 +    }
  1.1072 +    base = newBuf;
  1.1073 +    size = newSize;
  1.1074 +    base[size - 1] = 0;
  1.1075 +    return true;
  1.1076 +}
  1.1077 +
  1.1078 +Sprinter::Sprinter(ExclusiveContext *cx)
  1.1079 +  : context(cx),
  1.1080 +#ifdef DEBUG
  1.1081 +    initialized(false),
  1.1082 +#endif
  1.1083 +    base(nullptr), size(0), offset(0), reportedOOM(false)
  1.1084 +{ }
  1.1085 +
  1.1086 +Sprinter::~Sprinter()
  1.1087 +{
  1.1088 +#ifdef DEBUG
  1.1089 +    if (initialized)
  1.1090 +        checkInvariants();
  1.1091 +#endif
  1.1092 +    js_free(base);
  1.1093 +}
  1.1094 +
  1.1095 +bool
  1.1096 +Sprinter::init()
  1.1097 +{
  1.1098 +    JS_ASSERT(!initialized);
  1.1099 +    base = (char *) js_malloc(DefaultSize);
  1.1100 +    if (!base) {
  1.1101 +        reportOutOfMemory();
  1.1102 +        return false;
  1.1103 +    }
  1.1104 +#ifdef DEBUG
  1.1105 +    initialized = true;
  1.1106 +#endif
  1.1107 +    *base = 0;
  1.1108 +    size = DefaultSize;
  1.1109 +    base[size - 1] = 0;
  1.1110 +    return true;
  1.1111 +}
  1.1112 +
  1.1113 +void
  1.1114 +Sprinter::checkInvariants() const
  1.1115 +{
  1.1116 +    JS_ASSERT(initialized);
  1.1117 +    JS_ASSERT((size_t) offset < size);
  1.1118 +    JS_ASSERT(base[size - 1] == 0);
  1.1119 +}
  1.1120 +
  1.1121 +const char *
  1.1122 +Sprinter::string() const
  1.1123 +{
  1.1124 +    return base;
  1.1125 +}
  1.1126 +
  1.1127 +const char *
  1.1128 +Sprinter::stringEnd() const
  1.1129 +{
  1.1130 +    return base + offset;
  1.1131 +}
  1.1132 +
  1.1133 +char *
  1.1134 +Sprinter::stringAt(ptrdiff_t off) const
  1.1135 +{
  1.1136 +    JS_ASSERT(off >= 0 && (size_t) off < size);
  1.1137 +    return base + off;
  1.1138 +}
  1.1139 +
  1.1140 +char &
  1.1141 +Sprinter::operator[](size_t off)
  1.1142 +{
  1.1143 +    JS_ASSERT(off < size);
  1.1144 +    return *(base + off);
  1.1145 +}
  1.1146 +
  1.1147 +char *
  1.1148 +Sprinter::reserve(size_t len)
  1.1149 +{
  1.1150 +    InvariantChecker ic(this);
  1.1151 +
  1.1152 +    while (len + 1 > size - offset) { /* Include trailing \0 */
  1.1153 +        if (!realloc_(size * 2))
  1.1154 +            return nullptr;
  1.1155 +    }
  1.1156 +
  1.1157 +    char *sb = base + offset;
  1.1158 +    offset += len;
  1.1159 +    return sb;
  1.1160 +}
  1.1161 +
  1.1162 +ptrdiff_t
  1.1163 +Sprinter::put(const char *s, size_t len)
  1.1164 +{
  1.1165 +    InvariantChecker ic(this);
  1.1166 +
  1.1167 +    const char *oldBase = base;
  1.1168 +    const char *oldEnd = base + size;
  1.1169 +
  1.1170 +    ptrdiff_t oldOffset = offset;
  1.1171 +    char *bp = reserve(len);
  1.1172 +    if (!bp)
  1.1173 +        return -1;
  1.1174 +
  1.1175 +    /* s is within the buffer already */
  1.1176 +    if (s >= oldBase && s < oldEnd) {
  1.1177 +        /* buffer was realloc'ed */
  1.1178 +        if (base != oldBase)
  1.1179 +            s = stringAt(s - oldBase);  /* this is where it lives now */
  1.1180 +        memmove(bp, s, len);
  1.1181 +    } else {
  1.1182 +        js_memcpy(bp, s, len);
  1.1183 +    }
  1.1184 +
  1.1185 +    bp[len] = 0;
  1.1186 +    return oldOffset;
  1.1187 +}
  1.1188 +
  1.1189 +ptrdiff_t
  1.1190 +Sprinter::put(const char *s)
  1.1191 +{
  1.1192 +    return put(s, strlen(s));
  1.1193 +}
  1.1194 +
  1.1195 +ptrdiff_t
  1.1196 +Sprinter::putString(JSString *s)
  1.1197 +{
  1.1198 +    InvariantChecker ic(this);
  1.1199 +
  1.1200 +    size_t length = s->length();
  1.1201 +    const jschar *chars = s->getChars(context);
  1.1202 +    if (!chars)
  1.1203 +        return -1;
  1.1204 +
  1.1205 +    size_t size = length;
  1.1206 +    if (size == (size_t) -1)
  1.1207 +        return -1;
  1.1208 +
  1.1209 +    ptrdiff_t oldOffset = offset;
  1.1210 +    char *buffer = reserve(size);
  1.1211 +    if (!buffer)
  1.1212 +        return -1;
  1.1213 +    DeflateStringToBuffer(nullptr, chars, length, buffer, &size);
  1.1214 +    buffer[size] = 0;
  1.1215 +
  1.1216 +    return oldOffset;
  1.1217 +}
  1.1218 +
  1.1219 +int
  1.1220 +Sprinter::printf(const char *fmt, ...)
  1.1221 +{
  1.1222 +    InvariantChecker ic(this);
  1.1223 +
  1.1224 +    do {
  1.1225 +        va_list va;
  1.1226 +        va_start(va, fmt);
  1.1227 +        int i = vsnprintf(base + offset, size - offset, fmt, va);
  1.1228 +        va_end(va);
  1.1229 +
  1.1230 +        if (i > -1 && (size_t) i < size - offset) {
  1.1231 +            offset += i;
  1.1232 +            return i;
  1.1233 +        }
  1.1234 +    } while (realloc_(size * 2));
  1.1235 +
  1.1236 +    return -1;
  1.1237 +}
  1.1238 +
  1.1239 +ptrdiff_t
  1.1240 +Sprinter::getOffset() const
  1.1241 +{
  1.1242 +    return offset;
  1.1243 +}
  1.1244 +
  1.1245 +void
  1.1246 +Sprinter::reportOutOfMemory()
  1.1247 +{
  1.1248 +    if (reportedOOM)
  1.1249 +        return;
  1.1250 +    if (context)
  1.1251 +        js_ReportOutOfMemory(context);
  1.1252 +    reportedOOM = true;
  1.1253 +}
  1.1254 +
  1.1255 +bool
  1.1256 +Sprinter::hadOutOfMemory() const
  1.1257 +{
  1.1258 +    return reportedOOM;
  1.1259 +}
  1.1260 +
  1.1261 +ptrdiff_t
  1.1262 +js::Sprint(Sprinter *sp, const char *format, ...)
  1.1263 +{
  1.1264 +    va_list ap;
  1.1265 +    char *bp;
  1.1266 +    ptrdiff_t offset;
  1.1267 +
  1.1268 +    va_start(ap, format);
  1.1269 +    bp = JS_vsmprintf(format, ap);      /* XXX vsaprintf */
  1.1270 +    va_end(ap);
  1.1271 +    if (!bp) {
  1.1272 +        sp->reportOutOfMemory();
  1.1273 +        return -1;
  1.1274 +    }
  1.1275 +    offset = sp->put(bp);
  1.1276 +    js_free(bp);
  1.1277 +    return offset;
  1.1278 +}
  1.1279 +
  1.1280 +const char js_EscapeMap[] = {
  1.1281 +    '\b', 'b',
  1.1282 +    '\f', 'f',
  1.1283 +    '\n', 'n',
  1.1284 +    '\r', 'r',
  1.1285 +    '\t', 't',
  1.1286 +    '\v', 'v',
  1.1287 +    '"',  '"',
  1.1288 +    '\'', '\'',
  1.1289 +    '\\', '\\',
  1.1290 +    '\0'
  1.1291 +};
  1.1292 +
  1.1293 +#define DONT_ESCAPE     0x10000
  1.1294 +
  1.1295 +static char *
  1.1296 +QuoteString(Sprinter *sp, JSString *str, uint32_t quote)
  1.1297 +{
  1.1298 +    /* Sample off first for later return value pointer computation. */
  1.1299 +    bool dontEscape = (quote & DONT_ESCAPE) != 0;
  1.1300 +    jschar qc = (jschar) quote;
  1.1301 +    ptrdiff_t offset = sp->getOffset();
  1.1302 +    if (qc && Sprint(sp, "%c", (char)qc) < 0)
  1.1303 +        return nullptr;
  1.1304 +
  1.1305 +    const jschar *s = str->getChars(sp->context);
  1.1306 +    if (!s)
  1.1307 +        return nullptr;
  1.1308 +    const jschar *z = s + str->length();
  1.1309 +
  1.1310 +    /* Loop control variables: z points at end of string sentinel. */
  1.1311 +    for (const jschar *t = s; t < z; s = ++t) {
  1.1312 +        /* Move t forward from s past un-quote-worthy characters. */
  1.1313 +        jschar c = *t;
  1.1314 +        while (c < 127 && isprint(c) && c != qc && c != '\\' && c != '\t') {
  1.1315 +            c = *++t;
  1.1316 +            if (t == z)
  1.1317 +                break;
  1.1318 +        }
  1.1319 +
  1.1320 +        {
  1.1321 +            ptrdiff_t len = t - s;
  1.1322 +            ptrdiff_t base = sp->getOffset();
  1.1323 +            char *bp = sp->reserve(len);
  1.1324 +            if (!bp)
  1.1325 +                return nullptr;
  1.1326 +
  1.1327 +            for (ptrdiff_t i = 0; i < len; ++i)
  1.1328 +                (*sp)[base + i] = (char) *s++;
  1.1329 +            (*sp)[base + len] = 0;
  1.1330 +        }
  1.1331 +
  1.1332 +        if (t == z)
  1.1333 +            break;
  1.1334 +
  1.1335 +        /* Use js_EscapeMap, \u, or \x only if necessary. */
  1.1336 +        bool ok;
  1.1337 +        const char *e;
  1.1338 +        if (!(c >> 8) && c != 0 && (e = strchr(js_EscapeMap, (int)c)) != nullptr) {
  1.1339 +            ok = dontEscape
  1.1340 +                 ? Sprint(sp, "%c", (char)c) >= 0
  1.1341 +                 : Sprint(sp, "\\%c", e[1]) >= 0;
  1.1342 +        } else {
  1.1343 +            /*
  1.1344 +             * Use \x only if the high byte is 0 and we're in a quoted string,
  1.1345 +             * because ECMA-262 allows only \u, not \x, in Unicode identifiers
  1.1346 +             * (see bug 621814).
  1.1347 +             */
  1.1348 +            ok = Sprint(sp, (qc && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c) >= 0;
  1.1349 +        }
  1.1350 +        if (!ok)
  1.1351 +            return nullptr;
  1.1352 +    }
  1.1353 +
  1.1354 +    /* Sprint the closing quote and return the quoted string. */
  1.1355 +    if (qc && Sprint(sp, "%c", (char)qc) < 0)
  1.1356 +        return nullptr;
  1.1357 +
  1.1358 +    /*
  1.1359 +     * If we haven't Sprint'd anything yet, Sprint an empty string so that
  1.1360 +     * the return below gives a valid result.
  1.1361 +     */
  1.1362 +    if (offset == sp->getOffset() && Sprint(sp, "") < 0)
  1.1363 +        return nullptr;
  1.1364 +
  1.1365 +    return sp->stringAt(offset);
  1.1366 +}
  1.1367 +
  1.1368 +JSString *
  1.1369 +js_QuoteString(ExclusiveContext *cx, JSString *str, jschar quote)
  1.1370 +{
  1.1371 +    Sprinter sprinter(cx);
  1.1372 +    if (!sprinter.init())
  1.1373 +        return nullptr;
  1.1374 +    char *bytes = QuoteString(&sprinter, str, quote);
  1.1375 +    if (!bytes)
  1.1376 +        return nullptr;
  1.1377 +    return js_NewStringCopyZ<CanGC>(cx, bytes);
  1.1378 +}
  1.1379 +
  1.1380 +/************************************************************************/
  1.1381 +
  1.1382 +namespace {
  1.1383 +/*
  1.1384 + * The expression decompiler is invoked by error handling code to produce a
  1.1385 + * string representation of the erroring expression. As it's only a debugging
  1.1386 + * tool, it only supports basic expressions. For anything complicated, it simply
  1.1387 + * puts "(intermediate value)" into the error result.
  1.1388 + *
  1.1389 + * Here's the basic algorithm:
  1.1390 + *
  1.1391 + * 1. Find the stack location of the value whose expression we wish to
  1.1392 + * decompile. The error handler can explicitly pass this as an
  1.1393 + * argument. Otherwise, we search backwards down the stack for the offending
  1.1394 + * value.
  1.1395 + *
  1.1396 + * 2. Instantiate and run a BytecodeParser for the current frame. This creates a
  1.1397 + * stack of pcs parallel to the interpreter stack; given an interpreter stack
  1.1398 + * location, the corresponding pc stack location contains the opcode that pushed
  1.1399 + * the value in the interpreter. Now, with the result of step 1, we have the
  1.1400 + * opcode responsible for pushing the value we want to decompile.
  1.1401 + *
  1.1402 + * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler
  1.1403 + * routine, responsible for a string representation of the expression that
  1.1404 + * generated a certain stack location. decompilePC looks at one opcode and
  1.1405 + * returns the JS source equivalent of that opcode.
  1.1406 + *
  1.1407 + * 4. Expressions can, of course, contain subexpressions. For example, the
  1.1408 + * literals "4" and "5" are subexpressions of the addition operator in "4 +
  1.1409 + * 5". If we need to decompile a subexpression, we call decompilePC (step 2)
  1.1410 + * recursively on the operands' pcs. The result is a depth-first traversal of
  1.1411 + * the expression tree.
  1.1412 + *
  1.1413 + */
  1.1414 +struct ExpressionDecompiler
  1.1415 +{
  1.1416 +    JSContext *cx;
  1.1417 +    InterpreterFrame *fp;
  1.1418 +    RootedScript script;
  1.1419 +    RootedFunction fun;
  1.1420 +    BindingVector *localNames;
  1.1421 +    BytecodeParser parser;
  1.1422 +    Sprinter sprinter;
  1.1423 +
  1.1424 +    ExpressionDecompiler(JSContext *cx, JSScript *script, JSFunction *fun)
  1.1425 +        : cx(cx),
  1.1426 +          script(cx, script),
  1.1427 +          fun(cx, fun),
  1.1428 +          localNames(nullptr),
  1.1429 +          parser(cx, script),
  1.1430 +          sprinter(cx)
  1.1431 +    {}
  1.1432 +    ~ExpressionDecompiler();
  1.1433 +    bool init();
  1.1434 +    bool decompilePCForStackOperand(jsbytecode *pc, int i);
  1.1435 +    bool decompilePC(jsbytecode *pc);
  1.1436 +    JSAtom *getLocal(uint32_t local, jsbytecode *pc);
  1.1437 +    JSAtom *getArg(unsigned slot);
  1.1438 +    JSAtom *loadAtom(jsbytecode *pc);
  1.1439 +    bool quote(JSString *s, uint32_t quote);
  1.1440 +    bool write(const char *s);
  1.1441 +    bool write(JSString *str);
  1.1442 +    bool getOutput(char **out);
  1.1443 +};
  1.1444 +
  1.1445 +bool
  1.1446 +ExpressionDecompiler::decompilePCForStackOperand(jsbytecode *pc, int i)
  1.1447 +{
  1.1448 +    pc = parser.pcForStackOperand(pc, i);
  1.1449 +    if (!pc)
  1.1450 +        return write("(intermediate value)");
  1.1451 +    return decompilePC(pc);
  1.1452 +}
  1.1453 +
  1.1454 +bool
  1.1455 +ExpressionDecompiler::decompilePC(jsbytecode *pc)
  1.1456 +{
  1.1457 +    JS_ASSERT(script->containsPC(pc));
  1.1458 +
  1.1459 +    JSOp op = (JSOp)*pc;
  1.1460 +
  1.1461 +    if (const char *token = CodeToken[op]) {
  1.1462 +        // Handle simple cases of binary and unary operators.
  1.1463 +        switch (js_CodeSpec[op].nuses) {
  1.1464 +          case 2: {
  1.1465 +            jssrcnote *sn = js_GetSrcNote(cx, script, pc);
  1.1466 +            if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP)
  1.1467 +                return write("(") &&
  1.1468 +                       decompilePCForStackOperand(pc, -2) &&
  1.1469 +                       write(" ") &&
  1.1470 +                       write(token) &&
  1.1471 +                       write(" ") &&
  1.1472 +                       decompilePCForStackOperand(pc, -1) &&
  1.1473 +                       write(")");
  1.1474 +            break;
  1.1475 +          }
  1.1476 +          case 1:
  1.1477 +            return write(token) &&
  1.1478 +                   write("(") &&
  1.1479 +                   decompilePCForStackOperand(pc, -1) &&
  1.1480 +                   write(")");
  1.1481 +          default:
  1.1482 +            break;
  1.1483 +        }
  1.1484 +    }
  1.1485 +
  1.1486 +    switch (op) {
  1.1487 +      case JSOP_GETGNAME:
  1.1488 +      case JSOP_NAME:
  1.1489 +      case JSOP_GETINTRINSIC:
  1.1490 +        return write(loadAtom(pc));
  1.1491 +      case JSOP_GETARG: {
  1.1492 +        unsigned slot = GET_ARGNO(pc);
  1.1493 +        JSAtom *atom = getArg(slot);
  1.1494 +        return write(atom);
  1.1495 +      }
  1.1496 +      case JSOP_GETLOCAL: {
  1.1497 +        uint32_t i = GET_LOCALNO(pc);
  1.1498 +        if (JSAtom *atom = getLocal(i, pc))
  1.1499 +            return write(atom);
  1.1500 +        return write("(intermediate value)");
  1.1501 +      }
  1.1502 +      case JSOP_GETALIASEDVAR: {
  1.1503 +        JSAtom *atom = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc);
  1.1504 +        JS_ASSERT(atom);
  1.1505 +        return write(atom);
  1.1506 +      }
  1.1507 +      case JSOP_LENGTH:
  1.1508 +      case JSOP_GETPROP:
  1.1509 +      case JSOP_CALLPROP: {
  1.1510 +        RootedAtom prop(cx, (op == JSOP_LENGTH) ? cx->names().length : loadAtom(pc));
  1.1511 +        if (!decompilePCForStackOperand(pc, -1))
  1.1512 +            return false;
  1.1513 +        if (IsIdentifier(prop)) {
  1.1514 +            return write(".") &&
  1.1515 +                   quote(prop, '\0');
  1.1516 +        }
  1.1517 +        return write("[") &&
  1.1518 +               quote(prop, '\'') &&
  1.1519 +               write("]");
  1.1520 +      }
  1.1521 +      case JSOP_GETELEM:
  1.1522 +      case JSOP_CALLELEM:
  1.1523 +        return decompilePCForStackOperand(pc, -2) &&
  1.1524 +               write("[") &&
  1.1525 +               decompilePCForStackOperand(pc, -1) &&
  1.1526 +               write("]");
  1.1527 +      case JSOP_NULL:
  1.1528 +        return write(js_null_str);
  1.1529 +      case JSOP_TRUE:
  1.1530 +        return write(js_true_str);
  1.1531 +      case JSOP_FALSE:
  1.1532 +        return write(js_false_str);
  1.1533 +      case JSOP_ZERO:
  1.1534 +      case JSOP_ONE:
  1.1535 +      case JSOP_INT8:
  1.1536 +      case JSOP_UINT16:
  1.1537 +      case JSOP_UINT24:
  1.1538 +      case JSOP_INT32:
  1.1539 +        return sprinter.printf("%d", GetBytecodeInteger(pc)) >= 0;
  1.1540 +      case JSOP_STRING:
  1.1541 +        return quote(loadAtom(pc), '"');
  1.1542 +      case JSOP_UNDEFINED:
  1.1543 +        return write(js_undefined_str);
  1.1544 +      case JSOP_THIS:
  1.1545 +        // |this| could convert to a very long object initialiser, so cite it by
  1.1546 +        // its keyword name.
  1.1547 +        return write(js_this_str);
  1.1548 +      case JSOP_CALL:
  1.1549 +      case JSOP_FUNCALL:
  1.1550 +        return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) &&
  1.1551 +               write("(...)");
  1.1552 +      case JSOP_SPREADCALL:
  1.1553 +        return decompilePCForStackOperand(pc, -int32_t(3)) &&
  1.1554 +               write("(...)");
  1.1555 +      case JSOP_NEWARRAY:
  1.1556 +        return write("[]");
  1.1557 +      case JSOP_REGEXP:
  1.1558 +      case JSOP_OBJECT: {
  1.1559 +        JSObject *obj = (op == JSOP_REGEXP)
  1.1560 +                        ? script->getRegExp(GET_UINT32_INDEX(pc))
  1.1561 +                        : script->getObject(GET_UINT32_INDEX(pc));
  1.1562 +        RootedValue objv(cx, ObjectValue(*obj));
  1.1563 +        JSString *str = ValueToSource(cx, objv);
  1.1564 +        if (!str)
  1.1565 +            return false;
  1.1566 +        return write(str);
  1.1567 +      }
  1.1568 +      default:
  1.1569 +        break;
  1.1570 +    }
  1.1571 +    return write("(intermediate value)");
  1.1572 +}
  1.1573 +
  1.1574 +ExpressionDecompiler::~ExpressionDecompiler()
  1.1575 +{
  1.1576 +    js_delete<BindingVector>(localNames);
  1.1577 +}
  1.1578 +
  1.1579 +bool
  1.1580 +ExpressionDecompiler::init()
  1.1581 +{
  1.1582 +    assertSameCompartment(cx, script);
  1.1583 +
  1.1584 +    if (!sprinter.init())
  1.1585 +        return false;
  1.1586 +
  1.1587 +    localNames = cx->new_<BindingVector>(cx);
  1.1588 +    if (!localNames)
  1.1589 +        return false;
  1.1590 +    RootedScript script_(cx, script);
  1.1591 +    if (!FillBindingVector(script_, localNames))
  1.1592 +        return false;
  1.1593 +
  1.1594 +    if (!parser.parse())
  1.1595 +        return false;
  1.1596 +
  1.1597 +    return true;
  1.1598 +}
  1.1599 +
  1.1600 +bool
  1.1601 +ExpressionDecompiler::write(const char *s)
  1.1602 +{
  1.1603 +    return sprinter.put(s) >= 0;
  1.1604 +}
  1.1605 +
  1.1606 +bool
  1.1607 +ExpressionDecompiler::write(JSString *str)
  1.1608 +{
  1.1609 +    return sprinter.putString(str) >= 0;
  1.1610 +}
  1.1611 +
  1.1612 +bool
  1.1613 +ExpressionDecompiler::quote(JSString *s, uint32_t quote)
  1.1614 +{
  1.1615 +    return QuoteString(&sprinter, s, quote) >= 0;
  1.1616 +}
  1.1617 +
  1.1618 +JSAtom *
  1.1619 +ExpressionDecompiler::loadAtom(jsbytecode *pc)
  1.1620 +{
  1.1621 +    return script->getAtom(GET_UINT32_INDEX(pc));
  1.1622 +}
  1.1623 +
  1.1624 +JSAtom *
  1.1625 +ExpressionDecompiler::getArg(unsigned slot)
  1.1626 +{
  1.1627 +    JS_ASSERT(fun);
  1.1628 +    JS_ASSERT(slot < script->bindings.count());
  1.1629 +    return (*localNames)[slot].name();
  1.1630 +}
  1.1631 +
  1.1632 +JSAtom *
  1.1633 +ExpressionDecompiler::getLocal(uint32_t local, jsbytecode *pc)
  1.1634 +{
  1.1635 +    JS_ASSERT(local < script->nfixed());
  1.1636 +    if (local < script->nfixedvars()) {
  1.1637 +        JS_ASSERT(fun);
  1.1638 +        uint32_t slot = local + fun->nargs();
  1.1639 +        JS_ASSERT(slot < script->bindings.count());
  1.1640 +        return (*localNames)[slot].name();
  1.1641 +    }
  1.1642 +    for (NestedScopeObject *chain = script->getStaticScope(pc);
  1.1643 +         chain;
  1.1644 +         chain = chain->enclosingNestedScope()) {
  1.1645 +        if (!chain->is<StaticBlockObject>())
  1.1646 +            continue;
  1.1647 +        StaticBlockObject &block = chain->as<StaticBlockObject>();
  1.1648 +        if (local < block.localOffset())
  1.1649 +            continue;
  1.1650 +        local -= block.localOffset();
  1.1651 +        if (local >= block.numVariables())
  1.1652 +            return nullptr;
  1.1653 +        for (Shape::Range<NoGC> r(block.lastProperty()); !r.empty(); r.popFront()) {
  1.1654 +            const Shape &shape = r.front();
  1.1655 +            if (block.shapeToIndex(shape) == local)
  1.1656 +                return JSID_TO_ATOM(shape.propid());
  1.1657 +        }
  1.1658 +        break;
  1.1659 +    }
  1.1660 +    return nullptr;
  1.1661 +}
  1.1662 +
  1.1663 +bool
  1.1664 +ExpressionDecompiler::getOutput(char **res)
  1.1665 +{
  1.1666 +    ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0);
  1.1667 +    *res = cx->pod_malloc<char>(len + 1);
  1.1668 +    if (!*res)
  1.1669 +        return false;
  1.1670 +    js_memcpy(*res, sprinter.stringAt(0), len);
  1.1671 +    (*res)[len] = 0;
  1.1672 +    return true;
  1.1673 +}
  1.1674 +
  1.1675 +}  // anonymous namespace
  1.1676 +
  1.1677 +static bool
  1.1678 +FindStartPC(JSContext *cx, const FrameIter &iter, int spindex, int skipStackHits, Value v,
  1.1679 +            jsbytecode **valuepc)
  1.1680 +{
  1.1681 +    jsbytecode *current = *valuepc;
  1.1682 +
  1.1683 +    if (spindex == JSDVG_IGNORE_STACK)
  1.1684 +        return true;
  1.1685 +
  1.1686 +    /*
  1.1687 +     * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the
  1.1688 +     * previous pc (see bug 831120).
  1.1689 +     */
  1.1690 +    if (iter.isIon())
  1.1691 +        return true;
  1.1692 +
  1.1693 +    *valuepc = nullptr;
  1.1694 +
  1.1695 +    BytecodeParser parser(cx, iter.script());
  1.1696 +    if (!parser.parse())
  1.1697 +        return false;
  1.1698 +
  1.1699 +    if (spindex < 0 && spindex + int(parser.stackDepthAtPC(current)) < 0)
  1.1700 +        spindex = JSDVG_SEARCH_STACK;
  1.1701 +
  1.1702 +    if (spindex == JSDVG_SEARCH_STACK) {
  1.1703 +        size_t index = iter.numFrameSlots();
  1.1704 +        JS_ASSERT(index >= size_t(parser.stackDepthAtPC(current)));
  1.1705 +
  1.1706 +        // We search from fp->sp to base to find the most recently calculated
  1.1707 +        // value matching v under assumption that it is the value that caused
  1.1708 +        // the exception.
  1.1709 +        int stackHits = 0;
  1.1710 +        Value s;
  1.1711 +        do {
  1.1712 +            if (!index)
  1.1713 +                return true;
  1.1714 +            s = iter.frameSlotValue(--index);
  1.1715 +        } while (s != v || stackHits++ != skipStackHits);
  1.1716 +
  1.1717 +        // If the current PC has fewer values on the stack than the index we are
  1.1718 +        // looking for, the blamed value must be one pushed by the current
  1.1719 +        // bytecode, so restore *valuepc.
  1.1720 +        jsbytecode *pc = nullptr;
  1.1721 +        if (index < size_t(parser.stackDepthAtPC(current)))
  1.1722 +            pc = parser.pcForStackOperand(current, index);
  1.1723 +        *valuepc = pc ? pc : current;
  1.1724 +    } else {
  1.1725 +        jsbytecode *pc = parser.pcForStackOperand(current, spindex);
  1.1726 +        *valuepc = pc ? pc : current;
  1.1727 +    }
  1.1728 +    return true;
  1.1729 +}
  1.1730 +
  1.1731 +static bool
  1.1732 +DecompileExpressionFromStack(JSContext *cx, int spindex, int skipStackHits, HandleValue v, char **res)
  1.1733 +{
  1.1734 +    JS_ASSERT(spindex < 0 ||
  1.1735 +              spindex == JSDVG_IGNORE_STACK ||
  1.1736 +              spindex == JSDVG_SEARCH_STACK);
  1.1737 +
  1.1738 +    *res = nullptr;
  1.1739 +
  1.1740 +#ifdef JS_MORE_DETERMINISTIC
  1.1741 +    /*
  1.1742 +     * Give up if we need deterministic behavior for differential testing.
  1.1743 +     * IonMonkey doesn't use InterpreterFrames and this ensures we get the same
  1.1744 +     * error messages.
  1.1745 +     */
  1.1746 +    return true;
  1.1747 +#endif
  1.1748 +
  1.1749 +    FrameIter frameIter(cx);
  1.1750 +
  1.1751 +    if (frameIter.done() || !frameIter.hasScript())
  1.1752 +        return true;
  1.1753 +
  1.1754 +    RootedScript script(cx, frameIter.script());
  1.1755 +    AutoCompartment ac(cx, &script->global());
  1.1756 +    jsbytecode *valuepc = frameIter.pc();
  1.1757 +    RootedFunction fun(cx, frameIter.isFunctionFrame()
  1.1758 +                           ? frameIter.callee()
  1.1759 +                           : nullptr);
  1.1760 +
  1.1761 +    JS_ASSERT(script->containsPC(valuepc));
  1.1762 +
  1.1763 +    // Give up if in prologue.
  1.1764 +    if (valuepc < script->main())
  1.1765 +        return true;
  1.1766 +
  1.1767 +    if (!FindStartPC(cx, frameIter, spindex, skipStackHits, v, &valuepc))
  1.1768 +        return false;
  1.1769 +    if (!valuepc)
  1.1770 +        return true;
  1.1771 +
  1.1772 +    ExpressionDecompiler ed(cx, script, fun);
  1.1773 +    if (!ed.init())
  1.1774 +        return false;
  1.1775 +    if (!ed.decompilePC(valuepc))
  1.1776 +        return false;
  1.1777 +
  1.1778 +    return ed.getOutput(res);
  1.1779 +}
  1.1780 +
  1.1781 +char *
  1.1782 +js::DecompileValueGenerator(JSContext *cx, int spindex, HandleValue v,
  1.1783 +                            HandleString fallbackArg, int skipStackHits)
  1.1784 +{
  1.1785 +    RootedString fallback(cx, fallbackArg);
  1.1786 +    {
  1.1787 +        char *result;
  1.1788 +        if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result))
  1.1789 +            return nullptr;
  1.1790 +        if (result) {
  1.1791 +            if (strcmp(result, "(intermediate value)"))
  1.1792 +                return result;
  1.1793 +            js_free(result);
  1.1794 +        }
  1.1795 +    }
  1.1796 +    if (!fallback) {
  1.1797 +        if (v.isUndefined())
  1.1798 +            return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)"
  1.1799 +        fallback = ValueToSource(cx, v);
  1.1800 +        if (!fallback)
  1.1801 +            return nullptr;
  1.1802 +    }
  1.1803 +
  1.1804 +    Rooted<JSLinearString *> linear(cx, fallback->ensureLinear(cx));
  1.1805 +    if (!linear)
  1.1806 +        return nullptr;
  1.1807 +    TwoByteChars tbchars(linear->chars(), linear->length());
  1.1808 +    return LossyTwoByteCharsToNewLatin1CharsZ(cx, tbchars).c_str();
  1.1809 +}
  1.1810 +
  1.1811 +static bool
  1.1812 +DecompileArgumentFromStack(JSContext *cx, int formalIndex, char **res)
  1.1813 +{
  1.1814 +    JS_ASSERT(formalIndex >= 0);
  1.1815 +
  1.1816 +    *res = nullptr;
  1.1817 +
  1.1818 +#ifdef JS_MORE_DETERMINISTIC
  1.1819 +    /* See note in DecompileExpressionFromStack. */
  1.1820 +    return true;
  1.1821 +#endif
  1.1822 +
  1.1823 +    /*
  1.1824 +     * Settle on the nearest script frame, which should be the builtin that
  1.1825 +     * called the intrinsic.
  1.1826 +     */
  1.1827 +    FrameIter frameIter(cx);
  1.1828 +    JS_ASSERT(!frameIter.done());
  1.1829 +
  1.1830 +    /*
  1.1831 +     * Get the second-to-top frame, the caller of the builtin that called the
  1.1832 +     * intrinsic.
  1.1833 +     */
  1.1834 +    ++frameIter;
  1.1835 +    if (frameIter.done() || !frameIter.hasScript())
  1.1836 +        return true;
  1.1837 +
  1.1838 +    RootedScript script(cx, frameIter.script());
  1.1839 +    AutoCompartment ac(cx, &script->global());
  1.1840 +    jsbytecode *current = frameIter.pc();
  1.1841 +    RootedFunction fun(cx, frameIter.isFunctionFrame()
  1.1842 +                       ? frameIter.callee()
  1.1843 +                       : nullptr);
  1.1844 +
  1.1845 +    JS_ASSERT(script->containsPC(current));
  1.1846 +
  1.1847 +    if (current < script->main())
  1.1848 +        return true;
  1.1849 +
  1.1850 +    /* Don't handle getters, setters or calls from fun.call/fun.apply. */
  1.1851 +    if (JSOp(*current) != JSOP_CALL || static_cast<unsigned>(formalIndex) >= GET_ARGC(current))
  1.1852 +        return true;
  1.1853 +
  1.1854 +    BytecodeParser parser(cx, script);
  1.1855 +    if (!parser.parse())
  1.1856 +        return false;
  1.1857 +
  1.1858 +    int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) + formalIndex;
  1.1859 +    JS_ASSERT(formalStackIndex >= 0);
  1.1860 +    if (uint32_t(formalStackIndex) >= parser.stackDepthAtPC(current))
  1.1861 +        return true;
  1.1862 +
  1.1863 +    ExpressionDecompiler ed(cx, script, fun);
  1.1864 +    if (!ed.init())
  1.1865 +        return false;
  1.1866 +    if (!ed.decompilePCForStackOperand(current, formalStackIndex))
  1.1867 +        return false;
  1.1868 +
  1.1869 +    return ed.getOutput(res);
  1.1870 +}
  1.1871 +
  1.1872 +char *
  1.1873 +js::DecompileArgument(JSContext *cx, int formalIndex, HandleValue v)
  1.1874 +{
  1.1875 +    {
  1.1876 +        char *result;
  1.1877 +        if (!DecompileArgumentFromStack(cx, formalIndex, &result))
  1.1878 +            return nullptr;
  1.1879 +        if (result) {
  1.1880 +            if (strcmp(result, "(intermediate value)"))
  1.1881 +                return result;
  1.1882 +            js_free(result);
  1.1883 +        }
  1.1884 +    }
  1.1885 +    if (v.isUndefined())
  1.1886 +        return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)"
  1.1887 +    RootedString fallback(cx, ValueToSource(cx, v));
  1.1888 +    if (!fallback)
  1.1889 +        return nullptr;
  1.1890 +
  1.1891 +    Rooted<JSLinearString *> linear(cx, fallback->ensureLinear(cx));
  1.1892 +    if (!linear)
  1.1893 +        return nullptr;
  1.1894 +    return LossyTwoByteCharsToNewLatin1CharsZ(cx, linear->range()).c_str();
  1.1895 +}
  1.1896 +
  1.1897 +bool
  1.1898 +js::CallResultEscapes(jsbytecode *pc)
  1.1899 +{
  1.1900 +    /*
  1.1901 +     * If we see any of these sequences, the result is unused:
  1.1902 +     * - call / pop
  1.1903 +     *
  1.1904 +     * If we see any of these sequences, the result is only tested for nullness:
  1.1905 +     * - call / ifeq
  1.1906 +     * - call / not / ifeq
  1.1907 +     */
  1.1908 +
  1.1909 +    if (*pc == JSOP_CALL)
  1.1910 +        pc += JSOP_CALL_LENGTH;
  1.1911 +    else if (*pc == JSOP_SPREADCALL)
  1.1912 +        pc += JSOP_SPREADCALL_LENGTH;
  1.1913 +    else
  1.1914 +        return true;
  1.1915 +
  1.1916 +    if (*pc == JSOP_POP)
  1.1917 +        return false;
  1.1918 +
  1.1919 +    if (*pc == JSOP_NOT)
  1.1920 +        pc += JSOP_NOT_LENGTH;
  1.1921 +
  1.1922 +    return *pc != JSOP_IFEQ;
  1.1923 +}
  1.1924 +
  1.1925 +extern bool
  1.1926 +js::IsValidBytecodeOffset(JSContext *cx, JSScript *script, size_t offset)
  1.1927 +{
  1.1928 +    // This could be faster (by following jump instructions if the target is <= offset).
  1.1929 +    for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) {
  1.1930 +        size_t here = r.frontOffset();
  1.1931 +        if (here >= offset)
  1.1932 +            return here == offset;
  1.1933 +    }
  1.1934 +    return false;
  1.1935 +}
  1.1936 +
  1.1937 +JS_FRIEND_API(size_t)
  1.1938 +js::GetPCCountScriptCount(JSContext *cx)
  1.1939 +{
  1.1940 +    JSRuntime *rt = cx->runtime();
  1.1941 +
  1.1942 +    if (!rt->scriptAndCountsVector)
  1.1943 +        return 0;
  1.1944 +
  1.1945 +    return rt->scriptAndCountsVector->length();
  1.1946 +}
  1.1947 +
  1.1948 +enum MaybeComma {NO_COMMA, COMMA};
  1.1949 +
  1.1950 +static void
  1.1951 +AppendJSONProperty(StringBuffer &buf, const char *name, MaybeComma comma = COMMA)
  1.1952 +{
  1.1953 +    if (comma)
  1.1954 +        buf.append(',');
  1.1955 +
  1.1956 +    buf.append('\"');
  1.1957 +    buf.appendInflated(name, strlen(name));
  1.1958 +    buf.appendInflated("\":", 2);
  1.1959 +}
  1.1960 +
  1.1961 +static void
  1.1962 +AppendArrayJSONProperties(JSContext *cx, StringBuffer &buf,
  1.1963 +                          double *values, const char * const *names, unsigned count,
  1.1964 +                          MaybeComma &comma)
  1.1965 +{
  1.1966 +    for (unsigned i = 0; i < count; i++) {
  1.1967 +        if (values[i]) {
  1.1968 +            AppendJSONProperty(buf, names[i], comma);
  1.1969 +            comma = COMMA;
  1.1970 +            NumberValueToStringBuffer(cx, DoubleValue(values[i]), buf);
  1.1971 +        }
  1.1972 +    }
  1.1973 +}
  1.1974 +
  1.1975 +JS_FRIEND_API(JSString *)
  1.1976 +js::GetPCCountScriptSummary(JSContext *cx, size_t index)
  1.1977 +{
  1.1978 +    JSRuntime *rt = cx->runtime();
  1.1979 +
  1.1980 +    if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
  1.1981 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
  1.1982 +        return nullptr;
  1.1983 +    }
  1.1984 +
  1.1985 +    const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index];
  1.1986 +    RootedScript script(cx, sac.script);
  1.1987 +
  1.1988 +    /*
  1.1989 +     * OOM on buffer appends here will not be caught immediately, but since
  1.1990 +     * StringBuffer uses a ContextAllocPolicy will trigger an exception on the
  1.1991 +     * context if they occur, which we'll catch before returning.
  1.1992 +     */
  1.1993 +    StringBuffer buf(cx);
  1.1994 +
  1.1995 +    buf.append('{');
  1.1996 +
  1.1997 +    AppendJSONProperty(buf, "file", NO_COMMA);
  1.1998 +    JSString *str = JS_NewStringCopyZ(cx, script->filename());
  1.1999 +    if (!str || !(str = StringToSource(cx, str)))
  1.2000 +        return nullptr;
  1.2001 +    buf.append(str);
  1.2002 +
  1.2003 +    AppendJSONProperty(buf, "line");
  1.2004 +    NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf);
  1.2005 +
  1.2006 +    if (script->functionNonDelazifying()) {
  1.2007 +        JSAtom *atom = script->functionNonDelazifying()->displayAtom();
  1.2008 +        if (atom) {
  1.2009 +            AppendJSONProperty(buf, "name");
  1.2010 +            if (!(str = StringToSource(cx, atom)))
  1.2011 +                return nullptr;
  1.2012 +            buf.append(str);
  1.2013 +        }
  1.2014 +    }
  1.2015 +
  1.2016 +    double baseTotals[PCCounts::BASE_LIMIT] = {0.0};
  1.2017 +    double accessTotals[PCCounts::ACCESS_LIMIT - PCCounts::BASE_LIMIT] = {0.0};
  1.2018 +    double elementTotals[PCCounts::ELEM_LIMIT - PCCounts::ACCESS_LIMIT] = {0.0};
  1.2019 +    double propertyTotals[PCCounts::PROP_LIMIT - PCCounts::ACCESS_LIMIT] = {0.0};
  1.2020 +    double arithTotals[PCCounts::ARITH_LIMIT - PCCounts::BASE_LIMIT] = {0.0};
  1.2021 +
  1.2022 +    for (unsigned i = 0; i < script->length(); i++) {
  1.2023 +        PCCounts &counts = sac.getPCCounts(script->offsetToPC(i));
  1.2024 +        if (!counts)
  1.2025 +            continue;
  1.2026 +
  1.2027 +        JSOp op = (JSOp)script->code()[i];
  1.2028 +        unsigned numCounts = PCCounts::numCounts(op);
  1.2029 +
  1.2030 +        for (unsigned j = 0; j < numCounts; j++) {
  1.2031 +            double value = counts.get(j);
  1.2032 +            if (j < PCCounts::BASE_LIMIT) {
  1.2033 +                baseTotals[j] += value;
  1.2034 +            } else if (PCCounts::accessOp(op)) {
  1.2035 +                if (j < PCCounts::ACCESS_LIMIT)
  1.2036 +                    accessTotals[j - PCCounts::BASE_LIMIT] += value;
  1.2037 +                else if (PCCounts::elementOp(op))
  1.2038 +                    elementTotals[j - PCCounts::ACCESS_LIMIT] += value;
  1.2039 +                else if (PCCounts::propertyOp(op))
  1.2040 +                    propertyTotals[j - PCCounts::ACCESS_LIMIT] += value;
  1.2041 +                else
  1.2042 +                    MOZ_ASSUME_UNREACHABLE("Bad opcode");
  1.2043 +            } else if (PCCounts::arithOp(op)) {
  1.2044 +                arithTotals[j - PCCounts::BASE_LIMIT] += value;
  1.2045 +            } else {
  1.2046 +                MOZ_ASSUME_UNREACHABLE("Bad opcode");
  1.2047 +            }
  1.2048 +        }
  1.2049 +    }
  1.2050 +
  1.2051 +    AppendJSONProperty(buf, "totals");
  1.2052 +    buf.append('{');
  1.2053 +
  1.2054 +    MaybeComma comma = NO_COMMA;
  1.2055 +
  1.2056 +    AppendArrayJSONProperties(cx, buf, baseTotals, countBaseNames,
  1.2057 +                              JS_ARRAY_LENGTH(baseTotals), comma);
  1.2058 +    AppendArrayJSONProperties(cx, buf, accessTotals, countAccessNames,
  1.2059 +                              JS_ARRAY_LENGTH(accessTotals), comma);
  1.2060 +    AppendArrayJSONProperties(cx, buf, elementTotals, countElementNames,
  1.2061 +                              JS_ARRAY_LENGTH(elementTotals), comma);
  1.2062 +    AppendArrayJSONProperties(cx, buf, propertyTotals, countPropertyNames,
  1.2063 +                              JS_ARRAY_LENGTH(propertyTotals), comma);
  1.2064 +    AppendArrayJSONProperties(cx, buf, arithTotals, countArithNames,
  1.2065 +                              JS_ARRAY_LENGTH(arithTotals), comma);
  1.2066 +
  1.2067 +    uint64_t ionActivity = 0;
  1.2068 +    jit::IonScriptCounts *ionCounts = sac.getIonCounts();
  1.2069 +    while (ionCounts) {
  1.2070 +        for (size_t i = 0; i < ionCounts->numBlocks(); i++)
  1.2071 +            ionActivity += ionCounts->block(i).hitCount();
  1.2072 +        ionCounts = ionCounts->previous();
  1.2073 +    }
  1.2074 +    if (ionActivity) {
  1.2075 +        AppendJSONProperty(buf, "ion", comma);
  1.2076 +        NumberValueToStringBuffer(cx, DoubleValue(ionActivity), buf);
  1.2077 +    }
  1.2078 +
  1.2079 +    buf.append('}');
  1.2080 +    buf.append('}');
  1.2081 +
  1.2082 +    if (cx->isExceptionPending())
  1.2083 +        return nullptr;
  1.2084 +
  1.2085 +    return buf.finishString();
  1.2086 +}
  1.2087 +
  1.2088 +static bool
  1.2089 +GetPCCountJSON(JSContext *cx, const ScriptAndCounts &sac, StringBuffer &buf)
  1.2090 +{
  1.2091 +    RootedScript script(cx, sac.script);
  1.2092 +
  1.2093 +    buf.append('{');
  1.2094 +    AppendJSONProperty(buf, "text", NO_COMMA);
  1.2095 +
  1.2096 +    JSString *str = JS_DecompileScript(cx, script, nullptr, 0);
  1.2097 +    if (!str || !(str = StringToSource(cx, str)))
  1.2098 +        return false;
  1.2099 +
  1.2100 +    buf.append(str);
  1.2101 +
  1.2102 +    AppendJSONProperty(buf, "line");
  1.2103 +    NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf);
  1.2104 +
  1.2105 +    AppendJSONProperty(buf, "opcodes");
  1.2106 +    buf.append('[');
  1.2107 +    bool comma = false;
  1.2108 +
  1.2109 +    SrcNoteLineScanner scanner(script->notes(), script->lineno());
  1.2110 +
  1.2111 +    for (jsbytecode *pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) {
  1.2112 +        size_t offset = script->pcToOffset(pc);
  1.2113 +
  1.2114 +        JSOp op = (JSOp) *pc;
  1.2115 +
  1.2116 +        if (comma)
  1.2117 +            buf.append(',');
  1.2118 +        comma = true;
  1.2119 +
  1.2120 +        buf.append('{');
  1.2121 +
  1.2122 +        AppendJSONProperty(buf, "id", NO_COMMA);
  1.2123 +        NumberValueToStringBuffer(cx, Int32Value(offset), buf);
  1.2124 +
  1.2125 +        scanner.advanceTo(offset);
  1.2126 +
  1.2127 +        AppendJSONProperty(buf, "line");
  1.2128 +        NumberValueToStringBuffer(cx, Int32Value(scanner.getLine()), buf);
  1.2129 +
  1.2130 +        {
  1.2131 +            const char *name = js_CodeName[op];
  1.2132 +            AppendJSONProperty(buf, "name");
  1.2133 +            buf.append('\"');
  1.2134 +            buf.appendInflated(name, strlen(name));
  1.2135 +            buf.append('\"');
  1.2136 +        }
  1.2137 +
  1.2138 +        {
  1.2139 +            ExpressionDecompiler ed(cx, script, script->functionDelazifying());
  1.2140 +            if (!ed.init())
  1.2141 +                return false;
  1.2142 +            if (!ed.decompilePC(pc))
  1.2143 +                return false;
  1.2144 +            char *text;
  1.2145 +            if (!ed.getOutput(&text))
  1.2146 +                return false;
  1.2147 +            AppendJSONProperty(buf, "text");
  1.2148 +            JSString *str = JS_NewStringCopyZ(cx, text);
  1.2149 +            js_free(text);
  1.2150 +            if (!str || !(str = StringToSource(cx, str)))
  1.2151 +                return false;
  1.2152 +            buf.append(str);
  1.2153 +        }
  1.2154 +
  1.2155 +        PCCounts &counts = sac.getPCCounts(pc);
  1.2156 +        unsigned numCounts = PCCounts::numCounts(op);
  1.2157 +
  1.2158 +        AppendJSONProperty(buf, "counts");
  1.2159 +        buf.append('{');
  1.2160 +
  1.2161 +        MaybeComma comma = NO_COMMA;
  1.2162 +        for (unsigned i = 0; i < numCounts; i++) {
  1.2163 +            double value = counts.get(i);
  1.2164 +            if (value > 0) {
  1.2165 +                AppendJSONProperty(buf, PCCounts::countName(op, i), comma);
  1.2166 +                comma = COMMA;
  1.2167 +                NumberValueToStringBuffer(cx, DoubleValue(value), buf);
  1.2168 +            }
  1.2169 +        }
  1.2170 +
  1.2171 +        buf.append('}');
  1.2172 +        buf.append('}');
  1.2173 +    }
  1.2174 +
  1.2175 +    buf.append(']');
  1.2176 +
  1.2177 +    jit::IonScriptCounts *ionCounts = sac.getIonCounts();
  1.2178 +    if (ionCounts) {
  1.2179 +        AppendJSONProperty(buf, "ion");
  1.2180 +        buf.append('[');
  1.2181 +        bool comma = false;
  1.2182 +        while (ionCounts) {
  1.2183 +            if (comma)
  1.2184 +                buf.append(',');
  1.2185 +            comma = true;
  1.2186 +
  1.2187 +            buf.append('[');
  1.2188 +            for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
  1.2189 +                if (i)
  1.2190 +                    buf.append(',');
  1.2191 +                const jit::IonBlockCounts &block = ionCounts->block(i);
  1.2192 +
  1.2193 +                buf.append('{');
  1.2194 +                AppendJSONProperty(buf, "id", NO_COMMA);
  1.2195 +                NumberValueToStringBuffer(cx, Int32Value(block.id()), buf);
  1.2196 +                AppendJSONProperty(buf, "offset");
  1.2197 +                NumberValueToStringBuffer(cx, Int32Value(block.offset()), buf);
  1.2198 +                AppendJSONProperty(buf, "successors");
  1.2199 +                buf.append('[');
  1.2200 +                for (size_t j = 0; j < block.numSuccessors(); j++) {
  1.2201 +                    if (j)
  1.2202 +                        buf.append(',');
  1.2203 +                    NumberValueToStringBuffer(cx, Int32Value(block.successor(j)), buf);
  1.2204 +                }
  1.2205 +                buf.append(']');
  1.2206 +                AppendJSONProperty(buf, "hits");
  1.2207 +                NumberValueToStringBuffer(cx, DoubleValue(block.hitCount()), buf);
  1.2208 +
  1.2209 +                AppendJSONProperty(buf, "code");
  1.2210 +                JSString *str = JS_NewStringCopyZ(cx, block.code());
  1.2211 +                if (!str || !(str = StringToSource(cx, str)))
  1.2212 +                    return false;
  1.2213 +                buf.append(str);
  1.2214 +                buf.append('}');
  1.2215 +            }
  1.2216 +            buf.append(']');
  1.2217 +
  1.2218 +            ionCounts = ionCounts->previous();
  1.2219 +        }
  1.2220 +        buf.append(']');
  1.2221 +    }
  1.2222 +
  1.2223 +    buf.append('}');
  1.2224 +
  1.2225 +    return !cx->isExceptionPending();
  1.2226 +}
  1.2227 +
  1.2228 +JS_FRIEND_API(JSString *)
  1.2229 +js::GetPCCountScriptContents(JSContext *cx, size_t index)
  1.2230 +{
  1.2231 +    JSRuntime *rt = cx->runtime();
  1.2232 +
  1.2233 +    if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
  1.2234 +        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
  1.2235 +        return nullptr;
  1.2236 +    }
  1.2237 +
  1.2238 +    const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index];
  1.2239 +    JSScript *script = sac.script;
  1.2240 +
  1.2241 +    StringBuffer buf(cx);
  1.2242 +
  1.2243 +    if (!script->functionNonDelazifying() && !script->compileAndGo())
  1.2244 +        return buf.finishString();
  1.2245 +
  1.2246 +    {
  1.2247 +        AutoCompartment ac(cx, &script->global());
  1.2248 +        if (!GetPCCountJSON(cx, sac, buf))
  1.2249 +            return nullptr;
  1.2250 +    }
  1.2251 +
  1.2252 +    return buf.finishString();
  1.2253 +}

mercurial