michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * JS bytecode descriptors, disassemblers, and (expression) decompilers. michael@0: */ michael@0: michael@0: #include "jsopcodeinlines.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "jsanalyze.h" michael@0: #include "jsapi.h" michael@0: #include "jsatom.h" michael@0: #include "jscntxt.h" michael@0: #include "jscompartment.h" michael@0: #include "jsfun.h" michael@0: #include "jsnum.h" michael@0: #include "jsobj.h" michael@0: #include "jsprf.h" michael@0: #include "jsscript.h" michael@0: #include "jsstr.h" michael@0: #include "jstypes.h" michael@0: #include "jsutil.h" michael@0: michael@0: #include "frontend/BytecodeCompiler.h" michael@0: #include "frontend/SourceNotes.h" michael@0: #include "js/CharacterEncoding.h" michael@0: #include "vm/Opcodes.h" michael@0: #include "vm/ScopeObject.h" michael@0: #include "vm/Shape.h" michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: #include "jscntxtinlines.h" michael@0: #include "jscompartmentinlines.h" michael@0: #include "jsinferinlines.h" michael@0: #include "jsobjinlines.h" michael@0: #include "jsscriptinlines.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: michael@0: using js::frontend::IsIdentifier; michael@0: michael@0: /* michael@0: * Index limit must stay within 32 bits. michael@0: */ michael@0: JS_STATIC_ASSERT(sizeof(uint32_t) * JS_BITS_PER_BYTE >= INDEX_LIMIT_LOG2 + 1); michael@0: michael@0: const JSCodeSpec js_CodeSpec[] = { michael@0: #define MAKE_CODESPEC(op,val,name,token,length,nuses,ndefs,format) {length,nuses,ndefs,format}, michael@0: FOR_EACH_OPCODE(MAKE_CODESPEC) michael@0: #undef MAKE_CODESPEC michael@0: }; michael@0: michael@0: const unsigned js_NumCodeSpecs = JS_ARRAY_LENGTH(js_CodeSpec); michael@0: michael@0: /* michael@0: * Each element of the array is either a source literal associated with JS michael@0: * bytecode or null. michael@0: */ michael@0: static const char * const CodeToken[] = { michael@0: #define TOKEN(op, val, name, token, ...) token, michael@0: FOR_EACH_OPCODE(TOKEN) michael@0: #undef TOKEN michael@0: }; michael@0: michael@0: /* michael@0: * Array of JS bytecode names used by PC count JSON, DEBUG-only js_Disassemble michael@0: * and JIT debug spew. michael@0: */ michael@0: const char * const js_CodeName[] = { michael@0: #define OPNAME(op, val, name, ...) name, michael@0: FOR_EACH_OPCODE(OPNAME) michael@0: #undef OPNAME michael@0: }; michael@0: michael@0: /************************************************************************/ michael@0: michael@0: #define COUNTS_LEN 16 michael@0: michael@0: size_t michael@0: js_GetVariableBytecodeLength(jsbytecode *pc) michael@0: { michael@0: JSOp op = JSOp(*pc); michael@0: JS_ASSERT(js_CodeSpec[op].length == -1); michael@0: switch (op) { michael@0: case JSOP_TABLESWITCH: { michael@0: /* Structure: default-jump case-low case-high case1-jump ... */ michael@0: pc += JUMP_OFFSET_LEN; michael@0: int32_t low = GET_JUMP_OFFSET(pc); michael@0: pc += JUMP_OFFSET_LEN; michael@0: int32_t high = GET_JUMP_OFFSET(pc); michael@0: unsigned ncases = unsigned(high - low + 1); michael@0: return 1 + 3 * JUMP_OFFSET_LEN + ncases * JUMP_OFFSET_LEN; michael@0: } michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Unexpected op"); michael@0: } michael@0: } michael@0: michael@0: unsigned michael@0: js::StackUses(JSScript *script, jsbytecode *pc) michael@0: { michael@0: JSOp op = (JSOp) *pc; michael@0: const JSCodeSpec &cs = js_CodeSpec[op]; michael@0: if (cs.nuses >= 0) michael@0: return cs.nuses; michael@0: michael@0: JS_ASSERT(js_CodeSpec[op].nuses == -1); michael@0: switch (op) { michael@0: case JSOP_POPN: michael@0: return GET_UINT16(pc); michael@0: default: michael@0: /* stack: fun, this, [argc arguments] */ michael@0: JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL || michael@0: op == JSOP_FUNCALL || op == JSOP_FUNAPPLY); michael@0: return 2 + GET_ARGC(pc); michael@0: } michael@0: } michael@0: michael@0: unsigned michael@0: js::StackDefs(JSScript *script, jsbytecode *pc) michael@0: { michael@0: JSOp op = (JSOp) *pc; michael@0: const JSCodeSpec &cs = js_CodeSpec[op]; michael@0: JS_ASSERT (cs.ndefs >= 0); michael@0: return cs.ndefs; michael@0: } michael@0: michael@0: static const char * const countBaseNames[] = { michael@0: "interp" michael@0: }; michael@0: michael@0: JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) == PCCounts::BASE_LIMIT); michael@0: michael@0: static const char * const countAccessNames[] = { michael@0: "infer_mono", michael@0: "infer_di", michael@0: "infer_poly", michael@0: "infer_barrier", michael@0: "infer_nobarrier", michael@0: "observe_undefined", michael@0: "observe_null", michael@0: "observe_boolean", michael@0: "observe_int32", michael@0: "observe_double", michael@0: "observe_string", michael@0: "observe_object" michael@0: }; michael@0: michael@0: JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + michael@0: JS_ARRAY_LENGTH(countAccessNames) == PCCounts::ACCESS_LIMIT); michael@0: michael@0: static const char * const countElementNames[] = { michael@0: "id_int", michael@0: "id_double", michael@0: "id_other", michael@0: "id_unknown", michael@0: "elem_typed", michael@0: "elem_packed", michael@0: "elem_dense", michael@0: "elem_other" michael@0: }; michael@0: michael@0: JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + michael@0: JS_ARRAY_LENGTH(countAccessNames) + michael@0: JS_ARRAY_LENGTH(countElementNames) == PCCounts::ELEM_LIMIT); michael@0: michael@0: static const char * const countPropertyNames[] = { michael@0: "prop_static", michael@0: "prop_definite", michael@0: "prop_other" michael@0: }; michael@0: michael@0: JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + michael@0: JS_ARRAY_LENGTH(countAccessNames) + michael@0: JS_ARRAY_LENGTH(countPropertyNames) == PCCounts::PROP_LIMIT); michael@0: michael@0: static const char * const countArithNames[] = { michael@0: "arith_int", michael@0: "arith_double", michael@0: "arith_other", michael@0: "arith_unknown", michael@0: }; michael@0: michael@0: JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) + michael@0: JS_ARRAY_LENGTH(countArithNames) == PCCounts::ARITH_LIMIT); michael@0: michael@0: /* static */ const char * michael@0: PCCounts::countName(JSOp op, size_t which) michael@0: { michael@0: JS_ASSERT(which < numCounts(op)); michael@0: michael@0: if (which < BASE_LIMIT) michael@0: return countBaseNames[which]; michael@0: michael@0: if (accessOp(op)) { michael@0: if (which < ACCESS_LIMIT) michael@0: return countAccessNames[which - BASE_LIMIT]; michael@0: if (elementOp(op)) michael@0: return countElementNames[which - ACCESS_LIMIT]; michael@0: if (propertyOp(op)) michael@0: return countPropertyNames[which - ACCESS_LIMIT]; michael@0: MOZ_ASSUME_UNREACHABLE("bad op"); michael@0: } michael@0: michael@0: if (arithOp(op)) michael@0: return countArithNames[which - BASE_LIMIT]; michael@0: michael@0: MOZ_ASSUME_UNREACHABLE("bad op"); michael@0: } michael@0: michael@0: #ifdef JS_ION michael@0: void michael@0: js::DumpIonScriptCounts(Sprinter *sp, jit::IonScriptCounts *ionCounts) michael@0: { michael@0: Sprint(sp, "IonScript [%lu blocks]:\n", ionCounts->numBlocks()); michael@0: for (size_t i = 0; i < ionCounts->numBlocks(); i++) { michael@0: const jit::IonBlockCounts &block = ionCounts->block(i); michael@0: if (block.hitCount() < 10) michael@0: continue; michael@0: Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset()); michael@0: for (size_t j = 0; j < block.numSuccessors(); j++) michael@0: Sprint(sp, " -> #%lu", block.successor(j)); michael@0: Sprint(sp, " :: %llu hits\n", block.hitCount()); michael@0: Sprint(sp, "%s\n", block.code()); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: js_DumpPCCounts(JSContext *cx, HandleScript script, js::Sprinter *sp) michael@0: { michael@0: JS_ASSERT(script->hasScriptCounts()); michael@0: michael@0: #ifdef DEBUG michael@0: jsbytecode *pc = script->code(); michael@0: while (pc < script->codeEnd()) { michael@0: JSOp op = JSOp(*pc); michael@0: jsbytecode *next = GetNextPc(pc); michael@0: michael@0: if (!js_Disassemble1(cx, script, pc, script->pcToOffset(pc), true, sp)) michael@0: return; michael@0: michael@0: size_t total = PCCounts::numCounts(op); michael@0: double *raw = script->getPCCounts(pc).rawCounts(); michael@0: michael@0: Sprint(sp, " {"); michael@0: bool printed = false; michael@0: for (size_t i = 0; i < total; i++) { michael@0: double val = raw[i]; michael@0: if (val) { michael@0: if (printed) michael@0: Sprint(sp, ", "); michael@0: Sprint(sp, "\"%s\": %.0f", PCCounts::countName(op, i), val); michael@0: printed = true; michael@0: } michael@0: } michael@0: Sprint(sp, "}\n"); michael@0: michael@0: pc = next; michael@0: } michael@0: #endif michael@0: michael@0: #ifdef JS_ION michael@0: jit::IonScriptCounts *ionCounts = script->getIonCounts(); michael@0: michael@0: while (ionCounts) { michael@0: DumpIonScriptCounts(sp, ionCounts); michael@0: ionCounts = ionCounts->previous(); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // Bytecode Parser michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: namespace { michael@0: michael@0: class BytecodeParser michael@0: { michael@0: class Bytecode michael@0: { michael@0: public: michael@0: Bytecode() { mozilla::PodZero(this); } michael@0: michael@0: // Whether this instruction has been analyzed to get its output defines michael@0: // and stack. michael@0: bool parsed : 1; michael@0: michael@0: // Stack depth before this opcode. michael@0: uint32_t stackDepth; michael@0: michael@0: // Pointer to array of |stackDepth| offsets. An element at position N michael@0: // in the array is the offset of the opcode that defined the michael@0: // corresponding stack slot. The top of the stack is at position michael@0: // |stackDepth - 1|. michael@0: uint32_t *offsetStack; michael@0: michael@0: bool captureOffsetStack(LifoAlloc &alloc, const uint32_t *stack, uint32_t depth) { michael@0: stackDepth = depth; michael@0: offsetStack = alloc.newArray(stackDepth); michael@0: if (stackDepth) { michael@0: if (!offsetStack) michael@0: return false; michael@0: for (uint32_t n = 0; n < stackDepth; n++) michael@0: offsetStack[n] = stack[n]; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // When control-flow merges, intersect the stacks, marking slots that michael@0: // are defined by different offsets with the UINT32_MAX sentinel. michael@0: // This is sufficient for forward control-flow. It doesn't grok loops michael@0: // -- for that you would have to iterate to a fixed point -- but there michael@0: // shouldn't be operands on the stack at a loop back-edge anyway. michael@0: void mergeOffsetStack(const uint32_t *stack, uint32_t depth) { michael@0: JS_ASSERT(depth == stackDepth); michael@0: for (uint32_t n = 0; n < stackDepth; n++) michael@0: if (offsetStack[n] != stack[n]) michael@0: offsetStack[n] = UINT32_MAX; michael@0: } michael@0: }; michael@0: michael@0: JSContext *cx_; michael@0: LifoAllocScope allocScope_; michael@0: RootedScript script_; michael@0: michael@0: Bytecode **codeArray_; michael@0: michael@0: public: michael@0: BytecodeParser(JSContext *cx, JSScript *script) michael@0: : cx_(cx), michael@0: allocScope_(&cx->tempLifoAlloc()), michael@0: script_(cx, script), michael@0: codeArray_(nullptr) { } michael@0: michael@0: bool parse(); michael@0: michael@0: #ifdef DEBUG michael@0: bool isReachable(uint32_t offset) { return maybeCode(offset); } michael@0: bool isReachable(const jsbytecode *pc) { return maybeCode(pc); } michael@0: #endif michael@0: michael@0: uint32_t stackDepthAtPC(uint32_t offset) { michael@0: // Sometimes the code generator in debug mode asks about the stack depth michael@0: // of unreachable code (bug 932180 comment 22). Assume that unreachable michael@0: // code has no operands on the stack. michael@0: return getCode(offset).stackDepth; michael@0: } michael@0: uint32_t stackDepthAtPC(const jsbytecode *pc) { return stackDepthAtPC(script_->pcToOffset(pc)); } michael@0: michael@0: uint32_t offsetForStackOperand(uint32_t offset, int operand) { michael@0: Bytecode &code = getCode(offset); michael@0: if (operand < 0) { michael@0: operand += code.stackDepth; michael@0: JS_ASSERT(operand >= 0); michael@0: } michael@0: JS_ASSERT(uint32_t(operand) < code.stackDepth); michael@0: return code.offsetStack[operand]; michael@0: } michael@0: jsbytecode *pcForStackOperand(jsbytecode *pc, int operand) { michael@0: uint32_t offset = offsetForStackOperand(script_->pcToOffset(pc), operand); michael@0: if (offset == UINT32_MAX) michael@0: return nullptr; michael@0: return script_->offsetToPC(offsetForStackOperand(script_->pcToOffset(pc), operand)); michael@0: } michael@0: michael@0: private: michael@0: LifoAlloc &alloc() { michael@0: return allocScope_.alloc(); michael@0: } michael@0: michael@0: void reportOOM() { michael@0: allocScope_.releaseEarly(); michael@0: js_ReportOutOfMemory(cx_); michael@0: } michael@0: michael@0: uint32_t numSlots() { michael@0: return 1 + script_->nfixed() + michael@0: (script_->functionNonDelazifying() ? script_->functionNonDelazifying()->nargs() : 0); michael@0: } michael@0: michael@0: uint32_t maximumStackDepth() { michael@0: return script_->nslots() - script_->nfixed(); michael@0: } michael@0: michael@0: Bytecode& getCode(uint32_t offset) { michael@0: JS_ASSERT(offset < script_->length()); michael@0: JS_ASSERT(codeArray_[offset]); michael@0: return *codeArray_[offset]; michael@0: } michael@0: Bytecode& getCode(const jsbytecode *pc) { return getCode(script_->pcToOffset(pc)); } michael@0: michael@0: Bytecode* maybeCode(uint32_t offset) { michael@0: JS_ASSERT(offset < script_->length()); michael@0: return codeArray_[offset]; michael@0: } michael@0: Bytecode* maybeCode(const jsbytecode *pc) { return maybeCode(script_->pcToOffset(pc)); } michael@0: michael@0: uint32_t simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint32_t stackDepth); michael@0: michael@0: inline bool addJump(uint32_t offset, uint32_t *currentOffset, michael@0: uint32_t stackDepth, const uint32_t *offsetStack); michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: uint32_t michael@0: BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t *offsetStack, uint32_t stackDepth) michael@0: { michael@0: uint32_t nuses = GetUseCount(script_, offset); michael@0: uint32_t ndefs = GetDefCount(script_, offset); michael@0: michael@0: JS_ASSERT(stackDepth >= nuses); michael@0: stackDepth -= nuses; michael@0: JS_ASSERT(stackDepth + ndefs <= maximumStackDepth()); michael@0: michael@0: // Mark the current offset as defining its values on the offset stack, michael@0: // unless it just reshuffles the stack. In that case we want to preserve michael@0: // the opcode that generated the original value. michael@0: switch (op) { michael@0: default: michael@0: for (uint32_t n = 0; n != ndefs; ++n) michael@0: offsetStack[stackDepth + n] = offset; michael@0: break; michael@0: michael@0: case JSOP_CASE: michael@0: /* Keep the switch value. */ michael@0: JS_ASSERT(ndefs == 1); michael@0: break; michael@0: michael@0: case JSOP_DUP: michael@0: JS_ASSERT(ndefs == 2); michael@0: if (offsetStack) michael@0: offsetStack[stackDepth + 1] = offsetStack[stackDepth]; michael@0: break; michael@0: michael@0: case JSOP_DUP2: michael@0: JS_ASSERT(ndefs == 4); michael@0: if (offsetStack) { michael@0: offsetStack[stackDepth + 2] = offsetStack[stackDepth]; michael@0: offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1]; michael@0: } michael@0: break; michael@0: michael@0: case JSOP_DUPAT: { michael@0: JS_ASSERT(ndefs == 1); michael@0: jsbytecode *pc = script_->offsetToPC(offset); michael@0: unsigned n = GET_UINT24(pc); michael@0: JS_ASSERT(n < stackDepth); michael@0: if (offsetStack) michael@0: offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n]; michael@0: break; michael@0: } michael@0: michael@0: case JSOP_SWAP: michael@0: JS_ASSERT(ndefs == 2); michael@0: if (offsetStack) { michael@0: uint32_t tmp = offsetStack[stackDepth + 1]; michael@0: offsetStack[stackDepth + 1] = offsetStack[stackDepth]; michael@0: offsetStack[stackDepth] = tmp; michael@0: } michael@0: break; michael@0: } michael@0: stackDepth += ndefs; michael@0: return stackDepth; michael@0: } michael@0: michael@0: bool michael@0: BytecodeParser::addJump(uint32_t offset, uint32_t *currentOffset, michael@0: uint32_t stackDepth, const uint32_t *offsetStack) michael@0: { michael@0: JS_ASSERT(offset < script_->length()); michael@0: michael@0: Bytecode *&code = codeArray_[offset]; michael@0: if (!code) { michael@0: code = alloc().new_(); michael@0: if (!code) michael@0: return false; michael@0: if (!code->captureOffsetStack(alloc(), offsetStack, stackDepth)) { michael@0: reportOOM(); michael@0: return false; michael@0: } michael@0: } else { michael@0: code->mergeOffsetStack(offsetStack, stackDepth); michael@0: } michael@0: michael@0: if (offset < *currentOffset && !code->parsed) { michael@0: // Backedge in a while/for loop, whose body has not been parsed due michael@0: // to a lack of fallthrough at the loop head. Roll back the offset michael@0: // to analyze the body. michael@0: *currentOffset = offset; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BytecodeParser::parse() michael@0: { michael@0: JS_ASSERT(!codeArray_); michael@0: michael@0: uint32_t length = script_->length(); michael@0: codeArray_ = alloc().newArray(length); michael@0: michael@0: if (!codeArray_) { michael@0: reportOOM(); michael@0: return false; michael@0: } michael@0: michael@0: mozilla::PodZero(codeArray_, length); michael@0: michael@0: // Fill in stack depth and definitions at initial bytecode. michael@0: Bytecode *startcode = alloc().new_(); michael@0: if (!startcode) { michael@0: reportOOM(); michael@0: return false; michael@0: } michael@0: michael@0: // Fill in stack depth and definitions at initial bytecode. michael@0: uint32_t *offsetStack = alloc().newArray(maximumStackDepth()); michael@0: if (maximumStackDepth() && !offsetStack) { michael@0: reportOOM(); michael@0: return false; michael@0: } michael@0: michael@0: startcode->stackDepth = 0; michael@0: codeArray_[0] = startcode; michael@0: michael@0: uint32_t offset, nextOffset = 0; michael@0: while (nextOffset < length) { michael@0: offset = nextOffset; michael@0: michael@0: Bytecode *code = maybeCode(offset); michael@0: jsbytecode *pc = script_->offsetToPC(offset); michael@0: michael@0: JSOp op = (JSOp)*pc; michael@0: JS_ASSERT(op < JSOP_LIMIT); michael@0: michael@0: // Immediate successor of this bytecode. michael@0: uint32_t successorOffset = offset + GetBytecodeLength(pc); michael@0: michael@0: // Next bytecode to analyze. This is either the successor, or is an michael@0: // earlier bytecode if this bytecode has a loop backedge. michael@0: nextOffset = successorOffset; michael@0: michael@0: if (!code) { michael@0: // Haven't found a path by which this bytecode is reachable. michael@0: continue; michael@0: } michael@0: michael@0: if (code->parsed) { michael@0: // No need to reparse. michael@0: continue; michael@0: } michael@0: michael@0: code->parsed = true; michael@0: michael@0: uint32_t stackDepth = simulateOp(op, offset, offsetStack, code->stackDepth); michael@0: michael@0: switch (op) { michael@0: case JSOP_TABLESWITCH: { michael@0: uint32_t defaultOffset = offset + GET_JUMP_OFFSET(pc); michael@0: jsbytecode *pc2 = pc + JUMP_OFFSET_LEN; michael@0: int32_t low = GET_JUMP_OFFSET(pc2); michael@0: pc2 += JUMP_OFFSET_LEN; michael@0: int32_t high = GET_JUMP_OFFSET(pc2); michael@0: pc2 += JUMP_OFFSET_LEN; michael@0: michael@0: if (!addJump(defaultOffset, &nextOffset, stackDepth, offsetStack)) michael@0: return false; michael@0: michael@0: for (int32_t i = low; i <= high; i++) { michael@0: uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc2); michael@0: if (targetOffset != offset) { michael@0: if (!addJump(targetOffset, &nextOffset, stackDepth, offsetStack)) michael@0: return false; michael@0: } michael@0: pc2 += JUMP_OFFSET_LEN; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case JSOP_TRY: { michael@0: // Everything between a try and corresponding catch or finally is conditional. michael@0: // Note that there is no problem with code which is skipped by a thrown michael@0: // exception but is not caught by a later handler in the same function: michael@0: // no more code will execute, and it does not matter what is defined. michael@0: JSTryNote *tn = script_->trynotes()->vector; michael@0: JSTryNote *tnlimit = tn + script_->trynotes()->length; michael@0: for (; tn < tnlimit; tn++) { michael@0: uint32_t startOffset = script_->mainOffset() + tn->start; michael@0: if (startOffset == offset + 1) { michael@0: uint32_t catchOffset = startOffset + tn->length; michael@0: if (tn->kind != JSTRY_ITER && tn->kind != JSTRY_LOOP) { michael@0: if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack)) michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: // Check basic jump opcodes, which may or may not have a fallthrough. michael@0: if (IsJumpOpcode(op)) { michael@0: // Case instructions do not push the lvalue back when branching. michael@0: uint32_t newStackDepth = stackDepth; michael@0: if (op == JSOP_CASE) michael@0: newStackDepth--; michael@0: michael@0: uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc); michael@0: if (!addJump(targetOffset, &nextOffset, newStackDepth, offsetStack)) michael@0: return false; michael@0: } michael@0: michael@0: // Handle any fallthrough from this opcode. michael@0: if (BytecodeFallsThrough(op)) { michael@0: JS_ASSERT(successorOffset < script_->length()); michael@0: michael@0: Bytecode *&nextcode = codeArray_[successorOffset]; michael@0: michael@0: if (!nextcode) { michael@0: nextcode = alloc().new_(); michael@0: if (!nextcode) { michael@0: reportOOM(); michael@0: return false; michael@0: } michael@0: if (!nextcode->captureOffsetStack(alloc(), offsetStack, stackDepth)) { michael@0: reportOOM(); michael@0: return false; michael@0: } michael@0: } else { michael@0: nextcode->mergeOffsetStack(offsetStack, stackDepth); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: michael@0: bool michael@0: js::ReconstructStackDepth(JSContext *cx, JSScript *script, jsbytecode *pc, uint32_t *depth, bool *reachablePC) michael@0: { michael@0: BytecodeParser parser(cx, script); michael@0: if (!parser.parse()) michael@0: return false; michael@0: michael@0: *reachablePC = parser.isReachable(pc); michael@0: michael@0: if (*reachablePC) michael@0: *depth = parser.stackDepthAtPC(pc); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * If pc != nullptr, include a prefix indicating whether the PC is at the michael@0: * current line. If showAll is true, include the source note type and the michael@0: * entry stack depth. michael@0: */ michael@0: JS_FRIEND_API(bool) michael@0: js_DisassembleAtPC(JSContext *cx, JSScript *scriptArg, bool lines, michael@0: jsbytecode *pc, bool showAll, Sprinter *sp) michael@0: { michael@0: RootedScript script(cx, scriptArg); michael@0: BytecodeParser parser(cx, script); michael@0: michael@0: jsbytecode *next, *end; michael@0: unsigned len; michael@0: michael@0: if (showAll && !parser.parse()) michael@0: return false; michael@0: michael@0: if (showAll) michael@0: Sprint(sp, "%s:%u\n", script->filename(), script->lineno()); michael@0: michael@0: if (pc != nullptr) michael@0: sp->put(" "); michael@0: if (showAll) michael@0: sp->put("sn stack "); michael@0: sp->put("loc "); michael@0: if (lines) michael@0: sp->put("line"); michael@0: sp->put(" op\n"); michael@0: michael@0: if (pc != nullptr) michael@0: sp->put(" "); michael@0: if (showAll) michael@0: sp->put("-- ----- "); michael@0: sp->put("----- "); michael@0: if (lines) michael@0: sp->put("----"); michael@0: sp->put(" --\n"); michael@0: michael@0: next = script->code(); michael@0: end = script->codeEnd(); michael@0: while (next < end) { michael@0: if (next == script->main()) michael@0: sp->put("main:\n"); michael@0: if (pc != nullptr) { michael@0: if (pc == next) michael@0: sp->put("--> "); michael@0: else michael@0: sp->put(" "); michael@0: } michael@0: if (showAll) { michael@0: jssrcnote *sn = js_GetSrcNote(cx, script, next); michael@0: if (sn) { michael@0: JS_ASSERT(!SN_IS_TERMINATOR(sn)); michael@0: jssrcnote *next = SN_NEXT(sn); michael@0: while (!SN_IS_TERMINATOR(next) && SN_DELTA(next) == 0) { michael@0: Sprint(sp, "%02u\n ", SN_TYPE(sn)); michael@0: sn = next; michael@0: next = SN_NEXT(sn); michael@0: } michael@0: Sprint(sp, "%02u ", SN_TYPE(sn)); michael@0: } michael@0: else michael@0: sp->put(" "); michael@0: if (parser.isReachable(next)) michael@0: Sprint(sp, "%05u ", parser.stackDepthAtPC(next)); michael@0: else michael@0: Sprint(sp, " "); michael@0: } michael@0: len = js_Disassemble1(cx, script, next, script->pcToOffset(next), lines, sp); michael@0: if (!len) michael@0: return false; michael@0: next += len; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js_Disassemble(JSContext *cx, HandleScript script, bool lines, Sprinter *sp) michael@0: { michael@0: return js_DisassembleAtPC(cx, script, lines, nullptr, false, sp); michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js_DumpPC(JSContext *cx) michael@0: { michael@0: js::gc::AutoSuppressGC suppressGC(cx); michael@0: Sprinter sprinter(cx); michael@0: if (!sprinter.init()) michael@0: return false; michael@0: ScriptFrameIter iter(cx); michael@0: RootedScript script(cx, iter.script()); michael@0: bool ok = js_DisassembleAtPC(cx, script, true, iter.pc(), false, &sprinter); michael@0: fprintf(stdout, "%s", sprinter.string()); michael@0: return ok; michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js_DumpScript(JSContext *cx, JSScript *scriptArg) michael@0: { michael@0: js::gc::AutoSuppressGC suppressGC(cx); michael@0: Sprinter sprinter(cx); michael@0: if (!sprinter.init()) michael@0: return false; michael@0: RootedScript script(cx, scriptArg); michael@0: bool ok = js_Disassemble(cx, script, true, &sprinter); michael@0: fprintf(stdout, "%s", sprinter.string()); michael@0: return ok; michael@0: } michael@0: michael@0: /* michael@0: * Useful to debug ReconstructPCStack. michael@0: */ michael@0: JS_FRIEND_API(bool) michael@0: js_DumpScriptDepth(JSContext *cx, JSScript *scriptArg, jsbytecode *pc) michael@0: { michael@0: js::gc::AutoSuppressGC suppressGC(cx); michael@0: Sprinter sprinter(cx); michael@0: if (!sprinter.init()) michael@0: return false; michael@0: RootedScript script(cx, scriptArg); michael@0: bool ok = js_DisassembleAtPC(cx, script, true, pc, true, &sprinter); michael@0: fprintf(stdout, "%s", sprinter.string()); michael@0: return ok; michael@0: } michael@0: michael@0: static char * michael@0: QuoteString(Sprinter *sp, JSString *str, uint32_t quote); michael@0: michael@0: static bool michael@0: ToDisassemblySource(JSContext *cx, HandleValue v, JSAutoByteString *bytes) michael@0: { michael@0: if (JSVAL_IS_STRING(v)) { michael@0: Sprinter sprinter(cx); michael@0: if (!sprinter.init()) michael@0: return false; michael@0: char *nbytes = QuoteString(&sprinter, JSVAL_TO_STRING(v), '"'); michael@0: if (!nbytes) michael@0: return false; michael@0: nbytes = JS_sprintf_append(nullptr, "%s", nbytes); michael@0: if (!nbytes) michael@0: return false; michael@0: bytes->initBytes(nbytes); michael@0: return true; michael@0: } michael@0: michael@0: if (cx->runtime()->isHeapBusy() || cx->runtime()->noGCOrAllocationCheck) { michael@0: char *source = JS_sprintf_append(nullptr, ""); michael@0: if (!source) michael@0: return false; michael@0: bytes->initBytes(source); michael@0: return true; michael@0: } michael@0: michael@0: if (!JSVAL_IS_PRIMITIVE(v)) { michael@0: JSObject *obj = JSVAL_TO_OBJECT(v); michael@0: if (obj->is()) { michael@0: Rooted block(cx, &obj->as()); michael@0: char *source = JS_sprintf_append(nullptr, "depth %d {", block->localOffset()); michael@0: if (!source) michael@0: return false; michael@0: michael@0: Shape::Range r(cx, block->lastProperty()); michael@0: michael@0: while (!r.empty()) { michael@0: Rooted shape(cx, &r.front()); michael@0: JSAtom *atom = JSID_IS_INT(shape->propid()) michael@0: ? cx->names().empty michael@0: : JSID_TO_ATOM(shape->propid()); michael@0: michael@0: JSAutoByteString bytes; michael@0: if (!AtomToPrintableString(cx, atom, &bytes)) michael@0: return false; michael@0: michael@0: r.popFront(); michael@0: source = JS_sprintf_append(source, "%s: %d%s", michael@0: bytes.ptr(), michael@0: block->shapeToIndex(*shape), michael@0: !r.empty() ? ", " : ""); michael@0: if (!source) michael@0: return false; michael@0: } michael@0: michael@0: source = JS_sprintf_append(source, "}"); michael@0: if (!source) michael@0: return false; michael@0: bytes->initBytes(source); michael@0: return true; michael@0: } michael@0: michael@0: if (obj->is()) { michael@0: RootedFunction fun(cx, &obj->as()); michael@0: JSString *str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT); michael@0: if (!str) michael@0: return false; michael@0: return bytes->encodeLatin1(cx, str); michael@0: } michael@0: michael@0: if (obj->is()) { michael@0: JSString *source = obj->as().toString(cx); michael@0: if (!source) michael@0: return false; michael@0: JS::Anchor anchor(source); michael@0: return bytes->encodeLatin1(cx, source); michael@0: } michael@0: } michael@0: michael@0: return !!js_ValueToPrintable(cx, v, bytes, true); michael@0: } michael@0: michael@0: unsigned michael@0: js_Disassemble1(JSContext *cx, HandleScript script, jsbytecode *pc, michael@0: unsigned loc, bool lines, Sprinter *sp) michael@0: { michael@0: JSOp op = (JSOp)*pc; michael@0: if (op >= JSOP_LIMIT) { michael@0: char numBuf1[12], numBuf2[12]; michael@0: JS_snprintf(numBuf1, sizeof numBuf1, "%d", op); michael@0: JS_snprintf(numBuf2, sizeof numBuf2, "%d", JSOP_LIMIT); michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_BYTECODE_TOO_BIG, numBuf1, numBuf2); michael@0: return 0; michael@0: } michael@0: const JSCodeSpec *cs = &js_CodeSpec[op]; michael@0: ptrdiff_t len = (ptrdiff_t) cs->length; michael@0: Sprint(sp, "%05u:", loc); michael@0: if (lines) michael@0: Sprint(sp, "%4u", JS_PCToLineNumber(cx, script, pc)); michael@0: Sprint(sp, " %s", js_CodeName[op]); michael@0: michael@0: switch (JOF_TYPE(cs->format)) { michael@0: case JOF_BYTE: michael@0: // Scan the trynotes to find the associated catch block michael@0: // and make the try opcode look like a jump instruction michael@0: // with an offset. This simplifies code coverage analysis michael@0: // based on this disassembled output. michael@0: if (op == JSOP_TRY) { michael@0: TryNoteArray *trynotes = script->trynotes(); michael@0: uint32_t i; michael@0: for(i = 0; i < trynotes->length; i++) { michael@0: JSTryNote note = trynotes->vector[i]; michael@0: if (note.kind == JSTRY_CATCH && note.start == loc + 1) { michael@0: Sprint(sp, " %u (%+d)", michael@0: (unsigned int) (loc+note.length+1), michael@0: (int) (note.length+1)); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case JOF_JUMP: { michael@0: ptrdiff_t off = GET_JUMP_OFFSET(pc); michael@0: Sprint(sp, " %u (%+d)", loc + (int) off, (int) off); michael@0: break; michael@0: } michael@0: michael@0: case JOF_SCOPECOORD: { michael@0: RootedValue v(cx, michael@0: StringValue(ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc))); michael@0: JSAutoByteString bytes; michael@0: if (!ToDisassemblySource(cx, v, &bytes)) michael@0: return 0; michael@0: ScopeCoordinate sc(pc); michael@0: Sprint(sp, " %s (hops = %u, slot = %u)", bytes.ptr(), sc.hops(), sc.slot()); michael@0: break; michael@0: } michael@0: michael@0: case JOF_ATOM: { michael@0: RootedValue v(cx, StringValue(script->getAtom(GET_UINT32_INDEX(pc)))); michael@0: JSAutoByteString bytes; michael@0: if (!ToDisassemblySource(cx, v, &bytes)) michael@0: return 0; michael@0: Sprint(sp, " %s", bytes.ptr()); michael@0: break; michael@0: } michael@0: michael@0: case JOF_DOUBLE: { michael@0: RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc))); michael@0: JSAutoByteString bytes; michael@0: if (!ToDisassemblySource(cx, v, &bytes)) michael@0: return 0; michael@0: Sprint(sp, " %s", bytes.ptr()); michael@0: break; michael@0: } michael@0: michael@0: case JOF_OBJECT: { michael@0: /* Don't call obj.toSource if analysis/inference is active. */ michael@0: if (script->compartment()->activeAnalysis) { michael@0: Sprint(sp, " object"); michael@0: break; michael@0: } michael@0: michael@0: JSObject *obj = script->getObject(GET_UINT32_INDEX(pc)); michael@0: { michael@0: JSAutoByteString bytes; michael@0: RootedValue v(cx, ObjectValue(*obj)); michael@0: if (!ToDisassemblySource(cx, v, &bytes)) michael@0: return 0; michael@0: Sprint(sp, " %s", bytes.ptr()); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case JOF_REGEXP: { michael@0: JSObject *obj = script->getRegExp(GET_UINT32_INDEX(pc)); michael@0: JSAutoByteString bytes; michael@0: RootedValue v(cx, ObjectValue(*obj)); michael@0: if (!ToDisassemblySource(cx, v, &bytes)) michael@0: return 0; michael@0: Sprint(sp, " %s", bytes.ptr()); michael@0: break; michael@0: } michael@0: michael@0: case JOF_TABLESWITCH: michael@0: { michael@0: int32_t i, low, high; michael@0: michael@0: ptrdiff_t off = GET_JUMP_OFFSET(pc); michael@0: jsbytecode *pc2 = pc + JUMP_OFFSET_LEN; michael@0: low = GET_JUMP_OFFSET(pc2); michael@0: pc2 += JUMP_OFFSET_LEN; michael@0: high = GET_JUMP_OFFSET(pc2); michael@0: pc2 += JUMP_OFFSET_LEN; michael@0: Sprint(sp, " defaultOffset %d low %d high %d", int(off), low, high); michael@0: for (i = low; i <= high; i++) { michael@0: off = GET_JUMP_OFFSET(pc2); michael@0: Sprint(sp, "\n\t%d: %d", i, int(off)); michael@0: pc2 += JUMP_OFFSET_LEN; michael@0: } michael@0: len = 1 + pc2 - pc; michael@0: break; michael@0: } michael@0: michael@0: case JOF_QARG: michael@0: Sprint(sp, " %u", GET_ARGNO(pc)); michael@0: break; michael@0: michael@0: case JOF_LOCAL: michael@0: Sprint(sp, " %u", GET_LOCALNO(pc)); michael@0: break; michael@0: michael@0: { michael@0: int i; michael@0: michael@0: case JOF_UINT16: michael@0: i = (int)GET_UINT16(pc); michael@0: goto print_int; michael@0: michael@0: case JOF_UINT24: michael@0: JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY || michael@0: op == JSOP_DUPAT); michael@0: i = (int)GET_UINT24(pc); michael@0: goto print_int; michael@0: michael@0: case JOF_UINT8: michael@0: i = GET_UINT8(pc); michael@0: goto print_int; michael@0: michael@0: case JOF_INT8: michael@0: i = GET_INT8(pc); michael@0: goto print_int; michael@0: michael@0: case JOF_INT32: michael@0: JS_ASSERT(op == JSOP_INT32); michael@0: i = GET_INT32(pc); michael@0: print_int: michael@0: Sprint(sp, " %d", i); michael@0: break; michael@0: } michael@0: michael@0: default: { michael@0: char numBuf[12]; michael@0: JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) cs->format); michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_UNKNOWN_FORMAT, numBuf); michael@0: return 0; michael@0: } michael@0: } michael@0: sp->put("\n"); michael@0: return len; michael@0: } michael@0: michael@0: #endif /* DEBUG */ michael@0: michael@0: /************************************************************************/ michael@0: michael@0: const size_t Sprinter::DefaultSize = 64; michael@0: michael@0: bool michael@0: Sprinter::realloc_(size_t newSize) michael@0: { michael@0: JS_ASSERT(newSize > (size_t) offset); michael@0: char *newBuf = (char *) js_realloc(base, newSize); michael@0: if (!newBuf) { michael@0: reportOutOfMemory(); michael@0: return false; michael@0: } michael@0: base = newBuf; michael@0: size = newSize; michael@0: base[size - 1] = 0; michael@0: return true; michael@0: } michael@0: michael@0: Sprinter::Sprinter(ExclusiveContext *cx) michael@0: : context(cx), michael@0: #ifdef DEBUG michael@0: initialized(false), michael@0: #endif michael@0: base(nullptr), size(0), offset(0), reportedOOM(false) michael@0: { } michael@0: michael@0: Sprinter::~Sprinter() michael@0: { michael@0: #ifdef DEBUG michael@0: if (initialized) michael@0: checkInvariants(); michael@0: #endif michael@0: js_free(base); michael@0: } michael@0: michael@0: bool michael@0: Sprinter::init() michael@0: { michael@0: JS_ASSERT(!initialized); michael@0: base = (char *) js_malloc(DefaultSize); michael@0: if (!base) { michael@0: reportOutOfMemory(); michael@0: return false; michael@0: } michael@0: #ifdef DEBUG michael@0: initialized = true; michael@0: #endif michael@0: *base = 0; michael@0: size = DefaultSize; michael@0: base[size - 1] = 0; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: Sprinter::checkInvariants() const michael@0: { michael@0: JS_ASSERT(initialized); michael@0: JS_ASSERT((size_t) offset < size); michael@0: JS_ASSERT(base[size - 1] == 0); michael@0: } michael@0: michael@0: const char * michael@0: Sprinter::string() const michael@0: { michael@0: return base; michael@0: } michael@0: michael@0: const char * michael@0: Sprinter::stringEnd() const michael@0: { michael@0: return base + offset; michael@0: } michael@0: michael@0: char * michael@0: Sprinter::stringAt(ptrdiff_t off) const michael@0: { michael@0: JS_ASSERT(off >= 0 && (size_t) off < size); michael@0: return base + off; michael@0: } michael@0: michael@0: char & michael@0: Sprinter::operator[](size_t off) michael@0: { michael@0: JS_ASSERT(off < size); michael@0: return *(base + off); michael@0: } michael@0: michael@0: char * michael@0: Sprinter::reserve(size_t len) michael@0: { michael@0: InvariantChecker ic(this); michael@0: michael@0: while (len + 1 > size - offset) { /* Include trailing \0 */ michael@0: if (!realloc_(size * 2)) michael@0: return nullptr; michael@0: } michael@0: michael@0: char *sb = base + offset; michael@0: offset += len; michael@0: return sb; michael@0: } michael@0: michael@0: ptrdiff_t michael@0: Sprinter::put(const char *s, size_t len) michael@0: { michael@0: InvariantChecker ic(this); michael@0: michael@0: const char *oldBase = base; michael@0: const char *oldEnd = base + size; michael@0: michael@0: ptrdiff_t oldOffset = offset; michael@0: char *bp = reserve(len); michael@0: if (!bp) michael@0: return -1; michael@0: michael@0: /* s is within the buffer already */ michael@0: if (s >= oldBase && s < oldEnd) { michael@0: /* buffer was realloc'ed */ michael@0: if (base != oldBase) michael@0: s = stringAt(s - oldBase); /* this is where it lives now */ michael@0: memmove(bp, s, len); michael@0: } else { michael@0: js_memcpy(bp, s, len); michael@0: } michael@0: michael@0: bp[len] = 0; michael@0: return oldOffset; michael@0: } michael@0: michael@0: ptrdiff_t michael@0: Sprinter::put(const char *s) michael@0: { michael@0: return put(s, strlen(s)); michael@0: } michael@0: michael@0: ptrdiff_t michael@0: Sprinter::putString(JSString *s) michael@0: { michael@0: InvariantChecker ic(this); michael@0: michael@0: size_t length = s->length(); michael@0: const jschar *chars = s->getChars(context); michael@0: if (!chars) michael@0: return -1; michael@0: michael@0: size_t size = length; michael@0: if (size == (size_t) -1) michael@0: return -1; michael@0: michael@0: ptrdiff_t oldOffset = offset; michael@0: char *buffer = reserve(size); michael@0: if (!buffer) michael@0: return -1; michael@0: DeflateStringToBuffer(nullptr, chars, length, buffer, &size); michael@0: buffer[size] = 0; michael@0: michael@0: return oldOffset; michael@0: } michael@0: michael@0: int michael@0: Sprinter::printf(const char *fmt, ...) michael@0: { michael@0: InvariantChecker ic(this); michael@0: michael@0: do { michael@0: va_list va; michael@0: va_start(va, fmt); michael@0: int i = vsnprintf(base + offset, size - offset, fmt, va); michael@0: va_end(va); michael@0: michael@0: if (i > -1 && (size_t) i < size - offset) { michael@0: offset += i; michael@0: return i; michael@0: } michael@0: } while (realloc_(size * 2)); michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: ptrdiff_t michael@0: Sprinter::getOffset() const michael@0: { michael@0: return offset; michael@0: } michael@0: michael@0: void michael@0: Sprinter::reportOutOfMemory() michael@0: { michael@0: if (reportedOOM) michael@0: return; michael@0: if (context) michael@0: js_ReportOutOfMemory(context); michael@0: reportedOOM = true; michael@0: } michael@0: michael@0: bool michael@0: Sprinter::hadOutOfMemory() const michael@0: { michael@0: return reportedOOM; michael@0: } michael@0: michael@0: ptrdiff_t michael@0: js::Sprint(Sprinter *sp, const char *format, ...) michael@0: { michael@0: va_list ap; michael@0: char *bp; michael@0: ptrdiff_t offset; michael@0: michael@0: va_start(ap, format); michael@0: bp = JS_vsmprintf(format, ap); /* XXX vsaprintf */ michael@0: va_end(ap); michael@0: if (!bp) { michael@0: sp->reportOutOfMemory(); michael@0: return -1; michael@0: } michael@0: offset = sp->put(bp); michael@0: js_free(bp); michael@0: return offset; michael@0: } michael@0: michael@0: const char js_EscapeMap[] = { michael@0: '\b', 'b', michael@0: '\f', 'f', michael@0: '\n', 'n', michael@0: '\r', 'r', michael@0: '\t', 't', michael@0: '\v', 'v', michael@0: '"', '"', michael@0: '\'', '\'', michael@0: '\\', '\\', michael@0: '\0' michael@0: }; michael@0: michael@0: #define DONT_ESCAPE 0x10000 michael@0: michael@0: static char * michael@0: QuoteString(Sprinter *sp, JSString *str, uint32_t quote) michael@0: { michael@0: /* Sample off first for later return value pointer computation. */ michael@0: bool dontEscape = (quote & DONT_ESCAPE) != 0; michael@0: jschar qc = (jschar) quote; michael@0: ptrdiff_t offset = sp->getOffset(); michael@0: if (qc && Sprint(sp, "%c", (char)qc) < 0) michael@0: return nullptr; michael@0: michael@0: const jschar *s = str->getChars(sp->context); michael@0: if (!s) michael@0: return nullptr; michael@0: const jschar *z = s + str->length(); michael@0: michael@0: /* Loop control variables: z points at end of string sentinel. */ michael@0: for (const jschar *t = s; t < z; s = ++t) { michael@0: /* Move t forward from s past un-quote-worthy characters. */ michael@0: jschar c = *t; michael@0: while (c < 127 && isprint(c) && c != qc && c != '\\' && c != '\t') { michael@0: c = *++t; michael@0: if (t == z) michael@0: break; michael@0: } michael@0: michael@0: { michael@0: ptrdiff_t len = t - s; michael@0: ptrdiff_t base = sp->getOffset(); michael@0: char *bp = sp->reserve(len); michael@0: if (!bp) michael@0: return nullptr; michael@0: michael@0: for (ptrdiff_t i = 0; i < len; ++i) michael@0: (*sp)[base + i] = (char) *s++; michael@0: (*sp)[base + len] = 0; michael@0: } michael@0: michael@0: if (t == z) michael@0: break; michael@0: michael@0: /* Use js_EscapeMap, \u, or \x only if necessary. */ michael@0: bool ok; michael@0: const char *e; michael@0: if (!(c >> 8) && c != 0 && (e = strchr(js_EscapeMap, (int)c)) != nullptr) { michael@0: ok = dontEscape michael@0: ? Sprint(sp, "%c", (char)c) >= 0 michael@0: : Sprint(sp, "\\%c", e[1]) >= 0; michael@0: } else { michael@0: /* michael@0: * Use \x only if the high byte is 0 and we're in a quoted string, michael@0: * because ECMA-262 allows only \u, not \x, in Unicode identifiers michael@0: * (see bug 621814). michael@0: */ michael@0: ok = Sprint(sp, (qc && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c) >= 0; michael@0: } michael@0: if (!ok) michael@0: return nullptr; michael@0: } michael@0: michael@0: /* Sprint the closing quote and return the quoted string. */ michael@0: if (qc && Sprint(sp, "%c", (char)qc) < 0) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * If we haven't Sprint'd anything yet, Sprint an empty string so that michael@0: * the return below gives a valid result. michael@0: */ michael@0: if (offset == sp->getOffset() && Sprint(sp, "") < 0) michael@0: return nullptr; michael@0: michael@0: return sp->stringAt(offset); michael@0: } michael@0: michael@0: JSString * michael@0: js_QuoteString(ExclusiveContext *cx, JSString *str, jschar quote) michael@0: { michael@0: Sprinter sprinter(cx); michael@0: if (!sprinter.init()) michael@0: return nullptr; michael@0: char *bytes = QuoteString(&sprinter, str, quote); michael@0: if (!bytes) michael@0: return nullptr; michael@0: return js_NewStringCopyZ(cx, bytes); michael@0: } michael@0: michael@0: /************************************************************************/ michael@0: michael@0: namespace { michael@0: /* michael@0: * The expression decompiler is invoked by error handling code to produce a michael@0: * string representation of the erroring expression. As it's only a debugging michael@0: * tool, it only supports basic expressions. For anything complicated, it simply michael@0: * puts "(intermediate value)" into the error result. michael@0: * michael@0: * Here's the basic algorithm: michael@0: * michael@0: * 1. Find the stack location of the value whose expression we wish to michael@0: * decompile. The error handler can explicitly pass this as an michael@0: * argument. Otherwise, we search backwards down the stack for the offending michael@0: * value. michael@0: * michael@0: * 2. Instantiate and run a BytecodeParser for the current frame. This creates a michael@0: * stack of pcs parallel to the interpreter stack; given an interpreter stack michael@0: * location, the corresponding pc stack location contains the opcode that pushed michael@0: * the value in the interpreter. Now, with the result of step 1, we have the michael@0: * opcode responsible for pushing the value we want to decompile. michael@0: * michael@0: * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler michael@0: * routine, responsible for a string representation of the expression that michael@0: * generated a certain stack location. decompilePC looks at one opcode and michael@0: * returns the JS source equivalent of that opcode. michael@0: * michael@0: * 4. Expressions can, of course, contain subexpressions. For example, the michael@0: * literals "4" and "5" are subexpressions of the addition operator in "4 + michael@0: * 5". If we need to decompile a subexpression, we call decompilePC (step 2) michael@0: * recursively on the operands' pcs. The result is a depth-first traversal of michael@0: * the expression tree. michael@0: * michael@0: */ michael@0: struct ExpressionDecompiler michael@0: { michael@0: JSContext *cx; michael@0: InterpreterFrame *fp; michael@0: RootedScript script; michael@0: RootedFunction fun; michael@0: BindingVector *localNames; michael@0: BytecodeParser parser; michael@0: Sprinter sprinter; michael@0: michael@0: ExpressionDecompiler(JSContext *cx, JSScript *script, JSFunction *fun) michael@0: : cx(cx), michael@0: script(cx, script), michael@0: fun(cx, fun), michael@0: localNames(nullptr), michael@0: parser(cx, script), michael@0: sprinter(cx) michael@0: {} michael@0: ~ExpressionDecompiler(); michael@0: bool init(); michael@0: bool decompilePCForStackOperand(jsbytecode *pc, int i); michael@0: bool decompilePC(jsbytecode *pc); michael@0: JSAtom *getLocal(uint32_t local, jsbytecode *pc); michael@0: JSAtom *getArg(unsigned slot); michael@0: JSAtom *loadAtom(jsbytecode *pc); michael@0: bool quote(JSString *s, uint32_t quote); michael@0: bool write(const char *s); michael@0: bool write(JSString *str); michael@0: bool getOutput(char **out); michael@0: }; michael@0: michael@0: bool michael@0: ExpressionDecompiler::decompilePCForStackOperand(jsbytecode *pc, int i) michael@0: { michael@0: pc = parser.pcForStackOperand(pc, i); michael@0: if (!pc) michael@0: return write("(intermediate value)"); michael@0: return decompilePC(pc); michael@0: } michael@0: michael@0: bool michael@0: ExpressionDecompiler::decompilePC(jsbytecode *pc) michael@0: { michael@0: JS_ASSERT(script->containsPC(pc)); michael@0: michael@0: JSOp op = (JSOp)*pc; michael@0: michael@0: if (const char *token = CodeToken[op]) { michael@0: // Handle simple cases of binary and unary operators. michael@0: switch (js_CodeSpec[op].nuses) { michael@0: case 2: { michael@0: jssrcnote *sn = js_GetSrcNote(cx, script, pc); michael@0: if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP) michael@0: return write("(") && michael@0: decompilePCForStackOperand(pc, -2) && michael@0: write(" ") && michael@0: write(token) && michael@0: write(" ") && michael@0: decompilePCForStackOperand(pc, -1) && michael@0: write(")"); michael@0: break; michael@0: } michael@0: case 1: michael@0: return write(token) && michael@0: write("(") && michael@0: decompilePCForStackOperand(pc, -1) && michael@0: write(")"); michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: switch (op) { michael@0: case JSOP_GETGNAME: michael@0: case JSOP_NAME: michael@0: case JSOP_GETINTRINSIC: michael@0: return write(loadAtom(pc)); michael@0: case JSOP_GETARG: { michael@0: unsigned slot = GET_ARGNO(pc); michael@0: JSAtom *atom = getArg(slot); michael@0: return write(atom); michael@0: } michael@0: case JSOP_GETLOCAL: { michael@0: uint32_t i = GET_LOCALNO(pc); michael@0: if (JSAtom *atom = getLocal(i, pc)) michael@0: return write(atom); michael@0: return write("(intermediate value)"); michael@0: } michael@0: case JSOP_GETALIASEDVAR: { michael@0: JSAtom *atom = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc); michael@0: JS_ASSERT(atom); michael@0: return write(atom); michael@0: } michael@0: case JSOP_LENGTH: michael@0: case JSOP_GETPROP: michael@0: case JSOP_CALLPROP: { michael@0: RootedAtom prop(cx, (op == JSOP_LENGTH) ? cx->names().length : loadAtom(pc)); michael@0: if (!decompilePCForStackOperand(pc, -1)) michael@0: return false; michael@0: if (IsIdentifier(prop)) { michael@0: return write(".") && michael@0: quote(prop, '\0'); michael@0: } michael@0: return write("[") && michael@0: quote(prop, '\'') && michael@0: write("]"); michael@0: } michael@0: case JSOP_GETELEM: michael@0: case JSOP_CALLELEM: michael@0: return decompilePCForStackOperand(pc, -2) && michael@0: write("[") && michael@0: decompilePCForStackOperand(pc, -1) && michael@0: write("]"); michael@0: case JSOP_NULL: michael@0: return write(js_null_str); michael@0: case JSOP_TRUE: michael@0: return write(js_true_str); michael@0: case JSOP_FALSE: michael@0: return write(js_false_str); michael@0: case JSOP_ZERO: michael@0: case JSOP_ONE: michael@0: case JSOP_INT8: michael@0: case JSOP_UINT16: michael@0: case JSOP_UINT24: michael@0: case JSOP_INT32: michael@0: return sprinter.printf("%d", GetBytecodeInteger(pc)) >= 0; michael@0: case JSOP_STRING: michael@0: return quote(loadAtom(pc), '"'); michael@0: case JSOP_UNDEFINED: michael@0: return write(js_undefined_str); michael@0: case JSOP_THIS: michael@0: // |this| could convert to a very long object initialiser, so cite it by michael@0: // its keyword name. michael@0: return write(js_this_str); michael@0: case JSOP_CALL: michael@0: case JSOP_FUNCALL: michael@0: return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) && michael@0: write("(...)"); michael@0: case JSOP_SPREADCALL: michael@0: return decompilePCForStackOperand(pc, -int32_t(3)) && michael@0: write("(...)"); michael@0: case JSOP_NEWARRAY: michael@0: return write("[]"); michael@0: case JSOP_REGEXP: michael@0: case JSOP_OBJECT: { michael@0: JSObject *obj = (op == JSOP_REGEXP) michael@0: ? script->getRegExp(GET_UINT32_INDEX(pc)) michael@0: : script->getObject(GET_UINT32_INDEX(pc)); michael@0: RootedValue objv(cx, ObjectValue(*obj)); michael@0: JSString *str = ValueToSource(cx, objv); michael@0: if (!str) michael@0: return false; michael@0: return write(str); michael@0: } michael@0: default: michael@0: break; michael@0: } michael@0: return write("(intermediate value)"); michael@0: } michael@0: michael@0: ExpressionDecompiler::~ExpressionDecompiler() michael@0: { michael@0: js_delete(localNames); michael@0: } michael@0: michael@0: bool michael@0: ExpressionDecompiler::init() michael@0: { michael@0: assertSameCompartment(cx, script); michael@0: michael@0: if (!sprinter.init()) michael@0: return false; michael@0: michael@0: localNames = cx->new_(cx); michael@0: if (!localNames) michael@0: return false; michael@0: RootedScript script_(cx, script); michael@0: if (!FillBindingVector(script_, localNames)) michael@0: return false; michael@0: michael@0: if (!parser.parse()) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ExpressionDecompiler::write(const char *s) michael@0: { michael@0: return sprinter.put(s) >= 0; michael@0: } michael@0: michael@0: bool michael@0: ExpressionDecompiler::write(JSString *str) michael@0: { michael@0: return sprinter.putString(str) >= 0; michael@0: } michael@0: michael@0: bool michael@0: ExpressionDecompiler::quote(JSString *s, uint32_t quote) michael@0: { michael@0: return QuoteString(&sprinter, s, quote) >= 0; michael@0: } michael@0: michael@0: JSAtom * michael@0: ExpressionDecompiler::loadAtom(jsbytecode *pc) michael@0: { michael@0: return script->getAtom(GET_UINT32_INDEX(pc)); michael@0: } michael@0: michael@0: JSAtom * michael@0: ExpressionDecompiler::getArg(unsigned slot) michael@0: { michael@0: JS_ASSERT(fun); michael@0: JS_ASSERT(slot < script->bindings.count()); michael@0: return (*localNames)[slot].name(); michael@0: } michael@0: michael@0: JSAtom * michael@0: ExpressionDecompiler::getLocal(uint32_t local, jsbytecode *pc) michael@0: { michael@0: JS_ASSERT(local < script->nfixed()); michael@0: if (local < script->nfixedvars()) { michael@0: JS_ASSERT(fun); michael@0: uint32_t slot = local + fun->nargs(); michael@0: JS_ASSERT(slot < script->bindings.count()); michael@0: return (*localNames)[slot].name(); michael@0: } michael@0: for (NestedScopeObject *chain = script->getStaticScope(pc); michael@0: chain; michael@0: chain = chain->enclosingNestedScope()) { michael@0: if (!chain->is()) michael@0: continue; michael@0: StaticBlockObject &block = chain->as(); michael@0: if (local < block.localOffset()) michael@0: continue; michael@0: local -= block.localOffset(); michael@0: if (local >= block.numVariables()) michael@0: return nullptr; michael@0: for (Shape::Range r(block.lastProperty()); !r.empty(); r.popFront()) { michael@0: const Shape &shape = r.front(); michael@0: if (block.shapeToIndex(shape) == local) michael@0: return JSID_TO_ATOM(shape.propid()); michael@0: } michael@0: break; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: ExpressionDecompiler::getOutput(char **res) michael@0: { michael@0: ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0); michael@0: *res = cx->pod_malloc(len + 1); michael@0: if (!*res) michael@0: return false; michael@0: js_memcpy(*res, sprinter.stringAt(0), len); michael@0: (*res)[len] = 0; michael@0: return true; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: static bool michael@0: FindStartPC(JSContext *cx, const FrameIter &iter, int spindex, int skipStackHits, Value v, michael@0: jsbytecode **valuepc) michael@0: { michael@0: jsbytecode *current = *valuepc; michael@0: michael@0: if (spindex == JSDVG_IGNORE_STACK) michael@0: return true; michael@0: michael@0: /* michael@0: * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the michael@0: * previous pc (see bug 831120). michael@0: */ michael@0: if (iter.isIon()) michael@0: return true; michael@0: michael@0: *valuepc = nullptr; michael@0: michael@0: BytecodeParser parser(cx, iter.script()); michael@0: if (!parser.parse()) michael@0: return false; michael@0: michael@0: if (spindex < 0 && spindex + int(parser.stackDepthAtPC(current)) < 0) michael@0: spindex = JSDVG_SEARCH_STACK; michael@0: michael@0: if (spindex == JSDVG_SEARCH_STACK) { michael@0: size_t index = iter.numFrameSlots(); michael@0: JS_ASSERT(index >= size_t(parser.stackDepthAtPC(current))); michael@0: michael@0: // We search from fp->sp to base to find the most recently calculated michael@0: // value matching v under assumption that it is the value that caused michael@0: // the exception. michael@0: int stackHits = 0; michael@0: Value s; michael@0: do { michael@0: if (!index) michael@0: return true; michael@0: s = iter.frameSlotValue(--index); michael@0: } while (s != v || stackHits++ != skipStackHits); michael@0: michael@0: // If the current PC has fewer values on the stack than the index we are michael@0: // looking for, the blamed value must be one pushed by the current michael@0: // bytecode, so restore *valuepc. michael@0: jsbytecode *pc = nullptr; michael@0: if (index < size_t(parser.stackDepthAtPC(current))) michael@0: pc = parser.pcForStackOperand(current, index); michael@0: *valuepc = pc ? pc : current; michael@0: } else { michael@0: jsbytecode *pc = parser.pcForStackOperand(current, spindex); michael@0: *valuepc = pc ? pc : current; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DecompileExpressionFromStack(JSContext *cx, int spindex, int skipStackHits, HandleValue v, char **res) michael@0: { michael@0: JS_ASSERT(spindex < 0 || michael@0: spindex == JSDVG_IGNORE_STACK || michael@0: spindex == JSDVG_SEARCH_STACK); michael@0: michael@0: *res = nullptr; michael@0: michael@0: #ifdef JS_MORE_DETERMINISTIC michael@0: /* michael@0: * Give up if we need deterministic behavior for differential testing. michael@0: * IonMonkey doesn't use InterpreterFrames and this ensures we get the same michael@0: * error messages. michael@0: */ michael@0: return true; michael@0: #endif michael@0: michael@0: FrameIter frameIter(cx); michael@0: michael@0: if (frameIter.done() || !frameIter.hasScript()) michael@0: return true; michael@0: michael@0: RootedScript script(cx, frameIter.script()); michael@0: AutoCompartment ac(cx, &script->global()); michael@0: jsbytecode *valuepc = frameIter.pc(); michael@0: RootedFunction fun(cx, frameIter.isFunctionFrame() michael@0: ? frameIter.callee() michael@0: : nullptr); michael@0: michael@0: JS_ASSERT(script->containsPC(valuepc)); michael@0: michael@0: // Give up if in prologue. michael@0: if (valuepc < script->main()) michael@0: return true; michael@0: michael@0: if (!FindStartPC(cx, frameIter, spindex, skipStackHits, v, &valuepc)) michael@0: return false; michael@0: if (!valuepc) michael@0: return true; michael@0: michael@0: ExpressionDecompiler ed(cx, script, fun); michael@0: if (!ed.init()) michael@0: return false; michael@0: if (!ed.decompilePC(valuepc)) michael@0: return false; michael@0: michael@0: return ed.getOutput(res); michael@0: } michael@0: michael@0: char * michael@0: js::DecompileValueGenerator(JSContext *cx, int spindex, HandleValue v, michael@0: HandleString fallbackArg, int skipStackHits) michael@0: { michael@0: RootedString fallback(cx, fallbackArg); michael@0: { michael@0: char *result; michael@0: if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result)) michael@0: return nullptr; michael@0: if (result) { michael@0: if (strcmp(result, "(intermediate value)")) michael@0: return result; michael@0: js_free(result); michael@0: } michael@0: } michael@0: if (!fallback) { michael@0: if (v.isUndefined()) michael@0: return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)" michael@0: fallback = ValueToSource(cx, v); michael@0: if (!fallback) michael@0: return nullptr; michael@0: } michael@0: michael@0: Rooted linear(cx, fallback->ensureLinear(cx)); michael@0: if (!linear) michael@0: return nullptr; michael@0: TwoByteChars tbchars(linear->chars(), linear->length()); michael@0: return LossyTwoByteCharsToNewLatin1CharsZ(cx, tbchars).c_str(); michael@0: } michael@0: michael@0: static bool michael@0: DecompileArgumentFromStack(JSContext *cx, int formalIndex, char **res) michael@0: { michael@0: JS_ASSERT(formalIndex >= 0); michael@0: michael@0: *res = nullptr; michael@0: michael@0: #ifdef JS_MORE_DETERMINISTIC michael@0: /* See note in DecompileExpressionFromStack. */ michael@0: return true; michael@0: #endif michael@0: michael@0: /* michael@0: * Settle on the nearest script frame, which should be the builtin that michael@0: * called the intrinsic. michael@0: */ michael@0: FrameIter frameIter(cx); michael@0: JS_ASSERT(!frameIter.done()); michael@0: michael@0: /* michael@0: * Get the second-to-top frame, the caller of the builtin that called the michael@0: * intrinsic. michael@0: */ michael@0: ++frameIter; michael@0: if (frameIter.done() || !frameIter.hasScript()) michael@0: return true; michael@0: michael@0: RootedScript script(cx, frameIter.script()); michael@0: AutoCompartment ac(cx, &script->global()); michael@0: jsbytecode *current = frameIter.pc(); michael@0: RootedFunction fun(cx, frameIter.isFunctionFrame() michael@0: ? frameIter.callee() michael@0: : nullptr); michael@0: michael@0: JS_ASSERT(script->containsPC(current)); michael@0: michael@0: if (current < script->main()) michael@0: return true; michael@0: michael@0: /* Don't handle getters, setters or calls from fun.call/fun.apply. */ michael@0: if (JSOp(*current) != JSOP_CALL || static_cast(formalIndex) >= GET_ARGC(current)) michael@0: return true; michael@0: michael@0: BytecodeParser parser(cx, script); michael@0: if (!parser.parse()) michael@0: return false; michael@0: michael@0: int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) + formalIndex; michael@0: JS_ASSERT(formalStackIndex >= 0); michael@0: if (uint32_t(formalStackIndex) >= parser.stackDepthAtPC(current)) michael@0: return true; michael@0: michael@0: ExpressionDecompiler ed(cx, script, fun); michael@0: if (!ed.init()) michael@0: return false; michael@0: if (!ed.decompilePCForStackOperand(current, formalStackIndex)) michael@0: return false; michael@0: michael@0: return ed.getOutput(res); michael@0: } michael@0: michael@0: char * michael@0: js::DecompileArgument(JSContext *cx, int formalIndex, HandleValue v) michael@0: { michael@0: { michael@0: char *result; michael@0: if (!DecompileArgumentFromStack(cx, formalIndex, &result)) michael@0: return nullptr; michael@0: if (result) { michael@0: if (strcmp(result, "(intermediate value)")) michael@0: return result; michael@0: js_free(result); michael@0: } michael@0: } michael@0: if (v.isUndefined()) michael@0: return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)" michael@0: RootedString fallback(cx, ValueToSource(cx, v)); michael@0: if (!fallback) michael@0: return nullptr; michael@0: michael@0: Rooted linear(cx, fallback->ensureLinear(cx)); michael@0: if (!linear) michael@0: return nullptr; michael@0: return LossyTwoByteCharsToNewLatin1CharsZ(cx, linear->range()).c_str(); michael@0: } michael@0: michael@0: bool michael@0: js::CallResultEscapes(jsbytecode *pc) michael@0: { michael@0: /* michael@0: * If we see any of these sequences, the result is unused: michael@0: * - call / pop michael@0: * michael@0: * If we see any of these sequences, the result is only tested for nullness: michael@0: * - call / ifeq michael@0: * - call / not / ifeq michael@0: */ michael@0: michael@0: if (*pc == JSOP_CALL) michael@0: pc += JSOP_CALL_LENGTH; michael@0: else if (*pc == JSOP_SPREADCALL) michael@0: pc += JSOP_SPREADCALL_LENGTH; michael@0: else michael@0: return true; michael@0: michael@0: if (*pc == JSOP_POP) michael@0: return false; michael@0: michael@0: if (*pc == JSOP_NOT) michael@0: pc += JSOP_NOT_LENGTH; michael@0: michael@0: return *pc != JSOP_IFEQ; michael@0: } michael@0: michael@0: extern bool michael@0: js::IsValidBytecodeOffset(JSContext *cx, JSScript *script, size_t offset) michael@0: { michael@0: // This could be faster (by following jump instructions if the target is <= offset). michael@0: for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) { michael@0: size_t here = r.frontOffset(); michael@0: if (here >= offset) michael@0: return here == offset; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: JS_FRIEND_API(size_t) michael@0: js::GetPCCountScriptCount(JSContext *cx) michael@0: { michael@0: JSRuntime *rt = cx->runtime(); michael@0: michael@0: if (!rt->scriptAndCountsVector) michael@0: return 0; michael@0: michael@0: return rt->scriptAndCountsVector->length(); michael@0: } michael@0: michael@0: enum MaybeComma {NO_COMMA, COMMA}; michael@0: michael@0: static void michael@0: AppendJSONProperty(StringBuffer &buf, const char *name, MaybeComma comma = COMMA) michael@0: { michael@0: if (comma) michael@0: buf.append(','); michael@0: michael@0: buf.append('\"'); michael@0: buf.appendInflated(name, strlen(name)); michael@0: buf.appendInflated("\":", 2); michael@0: } michael@0: michael@0: static void michael@0: AppendArrayJSONProperties(JSContext *cx, StringBuffer &buf, michael@0: double *values, const char * const *names, unsigned count, michael@0: MaybeComma &comma) michael@0: { michael@0: for (unsigned i = 0; i < count; i++) { michael@0: if (values[i]) { michael@0: AppendJSONProperty(buf, names[i], comma); michael@0: comma = COMMA; michael@0: NumberValueToStringBuffer(cx, DoubleValue(values[i]), buf); michael@0: } michael@0: } michael@0: } michael@0: michael@0: JS_FRIEND_API(JSString *) michael@0: js::GetPCCountScriptSummary(JSContext *cx, size_t index) michael@0: { michael@0: JSRuntime *rt = cx->runtime(); michael@0: michael@0: if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL); michael@0: return nullptr; michael@0: } michael@0: michael@0: const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index]; michael@0: RootedScript script(cx, sac.script); michael@0: michael@0: /* michael@0: * OOM on buffer appends here will not be caught immediately, but since michael@0: * StringBuffer uses a ContextAllocPolicy will trigger an exception on the michael@0: * context if they occur, which we'll catch before returning. michael@0: */ michael@0: StringBuffer buf(cx); michael@0: michael@0: buf.append('{'); michael@0: michael@0: AppendJSONProperty(buf, "file", NO_COMMA); michael@0: JSString *str = JS_NewStringCopyZ(cx, script->filename()); michael@0: if (!str || !(str = StringToSource(cx, str))) michael@0: return nullptr; michael@0: buf.append(str); michael@0: michael@0: AppendJSONProperty(buf, "line"); michael@0: NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf); michael@0: michael@0: if (script->functionNonDelazifying()) { michael@0: JSAtom *atom = script->functionNonDelazifying()->displayAtom(); michael@0: if (atom) { michael@0: AppendJSONProperty(buf, "name"); michael@0: if (!(str = StringToSource(cx, atom))) michael@0: return nullptr; michael@0: buf.append(str); michael@0: } michael@0: } michael@0: michael@0: double baseTotals[PCCounts::BASE_LIMIT] = {0.0}; michael@0: double accessTotals[PCCounts::ACCESS_LIMIT - PCCounts::BASE_LIMIT] = {0.0}; michael@0: double elementTotals[PCCounts::ELEM_LIMIT - PCCounts::ACCESS_LIMIT] = {0.0}; michael@0: double propertyTotals[PCCounts::PROP_LIMIT - PCCounts::ACCESS_LIMIT] = {0.0}; michael@0: double arithTotals[PCCounts::ARITH_LIMIT - PCCounts::BASE_LIMIT] = {0.0}; michael@0: michael@0: for (unsigned i = 0; i < script->length(); i++) { michael@0: PCCounts &counts = sac.getPCCounts(script->offsetToPC(i)); michael@0: if (!counts) michael@0: continue; michael@0: michael@0: JSOp op = (JSOp)script->code()[i]; michael@0: unsigned numCounts = PCCounts::numCounts(op); michael@0: michael@0: for (unsigned j = 0; j < numCounts; j++) { michael@0: double value = counts.get(j); michael@0: if (j < PCCounts::BASE_LIMIT) { michael@0: baseTotals[j] += value; michael@0: } else if (PCCounts::accessOp(op)) { michael@0: if (j < PCCounts::ACCESS_LIMIT) michael@0: accessTotals[j - PCCounts::BASE_LIMIT] += value; michael@0: else if (PCCounts::elementOp(op)) michael@0: elementTotals[j - PCCounts::ACCESS_LIMIT] += value; michael@0: else if (PCCounts::propertyOp(op)) michael@0: propertyTotals[j - PCCounts::ACCESS_LIMIT] += value; michael@0: else michael@0: MOZ_ASSUME_UNREACHABLE("Bad opcode"); michael@0: } else if (PCCounts::arithOp(op)) { michael@0: arithTotals[j - PCCounts::BASE_LIMIT] += value; michael@0: } else { michael@0: MOZ_ASSUME_UNREACHABLE("Bad opcode"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: AppendJSONProperty(buf, "totals"); michael@0: buf.append('{'); michael@0: michael@0: MaybeComma comma = NO_COMMA; michael@0: michael@0: AppendArrayJSONProperties(cx, buf, baseTotals, countBaseNames, michael@0: JS_ARRAY_LENGTH(baseTotals), comma); michael@0: AppendArrayJSONProperties(cx, buf, accessTotals, countAccessNames, michael@0: JS_ARRAY_LENGTH(accessTotals), comma); michael@0: AppendArrayJSONProperties(cx, buf, elementTotals, countElementNames, michael@0: JS_ARRAY_LENGTH(elementTotals), comma); michael@0: AppendArrayJSONProperties(cx, buf, propertyTotals, countPropertyNames, michael@0: JS_ARRAY_LENGTH(propertyTotals), comma); michael@0: AppendArrayJSONProperties(cx, buf, arithTotals, countArithNames, michael@0: JS_ARRAY_LENGTH(arithTotals), comma); michael@0: michael@0: uint64_t ionActivity = 0; michael@0: jit::IonScriptCounts *ionCounts = sac.getIonCounts(); michael@0: while (ionCounts) { michael@0: for (size_t i = 0; i < ionCounts->numBlocks(); i++) michael@0: ionActivity += ionCounts->block(i).hitCount(); michael@0: ionCounts = ionCounts->previous(); michael@0: } michael@0: if (ionActivity) { michael@0: AppendJSONProperty(buf, "ion", comma); michael@0: NumberValueToStringBuffer(cx, DoubleValue(ionActivity), buf); michael@0: } michael@0: michael@0: buf.append('}'); michael@0: buf.append('}'); michael@0: michael@0: if (cx->isExceptionPending()) michael@0: return nullptr; michael@0: michael@0: return buf.finishString(); michael@0: } michael@0: michael@0: static bool michael@0: GetPCCountJSON(JSContext *cx, const ScriptAndCounts &sac, StringBuffer &buf) michael@0: { michael@0: RootedScript script(cx, sac.script); michael@0: michael@0: buf.append('{'); michael@0: AppendJSONProperty(buf, "text", NO_COMMA); michael@0: michael@0: JSString *str = JS_DecompileScript(cx, script, nullptr, 0); michael@0: if (!str || !(str = StringToSource(cx, str))) michael@0: return false; michael@0: michael@0: buf.append(str); michael@0: michael@0: AppendJSONProperty(buf, "line"); michael@0: NumberValueToStringBuffer(cx, Int32Value(script->lineno()), buf); michael@0: michael@0: AppendJSONProperty(buf, "opcodes"); michael@0: buf.append('['); michael@0: bool comma = false; michael@0: michael@0: SrcNoteLineScanner scanner(script->notes(), script->lineno()); michael@0: michael@0: for (jsbytecode *pc = script->code(); pc < script->codeEnd(); pc += GetBytecodeLength(pc)) { michael@0: size_t offset = script->pcToOffset(pc); michael@0: michael@0: JSOp op = (JSOp) *pc; michael@0: michael@0: if (comma) michael@0: buf.append(','); michael@0: comma = true; michael@0: michael@0: buf.append('{'); michael@0: michael@0: AppendJSONProperty(buf, "id", NO_COMMA); michael@0: NumberValueToStringBuffer(cx, Int32Value(offset), buf); michael@0: michael@0: scanner.advanceTo(offset); michael@0: michael@0: AppendJSONProperty(buf, "line"); michael@0: NumberValueToStringBuffer(cx, Int32Value(scanner.getLine()), buf); michael@0: michael@0: { michael@0: const char *name = js_CodeName[op]; michael@0: AppendJSONProperty(buf, "name"); michael@0: buf.append('\"'); michael@0: buf.appendInflated(name, strlen(name)); michael@0: buf.append('\"'); michael@0: } michael@0: michael@0: { michael@0: ExpressionDecompiler ed(cx, script, script->functionDelazifying()); michael@0: if (!ed.init()) michael@0: return false; michael@0: if (!ed.decompilePC(pc)) michael@0: return false; michael@0: char *text; michael@0: if (!ed.getOutput(&text)) michael@0: return false; michael@0: AppendJSONProperty(buf, "text"); michael@0: JSString *str = JS_NewStringCopyZ(cx, text); michael@0: js_free(text); michael@0: if (!str || !(str = StringToSource(cx, str))) michael@0: return false; michael@0: buf.append(str); michael@0: } michael@0: michael@0: PCCounts &counts = sac.getPCCounts(pc); michael@0: unsigned numCounts = PCCounts::numCounts(op); michael@0: michael@0: AppendJSONProperty(buf, "counts"); michael@0: buf.append('{'); michael@0: michael@0: MaybeComma comma = NO_COMMA; michael@0: for (unsigned i = 0; i < numCounts; i++) { michael@0: double value = counts.get(i); michael@0: if (value > 0) { michael@0: AppendJSONProperty(buf, PCCounts::countName(op, i), comma); michael@0: comma = COMMA; michael@0: NumberValueToStringBuffer(cx, DoubleValue(value), buf); michael@0: } michael@0: } michael@0: michael@0: buf.append('}'); michael@0: buf.append('}'); michael@0: } michael@0: michael@0: buf.append(']'); michael@0: michael@0: jit::IonScriptCounts *ionCounts = sac.getIonCounts(); michael@0: if (ionCounts) { michael@0: AppendJSONProperty(buf, "ion"); michael@0: buf.append('['); michael@0: bool comma = false; michael@0: while (ionCounts) { michael@0: if (comma) michael@0: buf.append(','); michael@0: comma = true; michael@0: michael@0: buf.append('['); michael@0: for (size_t i = 0; i < ionCounts->numBlocks(); i++) { michael@0: if (i) michael@0: buf.append(','); michael@0: const jit::IonBlockCounts &block = ionCounts->block(i); michael@0: michael@0: buf.append('{'); michael@0: AppendJSONProperty(buf, "id", NO_COMMA); michael@0: NumberValueToStringBuffer(cx, Int32Value(block.id()), buf); michael@0: AppendJSONProperty(buf, "offset"); michael@0: NumberValueToStringBuffer(cx, Int32Value(block.offset()), buf); michael@0: AppendJSONProperty(buf, "successors"); michael@0: buf.append('['); michael@0: for (size_t j = 0; j < block.numSuccessors(); j++) { michael@0: if (j) michael@0: buf.append(','); michael@0: NumberValueToStringBuffer(cx, Int32Value(block.successor(j)), buf); michael@0: } michael@0: buf.append(']'); michael@0: AppendJSONProperty(buf, "hits"); michael@0: NumberValueToStringBuffer(cx, DoubleValue(block.hitCount()), buf); michael@0: michael@0: AppendJSONProperty(buf, "code"); michael@0: JSString *str = JS_NewStringCopyZ(cx, block.code()); michael@0: if (!str || !(str = StringToSource(cx, str))) michael@0: return false; michael@0: buf.append(str); michael@0: buf.append('}'); michael@0: } michael@0: buf.append(']'); michael@0: michael@0: ionCounts = ionCounts->previous(); michael@0: } michael@0: buf.append(']'); michael@0: } michael@0: michael@0: buf.append('}'); michael@0: michael@0: return !cx->isExceptionPending(); michael@0: } michael@0: michael@0: JS_FRIEND_API(JSString *) michael@0: js::GetPCCountScriptContents(JSContext *cx, size_t index) michael@0: { michael@0: JSRuntime *rt = cx->runtime(); michael@0: michael@0: if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL); michael@0: return nullptr; michael@0: } michael@0: michael@0: const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index]; michael@0: JSScript *script = sac.script; michael@0: michael@0: StringBuffer buf(cx); michael@0: michael@0: if (!script->functionNonDelazifying() && !script->compileAndGo()) michael@0: return buf.finishString(); michael@0: michael@0: { michael@0: AutoCompartment ac(cx, &script->global()); michael@0: if (!GetPCCountJSON(cx, sac, buf)) michael@0: return nullptr; michael@0: } michael@0: michael@0: return buf.finishString(); michael@0: }