Permalink
Switch branches/tags
RELEASE_BASE_20140602 RELEASE_BASE_20140421 RELEASE_BASE_20140310 RELEASE_BASE_20140127 RELEASE_BASE_20131202 RELEASE_BASE_20131021 RELEASE_BASE_20130909 RELEASE_BASE_20130729 RELEASE_BASE_20130617 RELEASE_BASE_20130506 RELEASE_BASE_20130326 RELEASE_BASE_20130214 RELEASE_BASE_20130103 RELEASE_BASE_20121114 RELEASE_BASE_20121005 RELEASE_BASE_20120824 RELEASE_BASE_20120713 RELEASE_BASE_20120531 RELEASE_BASE_20120420 RELEASE_BASE_20120308 RELEASE_BASE_20120128 RELEASE_BASE_20111216 RELEASE_BASE_20111104 RELEASE_BASE_20110922 RELEASE_BASE_20110811 B2G_2_5_20160125_MERGEDAY B2G_2_5_20151214_MERGEDAY B2G_2_2_20151214_MERGEDAY B2G_2_2_20151029_MERGEDAY B2G_2_2_20150921_MERGEDAY B2G_2_2_20150810_MERGEDAY B2G_2_2_20150629_MERGEDAY B2G_2_2_20150511_MERGEDAY B2G_2_2_20150223_MERGEDAY B2G_2_2r_20151214_MERGEDAY B2G_2_2r_20151029_MERGEDAY B2G_2_2r_20150921_MERGEDAY B2G_2_2r_20150810_MERGEDAY B2G_2_1_20150629_MERGEDAY B2G_2_1_20150511_MERGEDAY B2G_2_1_20150223_MERGEDAY B2G_2_1_20150112_MERGEDAY B2G_2_1_20141013_MERGEDAY B2G_2_1_20140902_MERGEDAY B2G_2_1s_20150810_MERGEDAY B2G_2_1s_20150629_MERGEDAY B2G_2_1s_20150511_MERGEDAY B2G_2_1s_20150223_MERGEDAY B2G_2_1s_END B2G_2_1_END B2G_2_0_20150629_MERGEDAY B2G_2_0_20150511_MERGEDAY B2G_2_0_20150223_MERGEDAY B2G_2_0_20150112_MERGEDAY B2G_2_0_20141013_MERGEDAY B2G_2_0_20140902_MERGEDAY B2G_2_0_20140721_MERGEDAY B2G_2_0_20140609_MERGEDAY B2G_2_0M_END B2G_2_0_END B2G_1_4_20150511_MERGEDAY B2G_1_4_20150330_MERGEDAY B2G_1_4_20150223_MERGEDAY B2G_1_4_20150112_MERGEDAY B2G_1_4_20141013_MERGEDAY B2G_1_4_20140902_MERGEDAY B2G_1_4_20140721_MERGEDAY B2G_1_4_20140609_MERGEDAY B2G_1_4_20140428_MERGEDAY B2G_1_4_20140317_MERGEDAY B2G_1_3_20140902_MERGEDAY B2G_1_3_20140721_MERGEDAY B2G_1_3_20140609_MERGEDAY B2G_1_3_20140428_MERGEDAY B2G_1_3_20140317_MERGEDAY B2G_1_3_20140203_MERGEDAY B2G_1_3T_20141013_MERGEDAY B2G_1_3T_20140902_MERGEDAY B2G_1_3T_20140721_MERGEDAY B2G_1_3T_20140609_MERGEDAY B2G_1_3T_20140428_MERGEDAY B2G_1_3T_20140317_MERGEDAY B2G_1_2_20140428_MERGEDAY B2G_1_2_20140317_MERGEDAY B2G_1_2_20140203_MERGEDAY B2G_1_1_20140428_MERGEDAY B2G_1_1_20140317_MERGEDAY B2G_1_1_20140203_MERGEDAY B2G_1_1_0_hd_20140428_MERGEDAY B2G_1_1_0_hd_20140317_MERGEDAY B2G_1_1_0_hd_20140203_MERGEDAY B2G_1_1_0_hd_20130530182315 B2G_1_1_0_hd_20130530182315_BASE B2G_1_0_1_20130217163900 B2G_1_0_1_20130213094222 B2G_1_0_1_20130213094222_BASE B2G_1_0_0_20130125190500 B2G_1_0_0_20130115070201
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
9408 lines (8173 sloc) 287 KB
/* -*- 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 generation.
*/
#include "frontend/BytecodeEmitter.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/PodOperations.h"
#include "mozilla/ReverseIterator.h"
#include <string.h>
#include "jsnum.h"
#include "jstypes.h"
#include "jsutil.h"
#include "ds/Nestable.h"
#include "frontend/BytecodeControlStructures.h"
#include "frontend/CallOrNewEmitter.h"
#include "frontend/CForEmitter.h"
#include "frontend/DoWhileEmitter.h"
#include "frontend/ElemOpEmitter.h"
#include "frontend/EmitterScope.h"
#include "frontend/ExpressionStatementEmitter.h"
#include "frontend/ForInEmitter.h"
#include "frontend/ForOfEmitter.h"
#include "frontend/ForOfLoopControl.h"
#include "frontend/IfEmitter.h"
#include "frontend/NameOpEmitter.h"
#include "frontend/ParseNode.h"
#include "frontend/Parser.h"
#include "frontend/PropOpEmitter.h"
#include "frontend/SwitchEmitter.h"
#include "frontend/TDZCheckCache.h"
#include "frontend/TryEmitter.h"
#include "frontend/WhileEmitter.h"
#include "js/CompileOptions.h"
#include "vm/BytecodeUtil.h"
#include "vm/Debugger.h"
#include "vm/GeneratorObject.h"
#include "vm/JSAtom.h"
#include "vm/JSContext.h"
#include "vm/JSFunction.h"
#include "vm/JSScript.h"
#include "vm/Stack.h"
#include "wasm/AsmJS.h"
#include "vm/JSObject-inl.h"
using namespace js;
using namespace js::frontend;
using mozilla::AssertedCast;
using mozilla::DebugOnly;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::NumberEqualsInt32;
using mozilla::NumberIsInt32;
using mozilla::PodCopy;
using mozilla::Some;
using mozilla::Unused;
static bool
ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn)
{
// The few node types listed below are exceptions to the usual
// location-source-note-emitting code in BytecodeEmitter::emitTree().
// Single-line `while` loops and C-style `for` loops require careful
// handling to avoid strange stepping behavior.
// Functions usually shouldn't have location information (bug 1431202).
ParseNodeKind kind = pn->getKind();
return kind == ParseNodeKind::While ||
kind == ParseNodeKind::For ||
kind == ParseNodeKind::Function;
}
BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
SharedContext* sc, HandleScript script,
Handle<LazyScript*> lazyScript,
uint32_t lineNum, EmitterMode emitterMode)
: sc(sc),
cx(sc->context),
parent(parent),
script(cx, script),
lazyScript(cx, lazyScript),
prologue(cx, lineNum),
main(cx, lineNum),
current(&main),
parser(nullptr),
atomIndices(cx->frontendCollectionPool()),
firstLine(lineNum),
maxFixedSlots(0),
maxStackDepth(0),
stackDepth(0),
emitLevel(0),
bodyScopeIndex(UINT32_MAX),
varEmitterScope(nullptr),
innermostNestableControl(nullptr),
innermostEmitterScope_(nullptr),
innermostTDZCheckCache(nullptr),
#ifdef DEBUG
unstableEmitterScope(false),
#endif
numberList(cx),
scopeList(cx),
tryNoteList(cx),
scopeNoteList(cx),
resumeOffsetList(cx),
numYields(0),
typesetCount(0),
hasSingletons(false),
hasTryFinally(false),
emittingRunOnceLambda(false),
emitterMode(emitterMode),
scriptStartOffsetSet(false),
functionBodyEndPosSet(false)
{
MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript);
}
BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
BCEParserHandle* handle, SharedContext* sc,
HandleScript script, Handle<LazyScript*> lazyScript,
uint32_t lineNum, EmitterMode emitterMode)
: BytecodeEmitter(parent, sc, script, lazyScript, lineNum, emitterMode)
{
parser = handle;
}
BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
const EitherParser& parser, SharedContext* sc,
HandleScript script, Handle<LazyScript*> lazyScript,
uint32_t lineNum, EmitterMode emitterMode)
: BytecodeEmitter(parent, sc, script, lazyScript, lineNum, emitterMode)
{
ep_.emplace(parser);
this->parser = ep_.ptr();
}
void
BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition)
{
setScriptStartOffsetIfUnset(bodyPosition);
setFunctionBodyEndPos(bodyPosition);
}
bool
BytecodeEmitter::init()
{
return atomIndices.acquire(cx);
}
template <typename T>
T*
BytecodeEmitter::findInnermostNestableControl() const
{
return NestableControl::findNearest<T>(innermostNestableControl);
}
template <typename T, typename Predicate /* (T*) -> bool */>
T*
BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const
{
return NestableControl::findNearest<T>(innermostNestableControl, predicate);
}
NameLocation
BytecodeEmitter::lookupName(JSAtom* name)
{
return innermostEmitterScope()->lookup(this, name);
}
Maybe<NameLocation>
BytecodeEmitter::locationOfNameBoundInScope(JSAtom* name, EmitterScope* target)
{
return innermostEmitterScope()->locationBoundInScope(name, target);
}
Maybe<NameLocation>
BytecodeEmitter::locationOfNameBoundInFunctionScope(JSAtom* name, EmitterScope* source)
{
EmitterScope* funScope = source;
while (!funScope->scope(this)->is<FunctionScope>()) {
funScope = funScope->enclosingInFrame();
}
return source->locationBoundInScope(name, funScope);
}
bool
BytecodeEmitter::emitCheck(ptrdiff_t delta, ptrdiff_t* offset)
{
*offset = code().length();
if (!code().growBy(delta)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
void
BytecodeEmitter::updateDepth(ptrdiff_t target)
{
jsbytecode* pc = code(target);
int nuses = StackUses(pc);
int ndefs = StackDefs(pc);
stackDepth -= nuses;
MOZ_ASSERT(stackDepth >= 0);
stackDepth += ndefs;
if ((uint32_t)stackDepth > maxStackDepth) {
maxStackDepth = stackDepth;
}
}
#ifdef DEBUG
bool
BytecodeEmitter::checkStrictOrSloppy(JSOp op)
{
if (IsCheckStrictOp(op) && !sc->strict()) {
return false;
}
if (IsCheckSloppyOp(op) && sc->strict()) {
return false;
}
return true;
}
#endif
bool
BytecodeEmitter::emit1(JSOp op)
{
MOZ_ASSERT(checkStrictOrSloppy(op));
ptrdiff_t offset;
if (!emitCheck(1, &offset)) {
return false;
}
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
updateDepth(offset);
return true;
}
bool
BytecodeEmitter::emit2(JSOp op, uint8_t op1)
{
MOZ_ASSERT(checkStrictOrSloppy(op));
ptrdiff_t offset;
if (!emitCheck(2, &offset)) {
return false;
}
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
code[1] = jsbytecode(op1);
updateDepth(offset);
return true;
}
bool
BytecodeEmitter::emit3(JSOp op, jsbytecode op1, jsbytecode op2)
{
MOZ_ASSERT(checkStrictOrSloppy(op));
/* These should filter through emitVarOp. */
MOZ_ASSERT(!IsArgOp(op));
MOZ_ASSERT(!IsLocalOp(op));
ptrdiff_t offset;
if (!emitCheck(3, &offset)) {
return false;
}
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
code[1] = op1;
code[2] = op2;
updateDepth(offset);
return true;
}
bool
BytecodeEmitter::emitN(JSOp op, size_t extra, ptrdiff_t* offset)
{
MOZ_ASSERT(checkStrictOrSloppy(op));
ptrdiff_t length = 1 + ptrdiff_t(extra);
ptrdiff_t off;
if (!emitCheck(length, &off)) {
return false;
}
jsbytecode* code = this->code(off);
code[0] = jsbytecode(op);
/* The remaining |extra| bytes are set by the caller */
/*
* Don't updateDepth if op's use-count comes from the immediate
* operand yet to be stored in the extra bytes after op.
*/
if (CodeSpec[op].nuses >= 0) {
updateDepth(off);
}
if (offset) {
*offset = off;
}
return true;
}
bool
BytecodeEmitter::emitJumpTarget(JumpTarget* target)
{
ptrdiff_t off = offset();
// Alias consecutive jump targets.
if (off == current->lastTarget.offset + ptrdiff_t(JSOP_JUMPTARGET_LENGTH)) {
target->offset = current->lastTarget.offset;
return true;
}
target->offset = off;
current->lastTarget.offset = off;
if (!emit1(JSOP_JUMPTARGET)) {
return false;
}
return true;
}
bool
BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump)
{
ptrdiff_t offset;
if (!emitCheck(5, &offset)) {
return false;
}
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
MOZ_ASSERT(-1 <= jump->offset && jump->offset < offset);
jump->push(this->code(0), offset);
updateDepth(offset);
return true;
}
bool
BytecodeEmitter::emitJump(JSOp op, JumpList* jump)
{
if (!emitJumpNoFallthrough(op, jump)) {
return false;
}
if (BytecodeFallsThrough(op)) {
JumpTarget fallthrough;
if (!emitJumpTarget(&fallthrough)) {
return false;
}
}
return true;
}
bool
BytecodeEmitter::emitBackwardJump(JSOp op, JumpTarget target, JumpList* jump, JumpTarget* fallthrough)
{
if (!emitJumpNoFallthrough(op, jump)) {
return false;
}
patchJumpsToTarget(*jump, target);
// Unconditionally create a fallthrough for closing iterators, and as a
// target for break statements.
if (!emitJumpTarget(fallthrough)) {
return false;
}
return true;
}
void
BytecodeEmitter::patchJumpsToTarget(JumpList jump, JumpTarget target)
{
MOZ_ASSERT(-1 <= jump.offset && jump.offset <= offset());
MOZ_ASSERT(0 <= target.offset && target.offset <= offset());
MOZ_ASSERT_IF(jump.offset != -1 && target.offset + 4 <= offset(),
BytecodeIsJumpTarget(JSOp(*code(target.offset))));
jump.patchAll(code(0), target);
}
bool
BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump)
{
if (jump.offset == -1) {
return true;
}
JumpTarget target;
if (!emitJumpTarget(&target)) {
return false;
}
patchJumpsToTarget(jump, target);
return true;
}
bool
BytecodeEmitter::emitCall(JSOp op, uint16_t argc, const Maybe<uint32_t>& sourceCoordOffset)
{
if (sourceCoordOffset.isSome()) {
if (!updateSourceCoordNotes(*sourceCoordOffset)) {
return false;
}
}
return emit3(op, ARGC_LO(argc), ARGC_HI(argc));
}
bool
BytecodeEmitter::emitCall(JSOp op, uint16_t argc, ParseNode* pn)
{
return emitCall(op, argc, pn ? Some(pn->pn_pos.begin) : Nothing());
}
bool
BytecodeEmitter::emitDupAt(unsigned slotFromTop)
{
MOZ_ASSERT(slotFromTop < unsigned(stackDepth));
if (slotFromTop == 0) {
return emit1(JSOP_DUP);
}
if (slotFromTop >= JS_BIT(24)) {
reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
return false;
}
ptrdiff_t off;
if (!emitN(JSOP_DUPAT, 3, &off)) {
return false;
}
jsbytecode* pc = code(off);
SET_UINT24(pc, slotFromTop);
return true;
}
bool
BytecodeEmitter::emitPopN(unsigned n)
{
MOZ_ASSERT(n != 0);
if (n == 1) {
return emit1(JSOP_POP);
}
// 2 JSOP_POPs (2 bytes) are shorter than JSOP_POPN (3 bytes).
if (n == 2) {
return emit1(JSOP_POP) && emit1(JSOP_POP);
}
return emitUint16Operand(JSOP_POPN, n);
}
bool
BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind)
{
return emit2(JSOP_CHECKISOBJ, uint8_t(kind));
}
bool
BytecodeEmitter::emitCheckIsCallable(CheckIsCallableKind kind)
{
return emit2(JSOP_CHECKISCALLABLE, uint8_t(kind));
}
static inline unsigned
LengthOfSetLine(unsigned line)
{
return 1 /* SRC_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1);
}
/* Updates line number notes, not column notes. */
bool
BytecodeEmitter::updateLineNumberNotes(uint32_t offset)
{
ErrorReporter* er = &parser->errorReporter();
bool onThisLine;
if (!er->isOnThisLine(offset, currentLine(), &onThisLine)) {
er->reportErrorNoOffset(JSMSG_OUT_OF_MEMORY);
return false;
}
if (!onThisLine) {
unsigned line = er->lineAt(offset);
unsigned delta = line - currentLine();
/*
* Encode any change in the current source line number by using
* either several SRC_NEWLINE notes or just one SRC_SETLINE note,
* whichever consumes less space.
*
* NB: We handle backward line number deltas (possible with for
* loops where the update part is emitted after the body, but its
* line number is <= any line number in the body) here by letting
* unsigned delta_ wrap to a very large number, which triggers a
* SRC_SETLINE.
*/
current->currentLine = line;
current->lastColumn = 0;
if (delta >= LengthOfSetLine(line)) {
if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(line))) {
return false;
}
} else {
do {
if (!newSrcNote(SRC_NEWLINE)) {
return false;
}
} while (--delta != 0);
}
}
return true;
}
/* Updates the line number and column number information in the source notes. */
bool
BytecodeEmitter::updateSourceCoordNotes(uint32_t offset)
{
if (!updateLineNumberNotes(offset)) {
return false;
}
uint32_t columnIndex = parser->errorReporter().columnAt(offset);
ptrdiff_t colspan = ptrdiff_t(columnIndex) - ptrdiff_t(current->lastColumn);
if (colspan != 0) {
// If the column span is so large that we can't store it, then just
// discard this information. This can happen with minimized or otherwise
// machine-generated code. Even gigantic column numbers are still
// valuable if you have a source map to relate them to something real;
// but it's better to fail soft here.
if (!SN_REPRESENTABLE_COLSPAN(colspan)) {
return true;
}
if (!newSrcNote2(SRC_COLSPAN, SN_COLSPAN_TO_OFFSET(colspan))) {
return false;
}
current->lastColumn = columnIndex;
}
return true;
}
Maybe<uint32_t>
BytecodeEmitter::getOffsetForLoop(ParseNode* nextpn)
{
if (!nextpn) {
return Nothing();
}
// Try to give the JSOP_LOOPHEAD and JSOP_LOOPENTRY the same line number as
// the next instruction. nextpn is often a block, in which case the next
// instruction typically comes from the first statement inside.
if (nextpn->is<LexicalScopeNode>()) {
nextpn = nextpn->as<LexicalScopeNode>().scopeBody();
}
if (nextpn->isKind(ParseNodeKind::StatementList)) {
if (ParseNode* firstStatement = nextpn->as<ListNode>().head()) {
nextpn = firstStatement;
}
}
return Some(nextpn->pn_pos.begin);
}
void
BytecodeEmitter::checkTypeSet(JSOp op)
{
if (CodeSpec[op].format & JOF_TYPESET) {
if (typesetCount < UINT16_MAX) {
typesetCount++;
}
}
}
bool
BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand)
{
MOZ_ASSERT(operand <= UINT16_MAX);
if (!emit3(op, UINT16_LO(operand), UINT16_HI(operand))) {
return false;
}
checkTypeSet(op);
return true;
}
bool
BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand)
{
ptrdiff_t off;
if (!emitN(op, 4, &off)) {
return false;
}
SET_UINT32(code(off), operand);
checkTypeSet(op);
return true;
}
namespace {
class NonLocalExitControl
{
public:
enum Kind
{
// IteratorClose is handled especially inside the exception unwinder.
Throw,
// A 'continue' statement does not call IteratorClose for the loop it
// is continuing, i.e. excluding the target loop.
Continue,
// A 'break' or 'return' statement does call IteratorClose for the
// loop it is breaking out of or returning from, i.e. including the
// target loop.
Break,
Return
};
private:
BytecodeEmitter* bce_;
const uint32_t savedScopeNoteIndex_;
const int savedDepth_;
uint32_t openScopeNoteIndex_;
Kind kind_;
NonLocalExitControl(const NonLocalExitControl&) = delete;
MOZ_MUST_USE bool leaveScope(EmitterScope* scope);
public:
NonLocalExitControl(BytecodeEmitter* bce, Kind kind)
: bce_(bce),
savedScopeNoteIndex_(bce->scopeNoteList.length()),
savedDepth_(bce->stackDepth),
openScopeNoteIndex_(bce->innermostEmitterScope()->noteIndex()),
kind_(kind)
{ }
~NonLocalExitControl() {
for (uint32_t n = savedScopeNoteIndex_; n < bce_->scopeNoteList.length(); n++) {
bce_->scopeNoteList.recordEnd(n, bce_->offset(), bce_->inPrologue());
}
bce_->stackDepth = savedDepth_;
}
MOZ_MUST_USE bool prepareForNonLocalJump(NestableControl* target);
MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() {
return prepareForNonLocalJump(nullptr);
}
};
bool
NonLocalExitControl::leaveScope(EmitterScope* es)
{
if (!es->leave(bce_, /* nonLocal = */ true)) {
return false;
}
// As we pop each scope due to the non-local jump, emit notes that
// record the extent of the enclosing scope. These notes will have
// their ends recorded in ~NonLocalExitControl().
uint32_t enclosingScopeIndex = ScopeNote::NoScopeIndex;
if (es->enclosingInFrame()) {
enclosingScopeIndex = es->enclosingInFrame()->index();
}
if (!bce_->scopeNoteList.append(enclosingScopeIndex, bce_->offset(), bce_->inPrologue(),
openScopeNoteIndex_))
{
return false;
}
openScopeNoteIndex_ = bce_->scopeNoteList.length() - 1;
return true;
}
/*
* Emit additional bytecode(s) for non-local jumps.
*/
bool
NonLocalExitControl::prepareForNonLocalJump(NestableControl* target)
{
EmitterScope* es = bce_->innermostEmitterScope();
int npops = 0;
AutoCheckUnstableEmitterScope cues(bce_);
// For 'continue', 'break', and 'return' statements, emit IteratorClose
// bytecode inline. 'continue' statements do not call IteratorClose for
// the loop they are continuing.
bool emitIteratorClose = kind_ == Continue || kind_ == Break || kind_ == Return;
bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue;
auto flushPops = [&npops](BytecodeEmitter* bce) {
if (npops && !bce->emitPopN(npops)) {
return false;
}
npops = 0;
return true;
};
// Walk the nestable control stack and patch jumps.
for (NestableControl* control = bce_->innermostNestableControl;
control != target;
control = control->enclosing())
{
// Walk the scope stack and leave the scopes we entered. Leaving a scope
// may emit administrative ops like JSOP_POPLEXICALENV but never anything
// that manipulates the stack.
for (; es != control->emitterScope(); es = es->enclosingInFrame()) {
if (!leaveScope(es)) {
return false;
}
}
switch (control->kind()) {
case StatementKind::Finally: {
TryFinallyControl& finallyControl = control->as<TryFinallyControl>();
if (finallyControl.emittingSubroutine()) {
/*
* There's a [exception or hole, retsub pc-index] pair and the
* possible return value on the stack that we need to pop.
*/
npops += 3;
} else {
if (!flushPops(bce_)) {
return false;
}
if (!bce_->emitGoSub(&finallyControl.gosubs)) { // ...
return false;
}
}
break;
}
case StatementKind::ForOfLoop:
if (emitIteratorClose) {
if (!flushPops(bce_)) {
return false;
}
ForOfLoopControl& loopinfo = control->as<ForOfLoopControl>();
if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es,
/* isTarget = */ false))
{ // ...
return false;
}
} else {
// The iterator next method, the iterator, and the current
// value are on the stack.
npops += 3;
}
break;
case StatementKind::ForInLoop:
if (!flushPops(bce_)) {
return false;
}
// The iterator and the current value are on the stack.
if (!bce_->emit1(JSOP_POP)) { // ... ITER
return false;
}
if (!bce_->emit1(JSOP_ENDITER)) { // ...
return false;
}
break;
default:
break;
}
}
if (!flushPops(bce_)) {
return false;
}
if (target && emitIteratorCloseAtTarget && target->is<ForOfLoopControl>()) {
ForOfLoopControl& loopinfo = target->as<ForOfLoopControl>();
if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es,
/* isTarget = */ true))
{ // ... UNDEF UNDEF UNDEF
return false;
}
}
EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope;
for (; es != targetEmitterScope; es = es->enclosingInFrame()) {
if (!leaveScope(es)) {
return false;
}
}
return true;
}
} // anonymous namespace
bool
BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType)
{
NonLocalExitControl nle(this, noteType == SRC_CONTINUE
? NonLocalExitControl::Continue
: NonLocalExitControl::Break);
if (!nle.prepareForNonLocalJump(target)) {
return false;
}
if (noteType != SRC_NULL) {
if (!newSrcNote(noteType)) {
return false;
}
}
return emitJump(JSOP_GOTO, jumplist);
}
Scope*
BytecodeEmitter::innermostScope() const
{
return innermostEmitterScope()->scope(this);
}
bool
BytecodeEmitter::emitIndex32(JSOp op, uint32_t index)
{
MOZ_ASSERT(checkStrictOrSloppy(op));
const size_t len = 1 + UINT32_INDEX_LEN;
MOZ_ASSERT(len == size_t(CodeSpec[op].length));
ptrdiff_t offset;
if (!emitCheck(len, &offset)) {
return false;
}
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
SET_UINT32_INDEX(code, index);
checkTypeSet(op);
updateDepth(offset);
return true;
}
bool
BytecodeEmitter::emitIndexOp(JSOp op, uint32_t index)
{
MOZ_ASSERT(checkStrictOrSloppy(op));
const size_t len = CodeSpec[op].length;
MOZ_ASSERT(len >= 1 + UINT32_INDEX_LEN);
ptrdiff_t offset;
if (!emitCheck(len, &offset)) {
return false;
}
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
SET_UINT32_INDEX(code, index);
checkTypeSet(op);
updateDepth(offset);
return true;
}
bool
BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op)
{
MOZ_ASSERT(atom);
// .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of
// JSOP_GETNAME etc, to bypass |with| objects on the scope chain.
// It's safe to emit .this lookups though because |with| objects skip
// those.
MOZ_ASSERT_IF(op == JSOP_GETNAME || op == JSOP_GETGNAME,
atom != cx->names().dotGenerator);
if (op == JSOP_GETPROP && atom == cx->names().length) {
/* Specialize length accesses for the interpreter. */
op = JSOP_LENGTH;
}
uint32_t index;
if (!makeAtomIndex(atom, &index)) {
return false;
}
return emitAtomOp(index, op);
}
bool
BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op)
{
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
return emitIndexOp(op, atomIndex);
}
bool
BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op)
{
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
MOZ_ASSERT(index < scopeList.length());
return emitIndex32(op, index);
}
bool
BytecodeEmitter::emitInternedObjectOp(uint32_t index, JSOp op)
{
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
MOZ_ASSERT(index < objectList.length);
return emitIndex32(op, index);
}
bool
BytecodeEmitter::emitObjectOp(ObjectBox* objbox, JSOp op)
{
return emitInternedObjectOp(objectList.add(objbox), op);
}
bool
BytecodeEmitter::emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op)
{
uint32_t index = objectList.add(objbox1);
objectList.add(objbox2);
return emitInternedObjectOp(index, op);
}
bool
BytecodeEmitter::emitRegExp(uint32_t index)
{
return emitIndex32(JSOP_REGEXP, index);
}
bool
BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot)
{
MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD);
MOZ_ASSERT(IsLocalOp(op));
ptrdiff_t off;
if (!emitN(op, LOCALNO_LEN, &off)) {
return false;
}
SET_LOCALNO(code(off), slot);
return true;
}
bool
BytecodeEmitter::emitArgOp(JSOp op, uint16_t slot)
{
MOZ_ASSERT(IsArgOp(op));
ptrdiff_t off;
if (!emitN(op, ARGNO_LEN, &off)) {
return false;
}
SET_ARGNO(code(off), slot);
return true;
}
bool
BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec)
{
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD);
unsigned n = ENVCOORD_HOPS_LEN + ENVCOORD_SLOT_LEN;
MOZ_ASSERT(int(n) + 1 /* op */ == CodeSpec[op].length);
ptrdiff_t off;
if (!emitN(op, n, &off)) {
return false;
}
jsbytecode* pc = code(off);
SET_ENVCOORD_HOPS(pc, ec.hops());
pc += ENVCOORD_HOPS_LEN;
SET_ENVCOORD_SLOT(pc, ec.slot());
pc += ENVCOORD_SLOT_LEN;
checkTypeSet(op);
return true;
}
JSOp
BytecodeEmitter::strictifySetNameOp(JSOp op)
{
switch (op) {
case JSOP_SETNAME:
if (sc->strict()) {
op = JSOP_STRICTSETNAME;
}
break;
case JSOP_SETGNAME:
if (sc->strict()) {
op = JSOP_STRICTSETGNAME;
}
break;
default:;
}
return op;
}
bool
BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
{
if (!CheckRecursionLimit(cx)) {
return false;
}
restart:
switch (pn->getKind()) {
// Trivial cases with no side effects.
case ParseNodeKind::EmptyStatement:
case ParseNodeKind::True:
case ParseNodeKind::False:
case ParseNodeKind::Null:
case ParseNodeKind::RawUndefined:
case ParseNodeKind::Elision:
case ParseNodeKind::Generator:
MOZ_ASSERT(pn->is<NullaryNode>());
*answer = false;
return true;
case ParseNodeKind::ObjectPropertyName:
case ParseNodeKind::PrivateName: // no side effects, unlike ParseNodeKind::Name
case ParseNodeKind::String:
case ParseNodeKind::TemplateString:
MOZ_ASSERT(pn->is<NameNode>());
*answer = false;
return true;
case ParseNodeKind::RegExp:
MOZ_ASSERT(pn->is<RegExpLiteral>());
*answer = false;
return true;
case ParseNodeKind::Number:
MOZ_ASSERT(pn->is<NumericLiteral>());
*answer = false;
return true;
// |this| can throw in derived class constructors, including nested arrow
// functions or eval.
case ParseNodeKind::This:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = sc->needsThisTDZChecks();
return true;
// Trivial binary nodes with more token pos holders.
case ParseNodeKind::NewTarget:
case ParseNodeKind::ImportMeta: {
MOZ_ASSERT(pn->as<BinaryNode>().left()->isKind(ParseNodeKind::PosHolder));
MOZ_ASSERT(pn->as<BinaryNode>().right()->isKind(ParseNodeKind::PosHolder));
*answer = false;
return true;
}
case ParseNodeKind::Break:
MOZ_ASSERT(pn->is<BreakStatement>());
*answer = true;
return true;
case ParseNodeKind::Continue:
MOZ_ASSERT(pn->is<ContinueStatement>());
*answer = true;
return true;
case ParseNodeKind::Debugger:
MOZ_ASSERT(pn->is<DebuggerStatement>());
*answer = true;
return true;
// Watch out for getters!
case ParseNodeKind::Dot:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
// Unary cases with side effects only if the child has them.
case ParseNodeKind::TypeOfExpr:
case ParseNodeKind::Void:
case ParseNodeKind::Not:
return checkSideEffects(pn->as<UnaryNode>().kid(), answer);
// Even if the name expression is effect-free, performing ToPropertyKey on
// it might not be effect-free:
//
// RegExp.prototype.toString = () => { throw 42; };
// ({ [/regex/]: 0 }); // ToPropertyKey(/regex/) throws 42
//
// function Q() {
// ({ [new.target]: 0 });
// }
// Q.toString = () => { throw 17; };
// new Q; // new.target will be Q, ToPropertyKey(Q) throws 17
case ParseNodeKind::ComputedName:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = true;
return true;
// Looking up or evaluating the associated name could throw.
case ParseNodeKind::TypeOfName:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = true;
return true;
// This unary case has side effects on the enclosing object, sure. But
// that's not the question this function answers: it's whether the
// operation may have a side effect on something *other* than the result
// of the overall operation in which it's embedded. The answer to that
// is no, because an object literal having a mutated prototype only
// produces a value, without affecting anything else.
case ParseNodeKind::MutateProto:
return checkSideEffects(pn->as<UnaryNode>().kid(), answer);
// Unary cases with obvious side effects.
case ParseNodeKind::PreIncrement:
case ParseNodeKind::PostIncrement:
case ParseNodeKind::PreDecrement:
case ParseNodeKind::PostDecrement:
case ParseNodeKind::Throw:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = true;
return true;
// These might invoke valueOf/toString, even with a subexpression without
// side effects! Consider |+{ valueOf: null, toString: null }|.
case ParseNodeKind::BitNot:
case ParseNodeKind::Pos:
case ParseNodeKind::Neg:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = true;
return true;
// This invokes the (user-controllable) iterator protocol.
case ParseNodeKind::Spread:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = true;
return true;
case ParseNodeKind::InitialYield:
case ParseNodeKind::YieldStar:
case ParseNodeKind::Yield:
case ParseNodeKind::Await:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = true;
return true;
// Deletion generally has side effects, even if isolated cases have none.
case ParseNodeKind::DeleteName:
case ParseNodeKind::DeleteProp:
case ParseNodeKind::DeleteElem:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = true;
return true;
// Deletion of a non-Reference expression has side effects only through
// evaluating the expression.
case ParseNodeKind::DeleteExpr: {
ParseNode* expr = pn->as<UnaryNode>().kid();
return checkSideEffects(expr, answer);
}
case ParseNodeKind::ExpressionStatement:
return checkSideEffects(pn->as<UnaryNode>().kid(), answer);
// Binary cases with obvious side effects.
case ParseNodeKind::Assign:
case ParseNodeKind::AddAssign:
case ParseNodeKind::SubAssign:
case ParseNodeKind::BitOrAssign:
case ParseNodeKind::BitXorAssign:
case ParseNodeKind::BitAndAssign:
case ParseNodeKind::LshAssign:
case ParseNodeKind::RshAssign:
case ParseNodeKind::UrshAssign:
case ParseNodeKind::MulAssign:
case ParseNodeKind::DivAssign:
case ParseNodeKind::ModAssign:
case ParseNodeKind::PowAssign:
MOZ_ASSERT(pn->is<AssignmentNode>());
*answer = true;
return true;
case ParseNodeKind::SetThis:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
case ParseNodeKind::StatementList:
// Strict equality operations and logical operators are well-behaved and
// perform no conversions.
case ParseNodeKind::Or:
case ParseNodeKind::And:
case ParseNodeKind::StrictEq:
case ParseNodeKind::StrictNe:
// Any subexpression of a comma expression could be effectful.
case ParseNodeKind::Comma:
MOZ_ASSERT(!pn->as<ListNode>().empty());
MOZ_FALLTHROUGH;
// Subcomponents of a literal may be effectful.
case ParseNodeKind::Array:
case ParseNodeKind::Object:
for (ParseNode* item : pn->as<ListNode>().contents()) {
if (!checkSideEffects(item, answer)) {
return false;
}
if (*answer) {
return true;
}
}
return true;
// Most other binary operations (parsed as lists in SpiderMonkey) may
// perform conversions triggering side effects. Math operations perform
// ToNumber and may fail invoking invalid user-defined toString/valueOf:
// |5 < { toString: null }|. |instanceof| throws if provided a
// non-object constructor: |null instanceof null|. |in| throws if given
// a non-object RHS: |5 in null|.
case ParseNodeKind::BitOr:
case ParseNodeKind::BitXor:
case ParseNodeKind::BitAnd:
case ParseNodeKind::Eq:
case ParseNodeKind::Ne:
case ParseNodeKind::Lt:
case ParseNodeKind::Le:
case ParseNodeKind::Gt:
case ParseNodeKind::Ge:
case ParseNodeKind::InstanceOf:
case ParseNodeKind::In:
case ParseNodeKind::Lsh:
case ParseNodeKind::Rsh:
case ParseNodeKind::Ursh:
case ParseNodeKind::Add:
case ParseNodeKind::Sub:
case ParseNodeKind::Star:
case ParseNodeKind::Div:
case ParseNodeKind::Mod:
case ParseNodeKind::Pow:
MOZ_ASSERT(pn->as<ListNode>().count() >= 2);
*answer = true;
return true;
case ParseNodeKind::Colon:
case ParseNodeKind::Case: {
BinaryNode* node = &pn->as<BinaryNode>();
if (!checkSideEffects(node->left(), answer)) {
return false;
}
if (*answer) {
return true;
}
return checkSideEffects(node->right(), answer);
}
// More getters.
case ParseNodeKind::Elem:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
// These affect visible names in this code, or in other code.
case ParseNodeKind::Import:
case ParseNodeKind::ExportFrom:
case ParseNodeKind::ExportDefault:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
// Likewise.
case ParseNodeKind::Export:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = true;
return true;
case ParseNodeKind::CallImport:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
// Every part of a loop might be effect-free, but looping infinitely *is*
// an effect. (Language lawyer trivia: C++ says threads can be assumed
// to exit or have side effects, C++14 [intro.multithread]p27, so a C++
// implementation's equivalent of the below could set |*answer = false;|
// if all loop sub-nodes set |*answer = false|!)
case ParseNodeKind::DoWhile:
case ParseNodeKind::While:
case ParseNodeKind::For:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
// Declarations affect the name set of the relevant scope.
case ParseNodeKind::Var:
case ParseNodeKind::Const:
case ParseNodeKind::Let:
MOZ_ASSERT(pn->is<ListNode>());
*answer = true;
return true;
case ParseNodeKind::If:
case ParseNodeKind::Conditional:
{
TernaryNode* node = &pn->as<TernaryNode>();
if (!checkSideEffects(node->kid1(), answer)) {
return false;
}
if (*answer) {
return true;
}
if (!checkSideEffects(node->kid2(), answer)) {
return false;
}
if (*answer) {
return true;
}
if ((pn = node->kid3())) {
goto restart;
}
return true;
}
// Function calls can invoke non-local code.
case ParseNodeKind::New:
case ParseNodeKind::Call:
case ParseNodeKind::TaggedTemplate:
case ParseNodeKind::SuperCall:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
// Function arg lists can contain arbitrary expressions. Technically
// this only causes side-effects if one of the arguments does, but since
// the call being made will always trigger side-effects, it isn't needed.
case ParseNodeKind::Arguments:
MOZ_ASSERT(pn->is<ListNode>());
*answer = true;
return true;
case ParseNodeKind::Pipeline:
MOZ_ASSERT(pn->as<ListNode>().count() >= 2);
*answer = true;
return true;
// Classes typically introduce names. Even if no name is introduced,
// the heritage and/or class body (through computed property names)
// usually have effects.
case ParseNodeKind::Class:
MOZ_ASSERT(pn->is<ClassNode>());
*answer = true;
return true;
// |with| calls |ToObject| on its expression and so throws if that value
// is null/undefined.
case ParseNodeKind::With:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
case ParseNodeKind::Return:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
case ParseNodeKind::Name:
MOZ_ASSERT(pn->is<NameNode>());
*answer = true;
return true;
// Shorthands could trigger getters: the |x| in the object literal in
// |with ({ get x() { throw 42; } }) ({ x });|, for example, triggers
// one. (Of course, it isn't necessary to use |with| for a shorthand to
// trigger a getter.)
case ParseNodeKind::Shorthand:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
case ParseNodeKind::Function:
MOZ_ASSERT(pn->is<CodeNode>());
/*
* A named function, contrary to ES3, is no longer effectful, because
* we bind its name lexically (using JSOP_CALLEE) instead of creating
* an Object instance and binding a readonly, permanent property in it
* (the object and binding can be detected and hijacked or captured).
* This is a bug fix to ES3; it is fixed in ES3.1 drafts.
*/
*answer = false;
return true;
case ParseNodeKind::Module:
*answer = false;
return true;
case ParseNodeKind::Try:
{
TryNode* tryNode = &pn->as<TryNode>();
if (!checkSideEffects(tryNode->body(), answer)) {
return false;
}
if (*answer) {
return true;
}
if (LexicalScopeNode* catchScope = tryNode->catchScope()) {
if (!checkSideEffects(catchScope, answer)) {
return false;
}
if (*answer) {
return true;
}
}
if (ParseNode* finallyBlock = tryNode->finallyBlock()) {
if (!checkSideEffects(finallyBlock, answer)) {
return false;
}
}
return true;
}
case ParseNodeKind::Catch: {
BinaryNode* catchClause = &pn->as<BinaryNode>();
if (ParseNode* name = catchClause->left()) {
if (!checkSideEffects(name, answer)) {
return false;
}
if (*answer) {
return true;
}
}
return checkSideEffects(catchClause->right(), answer);
}
case ParseNodeKind::Switch: {
SwitchStatement* switchStmt = &pn->as<SwitchStatement>();
if (!checkSideEffects(&switchStmt->discriminant(), answer)) {
return false;
}
return *answer || checkSideEffects(&switchStmt->lexicalForCaseList(), answer);
}
case ParseNodeKind::Label:
return checkSideEffects(pn->as<LabeledStatement>().statement(), answer);
case ParseNodeKind::LexicalScope:
return checkSideEffects(pn->as<LexicalScopeNode>().scopeBody(), answer);
// We could methodically check every interpolated expression, but it's
// probably not worth the trouble. Treat template strings as effect-free
// only if they don't contain any substitutions.
case ParseNodeKind::TemplateStringList: {
ListNode* list = &pn->as<ListNode>();
MOZ_ASSERT(!list->empty());
MOZ_ASSERT((list->count() % 2) == 1,
"template strings must alternate template and substitution "
"parts");
*answer = list->count() > 1;
return true;
}
// This should be unreachable but is left as-is for now.
case ParseNodeKind::ParamsBody:
*answer = true;
return true;
case ParseNodeKind::ForIn: // by ParseNodeKind::For
case ParseNodeKind::ForOf: // by ParseNodeKind::For
case ParseNodeKind::ForHead: // by ParseNodeKind::For
case ParseNodeKind::ClassMethod: // by ParseNodeKind::Class
case ParseNodeKind::ClassField: // by ParseNodeKind::Class
case ParseNodeKind::ClassNames: // by ParseNodeKind::Class
case ParseNodeKind::ClassMemberList: // by ParseNodeKind::Class
case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import
case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import
case ParseNodeKind::ExportBatchSpec: // by ParseNodeKind::Export
case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export
case ParseNodeKind::ExportSpec: // by ParseNodeKind::Export
case ParseNodeKind::CallSiteObj: // by ParseNodeKind::TaggedTemplate
case ParseNodeKind::PosHolder: // by ParseNodeKind::NewTarget
case ParseNodeKind::SuperBase: // by ParseNodeKind::Elem and others
case ParseNodeKind::PropertyName: // by ParseNodeKind::Dot
MOZ_CRASH("handled by parent nodes");
case ParseNodeKind::Limit: // invalid sentinel value
MOZ_CRASH("invalid node kind");
}
MOZ_CRASH("invalid, unenumerated ParseNodeKind value encountered in "
"BytecodeEmitter::checkSideEffects");
}
bool
BytecodeEmitter::isInLoop()
{
return findInnermostNestableControl<LoopControl>();
}
bool
BytecodeEmitter::checkSingletonContext()
{
if (!script->treatAsRunOnce() || sc->isFunctionBox() || isInLoop()) {
return false;
}
hasSingletons = true;
return true;
}
bool
BytecodeEmitter::checkRunOnceContext()
{
return checkSingletonContext() || (!isInLoop() && isRunOnceLambda());
}
bool
BytecodeEmitter::needsImplicitThis()
{
// Short-circuit if there is an enclosing 'with' scope.
if (sc->inWith()) {
return true;
}
// Otherwise see if the current point is under a 'with'.
for (EmitterScope* es = innermostEmitterScope(); es; es = es->enclosingInFrame()) {
if (es->scope(this)->kind() == ScopeKind::With) {
return true;
}
}
return false;
}
void
BytecodeEmitter::tellDebuggerAboutCompiledScript(JSContext* cx)
{
// Note: when parsing off thread the resulting scripts need to be handed to
// the debugger after rejoining to the main thread.
if (cx->helperThread()) {
return;
}
// Lazy scripts are never top level (despite always being invoked with a
// nullptr parent), and so the hook should never be fired.
if (emitterMode != LazyFunction && !parent) {
Debugger::onNewScript(cx, script);
}
}
void
BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...)
{
MOZ_ASSERT_IF(!pn, this->scriptStartOffsetSet);
uint32_t offset = pn ? pn->pn_pos.begin : this->scriptStartOffset;
va_list args;
va_start(args, errorNumber);
parser->errorReporter().errorAtVA(offset, errorNumber, &args);
va_end(args);
}
void
BytecodeEmitter::reportError(const Maybe<uint32_t>& maybeOffset, unsigned errorNumber, ...)
{
MOZ_ASSERT_IF(!maybeOffset, this->scriptStartOffsetSet);
uint32_t offset = maybeOffset ? *maybeOffset : this->scriptStartOffset;
va_list args;
va_start(args, errorNumber);
parser->errorReporter().errorAtVA(offset, errorNumber, &args);
va_end(args);
}
bool
BytecodeEmitter::reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...)
{
MOZ_ASSERT_IF(!pn, this->scriptStartOffsetSet);
uint32_t offset = pn ? pn->pn_pos.begin : this->scriptStartOffset;
va_list args;
va_start(args, errorNumber);
bool result = parser->errorReporter().reportExtraWarningErrorNumberVA(nullptr, offset, errorNumber, &args);
va_end(args);
return result;
}
bool
BytecodeEmitter::reportExtraWarning(const Maybe<uint32_t>& maybeOffset,
unsigned errorNumber, ...)
{
MOZ_ASSERT_IF(!maybeOffset, this->scriptStartOffsetSet);
uint32_t offset = maybeOffset ? *maybeOffset : this->scriptStartOffset;
va_list args;
va_start(args, errorNumber);
bool result = parser->errorReporter().reportExtraWarningErrorNumberVA(nullptr, offset, errorNumber, &args);
va_end(args);
return result;
}
bool
BytecodeEmitter::emitNewInit()
{
const size_t len = 1 + UINT32_INDEX_LEN;
ptrdiff_t offset;
if (!emitCheck(len, &offset)) {
return false;
}
jsbytecode* code = this->code(offset);
code[0] = JSOP_NEWINIT;
code[1] = 0;
code[2] = 0;
code[3] = 0;
code[4] = 0;
checkTypeSet(JSOP_NEWINIT);
updateDepth(offset);
return true;
}
bool
BytecodeEmitter::iteratorResultShape(unsigned* shape)
{
// No need to do any guessing for the object kind, since we know exactly how
// many properties we plan to have.
gc::AllocKind kind = gc::GetGCObjectKind(2);
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
if (!obj) {
return false;
}
Rooted<jsid> value_id(cx, NameToId(cx->names().value));
Rooted<jsid> done_id(cx, NameToId(cx->names().done));
if (!NativeDefineDataProperty(cx, obj, value_id, UndefinedHandleValue, JSPROP_ENUMERATE)) {
return false;
}
if (!NativeDefineDataProperty(cx, obj, done_id, UndefinedHandleValue, JSPROP_ENUMERATE)) {
return false;
}
ObjectBox* objbox = parser->newObjectBox(obj);
if (!objbox) {
return false;
}
*shape = objectList.add(objbox);
return true;
}
bool
BytecodeEmitter::emitPrepareIteratorResult()
{
unsigned shape;
if (!iteratorResultShape(&shape)) {
return false;
}
return emitIndex32(JSOP_NEWOBJECT, shape);
}
bool
BytecodeEmitter::emitFinishIteratorResult(bool done)
{
uint32_t value_id;
if (!makeAtomIndex(cx->names().value, &value_id)) {
return false;
}
uint32_t done_id;
if (!makeAtomIndex(cx->names().done, &done_id)) {
return false;
}
if (!emitIndex32(JSOP_INITPROP, value_id)) {
return false;
}
if (!emit1(done ? JSOP_TRUE : JSOP_FALSE)) {
return false;
}
if (!emitIndex32(JSOP_INITPROP, done_id)) {
return false;
}
return true;
}
bool
BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc)
{
NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get);
if (!noe.emitGet()) {
return false;
}
return true;
}
bool
BytecodeEmitter::emitGetName(NameNode* name)
{
return emitGetName(name->name());
}
bool
BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc)
{
// Dynamic accesses have TDZ checks built into their VM code and should
// never emit explicit TDZ checks.
MOZ_ASSERT(loc.hasKnownSlot());
MOZ_ASSERT(loc.isLexical());
Maybe<MaybeCheckTDZ> check = innermostTDZCheckCache->needsTDZCheck(this, name);
if (!check) {
return false;
}
// We've already emitted a check in this basic block.
if (*check == DontCheckTDZ) {
return true;
}
if (loc.kind() == NameLocation::Kind::FrameSlot) {
if (!emitLocalOp(JSOP_CHECKLEXICAL, loc.frameSlot())) {
return false;
}
} else {
if (!emitEnvCoordOp(JSOP_CHECKALIASEDLEXICAL, loc.environmentCoordinate())) {
return false;
}
}
return innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ);
}
bool
BytecodeEmitter::emitPropLHS(PropertyAccess* prop)
{
MOZ_ASSERT(!prop->isSuper());
ParseNode* expr = &prop->expression();
if (!expr->is<PropertyAccess>() || expr->as<PropertyAccess>().isSuper()) {
// The non-optimized case.
return emitTree(expr);
}
// If the object operand is also a dotted property reference, reverse the
// list linked via expression() temporarily so we can iterate over it from
// the bottom up (reversing again as we go), to avoid excessive recursion.
PropertyAccess* pndot = &expr->as<PropertyAccess>();
ParseNode* pnup = nullptr;
ParseNode* pndown;
for (;;) {
// Reverse pndot->expression() to point up, not down.
pndown = &pndot->expression();
pndot->setExpression(pnup);
if (!pndown->is<PropertyAccess>() || pndown->as<PropertyAccess>().isSuper()) {
break;
}
pnup = pndot;
pndot = &pndown->as<PropertyAccess>();
}
// pndown is a primary expression, not a dotted property reference.
if (!emitTree(pndown)) {
return false;
}
while (true) {
// TODO(khyperia): Implement private field access.
// Walk back up the list, emitting annotated name ops.
if (!emitAtomOp(pndot->key().atom(), JSOP_GETPROP)) {
return false;
}
// Reverse the pndot->expression() link again.
pnup = pndot->maybeExpression();
pndot->setExpression(pndown);
pndown = pndot;
if (!pnup) {
break;
}
pndot = &pnup->as<PropertyAccess>();
}
return true;
}
bool
BytecodeEmitter::emitPropIncDec(UnaryNode* incDec)
{
PropertyAccess* prop = &incDec->kid()->as<PropertyAccess>();
// TODO(khyperia): Implement private field access.
bool isSuper = prop->isSuper();
ParseNodeKind kind = incDec->getKind();
PropOpEmitter poe(this,
kind == ParseNodeKind::PostIncrement ? PropOpEmitter::Kind::PostIncrement
: kind == ParseNodeKind::PreIncrement ? PropOpEmitter::Kind::PreIncrement
: kind == ParseNodeKind::PostDecrement ? PropOpEmitter::Kind::PostDecrement
: PropOpEmitter::Kind::PreDecrement,
isSuper
? PropOpEmitter::ObjKind::Super
: PropOpEmitter::ObjKind::Other);
if (!poe.prepareForObj()) {
return false;
}
if (isSuper) {
UnaryNode* base = &prop->expression().as<UnaryNode>();
if (!emitGetThisForSuperBase(base)) { // THIS
return false;
}
} else {
if (!emitPropLHS(prop)) {
return false; // OBJ
}
}
if (!poe.emitIncDec(prop->key().atom())) { // RESULT
return false;
}
return true;
}
bool
BytecodeEmitter::emitNameIncDec(UnaryNode* incDec)
{
MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name));
ParseNodeKind kind = incDec->getKind();
NameNode* name = &incDec->kid()->as<NameNode>();
NameOpEmitter noe(this, name->atom(),
kind == ParseNodeKind::PostIncrement ? NameOpEmitter::Kind::PostIncrement
: kind == ParseNodeKind::PreIncrement ? NameOpEmitter::Kind::PreIncrement
: kind == ParseNodeKind::PostDecrement ? NameOpEmitter::Kind::PostDecrement
: NameOpEmitter::Kind::PreDecrement);
if (!noe.emitIncDec()) {
return false;
}
return true;
}
bool
BytecodeEmitter::emitElemOpBase(JSOp op)
{
if (!emit1(op)) {
return false;
}
checkTypeSet(op);
return true;
}
bool
BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe)
{
if (isSuper) {
if (!eoe.prepareForObj()) { //
return false;
}
UnaryNode* base = &elem->expression().as<UnaryNode>();
if (!emitGetThisForSuperBase(base)) { // THIS
return false;
}
if (!eoe.prepareForKey()) { // THIS
return false;
}
if (!emitTree(&elem->key())) { // THIS KEY
return false;
}
return true;
}
if (!eoe.prepareForObj()) { //
return false;
}
if (!emitTree(&elem->expression())) { // OBJ
return false;
}
if (!eoe.prepareForKey()) { // OBJ? OBJ
return false;
}
if (!emitTree(&elem->key())) { // OBJ? OBJ KEY
return false;
}
return true;
}
bool
BytecodeEmitter::emitElemIncDec(UnaryNode* incDec)
{
PropertyByValue* elemExpr = &incDec->kid()->as<PropertyByValue>();
bool isSuper = elemExpr->isSuper();
ParseNodeKind kind = incDec->getKind();
ElemOpEmitter eoe(this,
kind == ParseNodeKind::PostIncrement ? ElemOpEmitter::Kind::PostIncrement
: kind == ParseNodeKind::PreIncrement ? ElemOpEmitter::Kind::PreIncrement
: kind == ParseNodeKind::PostDecrement ? ElemOpEmitter::Kind::PostDecrement
: ElemOpEmitter::Kind::PreDecrement,
isSuper
? ElemOpEmitter::ObjKind::Super
: ElemOpEmitter::ObjKind::Other);
if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) { // [Super]
// // THIS KEY
// // [Other]
// // OBJ KEY
return false;
}
if (!eoe.emitIncDec()) { // RESULT
return false;
}
return true;
}
bool
BytecodeEmitter::emitCallIncDec(UnaryNode* incDec)
{
MOZ_ASSERT(incDec->isKind(ParseNodeKind::PreIncrement) ||
incDec->isKind(ParseNodeKind::PostIncrement) ||
incDec->isKind(ParseNodeKind::PreDecrement) ||
incDec->isKind(ParseNodeKind::PostDecrement));
ParseNode* call = incDec->kid();
MOZ_ASSERT(call->isKind(ParseNodeKind::Call));
if (!emitTree(call)) { // CALLRESULT
return false;
}
if (!emit1(JSOP_POS)) { // N
return false;
}
// The increment/decrement has no side effects, so proceed to throw for
// invalid assignment target.
return emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS);
}
bool
BytecodeEmitter::emitNumberOp(double dval)
{
int32_t ival;
if (NumberIsInt32(dval, &ival)) {
if (ival == 0) {
return emit1(JSOP_ZERO);
}
if (ival == 1) {
return emit1(JSOP_ONE);
}
if ((int)(int8_t)ival == ival) {
return emit2(JSOP_INT8, uint8_t(int8_t(ival)));
}
uint32_t u = uint32_t(ival);
if (u < JS_BIT(16)) {
if (!emitUint16Operand(JSOP_UINT16, u)) {
return false;
}
} else if (u < JS_BIT(24)) {
ptrdiff_t off;
if (!emitN(JSOP_UINT24, 3, &off)) {
return false;
}
SET_UINT24(code(off), u);
} else {
ptrdiff_t off;
if (!emitN(JSOP_INT32, 4, &off)) {
return false;
}
SET_INT32(code(off), ival);
}
return true;
}
if (!numberList.append(dval)) {
return false;
}
return emitIndex32(JSOP_DOUBLE, numberList.length() - 1);
}
/*
* Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047.
* LLVM is deciding to inline this function which uses a lot of stack space
* into emitTree which is recursive and uses relatively little stack space.
*/
MOZ_NEVER_INLINE bool
BytecodeEmitter::emitSwitch(SwitchStatement* switchStmt)
{
LexicalScopeNode& lexical = switchStmt->lexicalForCaseList();
MOZ_ASSERT(lexical.isKind(ParseNodeKind::LexicalScope));
ListNode* cases = &lexical.scopeBody()->as<ListNode>();
MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList));
SwitchEmitter se(this);
if (!se.emitDiscriminant(Some(switchStmt->pn_pos.begin))) {
return false;
}
if (!emitTree(&switchStmt->discriminant())) {
return false;
}
// Enter the scope before pushing the switch BreakableControl since all
// breaks are under this scope.
if (!lexical.isEmptyScope()) {
if (!se.emitLexical(lexical.scopeBindings())) {
return false;
}
// A switch statement may contain hoisted functions inside its
// cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST
// bodies of the cases to the case list.
if (cases->hasTopLevelFunctionDeclarations()) {
for (ParseNode* item : cases->contents()) {
CaseClause* caseClause = &item->as<CaseClause>();
ListNode* statements = caseClause->statementList();
if (statements->hasTopLevelFunctionDeclarations()) {
if (!emitHoistedFunctionsInList(statements)) {
return false;
}
}
}
}
} else {
MOZ_ASSERT(!cases->hasTopLevelFunctionDeclarations());
}
SwitchEmitter::TableGenerator tableGen(this);
uint32_t caseCount = cases->count() - (switchStmt->hasDefault() ? 1 : 0);
if (caseCount == 0) {
tableGen.finish(0);
} else {
for (ParseNode* item : cases->contents()) {
CaseClause* caseClause = &item->as<CaseClause>();
if (caseClause->isDefault()) {
continue;
}
ParseNode* caseValue = caseClause->caseExpression();
if (caseValue->getKind() != ParseNodeKind::Number) {
tableGen.setInvalid();
break;
}
int32_t i;
if (!NumberEqualsInt32(caseValue->as<NumericLiteral>().value(), &i)) {
tableGen.setInvalid();
break;
}
if (!tableGen.addNumber(i)) {
return false;
}
}
tableGen.finish(caseCount);
}
if (!se.validateCaseCount(caseCount)) {
return false;
}
bool isTableSwitch = tableGen.isValid();
if (isTableSwitch) {
if (!se.emitTable(tableGen)) {
return false;
}
} else {
if (!se.emitCond()) {
return false;
}
// Emit code for evaluating cases and jumping to case statements.
for (ParseNode* item : cases->contents()) {
CaseClause* caseClause = &item->as<CaseClause>();
if (caseClause->isDefault()) {
continue;
}
if (!se.prepareForCaseValue()) {
return false;
}
ParseNode* caseValue = caseClause->caseExpression();
// If the expression is a literal, suppress line number emission so
// that debugging works more naturally.
if (!emitTree(caseValue, ValueUsage::WantValue,
caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE))
{
return false;
}
if (!se.emitCaseJump()) {
return false;
}
}
}
// Emit code for each case's statements.
for (ParseNode* item : cases->contents()) {
CaseClause* caseClause = &item->as<CaseClause>();
if (caseClause->isDefault()) {
if (!se.emitDefaultBody()) {
return false;
}
} else {
if (isTableSwitch) {
ParseNode* caseValue = caseClause->caseExpression();
MOZ_ASSERT(caseValue->isKind(ParseNodeKind::Number));