Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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 "vm/BytecodeUtil-inl.h"
#define __STDC_FORMAT_MACROS
#include "mozilla/Maybe.h"
#include "mozilla/ReverseIterator.h"
#include "mozilla/Sprintf.h"
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "jsapi.h"
#include "jstypes.h"
#include "gc/PublicIterators.h"
#include "jit/IonScript.h" // IonBlockCounts
#include "js/CharacterEncoding.h"
#include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin
#include "js/experimental/CodeCoverage.h"
#include "js/experimental/PCCountProfiling.h" // JS::{Start,Stop}PCCountProfiling, JS::PurgePCCounts, JS::GetPCCountScript{Count,Summary,Contents}
#include "js/friend/DumpFunctions.h" // js::DumpPC, js::DumpScript
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/Printer.h"
#include "js/Printf.h"
#include "js/Symbol.h"
#include "util/DifferentialTesting.h"
#include "util/Identifier.h" // IsIdentifier
#include "util/Memory.h"
#include "util/Text.h"
#include "vm/BuiltinObjectKind.h"
#include "vm/BytecodeIterator.h" // for AllBytecodesIterable
#include "vm/BytecodeLocation.h"
#include "vm/CodeCoverage.h"
#include "vm/EnvironmentObject.h"
#include "vm/FrameIter.h" // js::{,Script}FrameIter
#include "vm/JSAtomUtils.h" // AtomToPrintableString, Atomize
#include "vm/JSContext.h"
#include "vm/JSFunction.h"
#include "vm/JSObject.h"
#include "vm/JSONPrinter.h"
#include "vm/JSScript.h"
#include "vm/Opcodes.h"
#include "vm/Realm.h"
#include "vm/Shape.h"
#include "vm/ToSource.h" // js::ValueToSource
#include "vm/TypeofEqOperand.h" // TypeofEqOperand
#include "gc/GC-inl.h"
#include "vm/BytecodeIterator-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/Realm-inl.h"
using namespace js;
/*
* Index limit must stay within 32 bits.
*/
static_assert(sizeof(uint32_t) * CHAR_BIT >= INDEX_LIMIT_LOG2 + 1);
const JSCodeSpec js::CodeSpecTable[] = {
#define MAKE_CODESPEC(op, op_snake, token, length, nuses, ndefs, format) \
{length, nuses, ndefs, format},
FOR_EACH_OPCODE(MAKE_CODESPEC)
#undef MAKE_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, op_snake, 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::CodeNameTable[] = {
#define OPNAME(op, ...) #op,
FOR_EACH_OPCODE(OPNAME)
#undef OPNAME
};
/************************************************************************/
static bool DecompileArgumentFromStack(JSContext* cx, int formalIndex,
UniqueChars* res);
/* static */ const char PCCounts::numExecName[] = "interp";
[[nodiscard]] static bool DumpIonScriptCounts(StringPrinter* sp,
HandleScript script,
jit::IonScriptCounts* ionCounts) {
sp->printf("IonScript [%zu blocks]:\n", ionCounts->numBlocks());
for (size_t i = 0; i < ionCounts->numBlocks(); i++) {
const jit::IonBlockCounts& block = ionCounts->block(i);
unsigned lineNumber = 0;
JS::LimitedColumnNumberOneOrigin columnNumber;
lineNumber = PCToLineNumber(script, script->offsetToPC(block.offset()),
&columnNumber);
sp->printf("BB #%" PRIu32 " [%05u,%u,%u]", block.id(), block.offset(),
lineNumber, columnNumber.oneOriginValue());
if (block.description()) {
sp->printf(" [inlined %s]", block.description());
}
for (size_t j = 0; j < block.numSuccessors(); j++) {
sp->printf(" -> #%" PRIu32, block.successor(j));
}
sp->printf(" :: %" PRIu64 " hits\n", block.hitCount());
sp->printf("%s\n", block.code());
}
return true;
}
[[nodiscard]] static bool DumpPCCounts(JSContext* cx, HandleScript script,
StringPrinter* sp) {
// In some edge cases Disassemble1 can end up invoking JS code, so ensure
// script counts haven't been discarded.
if (!script->hasScriptCounts()) {
return true;
}
#ifdef DEBUG
jsbytecode* pc = script->code();
while (pc < script->codeEnd()) {
jsbytecode* next = GetNextPc(pc);
if (!Disassemble1(cx, script, pc, script->pcToOffset(pc), true, sp)) {
return false;
}
sp->put(" {");
if (script->hasScriptCounts()) {
PCCounts* counts = script->maybeGetPCCounts(pc);
if (double val = counts ? counts->numExec() : 0.0) {
sp->printf("\"%s\": %.0f", PCCounts::numExecName, val);
}
}
sp->put("}\n");
pc = next;
}
#endif
if (!script->hasScriptCounts()) {
return true;
}
jit::IonScriptCounts* ionCounts = script->getIonCounts();
while (ionCounts) {
if (!DumpIonScriptCounts(sp, script, ionCounts)) {
return false;
}
ionCounts = ionCounts->previous();
}
return true;
}
bool js::DumpRealmPCCounts(JSContext* cx) {
Rooted<GCVector<JSScript*>> scripts(cx, GCVector<JSScript*>(cx));
for (auto base = cx->zone()->cellIter<BaseScript>(); !base.done();
base.next()) {
if (base->realm() != cx->realm()) {
continue;
}
MOZ_ASSERT_IF(base->hasScriptCounts(), base->hasBytecode());
if (base->hasScriptCounts()) {
if (!scripts.append(base->asJSScript())) {
return false;
}
}
}
for (uint32_t i = 0; i < scripts.length(); i++) {
HandleScript script = scripts[i];
Sprinter sprinter(cx);
if (!sprinter.init()) {
return false;
}
const char* filename = script->filename();
if (!filename) {
filename = "(unknown)";
}
fprintf(stdout, "--- SCRIPT %s:%u ---\n", filename, script->lineno());
if (!DumpPCCounts(cx, script, &sprinter)) {
return false;
}
JS::UniqueChars out = sprinter.release();
if (!out) {
return false;
}
fputs(out.get(), stdout);
fprintf(stdout, "--- END SCRIPT %s:%u ---\n", filename, script->lineno());
}
return true;
}
/////////////////////////////////////////////////////////////////////
// Bytecode Parser
/////////////////////////////////////////////////////////////////////
// Stores the information about the stack slot, where the value comes from.
// Elements of BytecodeParser::Bytecode.{offsetStack,offsetStackAfter} arrays.
class OffsetAndDefIndex {
// The offset of the PC that pushed the value for this slot.
uint32_t offset_;
// The index in `ndefs` for the PC (0-origin)
uint8_t defIndex_;
enum : uint8_t {
Normal = 0,
// Ignored this value in the expression decompilation.
// Used by JSOp::NopDestructuring. See BytecodeParser::simulateOp.
Ignored,
// The value in this slot comes from 2 or more paths.
// offset_ and defIndex_ holds the information for the path that
// reaches here first.
Merged,
} type_;
public:
uint32_t offset() const {
MOZ_ASSERT(!isSpecial());
return offset_;
};
uint32_t specialOffset() const {
MOZ_ASSERT(isSpecial());
return offset_;
};
uint8_t defIndex() const {
MOZ_ASSERT(!isSpecial());
return defIndex_;
}
uint8_t specialDefIndex() const {
MOZ_ASSERT(isSpecial());
return defIndex_;
}
bool isSpecial() const { return type_ != Normal; }
bool isMerged() const { return type_ == Merged; }
bool isIgnored() const { return type_ == Ignored; }
void set(uint32_t aOffset, uint8_t aDefIndex) {
offset_ = aOffset;
defIndex_ = aDefIndex;
type_ = Normal;
}
// Keep offset_ and defIndex_ values for stack dump.
void setMerged() { type_ = Merged; }
void setIgnored() { type_ = Ignored; }
bool operator==(const OffsetAndDefIndex& rhs) const {
return offset_ == rhs.offset_ && defIndex_ == rhs.defIndex_;
}
bool operator!=(const OffsetAndDefIndex& rhs) const {
return !(*this == rhs);
}
};
namespace {
class BytecodeParser {
public:
enum class JumpKind {
Simple,
SwitchCase,
SwitchDefault,
TryCatch,
TryFinally
};
private:
class Bytecode {
public:
explicit Bytecode(const LifoAllocPolicy<Fallible>& alloc)
: parsed(false),
stackDepth(0),
offsetStack(nullptr)
#if defined(DEBUG) || defined(JS_JITSPEW)
,
stackDepthAfter(0),
offsetStackAfter(nullptr),
jumpOrigins(alloc)
#endif /* defined(DEBUG) || defined(JS_JITSPEW) */
{
}
// Whether this instruction has been analyzed to get its output defines
// and stack.
bool parsed;
// 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|.
OffsetAndDefIndex* offsetStack;
#if defined(DEBUG) || defined(JS_JITSPEW)
// stack depth after this opcode.
uint32_t stackDepthAfter;
// Pointer to array of |stackDepthAfter| offsets.
OffsetAndDefIndex* offsetStackAfter;
struct JumpInfo {
uint32_t from;
JumpKind kind;
JumpInfo(uint32_t from_, JumpKind kind_) : from(from_), kind(kind_) {}
};
// A list of offsets of the bytecode that jumps to this bytecode,
// exclusing previous bytecode.
Vector<JumpInfo, 0, LifoAllocPolicy<Fallible>> jumpOrigins;
#endif /* defined(DEBUG) || defined(JS_JITSPEW) */
bool captureOffsetStack(LifoAlloc& alloc, const OffsetAndDefIndex* stack,
uint32_t depth) {
stackDepth = depth;
if (stackDepth) {
offsetStack = alloc.newArray<OffsetAndDefIndex>(stackDepth);
if (!offsetStack) {
return false;
}
for (uint32_t n = 0; n < stackDepth; n++) {
offsetStack[n] = stack[n];
}
}
return true;
}
#if defined(DEBUG) || defined(JS_JITSPEW)
bool captureOffsetStackAfter(LifoAlloc& alloc,
const OffsetAndDefIndex* stack,
uint32_t depth) {
stackDepthAfter = depth;
if (stackDepthAfter) {
offsetStackAfter = alloc.newArray<OffsetAndDefIndex>(stackDepthAfter);
if (!offsetStackAfter) {
return false;
}
for (uint32_t n = 0; n < stackDepthAfter; n++) {
offsetStackAfter[n] = stack[n];
}
}
return true;
}
bool addJump(uint32_t from, JumpKind kind) {
return jumpOrigins.append(JumpInfo(from, kind));
}
#endif /* defined(DEBUG) || defined(JS_JITSPEW) */
// When control-flow merges, intersect the stacks, marking slots that
// are defined by different offsets and/or defIndices merged.
// 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 OffsetAndDefIndex* stack, uint32_t depth) {
MOZ_ASSERT(depth == stackDepth);
for (uint32_t n = 0; n < stackDepth; n++) {
if (stack[n].isIgnored()) {
continue;
}
if (offsetStack[n].isIgnored()) {
offsetStack[n] = stack[n];
}
if (offsetStack[n] != stack[n]) {
offsetStack[n].setMerged();
}
}
}
};
JSContext* cx_;
LifoAlloc& alloc_;
RootedScript script_;
Bytecode** codeArray_;
#if defined(DEBUG) || defined(JS_JITSPEW)
// Dedicated mode for stack dump.
// Capture stack after each opcode, and also enable special handling for
// some opcodes to make stack transition clearer.
bool isStackDump = false;
#endif
public:
BytecodeParser(JSContext* cx, LifoAlloc& alloc, JSScript* script)
: cx_(cx), alloc_(alloc), script_(cx, script), codeArray_(nullptr) {}
bool parse();
#if defined(DEBUG) || defined(JS_JITSPEW)
bool isReachable(const jsbytecode* pc) const { return maybeCode(pc); }
#endif
uint32_t stackDepthAtPC(uint32_t offset) const {
// 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) const {
return stackDepthAtPC(script_->pcToOffset(pc));
}
#if defined(DEBUG) || defined(JS_JITSPEW)
uint32_t stackDepthAfterPC(uint32_t offset) const {
return getCode(offset).stackDepthAfter;
}
uint32_t stackDepthAfterPC(const jsbytecode* pc) const {
return stackDepthAfterPC(script_->pcToOffset(pc));
}
#endif
const OffsetAndDefIndex& offsetForStackOperand(uint32_t offset,
int operand) const {
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,
uint8_t* defIndex) const {
size_t offset = script_->pcToOffset(pc);
const OffsetAndDefIndex& offsetAndDefIndex =
offsetForStackOperand(offset, operand);
if (offsetAndDefIndex.isSpecial()) {
return nullptr;
}
*defIndex = offsetAndDefIndex.defIndex();
return script_->offsetToPC(offsetAndDefIndex.offset());
}
#if defined(DEBUG) || defined(JS_JITSPEW)
const OffsetAndDefIndex& offsetForStackOperandAfterPC(uint32_t offset,
int operand) const {
Bytecode& code = getCode(offset);
if (operand < 0) {
operand += code.stackDepthAfter;
MOZ_ASSERT(operand >= 0);
}
MOZ_ASSERT(uint32_t(operand) < code.stackDepthAfter);
return code.offsetStackAfter[operand];
}
template <typename Callback>
bool forEachJumpOrigins(jsbytecode* pc, Callback callback) const {
Bytecode& code = getCode(script_->pcToOffset(pc));
for (Bytecode::JumpInfo& info : code.jumpOrigins) {
if (!callback(script_->offsetToPC(info.from), info.kind)) {
return false;
}
}
return true;
}
void setStackDump() { isStackDump = true; }
#endif /* defined(DEBUG) || defined(JS_JITSPEW) */
private:
LifoAlloc& alloc() { return alloc_; }
void reportOOM() { ReportOutOfMemory(cx_); }
uint32_t maximumStackDepth() const {
return script_->nslots() - script_->nfixed();
}
Bytecode& getCode(uint32_t offset) const {
MOZ_ASSERT(offset < script_->length());
MOZ_ASSERT(codeArray_[offset]);
return *codeArray_[offset];
}
Bytecode* maybeCode(uint32_t offset) const {
MOZ_ASSERT(offset < script_->length());
return codeArray_[offset];
}
#if defined(DEBUG) || defined(JS_JITSPEW)
Bytecode* maybeCode(const jsbytecode* pc) const {
return maybeCode(script_->pcToOffset(pc));
}
#endif
uint32_t simulateOp(JSOp op, uint32_t offset, OffsetAndDefIndex* offsetStack,
uint32_t stackDepth);
inline bool recordBytecode(uint32_t offset,
const OffsetAndDefIndex* offsetStack,
uint32_t stackDepth);
inline bool addJump(uint32_t offset, uint32_t stackDepth,
const OffsetAndDefIndex* offsetStack, jsbytecode* pc,
JumpKind kind);
};
} // anonymous namespace
uint32_t BytecodeParser::simulateOp(JSOp op, uint32_t offset,
OffsetAndDefIndex* offsetStack,
uint32_t stackDepth) {
jsbytecode* pc = script_->offsetToPC(offset);
uint32_t nuses = GetUseCount(pc);
uint32_t ndefs = GetDefCount(pc);
MOZ_RELEASE_ASSERT(stackDepth >= nuses);
stackDepth -= nuses;
MOZ_RELEASE_ASSERT(stackDepth + ndefs <= maximumStackDepth());
#ifdef DEBUG
if (isStackDump) {
// Opcodes that modifies the object but keeps it on the stack while
// initialization should be listed here instead of switch below.
// For error message, they shouldn't be shown as the original object
// after adding properties.
// For stack dump, keeping the input is better.
switch (op) {
case JSOp::InitHiddenProp:
case JSOp::InitHiddenPropGetter:
case JSOp::InitHiddenPropSetter:
case JSOp::InitLockedProp:
case JSOp::InitProp:
case JSOp::InitPropGetter:
case JSOp::InitPropSetter:
case JSOp::MutateProto:
case JSOp::SetFunName:
// Keep the second value.
MOZ_ASSERT(nuses == 2);
MOZ_ASSERT(ndefs == 1);
goto end;
case JSOp::InitElem:
case JSOp::InitElemGetter:
case JSOp::InitElemSetter:
case JSOp::InitHiddenElem:
case JSOp::InitHiddenElemGetter:
case JSOp::InitHiddenElemSetter:
case JSOp::InitLockedElem:
// Keep the third value.
MOZ_ASSERT(nuses == 3);
MOZ_ASSERT(ndefs == 1);
goto end;
default:
break;
}
}
#endif /* DEBUG */
// 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].set(offset, n);
}
break;
case JSOp::NopDestructuring:
// Poison the last offset to not obfuscate the error message.
offsetStack[stackDepth - 1].setIgnored();
break;
case JSOp::Case:
// Keep the switch value.
MOZ_ASSERT(ndefs == 1);
break;
case JSOp::Dup:
MOZ_ASSERT(ndefs == 2);
offsetStack[stackDepth + 1] = offsetStack[stackDepth];
break;
case JSOp::Dup2:
MOZ_ASSERT(ndefs == 4);
offsetStack[stackDepth + 2] = offsetStack[stackDepth];
offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1];
break;
case JSOp::DupAt: {
MOZ_ASSERT(ndefs == 1);
unsigned n = GET_UINT24(pc);
MOZ_ASSERT(n < stackDepth);
offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n];
break;
}
case JSOp::Swap: {
MOZ_ASSERT(ndefs == 2);
OffsetAndDefIndex tmp = offsetStack[stackDepth + 1];
offsetStack[stackDepth + 1] = offsetStack[stackDepth];
offsetStack[stackDepth] = tmp;
break;
}
case JSOp::Pick: {
unsigned n = GET_UINT8(pc);
MOZ_ASSERT(ndefs == n + 1);
uint32_t top = stackDepth + n;
OffsetAndDefIndex tmp = offsetStack[stackDepth];
for (uint32_t i = stackDepth; i < top; i++) {
offsetStack[i] = offsetStack[i + 1];
}
offsetStack[top] = tmp;
break;
}
case JSOp::Unpick: {
unsigned n = GET_UINT8(pc);
MOZ_ASSERT(ndefs == n + 1);
uint32_t top = stackDepth + n;
OffsetAndDefIndex tmp = offsetStack[top];
for (uint32_t i = top; i > stackDepth; i--) {
offsetStack[i] = offsetStack[i - 1];
}
offsetStack[stackDepth] = tmp;
break;
}
case JSOp::And:
case JSOp::CheckIsObj:
case JSOp::CheckObjCoercible:
case JSOp::CheckThis:
case JSOp::CheckThisReinit:
case JSOp::CheckClassHeritage:
case JSOp::DebugCheckSelfHosted:
case JSOp::InitGLexical:
case JSOp::InitLexical:
case JSOp::Or:
case JSOp::Coalesce:
case JSOp::SetAliasedVar:
case JSOp::SetArg:
case JSOp::SetIntrinsic:
case JSOp::SetLocal:
case JSOp::InitAliasedLexical:
case JSOp::CheckLexical:
case JSOp::CheckAliasedLexical:
// Keep the top value.
MOZ_ASSERT(nuses == 1);
MOZ_ASSERT(ndefs == 1);
break;
case JSOp::InitHomeObject:
// Pop the top value, keep the other value.
MOZ_ASSERT(nuses == 2);
MOZ_ASSERT(ndefs == 1);
break;
case JSOp::CheckResumeKind:
// Pop the top two values, keep the other value.
MOZ_ASSERT(nuses == 3);
MOZ_ASSERT(ndefs == 1);
break;
case JSOp::SetGName:
case JSOp::SetName:
case JSOp::SetProp:
case JSOp::StrictSetGName:
case JSOp::StrictSetName:
case JSOp::StrictSetProp:
// Keep the top value, removing other 1 value.
MOZ_ASSERT(nuses == 2);
MOZ_ASSERT(ndefs == 1);
offsetStack[stackDepth] = offsetStack[stackDepth + 1];
break;
case JSOp::SetPropSuper:
case JSOp::StrictSetPropSuper:
// Keep the top value, removing other 2 values.
MOZ_ASSERT(nuses == 3);
MOZ_ASSERT(ndefs == 1);
offsetStack[stackDepth] = offsetStack[stackDepth + 2];
break;
case JSOp::SetElemSuper:
case JSOp::StrictSetElemSuper:
// Keep the top value, removing other 3 values.
MOZ_ASSERT(nuses == 4);
MOZ_ASSERT(ndefs == 1);
offsetStack[stackDepth] = offsetStack[stackDepth + 3];
break;
case JSOp::IsGenClosing:
case JSOp::IsNoIter:
case JSOp::IsNullOrUndefined:
case JSOp::MoreIter:
case JSOp::CanSkipAwait:
// Keep the top value and push one more value.
MOZ_ASSERT(nuses == 1);
MOZ_ASSERT(ndefs == 2);
offsetStack[stackDepth + 1].set(offset, 1);
break;
case JSOp::MaybeExtractAwaitValue:
// Keep the top value and replace the second to top value.
MOZ_ASSERT(nuses == 2);
MOZ_ASSERT(ndefs == 2);
offsetStack[stackDepth].set(offset, 0);
break;
case JSOp::CheckPrivateField:
// Keep the top two values, and push one new value.
MOZ_ASSERT(nuses == 2);
MOZ_ASSERT(ndefs == 3);
offsetStack[stackDepth + 2].set(offset, 2);
break;
}
#ifdef DEBUG
end:
#endif /* DEBUG */
stackDepth += ndefs;
return stackDepth;
}
bool BytecodeParser::recordBytecode(uint32_t offset,
const OffsetAndDefIndex* offsetStack,
uint32_t stackDepth) {
MOZ_RELEASE_ASSERT(offset < script_->length());
MOZ_RELEASE_ASSERT(stackDepth <= maximumStackDepth());
Bytecode*& code = codeArray_[offset];
if (!code) {
code = alloc().new_<Bytecode>(alloc());
if (!code || !code->captureOffsetStack(alloc(), offsetStack, stackDepth)) {
reportOOM();
return false;
}
} else {
code->mergeOffsetStack(offsetStack, stackDepth);
}
return true;
}
bool BytecodeParser::addJump(uint32_t offset, uint32_t stackDepth,
const OffsetAndDefIndex* offsetStack,
jsbytecode* pc, JumpKind kind) {
if (!recordBytecode(offset, offsetStack, stackDepth)) {
return false;
}
#ifdef DEBUG
uint32_t currentOffset = script_->pcToOffset(pc);
if (isStackDump) {
if (!codeArray_[offset]->addJump(currentOffset, kind)) {
reportOOM();
return false;
}
}
// If this is a backedge, assert we parsed the target JSOp::LoopHead.
MOZ_ASSERT_IF(offset < currentOffset, codeArray_[offset]->parsed);
#endif /* DEBUG */
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>(alloc());
if (!startcode) {
reportOOM();
return false;
}
// Fill in stack depth and definitions at initial bytecode.
OffsetAndDefIndex* offsetStack =
alloc().newArray<OffsetAndDefIndex>(maximumStackDepth());
if (maximumStackDepth() && !offsetStack) {
reportOOM();
return false;
}
startcode->stackDepth = 0;
codeArray_[0] = startcode;
for (uint32_t offset = 0, nextOffset = 0; offset < length;
offset = nextOffset) {
Bytecode* code = maybeCode(offset);
jsbytecode* pc = script_->offsetToPC(offset);
// Next bytecode to analyze.
nextOffset = offset + GetBytecodeLength(pc);
MOZ_RELEASE_ASSERT(*pc < JSOP_LIMIT);
JSOp op = JSOp(*pc);
if (!code) {
// Haven't found a path by which this bytecode is reachable.
continue;
}
// On a jump target, we reload the offsetStack saved for the current
// bytecode, as it contains either the original offset stack, or the
// merged offset stack.
if (BytecodeIsJumpTarget(op)) {
for (uint32_t n = 0; n < code->stackDepth; ++n) {
offsetStack[n] = code->offsetStack[n];
}
}
if (code->parsed) {