https://github.com/mozilla/gecko-dev
Tip revision: 0f7f9fad114b25dcae74e0d7f14d5359c64e174e authored by ffxbld on 07 July 2015, 02:40:09 UTC
Added FENNEC_39_0_1_RELEASE FENNEC_39_0_1_BUILD2 tag(s) for changeset 96c214a15c79. DONTBUILD CLOSED TREE a=release
Added FENNEC_39_0_1_RELEASE FENNEC_39_0_1_BUILD2 tag(s) for changeset 96c214a15c79. DONTBUILD CLOSED TREE a=release
Tip revision: 0f7f9fa
jsopcode.cpp
/* -*- 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 "mozilla/SizePrintfMacros.h"
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.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 "asmjs/AsmJSModule.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 "jsobjinlines.h"
#include "jsscriptinlines.h"
using namespace js;
using namespace js::gc;
using JS::AutoCheckCannotGC;
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 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);
MOZ_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_CRASH("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;
MOZ_ASSERT(js_CodeSpec[op].nuses == -1);
switch (op) {
case JSOP_POPN:
return GET_UINT16(pc);
default:
/* stack: fun, this, [argc arguments] */
MOZ_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL ||
op == JSOP_STRICTEVAL || 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];
MOZ_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)
{
MOZ_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_CRASH("bad op");
}
if (arithOp(op))
return countArithNames[which - BASE_LIMIT];
MOZ_CRASH("bad op");
}
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);
Sprint(sp, "BB #%lu [%05u]", block.id(), block.offset());
if (block.description())
Sprint(sp, " [inlined %s]", block.description());
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());
}
}
void
js::DumpPCCounts(JSContext* cx, HandleScript script, Sprinter* sp)
{
MOZ_ASSERT(script->hasScriptCounts());
#ifdef DEBUG
jsbytecode* pc = script->code();
while (pc < script->codeEnd()) {
JSOp op = JSOp(*pc);
jsbytecode* next = GetNextPc(pc);
if (!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
jit::IonScriptCounts* ionCounts = script->getIonCounts();
while (ionCounts) {
DumpIonScriptCounts(sp, ionCounts);
ionCounts = ionCounts->previous();
}
}
void
js::DumpCompartmentPCCounts(JSContext* cx)
{
for (ZoneCellIter i(cx->zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
RootedScript script(cx, i.get<JSScript>());
if (script->compartment() != cx->compartment())
continue;
if (script->hasScriptCounts()) {
Sprinter sprinter(cx);
if (!sprinter.init())
return;
fprintf(stdout, "--- SCRIPT %s:%" PRIuSIZE " ---\n", script->filename(), script->lineno());
DumpPCCounts(cx, script, &sprinter);
fputs(sprinter.string(), stdout);
fprintf(stdout, "--- END SCRIPT %s:%" PRIuSIZE " ---\n", script->filename(), script->lineno());
}
}
for (auto thingKind : ObjectAllocKinds()) {
for (ZoneCellIter i(cx->zone(), thingKind); !i.done(); i.next()) {
JSObject* obj = i.get<JSObject>();
if (obj->compartment() != cx->compartment())
continue;
if (obj->is<AsmJSModuleObject>()) {
AsmJSModule& module = obj->as<AsmJSModuleObject>().module();
Sprinter sprinter(cx);
if (!sprinter.init())
return;
fprintf(stdout, "--- Asm.js Module ---\n");
for (size_t i = 0; i < module.numFunctionCounts(); i++) {
jit::IonScriptCounts* counts = module.functionCounts(i);
DumpIonScriptCounts(&sprinter, counts);
}
fputs(sprinter.string(), stdout);
fprintf(stdout, "--- END Asm.js Module ---\n");
}
}
}
}
/////////////////////////////////////////////////////////////////////
// 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<uint32_t>(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) {
MOZ_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;
MOZ_ASSERT(operand >= 0);
}
MOZ_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();
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) {
MOZ_ASSERT(offset < script_->length());
MOZ_ASSERT(codeArray_[offset]);
return *codeArray_[offset];
}
Bytecode& getCode(const jsbytecode* pc) { return getCode(script_->pcToOffset(pc)); }
Bytecode* maybeCode(uint32_t offset) {
MOZ_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);
MOZ_ASSERT(stackDepth >= nuses);
stackDepth -= nuses;
MOZ_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. */
MOZ_ASSERT(ndefs == 1);
break;
case JSOP_DUP:
MOZ_ASSERT(ndefs == 2);
if (offsetStack)
offsetStack[stackDepth + 1] = offsetStack[stackDepth];
break;
case JSOP_DUP2:
MOZ_ASSERT(ndefs == 4);
if (offsetStack) {
offsetStack[stackDepth + 2] = offsetStack[stackDepth];
offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1];
}
break;
case JSOP_DUPAT: {
MOZ_ASSERT(ndefs == 1);
jsbytecode* pc = script_->offsetToPC(offset);
unsigned n = GET_UINT24(pc);
MOZ_ASSERT(n < stackDepth);
if (offsetStack)
offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n];
break;
}
case JSOP_SWAP:
MOZ_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)
{
MOZ_ASSERT(offset < script_->length());
Bytecode*& code = codeArray_[offset];
if (!code) {
code = alloc().new_<Bytecode>();
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()
{
MOZ_ASSERT(!codeArray_);
uint32_t length = script_->length();
codeArray_ = alloc().newArray<Bytecode*>(length);
if (!codeArray_) {
reportOOM();
return false;
}
mozilla::PodZero(codeArray_, length);
// Fill in stack depth and definitions at initial bytecode.
Bytecode* startcode = alloc().new_<Bytecode>();
if (!startcode) {
reportOOM();
return false;
}
// Fill in stack depth and definitions at initial bytecode.
uint32_t* offsetStack = alloc().newArray<uint32_t>(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;
MOZ_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_CATCH || tn->kind == JSTRY_FINALLY) {
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)) {
MOZ_ASSERT(successorOffset < script_->length());
Bytecode*& nextcode = codeArray_[successorOffset];
if (!nextcode) {
nextcode = alloc().new_<Bytecode>();
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.
*/
static bool
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:%" PRIuSIZE "\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 = GetSrcNote(cx, script, next);
if (sn) {
MOZ_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 = 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 DisassembleAtPC(cx, script, lines, nullptr, false, sp);
}
JS_FRIEND_API(bool)
js::DumpPC(JSContext* cx)
{
gc::AutoSuppressGC suppressGC(cx);
Sprinter sprinter(cx);
if (!sprinter.init())
return false;
ScriptFrameIter iter(cx);
if (iter.done()) {
fprintf(stdout, "Empty stack.\n");
return true;
}
RootedScript script(cx, iter.script());
bool ok = 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)
{
gc::AutoSuppressGC suppressGC(cx);
Sprinter sprinter(cx);
if (!sprinter.init())
return false;
RootedScript script(cx, scriptArg);
bool ok = Disassemble(cx, script, true, &sprinter);
fprintf(stdout, "%s", sprinter.string());
return ok;
}
static char*
QuoteString(Sprinter* sp, JSString* str, char16_t quote);
static bool
ToDisassemblySource(JSContext* cx, HandleValue v, JSAutoByteString* bytes)
{
if (v.isString()) {
Sprinter sprinter(cx);
if (!sprinter.init())
return false;
char* nbytes = QuoteString(&sprinter, v.toString(), '"');
if (!nbytes)
return false;
nbytes = JS_sprintf_append(nullptr, "%s", nbytes);
if (!nbytes)
return false;
bytes->initBytes(nbytes);
return true;
}
JSRuntime* rt = cx->runtime();
if (rt->isHeapBusy() || !rt->gc.isAllocAllowed()) {
char* source = JS_sprintf_append(nullptr, "<value>");
if (!source)
return false;
bytes->initBytes(source);
return true;
}
if (v.isObject()) {
JSObject& obj = v.toObject();
if (obj.is<StaticBlockObject>()) {
Rooted<StaticBlockObject*> block(cx, &obj.as<StaticBlockObject>());
char* source = JS_sprintf_append(nullptr, "depth %d {", block->localOffset());
if (!source)
return false;
Shape::Range<CanGC> r(cx, block->lastProperty());
while (!r.empty()) {
Rooted<Shape*> 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<JSFunction>()) {
RootedFunction fun(cx, &obj.as<JSFunction>());
JSString* str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT);
if (!str)
return false;
return bytes->encodeLatin1(cx, str);
}
if (obj.is<RegExpObject>()) {
JSString* source = obj.as<RegExpObject>().toString(cx);
if (!source)
return false;
return bytes->encodeLatin1(cx, source);
}
}
return !!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, 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", PCToLineNumber(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->zone()->types.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;
case JOF_UINT32:
Sprint(sp, " %u", GET_UINT32(pc));
break;
{
int i;
case JOF_UINT16:
i = (int)GET_UINT16(pc);
goto print_int;
case JOF_UINT24:
MOZ_ASSERT(len == 4);
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:
MOZ_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, 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)
{
MOZ_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()
{
MOZ_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
{
MOZ_ASSERT(initialized);
MOZ_ASSERT((size_t) offset < size);
MOZ_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
{
MOZ_ASSERT(off >= 0 && (size_t) off < size);
return base + off;
}
char&
Sprinter::operator[](size_t off)
{
MOZ_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();
size_t size = length;
ptrdiff_t oldOffset = offset;
char* buffer = reserve(size);
if (!buffer)
return -1;
JSLinearString* linear = s->ensureLinear(context);
if (!linear)
return -1;
AutoCheckCannotGC nogc;
if (linear->hasLatin1Chars())
mozilla::PodCopy(reinterpret_cast<Latin1Char*>(buffer), linear->latin1Chars(nogc), length);
else
DeflateStringToBuffer(nullptr, linear->twoByteChars(nogc), 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)
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'
};
template <typename CharT>
static char*
QuoteString(Sprinter* sp, const CharT* s, size_t length, char16_t quote)
{
/* Sample off first for later return value pointer computation. */
ptrdiff_t offset = sp->getOffset();
if (quote && Sprint(sp, "%c", char(quote)) < 0)
return nullptr;
const CharT* end = s + length;
/* Loop control variables: end points at end of string sentinel. */
for (const CharT* t = s; t < end; s = ++t) {
/* Move t forward from s past un-quote-worthy characters. */
char16_t c = *t;
while (c < 127 && isprint(c) && c != quote && c != '\\' && c != '\t') {
c = *++t;
if (t == end)
break;
}
{
ptrdiff_t len = t - s;
ptrdiff_t base = sp->getOffset();
if (!sp->reserve(len))
return nullptr;
for (ptrdiff_t i = 0; i < len; ++i)
(*sp)[base + i] = char(*s++);
(*sp)[base + len] = 0;
}
if (t == end)
break;
/* Use js_EscapeMap, \u, or \x only if necessary. */
const char* escape;
if (!(c >> 8) && c != 0 && (escape = strchr(js_EscapeMap, int(c))) != nullptr) {
if (Sprint(sp, "\\%c", escape[1]) < 0)
return nullptr;
} 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).
*/
if (Sprint(sp, (quote && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c) < 0)
return nullptr;
}
}
/* Sprint the closing quote and return the quoted string. */
if (quote && Sprint(sp, "%c", char(quote)) < 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);
}
static char*
QuoteString(Sprinter* sp, JSString* str, char16_t quote)
{
JSLinearString* linear = str->ensureLinear(sp->context);
if (!linear)
return nullptr;
AutoCheckCannotGC nogc;
return linear->hasLatin1Chars()
? QuoteString(sp, linear->latin1Chars(nogc), linear->length(), quote)
: QuoteString(sp, linear->twoByteChars(nogc), linear->length(), quote);
}
JSString*
js::QuoteString(ExclusiveContext* cx, JSString* str, char16_t quote)
{
Sprinter sprinter(cx);
if (!sprinter.init())
return nullptr;
char* bytes = QuoteString(&sprinter, str, quote);
if (!bytes)
return nullptr;
return NewStringCopyZ<CanGC>(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;
RootedScript script;
RootedFunction fun;
BytecodeParser parser;
Sprinter sprinter;
ExpressionDecompiler(JSContext* cx, JSScript* script, JSFunction* fun)
: cx(cx),
script(cx, script),
fun(cx, fun),
parser(cx, script),
sprinter(cx)
{}
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)
{
MOZ_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 = 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_GETNAME:
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);
MOZ_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_SYMBOL: {
unsigned i = uint8_t(pc[1]);
MOZ_ASSERT(i < JS::WellKnownSymbolLimit);
if (i < JS::WellKnownSymbolLimit)
return write(cx->names().wellKnownSymbolDescriptions()[i]);
break;
}
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:
case JSOP_NEWARRAY_COPYONWRITE: {
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);
}
case JSOP_VOID:
return write("void ") && decompilePCForStackOperand(pc, -1);
default:
break;
}
return write("(intermediate value)");
}
bool
ExpressionDecompiler::init()
{
assertSameCompartment(cx, script);
if (!sprinter.init())
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) != nullptr;
}
JSAtom*
ExpressionDecompiler::loadAtom(jsbytecode* pc)
{
return script->getAtom(GET_UINT32_INDEX(pc));
}
JSAtom*
ExpressionDecompiler::getArg(unsigned slot)
{
MOZ_ASSERT(fun);
MOZ_ASSERT(slot < script->bindings.numArgs());
for (BindingIter bi(script); bi; bi++) {
MOZ_ASSERT(bi->kind() == Binding::ARGUMENT);
if (bi.argIndex() == slot)
return bi->name();
}
MOZ_CRASH("No binding");
}
JSAtom*
ExpressionDecompiler::getLocal(uint32_t local, jsbytecode* pc)
{
MOZ_ASSERT(local < script->nfixed());
if (local < script->nbodyfixed()) {
for (BindingIter bi(script); bi; bi++) {
if (bi->kind() != Binding::ARGUMENT && !bi->aliased() && bi.frameIndex() == local)
return bi->name();
}
MOZ_CRASH("No binding");
}
for (NestedScopeObject* chain = script->getStaticBlockScope(pc);
chain;
chain = chain->enclosingNestedScope())
{
if (!chain->is<StaticBlockObject>())
continue;
StaticBlockObject& block = chain->as<StaticBlockObject>();
if (local < block.localOffset())
continue;
local -= block.localOffset();
if (local >= block.numVariables())
return nullptr;
for (Shape::Range<NoGC> 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<char>(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();
MOZ_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)
{
MOZ_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.calleeTemplate()
: nullptr);
MOZ_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);
}
typedef mozilla::UniquePtr<char[], JS::FreePolicy> UniquePtrChars;
UniquePtrChars
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 UniquePtrChars(result);
js_free(result);
}
}
if (!fallback) {
if (v.isUndefined())
return UniquePtrChars(JS_strdup(cx, js_undefined_str)); // Prevent users from seeing "(void 0)"
fallback = ValueToSource(cx, v);
if (!fallback)
return UniquePtrChars(nullptr);
}
return UniquePtrChars(JS_EncodeString(cx, fallback));
}
static bool
DecompileArgumentFromStack(JSContext* cx, int formalIndex, char** res)
{
MOZ_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);
MOZ_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.calleeTemplate()
: nullptr);
MOZ_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<unsigned>(formalIndex) >= GET_ARGC(current))
return true;
BytecodeParser parser(cx, script);
if (!parser.parse())
return false;
int formalStackIndex = parser.stackDepthAtPC(current) - GET_ARGC(current) + formalIndex;
MOZ_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;
return JS_EncodeString(cx, fallback);
}
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;
}
/*
* There are three possible PCCount profiling states:
*
* 1. None: Neither scripts nor the runtime have count information.
* 2. Profile: Active scripts have count information, the runtime does not.
* 3. Query: Scripts do not have count information, the runtime does.
*
* When starting to profile scripts, counting begins immediately, with all JIT
* code discarded and recompiled with counts as necessary. Active interpreter
* frames will not begin profiling until they begin executing another script
* (via a call or return).
*
* The below API functions manage transitions to new states, according
* to the table below.
*
* Old State
* -------------------------
* Function None Profile Query
* --------
* StartPCCountProfiling Profile Profile Profile
* StopPCCountProfiling None Query Query
* PurgePCCounts None None None
*/
static void
ReleaseScriptCounts(FreeOp* fop)
{
JSRuntime* rt = fop->runtime();
MOZ_ASSERT(rt->scriptAndCountsVector);
ScriptAndCountsVector& vec = *rt->scriptAndCountsVector;
for (size_t i = 0; i < vec.length(); i++)
vec[i].scriptCounts.destroy(fop);
fop->delete_(rt->scriptAndCountsVector);
rt->scriptAndCountsVector = nullptr;
}
JS_FRIEND_API(void)
js::StartPCCountProfiling(JSContext* cx)
{
JSRuntime* rt = cx->runtime();
if (rt->profilingScripts)
return;
if (rt->scriptAndCountsVector)
ReleaseScriptCounts(rt->defaultFreeOp());
ReleaseAllJITCode(rt->defaultFreeOp());
rt->profilingScripts = true;
}
JS_FRIEND_API(void)
js::StopPCCountProfiling(JSContext* cx)
{
JSRuntime* rt = cx->runtime();
if (!rt->profilingScripts)
return;
MOZ_ASSERT(!rt->scriptAndCountsVector);
ReleaseAllJITCode(rt->defaultFreeOp());
ScriptAndCountsVector* vec = cx->new_<ScriptAndCountsVector>(SystemAllocPolicy());
if (!vec)
return;
for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
JSScript* script = i.get<JSScript>();
if (script->hasScriptCounts() && script->types()) {
ScriptAndCounts sac;
sac.script = script;
sac.scriptCounts.set(script->releaseScriptCounts());
if (!vec->append(sac))
sac.scriptCounts.destroy(rt->defaultFreeOp());
}
}
}
rt->profilingScripts = false;
rt->scriptAndCountsVector = vec;
}
JS_FRIEND_API(void)
js::PurgePCCounts(JSContext* cx)
{
JSRuntime* rt = cx->runtime();
if (!rt->scriptAndCountsVector)
return;
MOZ_ASSERT(!rt->profilingScripts);
ReleaseScriptCounts(rt->defaultFreeOp());
}
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.append(name, strlen(name));
buf.append("\":", 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, 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 TempAllocPolicy 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_CRASH("Bad opcode");
} else if (PCCounts::arithOp(op)) {
arithTotals[j - PCCounts::BASE_LIMIT] += value;
} else {
MOZ_CRASH("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.append(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, GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
return nullptr;
}
const ScriptAndCounts& sac = (*rt->scriptAndCountsVector)[index];
JSScript* script = sac.script;
StringBuffer buf(cx);
{
AutoCompartment ac(cx, &script->global());
if (!GetPCCountJSON(cx, sac, buf))
return nullptr;
}
return buf.finishString();
}