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