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 generation.
*/
#include "frontend/BytecodeEmitter.h"
#include "mozilla/Casting.h" // mozilla::AssertedCast
#include "mozilla/DebugOnly.h" // mozilla::DebugOnly
#include "mozilla/FloatingPoint.h" // mozilla::NumberEqualsInt32, mozilla::NumberIsInt32
#include "mozilla/HashTable.h" // mozilla::HashSet
#include "mozilla/Maybe.h" // mozilla::{Maybe,Nothing,Some}
#include "mozilla/PodOperations.h" // mozilla::PodCopy
#include "mozilla/Saturate.h"
#include "mozilla/Variant.h" // mozilla::AsVariant
#include <algorithm>
#include <iterator>
#include <string.h>
#include "jstypes.h" // JS_BIT
#include "frontend/AbstractScopePtr.h" // ScopeIndex
#include "frontend/BytecodeControlStructures.h" // NestableControl, BreakableControl, LabelControl, LoopControl, TryFinallyControl
#include "frontend/CallOrNewEmitter.h" // CallOrNewEmitter
#include "frontend/CForEmitter.h" // CForEmitter
#include "frontend/DecoratorEmitter.h" // DecoratorEmitter
#include "frontend/DefaultEmitter.h" // DefaultEmitter
#include "frontend/DoWhileEmitter.h" // DoWhileEmitter
#include "frontend/ElemOpEmitter.h" // ElemOpEmitter
#include "frontend/EmitterScope.h" // EmitterScope
#include "frontend/ExpressionStatementEmitter.h" // ExpressionStatementEmitter
#include "frontend/ForInEmitter.h" // ForInEmitter
#include "frontend/ForOfEmitter.h" // ForOfEmitter
#include "frontend/FunctionEmitter.h" // FunctionEmitter, FunctionScriptEmitter, FunctionParamsEmitter
#include "frontend/IfEmitter.h" // IfEmitter, InternalIfEmitter, CondEmitter
#include "frontend/LabelEmitter.h" // LabelEmitter
#include "frontend/LexicalScopeEmitter.h" // LexicalScopeEmitter
#include "frontend/ModuleSharedContext.h" // ModuleSharedContext
#include "frontend/NameAnalysisTypes.h" // PrivateNameKind
#include "frontend/NameFunctions.h" // NameFunctions
#include "frontend/NameOpEmitter.h" // NameOpEmitter
#include "frontend/ObjectEmitter.h" // PropertyEmitter, ObjectEmitter, ClassEmitter
#include "frontend/OptionalEmitter.h" // OptionalEmitter
#include "frontend/ParseContext.h" // ParseContext::Scope
#include "frontend/ParseNode.h" // ParseNodeKind, ParseNode and subclasses
#include "frontend/Parser.h" // Parser
#include "frontend/ParserAtom.h" // ParserAtomsTable, ParserAtom
#include "frontend/PrivateOpEmitter.h" // PrivateOpEmitter
#include "frontend/PropOpEmitter.h" // PropOpEmitter
#include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteWriter
#include "frontend/SwitchEmitter.h" // SwitchEmitter
#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher
#include "frontend/TDZCheckCache.h" // TDZCheckCache
#include "frontend/TryEmitter.h" // TryEmitter
#include "frontend/WhileEmitter.h" // WhileEmitter
#include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::ColumnNumberOffset
#include "js/friend/ErrorMessages.h" // JSMSG_*
#include "js/friend/StackLimits.h" // AutoCheckRecursionLimit
#include "util/StringBuffer.h" // StringBuffer
#include "vm/BytecodeUtil.h" // JOF_*, IsArgOp, IsLocalOp, SET_UINT24, SET_ICINDEX, BytecodeFallsThrough, BytecodeIsJumpTarget
#include "vm/CompletionKind.h" // CompletionKind
#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind
#include "vm/GeneratorObject.h" // AbstractGeneratorObject
#include "vm/Opcodes.h" // JSOp, JSOpLength_*
#include "vm/PropMap.h" // SharedPropMap::MaxPropsForNonDictionary
#include "vm/Scope.h" // GetScopeDataTrailingNames
#include "vm/SharedStencil.h" // ScopeNote
#include "vm/ThrowMsgKind.h" // ThrowMsgKind
#include "vm/TypeofEqOperand.h" // TypeofEqOperand
using namespace js;
using namespace js::frontend;
using mozilla::AssertedCast;
using mozilla::AsVariant;
using mozilla::DebugOnly;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::NumberEqualsInt32;
using mozilla::NumberIsInt32;
using mozilla::PodCopy;
using mozilla::Some;
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::WhileStmt || kind == ParseNodeKind::ForStmt ||
kind == ParseNodeKind::Function;
}
static bool NeedsFieldInitializer(ParseNode* member, bool inStaticContext) {
// For the purposes of bytecode emission, StaticClassBlocks are treated as if
// they were static initializers.
return (member->is<StaticClassBlock>() && inStaticContext) ||
(member->is<ClassField>() &&
member->as<ClassField>().isStatic() == inStaticContext);
}
static bool NeedsAccessorInitializer(ParseNode* member, bool isStatic) {
if (isStatic) {
return false;
}
return member->is<ClassMethod>() &&
member->as<ClassMethod>().name().isKind(ParseNodeKind::PrivateName) &&
!member->as<ClassMethod>().isStatic() &&
member->as<ClassMethod>().accessorType() != AccessorType::None;
}
static bool ShouldSuppressBreakpointsAndSourceNotes(
SharedContext* sc, BytecodeEmitter::EmitterMode emitterMode) {
// Suppress for all self-hosting code.
if (emitterMode == BytecodeEmitter::EmitterMode::SelfHosting) {
return true;
}
// Suppress for synthesized class constructors.
if (sc->isFunctionBox()) {
FunctionBox* funbox = sc->asFunctionBox();
return funbox->isSyntheticFunction() && funbox->isClassConstructor();
}
return false;
}
BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, FrontendContext* fc,
SharedContext* sc,
const ErrorReporter& errorReporter,
CompilationState& compilationState,
EmitterMode emitterMode)
: sc(sc),
fc(fc),
parent(parent),
bytecodeSection_(fc, sc->extent().lineno,
JS::LimitedColumnNumberOneOrigin(sc->extent().column)),
perScriptData_(fc, compilationState),
errorReporter_(errorReporter),
compilationState(compilationState),
suppressBreakpointsAndSourceNotes(
ShouldSuppressBreakpointsAndSourceNotes(sc, emitterMode)),
emitterMode(emitterMode) {
MOZ_ASSERT_IF(parent, fc == parent->fc);
}
BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc)
: BytecodeEmitter(parent, parent->fc, sc, parent->errorReporter_,
parent->compilationState, parent->emitterMode) {}
BytecodeEmitter::BytecodeEmitter(FrontendContext* fc,
const EitherParser& parser, SharedContext* sc,
CompilationState& compilationState,
EmitterMode emitterMode)
: BytecodeEmitter(nullptr, fc, sc, parser.errorReporter(), compilationState,
emitterMode) {
ep_.emplace(parser);
}
void BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition) {
setScriptStartOffsetIfUnset(bodyPosition.begin);
setFunctionBodyEndPos(bodyPosition.end);
}
bool BytecodeEmitter::init() {
if (!parent) {
if (!compilationState.prepareSharedDataStorage(fc)) {
return false;
}
}
return perScriptData_.init(fc);
}
bool BytecodeEmitter::init(TokenPos bodyPosition) {
initFromBodyPosition(bodyPosition);
return init();
}
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(TaggedParserAtomIndex name) {
return innermostEmitterScope()->lookup(this, name);
}
void BytecodeEmitter::lookupPrivate(TaggedParserAtomIndex name,
NameLocation& loc,
Maybe<NameLocation>& brandLoc) {
innermostEmitterScope()->lookupPrivate(this, name, loc, brandLoc);
}
Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScope(
TaggedParserAtomIndex name, EmitterScope* target) {
return innermostEmitterScope()->locationBoundInScope(name, target);
}
template <typename T>
Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScopeType(
TaggedParserAtomIndex name, EmitterScope* source) {
EmitterScope* aScope = source;
while (!aScope->scope(this).is<T>()) {
aScope = aScope->enclosingInFrame();
}
return source->locationBoundInScope(name, aScope);
}
bool BytecodeEmitter::markStepBreakpoint() {
if (skipBreakpointSrcNotes()) {
return true;
}
if (!newSrcNote(SrcNoteType::BreakpointStepSep)) {
return false;
}
// We track the location of the most recent separator for use in
// markSimpleBreakpoint. Note that this means that the position must already
// be set before markStepBreakpoint is called.
bytecodeSection().updateSeparatorPosition();
return true;
}
bool BytecodeEmitter::markSimpleBreakpoint() {
if (skipBreakpointSrcNotes()) {
return true;
}
// If a breakable call ends up being the same location as the most recent
// expression start, we need to skip marking it breakable in order to avoid
// having two breakpoints with the same line/column position.
// Note: This assumes that the position for the call has already been set.
if (!bytecodeSection().isDuplicateLocation()) {
if (!newSrcNote(SrcNoteType::Breakpoint)) {
return false;
}
}
return true;
}
bool BytecodeEmitter::emitCheck(JSOp op, ptrdiff_t delta,
BytecodeOffset* offset) {
size_t oldLength = bytecodeSection().code().length();
*offset = BytecodeOffset(oldLength);
size_t newLength = oldLength + size_t(delta);
if (MOZ_UNLIKELY(newLength > MaxBytecodeLength)) {
ReportAllocationOverflow(fc);
return false;
}
if (!bytecodeSection().code().growByUninitialized(delta)) {
return false;
}
if (BytecodeOpHasIC(op)) {
// Even if every bytecode op is a JOF_IC op and the function has ARGC_LIMIT
// arguments, numICEntries cannot overflow.
static_assert(MaxBytecodeLength + 1 /* this */ + ARGC_LIMIT <= UINT32_MAX,
"numICEntries must not overflow");
bytecodeSection().incrementNumICEntries();
}
return true;
}
#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));
BytecodeOffset offset;
if (!emitCheck(op, 1, &offset)) {
return false;
}
jsbytecode* code = bytecodeSection().code(offset);
code[0] = jsbytecode(op);
bytecodeSection().updateDepth(op, offset);
return true;
}
bool BytecodeEmitter::emit2(JSOp op, uint8_t op1) {
MOZ_ASSERT(checkStrictOrSloppy(op));
BytecodeOffset offset;
if (!emitCheck(op, 2, &offset)) {
return false;
}
jsbytecode* code = bytecodeSection().code(offset);
code[0] = jsbytecode(op);
code[1] = jsbytecode(op1);
bytecodeSection().updateDepth(op, 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));
BytecodeOffset offset;
if (!emitCheck(op, 3, &offset)) {
return false;
}
jsbytecode* code = bytecodeSection().code(offset);
code[0] = jsbytecode(op);
code[1] = op1;
code[2] = op2;
bytecodeSection().updateDepth(op, offset);
return true;
}
bool BytecodeEmitter::emitN(JSOp op, size_t extra, BytecodeOffset* offset) {
MOZ_ASSERT(checkStrictOrSloppy(op));
ptrdiff_t length = 1 + ptrdiff_t(extra);
BytecodeOffset off;
if (!emitCheck(op, length, &off)) {
return false;
}
jsbytecode* code = bytecodeSection().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) {
bytecodeSection().updateDepth(op, off);
}
if (offset) {
*offset = off;
}
return true;
}
bool BytecodeEmitter::emitJumpTargetOp(JSOp op, BytecodeOffset* off) {
MOZ_ASSERT(BytecodeIsJumpTarget(op));
// Record the current IC-entry index at start of this op.
uint32_t numEntries = bytecodeSection().numICEntries();
size_t n = GetOpLength(op) - 1;
MOZ_ASSERT(GetOpLength(op) >= 1 + ICINDEX_LEN);
if (!emitN(op, n, off)) {
return false;
}
SET_ICINDEX(bytecodeSection().code(*off), numEntries);
return true;
}
bool BytecodeEmitter::emitJumpTarget(JumpTarget* target) {
BytecodeOffset off = bytecodeSection().offset();
// Alias consecutive jump targets.
if (bytecodeSection().lastTargetOffset().valid() &&
off == bytecodeSection().lastTargetOffset() +
BytecodeOffsetDiff(JSOpLength_JumpTarget)) {
target->offset = bytecodeSection().lastTargetOffset();
return true;
}
target->offset = off;
bytecodeSection().setLastTargetOffset(off);
BytecodeOffset opOff;
return emitJumpTargetOp(JSOp::JumpTarget, &opOff);
}
bool BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump) {
BytecodeOffset offset;
if (!emitCheck(op, 5, &offset)) {
return false;
}
jsbytecode* code = bytecodeSection().code(offset);
code[0] = jsbytecode(op);
MOZ_ASSERT(!jump->offset.valid() ||
(0 <= jump->offset.value() && jump->offset < offset));
jump->push(bytecodeSection().code(BytecodeOffset(0)), offset);
bytecodeSection().updateDepth(op, 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;
}
void BytecodeEmitter::patchJumpsToTarget(JumpList jump, JumpTarget target) {
MOZ_ASSERT(
!jump.offset.valid() ||
(0 <= jump.offset.value() && jump.offset <= bytecodeSection().offset()));
MOZ_ASSERT(0 <= target.offset.value() &&
target.offset <= bytecodeSection().offset());
MOZ_ASSERT_IF(
jump.offset.valid() &&
target.offset + BytecodeOffsetDiff(4) <= bytecodeSection().offset(),
BytecodeIsJumpTarget(JSOp(*bytecodeSection().code(target.offset))));
jump.patchAll(bytecodeSection().code(BytecodeOffset(0)), target);
}
bool BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump) {
if (!jump.offset.valid()) {
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, unsigned count) {
MOZ_ASSERT(slotFromTop < unsigned(bytecodeSection().stackDepth()));
MOZ_ASSERT(slotFromTop + 1 >= count);
if (slotFromTop == 0 && count == 1) {
return emit1(JSOp::Dup);
}
if (slotFromTop == 1 && count == 2) {
return emit1(JSOp::Dup2);
}
if (slotFromTop >= Bit(24)) {
reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
return false;
}
for (unsigned i = 0; i < count; i++) {
BytecodeOffset off;
if (!emitN(JSOp::DupAt, 3, &off)) {
return false;
}
jsbytecode* pc = bytecodeSection().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::Pop instructions (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::emitPickN(uint8_t n) {
MOZ_ASSERT(n != 0);
if (n == 1) {
return emit1(JSOp::Swap);
}
return emit2(JSOp::Pick, n);
}
bool BytecodeEmitter::emitUnpickN(uint8_t n) {
MOZ_ASSERT(n != 0);
if (n == 1) {
return emit1(JSOp::Swap);
}
return emit2(JSOp::Unpick, n);
}
bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) {
return emit2(JSOp::CheckIsObj, uint8_t(kind));
}
bool BytecodeEmitter::emitBuiltinObject(BuiltinObjectKind kind) {
return emit2(JSOp::BuiltinObject, uint8_t(kind));
}
/* Updates line number notes, not column notes. */
bool BytecodeEmitter::updateLineNumberNotes(uint32_t offset) {
if (skipLocationSrcNotes()) {
return true;
}
const ErrorReporter& er = errorReporter();
std::optional<bool> onThisLineStatus =
er.isOnThisLine(offset, bytecodeSection().currentLine());
if (!onThisLineStatus.has_value()) {
er.errorNoOffset(JSMSG_OUT_OF_MEMORY);
return false;
}
bool onThisLine = *onThisLineStatus;
if (!onThisLine) {
unsigned line = er.lineAt(offset);
unsigned delta = line - bytecodeSection().currentLine();
// If we use a `SetLine` note below, we want it to be relative to the
// scripts initial line number for better chance of sharing.
unsigned initialLine = sc->extent().lineno;
MOZ_ASSERT(line >= initialLine);
/*
* Encode any change in the current source line number by using
* either several SrcNoteType::NewLine notes or just one
* SrcNoteType::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
* SrcNoteType::SetLine.
*/
bytecodeSection().setCurrentLine(line, offset);
if (delta >= SrcNote::SetLine::lengthFor(line, initialLine)) {
if (!newSrcNote2(SrcNoteType::SetLine,
SrcNote::SetLine::toOperand(line, initialLine))) {
return false;
}
} else {
do {
if (!newSrcNote(SrcNoteType::NewLine)) {
return false;
}
} while (--delta != 0);
}
bytecodeSection().updateSeparatorPositionIfPresent();
}
return true;
}
/* Updates the line number and column number information in the source notes. */
bool BytecodeEmitter::updateSourceCoordNotes(uint32_t offset) {
if (skipLocationSrcNotes()) {
return true;
}
if (!updateLineNumberNotes(offset)) {
return false;
}
JS::LimitedColumnNumberOneOrigin columnIndex =
errorReporter().columnAt(offset);
// Assert colspan is always representable.
static_assert((0 - ptrdiff_t(JS::LimitedColumnNumberOneOrigin::Limit)) >=
SrcNote::ColSpan::MinColSpan);
static_assert((ptrdiff_t(JS::LimitedColumnNumberOneOrigin::Limit) - 0) <=
SrcNote::ColSpan::MaxColSpan);
JS::ColumnNumberOffset colspan = columnIndex - bytecodeSection().lastColumn();
if (colspan != JS::ColumnNumberOffset::zero()) {
if (lastLineOnlySrcNoteIndex != LastSrcNoteIsNotLineOnly) {
MOZ_ASSERT(bytecodeSection().lastColumn() ==
JS::LimitedColumnNumberOneOrigin());
const SrcNotesVector& notes = bytecodeSection().notes();
SrcNoteType type = notes[lastLineOnlySrcNoteIndex].type();
if (type == SrcNoteType::NewLine) {
if (!convertLastNewLineToNewLineColumn(columnIndex)) {
return false;
}
} else {
MOZ_ASSERT(type == SrcNoteType::SetLine);
if (!convertLastSetLineToSetLineColumn(columnIndex)) {
return false;
}
}
} else {
if (!newSrcNote2(SrcNoteType::ColSpan,
SrcNote::ColSpan::toOperand(colspan))) {
return false;
}
}
bytecodeSection().setLastColumn(columnIndex, offset);
bytecodeSection().updateSeparatorPositionIfPresent();
}
return true;
}
bool BytecodeEmitter::updateSourceCoordNotesIfNonLiteral(ParseNode* node) {
if (node->isLiteral()) {
return true;
}
return updateSourceCoordNotes(node->pn_pos.begin);
}
uint32_t BytecodeEmitter::getOffsetForLoop(ParseNode* nextpn) {
// Try to give the JSOp::LoopHead 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 nextpn->pn_pos.begin;
}
bool BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand) {
MOZ_ASSERT(operand <= UINT16_MAX);
if (!emit3(op, UINT16_LO(operand), UINT16_HI(operand))) {
return false;
}
return true;
}
bool BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) {
BytecodeOffset off;
if (!emitN(op, 4, &off)) {
return false;
}
SET_UINT32(bytecodeSection().code(off), operand);
return true;
}
bool BytecodeEmitter::emitGoto(NestableControl* target, GotoKind kind) {
NonLocalExitControl nle(this, kind == GotoKind::Continue
? NonLocalExitKind::Continue
: NonLocalExitKind::Break);
return nle.emitNonLocalJump(target);
}
AbstractScopePtr BytecodeEmitter::innermostScope() const {
return innermostEmitterScope()->scope(this);
}
ScopeIndex BytecodeEmitter::innermostScopeIndex() const {
return *innermostEmitterScope()->scopeIndex(this);
}
bool BytecodeEmitter::emitGCIndexOp(JSOp op, GCThingIndex index) {
MOZ_ASSERT(checkStrictOrSloppy(op));
constexpr size_t OpLength = 1 + GCTHING_INDEX_LEN;
MOZ_ASSERT(GetOpLength(op) == OpLength);
BytecodeOffset offset;
if (!emitCheck(op, OpLength, &offset)) {
return false;
}
jsbytecode* code = bytecodeSection().code(offset);
code[0] = jsbytecode(op);
SET_GCTHING_INDEX(code, index);
bytecodeSection().updateDepth(op, offset);
return true;
}
bool BytecodeEmitter::emitAtomOp(JSOp op, TaggedParserAtomIndex atom) {
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 != TaggedParserAtomIndex::WellKnown::dot_generator_());
GCThingIndex index;
if (!makeAtomIndex(atom, ParserAtom::Atomize::Yes, &index)) {
return false;
}
return emitAtomOp(op, index);
}
bool BytecodeEmitter::emitAtomOp(JSOp op, GCThingIndex atomIndex) {
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
#ifdef DEBUG
auto atom = perScriptData().gcThingList().getAtom(atomIndex);
MOZ_ASSERT(compilationState.parserAtoms.isInstantiatedAsJSAtom(atom));
#endif
return emitGCIndexOp(op, atomIndex);
}
bool BytecodeEmitter::emitStringOp(JSOp op, TaggedParserAtomIndex atom) {
MOZ_ASSERT(atom);
GCThingIndex index;
if (!makeAtomIndex(atom, ParserAtom::Atomize::No, &index)) {
return false;
}
return emitStringOp(op, index);
}
bool BytecodeEmitter::emitStringOp(JSOp op, GCThingIndex atomIndex) {
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_STRING);
return emitGCIndexOp(op, atomIndex);
}
bool BytecodeEmitter::emitInternedScopeOp(GCThingIndex index, JSOp op) {
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
MOZ_ASSERT(index < perScriptData().gcThingList().length());
return emitGCIndexOp(op, index);
}
bool BytecodeEmitter::emitInternedObjectOp(GCThingIndex index, JSOp op) {
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
MOZ_ASSERT(index < perScriptData().gcThingList().length());
return emitGCIndexOp(op, index);
}
bool BytecodeEmitter::emitRegExp(GCThingIndex index) {
return emitGCIndexOp(JSOp::RegExp, index);
}
bool BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) {
MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD);
MOZ_ASSERT(IsLocalOp(op));
BytecodeOffset off;
if (!emitN(op, LOCALNO_LEN, &off)) {
return false;
}
SET_LOCALNO(bytecodeSection().code(off), slot);
return true;
}
bool BytecodeEmitter::emitArgOp(JSOp op, uint16_t slot) {
MOZ_ASSERT(IsArgOp(op));
BytecodeOffset off;
if (!emitN(op, ARGNO_LEN, &off)) {
return false;
}
SET_ARGNO(bytecodeSection().code(off), slot);
return true;
}
bool BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec) {
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD ||
JOF_OPTYPE(op) == JOF_DEBUGCOORD);
constexpr size_t N = ENVCOORD_HOPS_LEN + ENVCOORD_SLOT_LEN;
MOZ_ASSERT(GetOpLength(op) == 1 + N);
BytecodeOffset off;
if (!emitN(op, N, &off)) {
return false;
}
jsbytecode* pc = bytecodeSection().code(off);
SET_ENVCOORD_HOPS(pc, ec.hops());
pc += ENVCOORD_HOPS_LEN;
SET_ENVCOORD_SLOT(pc, ec.slot());
pc += ENVCOORD_SLOT_LEN;
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) {
AutoCheckRecursionLimit recursion(fc);
if (!recursion.check(fc)) {
return false;
}
restart:
switch (pn->getKind()) {
// Trivial cases with no side effects.
case ParseNodeKind::EmptyStmt:
case ParseNodeKind::TrueExpr:
case ParseNodeKind::FalseExpr:
case ParseNodeKind::NullExpr:
case ParseNodeKind::RawUndefinedExpr:
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::StringExpr:
case ParseNodeKind::TemplateStringExpr:
MOZ_ASSERT(pn->is<NameNode>());
*answer = false;
return true;
case ParseNodeKind::RegExpExpr:
MOZ_ASSERT(pn->is<RegExpLiteral>());
*answer = false;
return true;
case ParseNodeKind::NumberExpr:
MOZ_ASSERT(pn->is<NumericLiteral>());
*answer = false;
return true;
case ParseNodeKind::BigIntExpr:
MOZ_ASSERT(pn->is<BigIntLiteral>());
*answer = false;
return true;
// |this| can throw in derived class constructors, including nested arrow
// functions or eval.
case ParseNodeKind::ThisExpr:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = sc->needsThisTDZChecks();
return true;
// |new.target| doesn't have any side-effects.
case ParseNodeKind::NewTargetExpr: {
MOZ_ASSERT(pn->is<NewTargetNode>());
*answer = false;
return true;
}
// Trivial binary nodes with more token pos holders.
case ParseNodeKind::ImportMetaExpr: {
MOZ_ASSERT(pn->as<BinaryNode>().left()->isKind(ParseNodeKind::PosHolder));
MOZ_ASSERT(
pn->as<BinaryNode>().right()->isKind(ParseNodeKind::PosHolder));
*answer = false;
return true;
}
case ParseNodeKind::BreakStmt:
MOZ_ASSERT(pn->is<BreakStatement>());
*answer = true;
return true;
case ParseNodeKind::ContinueStmt:
MOZ_ASSERT(pn->is<ContinueStatement>());
*answer = true;
return true;
case ParseNodeKind::DebuggerStmt:
MOZ_ASSERT(pn->is<DebuggerStatement>());
*answer = true;
return true;
// Watch out for getters!
case ParseNodeKind::OptionalDotExpr:
case ParseNodeKind::DotExpr:
case ParseNodeKind::ArgumentsLength:
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::VoidExpr:
case ParseNodeKind::NotExpr:
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::TypeOfNameExpr:
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::PreIncrementExpr:
case ParseNodeKind::PostIncrementExpr:
case ParseNodeKind::PreDecrementExpr:
case ParseNodeKind::PostDecrementExpr:
case ParseNodeKind::ThrowStmt:
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::BitNotExpr:
case ParseNodeKind::PosExpr:
case ParseNodeKind::NegExpr:
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::YieldStarExpr:
case ParseNodeKind::YieldExpr:
case ParseNodeKind::AwaitExpr:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = true;
return true;
// Deletion generally has side effects, even if isolated cases have none.
case ParseNodeKind::DeleteNameExpr:
case ParseNodeKind::DeletePropExpr:
case ParseNodeKind::DeleteElemExpr:
case ParseNodeKind::DeleteOptionalChainExpr:
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::ExpressionStmt:
return checkSideEffects(pn->as<UnaryNode>().kid(), answer);
// Binary cases with obvious side effects.
case ParseNodeKind::InitExpr:
*answer = true;
return true;
case ParseNodeKind::AssignExpr:
case ParseNodeKind::AddAssignExpr:
case ParseNodeKind::SubAssignExpr:
case ParseNodeKind::CoalesceAssignExpr:
case ParseNodeKind::OrAssignExpr:
case ParseNodeKind::AndAssignExpr:
case ParseNodeKind::BitOrAssignExpr:
case ParseNodeKind::BitXorAssignExpr:
case ParseNodeKind::BitAndAssignExpr:
case ParseNodeKind::LshAssignExpr:
case ParseNodeKind::RshAssignExpr:
case ParseNodeKind::UrshAssignExpr:
case ParseNodeKind::MulAssignExpr:
case ParseNodeKind::DivAssignExpr:
case ParseNodeKind::ModAssignExpr:
case ParseNodeKind::PowAssignExpr:
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 short circuit operators are well-behaved
// and perform no conversions.
case ParseNodeKind::CoalesceExpr:
case ParseNodeKind::OrExpr:
case ParseNodeKind::AndExpr:
case ParseNodeKind::StrictEqExpr:
case ParseNodeKind::StrictNeExpr:
// Any subexpression of a comma expression could be effectful.
case ParseNodeKind::CommaExpr:
MOZ_ASSERT(!pn->as<ListNode>().empty());
[[fallthrough]];
// Subcomponents of a literal may be effectful.
case ParseNodeKind::ArrayExpr:
case ParseNodeKind::ObjectExpr:
for (ParseNode* item : pn->as<ListNode>().contents()) {
if (!checkSideEffects(item, answer)) {
return false;
}
if (*answer) {
return true;
}
}
return true;
#ifdef ENABLE_RECORD_TUPLE
case ParseNodeKind::RecordExpr:
case ParseNodeKind::TupleExpr:
MOZ_CRASH("Record and Tuple are not supported yet");
#endif
#ifdef ENABLE_DECORATORS
case ParseNodeKind::DecoratorList:
MOZ_CRASH("Decorators are not supported yet");
#endif
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
case ParseNodeKind::UsingDecl:
case ParseNodeKind::AwaitUsingDecl:
MOZ_CRASH("Using declarations are not supported yet");
#endif
// 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::BitOrExpr:
case ParseNodeKind::BitXorExpr:
case ParseNodeKind::BitAndExpr:
case ParseNodeKind::EqExpr:
case ParseNodeKind::NeExpr:
case ParseNodeKind::LtExpr:
case ParseNodeKind::LeExpr:
case ParseNodeKind::GtExpr:
case ParseNodeKind::GeExpr:
case ParseNodeKind::InstanceOfExpr:
case ParseNodeKind::InExpr:
case ParseNodeKind::PrivateInExpr:
case ParseNodeKind::LshExpr:
case ParseNodeKind::RshExpr:
case ParseNodeKind::UrshExpr:
case ParseNodeKind::AddExpr:
case ParseNodeKind::SubExpr:
case ParseNodeKind::MulExpr:
case ParseNodeKind::DivExpr:
case ParseNodeKind::ModExpr:
case ParseNodeKind::PowExpr:
MOZ_ASSERT(pn->as<ListNode>().count() >= 2);
*answer = true;
return true;
case ParseNodeKind::PropertyDefinition:
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::ElemExpr:
case ParseNodeKind::OptionalElemExpr:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
// Throws if the operand is not of the right class. Can also call a private
// getter.
case ParseNodeKind::PrivateMemberExpr:
case ParseNodeKind::OptionalPrivateMemberExpr:
*answer = true;
return true;
// These affect visible names in this code, or in other code.
case ParseNodeKind::ImportDecl:
case ParseNodeKind::ExportFromStmt:
case ParseNodeKind::ExportDefaultStmt:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
// Likewise.
case ParseNodeKind::ExportStmt:
MOZ_ASSERT(pn->is<UnaryNode>());
*answer = true;
return true;
case ParseNodeKind::CallImportExpr:
case ParseNodeKind::CallImportSpec:
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::DoWhileStmt:
case ParseNodeKind::WhileStmt:
case ParseNodeKind::ForStmt:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
// Declarations affect the name set of the relevant scope.
case ParseNodeKind::VarStmt:
case ParseNodeKind::ConstDecl:
case ParseNodeKind::LetDecl:
MOZ_ASSERT(pn->is<ListNode>());
*answer = true;
return true;
case ParseNodeKind::IfStmt:
case ParseNodeKind::ConditionalExpr: {
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::NewExpr:
case ParseNodeKind::CallExpr:
case ParseNodeKind::OptionalCallExpr:
case ParseNodeKind::TaggedTemplateExpr:
case ParseNodeKind::SuperCallExpr:
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::OptionalChain:
MOZ_ASSERT(pn->is<UnaryNode>());
*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::ClassDecl:
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::WithStmt:
MOZ_ASSERT(pn->is<BinaryNode>());
*answer = true;
return true;
case ParseNodeKind::ReturnStmt:
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<FunctionNode>());
/*
* 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::TryStmt: {
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::SwitchStmt: {
SwitchStatement* switchStmt = &pn->as<SwitchStatement>();
if (!checkSideEffects(&switchStmt->discriminant(), answer)) {
return false;
}
return *answer ||
checkSideEffects(&switchStmt->lexicalForCaseList(), answer);
}
case ParseNodeKind::LabelStmt:
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::TemplateStringListExpr: {
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::DefaultConstructor: // by ParseNodeKind::ClassDecl
case ParseNodeKind::ClassBodyScope: // by ParseNodeKind::ClassDecl
case ParseNodeKind::ClassMethod: // by ParseNodeKind::ClassDecl
case ParseNodeKind::ClassField: // by ParseNodeKind::ClassDecl
case ParseNodeKind::ClassNames: // by ParseNodeKind::ClassDecl
case ParseNodeKind::StaticClassBlock: // by ParseNodeKind::ClassDecl
case ParseNodeKind::ClassMemberList: // by ParseNodeKind::ClassDecl
case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import
case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import
case ParseNodeKind::ImportNamespaceSpec: // by ParseNodeKind::Import
case ParseNodeKind::ImportAttribute: // by ParseNodeKind::Import
case ParseNodeKind::ImportAttributeList: // by ParseNodeKind::Import
case ParseNodeKind::ImportModuleRequest: // by ParseNodeKind::Import
case ParseNodeKind::ExportBatchSpecStmt: // by ParseNodeKind::Export
case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export
case ParseNodeKind::ExportSpec: // by ParseNodeKind::Export
case ParseNodeKind::ExportNamespaceSpec: // 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::PropertyNameExpr: // by ParseNodeKind::Dot
MOZ_CRASH("handled by parent nodes");
case ParseNodeKind::LastUnused:
case ParseNodeKind::Limit:
MOZ_CRASH("invalid node kind");
}
MOZ_CRASH(
"invalid, unenumerated ParseNodeKind value encountered in "
"BytecodeEmitter::checkSideEffects");
}
bool BytecodeEmitter::isInLoop() {
return findInnermostNestableControl<LoopControl>();
}
bool BytecodeEmitter::checkSingletonContext() {
MOZ_ASSERT_IF(sc->treatAsRunOnce(), sc->isTopLevelContext());
return sc->treatAsRunOnce() && !isInLoop();
}
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;
}
size_t BytecodeEmitter::countThisEnvironmentHops() {
unsigned numHops = 0;
for (BytecodeEmitter* current = this; current; current = current->parent) {
for (EmitterScope* es = current->innermostEmitterScope(); es;
es = es->enclosingInFrame()) {
if (es->scope(current).is<FunctionScope>()) {
if (!es->scope(current).isArrow()) {
// The Parser is responsible for marking the environment as either
// closed-over or used-by-eval which ensure that is must exist.
MOZ_ASSERT(es->scope(current).hasEnvironment());
return numHops;
}
}
if (es->scope(current).hasEnvironment()) {
numHops++;
}
}
}
// The "this" environment exists outside of the compilation, but the
// `ScopeContext` recorded the number of additional hops needed, so add
// those in now.
MOZ_ASSERT(sc->allowSuperProperty());
numHops += compilationState.scopeContext.enclosingThisEnvironmentHops;
return numHops;
}
bool BytecodeEmitter::emitThisEnvironmentCallee() {
// Get the innermost enclosing function that has a |this| binding.
// Directly load callee from the frame if possible.
if (sc->isFunctionBox() && !sc->asFunctionBox()->isArrow()) {
return emit1(JSOp::Callee);
}
// We have to load the callee from the environment chain.
size_t numHops = countThisEnvironmentHops();
static_assert(
ENVCOORD_HOPS_LIMIT - 1 <= UINT8_MAX,
"JSOp::EnvCallee operand size should match ENVCOORD_HOPS_LIMIT");
MOZ_ASSERT(numHops < ENVCOORD_HOPS_LIMIT - 1);
return emit2(JSOp::EnvCallee, numHops);
}
bool BytecodeEmitter::emitSuperBase() {
if (!emitThisEnvironmentCallee()) {
return false;
}
return emit1(JSOp::SuperBase);
}
void BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) {
uint32_t offset = pn ? pn->pn_pos.begin : *scriptStartOffset;
va_list args;
va_start(args, errorNumber);
errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), errorNumber,
&args);
va_end(args);
}
void BytecodeEmitter::reportError(uint32_t offset, unsigned errorNumber, ...) {
va_list args;
va_start(args, errorNumber);
errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), errorNumber,
&args);
va_end(args);
}
bool BytecodeEmitter::addObjLiteralData(ObjLiteralWriter& writer,
GCThingIndex* outIndex) {
if (!writer.checkForDuplicatedNames(fc)) {
return false;
}
size_t len = writer.getCode().size();
auto* code = compilationState.alloc.newArrayUninitialized<uint8_t>(len);
if (!code) {
js::ReportOutOfMemory(fc);
return false;
}
memcpy(code, writer.getCode().data(), len);
ObjLiteralIndex objIndex(compilationState.objLiteralData.length());
if (uint32_t(objIndex) >= TaggedScriptThingIndex::IndexLimit) {
ReportAllocationOverflow(fc);
return false;
}
if (!compilationState.objLiteralData.emplaceBack(code, len, writer.getKind(),
writer.getFlags(),
writer.getPropertyCount())) {
js::ReportOutOfMemory(fc);
return false;
}
return perScriptData().gcThingList().append(objIndex, outIndex);
}
bool BytecodeEmitter::emitPrepareIteratorResult() {
constexpr JSOp op = JSOp::NewObject;
ObjLiteralWriter writer;
writer.beginShape(op);
writer.setPropNameNoDuplicateCheck(parserAtoms(),
TaggedParserAtomIndex::WellKnown::value());
if (!writer.propWithUndefinedValue(fc)) {
return false;
}
writer.setPropNameNoDuplicateCheck(parserAtoms(),
TaggedParserAtomIndex::WellKnown::done());
if (!writer.propWithUndefinedValue(fc)) {
return false;
}
GCThingIndex shape;
if (!addObjLiteralData(writer, &shape)) {
return false;
}
return emitGCIndexOp(op, shape);
}
bool BytecodeEmitter::emitFinishIteratorResult(bool done) {
if (!emitAtomOp(JSOp::InitProp, TaggedParserAtomIndex::WellKnown::value())) {
return false;
}
if (!emit1(done ? JSOp::True : JSOp::False)) {
return false;
}
if (!emitAtomOp(JSOp::InitProp, TaggedParserAtomIndex::WellKnown::done())) {
return false;
}
return true;
}
bool BytecodeEmitter::emitGetNameAtLocation(TaggedParserAtomIndex name,
const NameLocation& loc) {
NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get);
if (!noe.emitGet()) {
return false;
}
return true;
}
bool BytecodeEmitter::emitGetName(NameNode* name) {
MOZ_ASSERT(name->isKind(ParseNodeKind::Name));
return emitGetName(name->name());
}
bool BytecodeEmitter::emitGetPrivateName(NameNode* name) {
MOZ_ASSERT(name->isKind(ParseNodeKind::PrivateName));
return emitGetPrivateName(name->name());
}
bool BytecodeEmitter::emitGetPrivateName(TaggedParserAtomIndex nameAtom) {
// The parser ensures the private name is present on the environment chain,
// but its location can be Dynamic or Global when emitting debugger
// eval-in-frame code.
NameLocation location = lookupName(nameAtom);
MOZ_ASSERT(location.kind() == NameLocation::Kind::FrameSlot ||
location.kind() == NameLocation::Kind::EnvironmentCoordinate ||
location.kind() == NameLocation::Kind::Dynamic ||
location.kind() == NameLocation::Kind::Global);
return emitGetNameAtLocation(nameAtom, location);
}
bool BytecodeEmitter::emitTDZCheckIfNeeded(TaggedParserAtomIndex name,
const NameLocation& loc,
ValueIsOnStack isOnStack) {
// 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() || loc.isPrivateMethod() || loc.isSynthetic());
// Private names are implemented as lexical bindings, but it's just an
// implementation detail. Per spec there's no TDZ check when using them.
if (parserAtoms().isPrivateName(name)) {
return true;
}
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 the value is not on the stack, we have to load it first.
if (isOnStack == ValueIsOnStack::No) {
if (loc.kind() == NameLocation::Kind::FrameSlot) {
if (!emitLocalOp(JSOp::GetLocal, loc.frameSlot())) {
return false;
}
} else {
if (!emitEnvCoordOp(JSOp::GetAliasedVar, loc.environmentCoordinate())) {
return false;
}
}
}
// Emit the lexical check.
if (loc.kind() == NameLocation::Kind::FrameSlot) {
if (!emitLocalOp(JSOp::CheckLexical, loc.frameSlot())) {
return false;
}
} else {
if (!emitEnvCoordOp(JSOp::CheckAliasedLexical,
loc.environmentCoordinate())) {
return false;
}
}
// Pop the value if needed.
if (isOnStack == ValueIsOnStack::No) {
if (!emit1(JSOp::Pop)) {
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) {
// Walk back up the list, emitting annotated name ops.
if (!emitAtomOp(JSOp::GetProp, pndot->key().atom())) {
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, ValueUsage valueUsage) {
PropertyAccess* prop = &incDec->kid()->as<PropertyAccess>();
bool isSuper = prop->isSuper();
ParseNodeKind kind = incDec->getKind();