diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /js/src/frontend/BytecodeEmitter.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/frontend/BytecodeEmitter.cpp')
-rw-r--r-- | js/src/frontend/BytecodeEmitter.cpp | 10170 |
1 files changed, 10170 insertions, 0 deletions
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp new file mode 100644 index 000000000..1e9d8f224 --- /dev/null +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -0,0 +1,10170 @@ +/* -*- 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 <string.h> + +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsfun.h" +#include "jsnum.h" +#include "jsopcode.h" +#include "jsscript.h" +#include "jstypes.h" +#include "jsutil.h" + +#include "frontend/Parser.h" +#include "frontend/TokenStream.h" +#include "vm/Debugger.h" +#include "vm/GeneratorObject.h" +#include "vm/Stack.h" +#include "wasm/AsmJS.h" + +#include "jsatominlines.h" +#include "jsobjinlines.h" +#include "jsscriptinlines.h" + +#include "frontend/ParseNode-inl.h" +#include "vm/EnvironmentObject-inl.h" +#include "vm/NativeObject-inl.h" + +using namespace js; +using namespace js::gc; +using namespace js::frontend; + +using mozilla::AssertedCast; +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::NumberIsInt32; +using mozilla::PodCopy; +using mozilla::Some; + +class BreakableControl; +class LabelControl; +class LoopControl; +class TryFinallyControl; + +static bool +ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn) +{ + return pn->getKind() == PNK_WHILE || pn->getKind() == PNK_FOR; +} + +// A cache that tracks superfluous TDZ checks. +// +// Each basic block should have a TDZCheckCache in scope. Some NestableControl +// subclasses contain a TDZCheckCache. +class BytecodeEmitter::TDZCheckCache : public Nestable<BytecodeEmitter::TDZCheckCache> +{ + PooledMapPtr<CheckTDZMap> cache_; + + MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) { + return cache_ || cache_.acquire(bce->cx); + } + + public: + explicit TDZCheckCache(BytecodeEmitter* bce) + : Nestable<TDZCheckCache>(&bce->innermostTDZCheckCache), + cache_(bce->cx->frontendCollectionPool()) + { } + + Maybe<MaybeCheckTDZ> needsTDZCheck(BytecodeEmitter* bce, JSAtom* name); + MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check); +}; + +class BytecodeEmitter::NestableControl : public Nestable<BytecodeEmitter::NestableControl> +{ + StatementKind kind_; + + // The innermost scope when this was pushed. + EmitterScope* emitterScope_; + + protected: + NestableControl(BytecodeEmitter* bce, StatementKind kind) + : Nestable<NestableControl>(&bce->innermostNestableControl), + kind_(kind), + emitterScope_(bce->innermostEmitterScope) + { } + + public: + using Nestable<NestableControl>::enclosing; + using Nestable<NestableControl>::findNearest; + + StatementKind kind() const { + return kind_; + } + + EmitterScope* emitterScope() const { + return emitterScope_; + } + + template <typename T> + bool is() const; + + template <typename T> + T& as() { + MOZ_ASSERT(this->is<T>()); + return static_cast<T&>(*this); + } +}; + +// Template specializations are disallowed in different namespaces; specialize +// all the NestableControl subtypes up front. +namespace js { +namespace frontend { + +template <> +bool +BytecodeEmitter::NestableControl::is<BreakableControl>() const +{ + return StatementKindIsUnlabeledBreakTarget(kind_) || kind_ == StatementKind::Label; +} + +template <> +bool +BytecodeEmitter::NestableControl::is<LabelControl>() const +{ + return kind_ == StatementKind::Label; +} + +template <> +bool +BytecodeEmitter::NestableControl::is<LoopControl>() const +{ + return StatementKindIsLoop(kind_); +} + +template <> +bool +BytecodeEmitter::NestableControl::is<TryFinallyControl>() const +{ + return kind_ == StatementKind::Try || kind_ == StatementKind::Finally; +} + +} // namespace frontend +} // namespace js + +class BreakableControl : public BytecodeEmitter::NestableControl +{ + public: + // Offset of the last break. + JumpList breaks; + + BreakableControl(BytecodeEmitter* bce, StatementKind kind) + : NestableControl(bce, kind) + { + MOZ_ASSERT(is<BreakableControl>()); + } + + MOZ_MUST_USE bool patchBreaks(BytecodeEmitter* bce) { + return bce->emitJumpTargetAndPatch(breaks); + } +}; + +class LabelControl : public BreakableControl +{ + RootedAtom label_; + + // The code offset when this was pushed. Used for effectfulness checking. + ptrdiff_t startOffset_; + + public: + LabelControl(BytecodeEmitter* bce, JSAtom* label, ptrdiff_t startOffset) + : BreakableControl(bce, StatementKind::Label), + label_(bce->cx, label), + startOffset_(startOffset) + { } + + HandleAtom label() const { + return label_; + } + + ptrdiff_t startOffset() const { + return startOffset_; + } +}; + +class LoopControl : public BreakableControl +{ + // Loops' children are emitted in dominance order, so they can always + // have a TDZCheckCache. + BytecodeEmitter::TDZCheckCache tdzCache_; + + // Stack depth when this loop was pushed on the control stack. + int32_t stackDepth_; + + // The loop nesting depth. Used as a hint to Ion. + uint32_t loopDepth_; + + // Can we OSR into Ion from here? True unless there is non-loop state on the stack. + bool canIonOsr_; + + public: + // The target of continue statement jumps, e.g., the update portion of a + // for(;;) loop. + JumpTarget continueTarget; + + // Offset of the last continue in the loop. + JumpList continues; + + LoopControl(BytecodeEmitter* bce, StatementKind loopKind) + : BreakableControl(bce, loopKind), + tdzCache_(bce), + continueTarget({ -1 }) + { + MOZ_ASSERT(is<LoopControl>()); + + LoopControl* enclosingLoop = findNearest<LoopControl>(enclosing()); + + stackDepth_ = bce->stackDepth; + loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1; + + int loopSlots; + if (loopKind == StatementKind::Spread) + loopSlots = 3; + else if (loopKind == StatementKind::ForInLoop || loopKind == StatementKind::ForOfLoop) + loopSlots = 2; + else + loopSlots = 0; + + MOZ_ASSERT(loopSlots <= stackDepth_); + + if (enclosingLoop) { + canIonOsr_ = (enclosingLoop->canIonOsr_ && + stackDepth_ == enclosingLoop->stackDepth_ + loopSlots); + } else { + canIonOsr_ = stackDepth_ == loopSlots; + } + } + + uint32_t loopDepth() const { + return loopDepth_; + } + + bool canIonOsr() const { + return canIonOsr_; + } + + MOZ_MUST_USE bool patchBreaksAndContinues(BytecodeEmitter* bce) { + MOZ_ASSERT(continueTarget.offset != -1); + if (!patchBreaks(bce)) + return false; + bce->patchJumpsToTarget(continues, continueTarget); + return true; + } +}; + +class TryFinallyControl : public BytecodeEmitter::NestableControl +{ + bool emittingSubroutine_; + + public: + // The subroutine when emitting a finally block. + JumpList gosubs; + + // Offset of the last catch guard, if any. + JumpList guardJump; + + TryFinallyControl(BytecodeEmitter* bce, StatementKind kind) + : NestableControl(bce, kind), + emittingSubroutine_(false) + { + MOZ_ASSERT(is<TryFinallyControl>()); + } + + void setEmittingSubroutine() { + emittingSubroutine_ = true; + } + + bool emittingSubroutine() const { + return emittingSubroutine_; + } +}; + +static bool +ScopeKindIsInBody(ScopeKind kind) +{ + return kind == ScopeKind::Lexical || + kind == ScopeKind::SimpleCatch || + kind == ScopeKind::Catch || + kind == ScopeKind::With || + kind == ScopeKind::FunctionBodyVar || + kind == ScopeKind::ParameterExpressionVar; +} + +static inline void +MarkAllBindingsClosedOver(LexicalScope::Data& data) +{ + BindingName* names = data.names; + for (uint32_t i = 0; i < data.length; i++) + names[i] = BindingName(names[i].name(), true); +} + +// A scope that introduces bindings. +class BytecodeEmitter::EmitterScope : public Nestable<BytecodeEmitter::EmitterScope> +{ + // The cache of bound names that may be looked up in the + // scope. Initially populated as the set of names this scope binds. As + // names are looked up in enclosing scopes, they are cached on the + // current scope. + PooledMapPtr<NameLocationMap> nameCache_; + + // If this scope's cache does not include free names, such as the + // global scope, the NameLocation to return. + Maybe<NameLocation> fallbackFreeNameLocation_; + + // True if there is a corresponding EnvironmentObject on the environment + // chain, false if all bindings are stored in frame slots on the stack. + bool hasEnvironment_; + + // The number of enclosing environments. Used for error checking. + uint8_t environmentChainLength_; + + // The next usable slot on the frame for not-closed over bindings. + // + // The initial frame slot when assigning slots to bindings is the + // enclosing scope's nextFrameSlot. For the first scope in a frame, + // the initial frame slot is 0. + uint32_t nextFrameSlot_; + + // The index in the BytecodeEmitter's interned scope vector, otherwise + // ScopeNote::NoScopeIndex. + uint32_t scopeIndex_; + + // If kind is Lexical, Catch, or With, the index in the BytecodeEmitter's + // block scope note list. Otherwise ScopeNote::NoScopeNote. + uint32_t noteIndex_; + + MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) { + return nameCache_.acquire(bce->cx); + } + + template <typename BindingIter> + MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce, const BindingIter& bi) { + if (bi.nextFrameSlot() >= LOCALNO_LIMIT || + bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT) + { + return bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); + } + return true; + } + + MOZ_MUST_USE bool checkEnvironmentChainLength(BytecodeEmitter* bce) { + uint32_t hops; + if (EmitterScope* emitterScope = enclosing(&bce)) + hops = emitterScope->environmentChainLength_; + else + hops = bce->sc->compilationEnclosingScope()->environmentChainLength(); + if (hops >= ENVCOORD_HOPS_LIMIT - 1) + return bce->reportError(nullptr, JSMSG_TOO_DEEP, js_function_str); + environmentChainLength_ = mozilla::AssertedCast<uint8_t>(hops + 1); + return true; + } + + void updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi) { + nextFrameSlot_ = bi.nextFrameSlot(); + if (nextFrameSlot_ > bce->maxFixedSlots) + bce->maxFixedSlots = nextFrameSlot_; + MOZ_ASSERT_IF(bce->sc->isFunctionBox() && bce->sc->asFunctionBox()->isGenerator(), + bce->maxFixedSlots == 0); + } + + MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name, NameLocation loc) { + NameLocationMap& cache = *nameCache_; + NameLocationMap::AddPtr p = cache.lookupForAdd(name); + MOZ_ASSERT(!p); + if (!cache.add(p, name, loc)) { + ReportOutOfMemory(bce->cx); + return false; + } + return true; + } + + Maybe<NameLocation> lookupInCache(BytecodeEmitter* bce, JSAtom* name) { + if (NameLocationMap::Ptr p = nameCache_->lookup(name)) + return Some(p->value().wrapped); + if (fallbackFreeNameLocation_ && nameCanBeFree(bce, name)) + return fallbackFreeNameLocation_; + return Nothing(); + } + + friend bool BytecodeEmitter::needsImplicitThis(); + + EmitterScope* enclosing(BytecodeEmitter** bce) const { + // There is an enclosing scope with access to the same frame. + if (EmitterScope* inFrame = enclosingInFrame()) + return inFrame; + + // We are currently compiling the enclosing script, look in the + // enclosing BCE. + if ((*bce)->parent) { + *bce = (*bce)->parent; + return (*bce)->innermostEmitterScope; + } + + return nullptr; + } + + Scope* enclosingScope(BytecodeEmitter* bce) const { + if (EmitterScope* es = enclosing(&bce)) + return es->scope(bce); + + // The enclosing script is already compiled or the current script is the + // global script. + return bce->sc->compilationEnclosingScope(); + } + + static bool nameCanBeFree(BytecodeEmitter* bce, JSAtom* name) { + // '.generator' cannot be accessed by name. + return name != bce->cx->names().dotGenerator; + } + + static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops); + NameLocation searchAndCache(BytecodeEmitter* bce, JSAtom* name); + + template <typename ScopeCreator> + MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope); + template <typename ScopeCreator> + MOZ_MUST_USE bool internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope); + MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce); + + MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, + uint32_t slotEnd); + + public: + explicit EmitterScope(BytecodeEmitter* bce) + : Nestable<EmitterScope>(&bce->innermostEmitterScope), + nameCache_(bce->cx->frontendCollectionPool()), + hasEnvironment_(false), + environmentChainLength_(0), + nextFrameSlot_(0), + scopeIndex_(ScopeNote::NoScopeIndex), + noteIndex_(ScopeNote::NoScopeNoteIndex) + { } + + void dump(BytecodeEmitter* bce); + + MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind, + Handle<LexicalScope::Data*> bindings); + MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox); + MOZ_MUST_USE bool enterComprehensionFor(BytecodeEmitter* bce, + Handle<LexicalScope::Data*> bindings); + MOZ_MUST_USE bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox); + MOZ_MUST_USE bool enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox); + MOZ_MUST_USE bool enterParameterExpressionVar(BytecodeEmitter* bce); + MOZ_MUST_USE bool enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc); + MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc); + MOZ_MUST_USE bool enterModule(BytecodeEmitter* module, ModuleSharedContext* modulesc); + MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce); + MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce); + + MOZ_MUST_USE bool leave(BytecodeEmitter* bce, bool nonLocal = false); + + uint32_t index() const { + MOZ_ASSERT(scopeIndex_ != ScopeNote::NoScopeIndex, "Did you forget to intern a Scope?"); + return scopeIndex_; + } + + uint32_t noteIndex() const { + return noteIndex_; + } + + Scope* scope(const BytecodeEmitter* bce) const { + return bce->scopeList.vector[index()]; + } + + bool hasEnvironment() const { + return hasEnvironment_; + } + + // The first frame slot used. + uint32_t frameSlotStart() const { + if (EmitterScope* inFrame = enclosingInFrame()) + return inFrame->nextFrameSlot_; + return 0; + } + + // The last frame slot used + 1. + uint32_t frameSlotEnd() const { + return nextFrameSlot_; + } + + uint32_t numFrameSlots() const { + return frameSlotEnd() - frameSlotStart(); + } + + EmitterScope* enclosingInFrame() const { + return Nestable<EmitterScope>::enclosing(); + } + + NameLocation lookup(BytecodeEmitter* bce, JSAtom* name) { + if (Maybe<NameLocation> loc = lookupInCache(bce, name)) + return *loc; + return searchAndCache(bce, name); + } + + Maybe<NameLocation> locationBoundInScope(BytecodeEmitter* bce, JSAtom* name, + EmitterScope* target); +}; + +void +BytecodeEmitter::EmitterScope::dump(BytecodeEmitter* bce) +{ + fprintf(stdout, "EmitterScope [%s] %p\n", ScopeKindString(scope(bce)->kind()), this); + + for (NameLocationMap::Range r = nameCache_->all(); !r.empty(); r.popFront()) { + const NameLocation& l = r.front().value(); + + JSAutoByteString bytes; + if (!AtomToPrintableString(bce->cx, r.front().key(), &bytes)) + return; + if (l.kind() != NameLocation::Kind::Dynamic) + fprintf(stdout, " %s %s ", BindingKindString(l.bindingKind()), bytes.ptr()); + else + fprintf(stdout, " %s ", bytes.ptr()); + + switch (l.kind()) { + case NameLocation::Kind::Dynamic: + fprintf(stdout, "dynamic\n"); + break; + case NameLocation::Kind::Global: + fprintf(stdout, "global\n"); + break; + case NameLocation::Kind::Intrinsic: + fprintf(stdout, "intrinsic\n"); + break; + case NameLocation::Kind::NamedLambdaCallee: + fprintf(stdout, "named lambda callee\n"); + break; + case NameLocation::Kind::Import: + fprintf(stdout, "import\n"); + break; + case NameLocation::Kind::ArgumentSlot: + fprintf(stdout, "arg slot=%u\n", l.argumentSlot()); + break; + case NameLocation::Kind::FrameSlot: + fprintf(stdout, "frame slot=%u\n", l.frameSlot()); + break; + case NameLocation::Kind::EnvironmentCoordinate: + fprintf(stdout, "environment hops=%u slot=%u\n", + l.environmentCoordinate().hops(), l.environmentCoordinate().slot()); + break; + case NameLocation::Kind::DynamicAnnexBVar: + fprintf(stdout, "dynamic annex b var\n"); + break; + } + } + + fprintf(stdout, "\n"); +} + +template <typename ScopeCreator> +bool +BytecodeEmitter::EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope) +{ + RootedScope enclosing(bce->cx, enclosingScope(bce)); + Scope* scope = createScope(bce->cx, enclosing); + if (!scope) + return false; + hasEnvironment_ = scope->hasEnvironment(); + scopeIndex_ = bce->scopeList.length(); + return bce->scopeList.append(scope); +} + +template <typename ScopeCreator> +bool +BytecodeEmitter::EmitterScope::internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope) +{ + MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX, "There can be only one body scope"); + bce->bodyScopeIndex = bce->scopeList.length(); + return internScope(bce, createScope); +} + +bool +BytecodeEmitter::EmitterScope::appendScopeNote(BytecodeEmitter* bce) +{ + MOZ_ASSERT(ScopeKindIsInBody(scope(bce)->kind()) && enclosingInFrame(), + "Scope notes are not needed for body-level scopes."); + noteIndex_ = bce->scopeNoteList.length(); + return bce->scopeNoteList.append(index(), bce->offset(), bce->inPrologue(), + enclosingInFrame() ? enclosingInFrame()->noteIndex() + : ScopeNote::NoScopeNoteIndex); +} + +#ifdef DEBUG +static bool +NameIsOnEnvironment(Scope* scope, JSAtom* name) +{ + for (BindingIter bi(scope); bi; bi++) { + // If found, the name must already be on the environment or an import, + // or else there is a bug in the closed-over name analysis in the + // Parser. + if (bi.name() == name) { + BindingLocation::Kind kind = bi.location().kind(); + + if (bi.hasArgumentSlot()) { + JSScript* script = scope->as<FunctionScope>().script(); + if (!script->strict() && !script->functionHasParameterExprs()) { + // Check for duplicate positional formal parameters. + for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) { + if (bi2.name() == name) + kind = bi2.location().kind(); + } + } + } + + return kind == BindingLocation::Kind::Global || + kind == BindingLocation::Kind::Environment || + kind == BindingLocation::Kind::Import; + } + } + + // If not found, assume it's on the global or dynamically accessed. + return true; +} +#endif + +/* static */ NameLocation +BytecodeEmitter::EmitterScope::searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops) +{ + for (ScopeIter si(scope); si; si++) { + MOZ_ASSERT(NameIsOnEnvironment(si.scope(), name)); + + bool hasEnv = si.hasSyntacticEnvironment(); + + switch (si.kind()) { + case ScopeKind::Function: + if (hasEnv) { + JSScript* script = si.scope()->as<FunctionScope>().script(); + if (script->funHasExtensibleScope()) + return NameLocation::Dynamic(); + + for (BindingIter bi(si.scope()); bi; bi++) { + if (bi.name() != name) + continue; + + BindingLocation bindLoc = bi.location(); + if (bi.hasArgumentSlot() && + !script->strict() && + !script->functionHasParameterExprs()) + { + // Check for duplicate positional formal parameters. + for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) { + if (bi2.name() == name) + bindLoc = bi2.location(); + } + } + + MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); + return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot()); + } + } + break; + + case ScopeKind::FunctionBodyVar: + case ScopeKind::ParameterExpressionVar: + case ScopeKind::Lexical: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + if (hasEnv) { + for (BindingIter bi(si.scope()); bi; bi++) { + if (bi.name() != name) + continue; + + // The name must already have been marked as closed + // over. If this assertion is hit, there is a bug in the + // name analysis. + BindingLocation bindLoc = bi.location(); + MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); + return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot()); + } + } + break; + + case ScopeKind::Module: + if (hasEnv) { + for (BindingIter bi(si.scope()); bi; bi++) { + if (bi.name() != name) + continue; + + BindingLocation bindLoc = bi.location(); + + // Imports are on the environment but are indirect + // bindings and must be accessed dynamically instead of + // using an EnvironmentCoordinate. + if (bindLoc.kind() == BindingLocation::Kind::Import) { + MOZ_ASSERT(si.kind() == ScopeKind::Module); + return NameLocation::Import(); + } + + MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); + return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot()); + } + } + break; + + case ScopeKind::Eval: + case ScopeKind::StrictEval: + // As an optimization, if the eval doesn't have its own var + // environment and its immediate enclosing scope is a global + // scope, all accesses are global. + if (!hasEnv && si.scope()->enclosing()->is<GlobalScope>()) + return NameLocation::Global(BindingKind::Var); + return NameLocation::Dynamic(); + + case ScopeKind::Global: + return NameLocation::Global(BindingKind::Var); + + case ScopeKind::With: + case ScopeKind::NonSyntactic: + return NameLocation::Dynamic(); + } + + if (hasEnv) { + MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1); + hops++; + } + } + + MOZ_CRASH("Malformed scope chain"); +} + +NameLocation +BytecodeEmitter::EmitterScope::searchAndCache(BytecodeEmitter* bce, JSAtom* name) +{ + Maybe<NameLocation> loc; + uint8_t hops = hasEnvironment() ? 1 : 0; + DebugOnly<bool> inCurrentScript = enclosingInFrame(); + + // Start searching in the current compilation. + for (EmitterScope* es = enclosing(&bce); es; es = es->enclosing(&bce)) { + loc = es->lookupInCache(bce, name); + if (loc) { + if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate) + *loc = loc->addHops(hops); + break; + } + + if (es->hasEnvironment()) + hops++; + +#ifdef DEBUG + if (!es->enclosingInFrame()) + inCurrentScript = false; +#endif + } + + // If the name is not found in the current compilation, walk the Scope + // chain encompassing the compilation. + if (!loc) { + inCurrentScript = false; + loc = Some(searchInEnclosingScope(name, bce->sc->compilationEnclosingScope(), hops)); + } + + // Each script has its own frame. A free name that is accessed + // from an inner script must not be a frame slot access. If this + // assertion is hit, it is a bug in the free name analysis in the + // parser. + MOZ_ASSERT_IF(!inCurrentScript, loc->kind() != NameLocation::Kind::FrameSlot); + + // It is always correct to not cache the location. Ignore OOMs to make + // lookups infallible. + if (!putNameInCache(bce, name, *loc)) + bce->cx->recoverFromOutOfMemory(); + + return *loc; +} + +Maybe<NameLocation> +BytecodeEmitter::EmitterScope::locationBoundInScope(BytecodeEmitter* bce, JSAtom* name, + EmitterScope* target) +{ + // The target scope must be an intra-frame enclosing scope of this + // one. Count the number of extra hops to reach it. + uint8_t extraHops = 0; + for (EmitterScope* es = this; es != target; es = es->enclosingInFrame()) { + if (es->hasEnvironment()) + extraHops++; + } + + // Caches are prepopulated with bound names. So if the name is bound in a + // particular scope, it must already be in the cache. Furthermore, don't + // consult the fallback location as we only care about binding names. + Maybe<NameLocation> loc; + if (NameLocationMap::Ptr p = target->nameCache_->lookup(name)) { + NameLocation l = p->value().wrapped; + if (l.kind() == NameLocation::Kind::EnvironmentCoordinate) + loc = Some(l.addHops(extraHops)); + else + loc = Some(l); + } + return loc; +} + +bool +BytecodeEmitter::EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, + uint32_t slotEnd) +{ + // Lexical bindings throw ReferenceErrors if they are used before + // initialization. See ES6 8.1.1.1.6. + // + // For completeness, lexical bindings are initialized in ES6 by calling + // InitializeBinding, after which touching the binding will no longer + // throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6, + // 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15. + if (slotStart != slotEnd) { + if (!bce->emit1(JSOP_UNINITIALIZED)) + return false; + for (uint32_t slot = slotStart; slot < slotEnd; slot++) { + if (!bce->emitLocalOp(JSOP_INITLEXICAL, slot)) + return false; + } + if (!bce->emit1(JSOP_POP)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) +{ + return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd()); +} + +bool +BytecodeEmitter::EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind, + Handle<LexicalScope::Data*> bindings) +{ + MOZ_ASSERT(kind != ScopeKind::NamedLambda && kind != ScopeKind::StrictNamedLambda); + MOZ_ASSERT(this == bce->innermostEmitterScope); + + if (!ensureCache(bce)) + return false; + + // Marks all names as closed over if the the context requires it. This + // cannot be done in the Parser as we may not know if the context requires + // all bindings to be closed over until after parsing is finished. For + // example, legacy generators require all bindings to be closed over but + // it is unknown if a function is a legacy generator until the first + // 'yield' expression is parsed. + // + // This is not a problem with other scopes, as all other scopes with + // bindings are body-level. At the time of their creation, whether or not + // the context requires all bindings to be closed over is already known. + if (bce->sc->allBindingsClosedOver()) + MarkAllBindingsClosedOver(*bindings); + + // Resolve bindings. + TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; + uint32_t firstFrameSlot = frameSlotStart(); + BindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) + return false; + + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + if (!putNameInCache(bce, bi.name(), loc)) + return false; + + if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) + return false; + } + + updateFrameFixedSlots(bce, bi); + + // Create and intern the VM scope. + auto createScope = [kind, bindings, firstFrameSlot](ExclusiveContext* cx, + HandleScope enclosing) + { + return LexicalScope::create(cx, kind, bindings, firstFrameSlot, enclosing); + }; + if (!internScope(bce, createScope)) + return false; + + if (ScopeKindIsInBody(kind) && hasEnvironment()) { + // After interning the VM scope we can get the scope index. + if (!bce->emitInternedScopeOp(index(), JSOP_PUSHLEXICALENV)) + return false; + } + + // Lexical scopes need notes to be mapped from a pc. + if (!appendScopeNote(bce)) + return false; + + // Put frame slots in TDZ. Environment slots are poisoned during + // environment creation. + // + // This must be done after appendScopeNote to be considered in the extent + // of the scope. + if (!deadZoneFrameSlotRange(bce, firstFrameSlot, frameSlotEnd())) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +BytecodeEmitter::EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox) +{ + MOZ_ASSERT(this == bce->innermostEmitterScope); + MOZ_ASSERT(funbox->namedLambdaBindings()); + + if (!ensureCache(bce)) + return false; + + // See comment in enterLexical about allBindingsClosedOver. + if (funbox->allBindingsClosedOver()) + MarkAllBindingsClosedOver(*funbox->namedLambdaBindings()); + + BindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT, /* isNamedLambda = */ true); + MOZ_ASSERT(bi.kind() == BindingKind::NamedLambdaCallee); + + // The lambda name, if not closed over, is accessed via JSOP_CALLEE and + // not a frame slot. Do not update frame slot information. + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + if (!putNameInCache(bce, bi.name(), loc)) + return false; + + bi++; + MOZ_ASSERT(!bi, "There should be exactly one binding in a NamedLambda scope"); + + auto createScope = [funbox](ExclusiveContext* cx, HandleScope enclosing) { + ScopeKind scopeKind = + funbox->strict() ? ScopeKind::StrictNamedLambda : ScopeKind::NamedLambda; + return LexicalScope::create(cx, scopeKind, funbox->namedLambdaBindings(), + LOCALNO_LIMIT, enclosing); + }; + if (!internScope(bce, createScope)) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +BytecodeEmitter::EmitterScope::enterComprehensionFor(BytecodeEmitter* bce, + Handle<LexicalScope::Data*> bindings) +{ + if (!enterLexical(bce, ScopeKind::Lexical, bindings)) + return false; + + // For comprehensions, initialize all lexical names up front to undefined + // because they're now a dead feature and don't interact properly with + // TDZ. + auto nop = [](BytecodeEmitter*, const NameLocation&, bool) { + return true; + }; + + if (!bce->emit1(JSOP_UNDEFINED)) + return false; + + RootedAtom name(bce->cx); + for (BindingIter bi(*bindings, frameSlotStart(), /* isNamedLambda = */ false); bi; bi++) { + name = bi.name(); + if (!bce->emitInitializeName(name, nop)) + return false; + } + + if (!bce->emit1(JSOP_POP)) + return false; + + return true; +} + +bool +BytecodeEmitter::EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce) +{ + MOZ_ASSERT(this == bce->innermostEmitterScope); + + if (!ensureCache(bce)) + return false; + + // Parameter expressions var scopes have no pre-set bindings and are + // always extensible, as they are needed for eval. + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + // Create and intern the VM scope. + uint32_t firstFrameSlot = frameSlotStart(); + auto createScope = [firstFrameSlot](ExclusiveContext* cx, HandleScope enclosing) { + return VarScope::create(cx, ScopeKind::ParameterExpressionVar, + /* data = */ nullptr, firstFrameSlot, + /* needsEnvironment = */ true, enclosing); + }; + if (!internScope(bce, createScope)) + return false; + + MOZ_ASSERT(hasEnvironment()); + if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) + return false; + + // The extra var scope needs a note to be mapped from a pc. + if (!appendScopeNote(bce)) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +BytecodeEmitter::EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox) +{ + MOZ_ASSERT(this == bce->innermostEmitterScope); + + // If there are parameter expressions, there is an extra var scope. + if (!funbox->hasExtraBodyVarScope()) + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) + return false; + + // Resolve body-level bindings, if there are any. + auto bindings = funbox->functionScopeBindings(); + Maybe<uint32_t> lastLexicalSlot; + if (bindings) { + NameLocationMap& cache = *nameCache_; + + BindingIter bi(*bindings, funbox->hasParameterExprs); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) + return false; + + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + NameLocationMap::AddPtr p = cache.lookupForAdd(bi.name()); + + // The only duplicate bindings that occur are simple formal + // parameters, in which case the last position counts, so update the + // location. + if (p) { + MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter); + MOZ_ASSERT(!funbox->hasDestructuringArgs); + MOZ_ASSERT(!funbox->function()->hasRest()); + p->value() = loc; + continue; + } + + if (!cache.add(p, bi.name(), loc)) { + ReportOutOfMemory(bce->cx); + return false; + } + } + + updateFrameFixedSlots(bce, bi); + } else { + nextFrameSlot_ = 0; + } + + // If the function's scope may be extended at runtime due to sloppy direct + // eval and there is no extra var scope, any names beyond the function + // scope must be accessed dynamically as we don't know if the name will + // become a 'var' binding due to direct eval. + if (!funbox->hasParameterExprs && funbox->hasExtensibleScope()) + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + // In case of parameter expressions, the parameters are lexical + // bindings and have TDZ. + if (funbox->hasParameterExprs && nextFrameSlot_) { + uint32_t paramFrameSlotEnd = 0; + for (BindingIter bi(*bindings, true); bi; bi++) { + if (!BindingKindIsLexical(bi.kind())) + break; + + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + if (loc.kind() == NameLocation::Kind::FrameSlot) { + MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot()); + paramFrameSlotEnd = loc.frameSlot() + 1; + } + } + + if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd)) + return false; + } + + // Create and intern the VM scope. + auto createScope = [funbox](ExclusiveContext* cx, HandleScope enclosing) { + RootedFunction fun(cx, funbox->function()); + return FunctionScope::create(cx, funbox->functionScopeBindings(), + funbox->hasParameterExprs, + funbox->needsCallObjectRegardlessOfBindings(), + fun, enclosing); + }; + if (!internBodyScope(bce, createScope)) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +BytecodeEmitter::EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox) +{ + MOZ_ASSERT(funbox->hasParameterExprs); + MOZ_ASSERT(funbox->extraVarScopeBindings() || + funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings()); + MOZ_ASSERT(this == bce->innermostEmitterScope); + + // The extra var scope is never popped once it's entered. It replaces the + // function scope as the var emitter scope. + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) + return false; + + // Resolve body-level bindings, if there are any. + uint32_t firstFrameSlot = frameSlotStart(); + if (auto bindings = funbox->extraVarScopeBindings()) { + BindingIter bi(*bindings, firstFrameSlot); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) + return false; + + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + if (!putNameInCache(bce, bi.name(), loc)) + return false; + } + + updateFrameFixedSlots(bce, bi); + } else { + nextFrameSlot_ = firstFrameSlot; + } + + // If the extra var scope may be extended at runtime due to sloppy + // direct eval, any names beyond the var scope must be accessed + // dynamically as we don't know if the name will become a 'var' binding + // due to direct eval. + if (funbox->hasExtensibleScope()) + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + // Create and intern the VM scope. + auto createScope = [funbox, firstFrameSlot](ExclusiveContext* cx, HandleScope enclosing) { + return VarScope::create(cx, ScopeKind::FunctionBodyVar, + funbox->extraVarScopeBindings(), firstFrameSlot, + funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(), + enclosing); + }; + if (!internScope(bce, createScope)) + return false; + + if (hasEnvironment()) { + if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) + return false; + } + + // The extra var scope needs a note to be mapped from a pc. + if (!appendScopeNote(bce)) + return false; + + return checkEnvironmentChainLength(bce); +} + +class DynamicBindingIter : public BindingIter +{ + public: + explicit DynamicBindingIter(GlobalSharedContext* sc) + : BindingIter(*sc->bindings) + { } + + explicit DynamicBindingIter(EvalSharedContext* sc) + : BindingIter(*sc->bindings, /* strict = */ false) + { + MOZ_ASSERT(!sc->strict()); + } + + JSOp bindingOp() const { + switch (kind()) { + case BindingKind::Var: + return JSOP_DEFVAR; + case BindingKind::Let: + return JSOP_DEFLET; + case BindingKind::Const: + return JSOP_DEFCONST; + default: + MOZ_CRASH("Bad BindingKind"); + } + } +}; + +bool +BytecodeEmitter::EmitterScope::enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc) +{ + MOZ_ASSERT(this == bce->innermostEmitterScope); + + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) + return false; + + if (bce->emitterMode == BytecodeEmitter::SelfHosting) { + // In self-hosting, it is incorrect to consult the global scope because + // self-hosted scripts are cloned into their target compartments before + // they are run. Instead of Global, Intrinsic is used for all names. + // + // Intrinsic lookups are redirected to the special intrinsics holder + // in the global object, into which any missing values are cloned + // lazily upon first access. + fallbackFreeNameLocation_ = Some(NameLocation::Intrinsic()); + + auto createScope = [](ExclusiveContext* cx, HandleScope enclosing) { + MOZ_ASSERT(!enclosing); + return &cx->global()->emptyGlobalScope(); + }; + return internBodyScope(bce, createScope); + } + + // Resolve binding names and emit DEF{VAR,LET,CONST} prologue ops. + if (globalsc->bindings) { + for (DynamicBindingIter bi(globalsc); bi; bi++) { + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + JSAtom* name = bi.name(); + if (!putNameInCache(bce, name, loc)) + return false; + + // Define the name in the prologue. Do not emit DEFVAR for + // functions that we'll emit DEFFUN for. + if (bi.isTopLevelFunction()) + continue; + + if (!bce->emitAtomOp(name, bi.bindingOp())) + return false; + } + } + + // Note that to save space, we don't add free names to the cache for + // global scopes. They are assumed to be global vars in the syntactic + // global scope, dynamic accesses under non-syntactic global scope. + if (globalsc->scopeKind() == ScopeKind::Global) + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + else + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + auto createScope = [globalsc](ExclusiveContext* cx, HandleScope enclosing) { + MOZ_ASSERT(!enclosing); + return GlobalScope::create(cx, globalsc->scopeKind(), globalsc->bindings); + }; + return internBodyScope(bce, createScope); +} + +bool +BytecodeEmitter::EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc) +{ + MOZ_ASSERT(this == bce->innermostEmitterScope); + + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) + return false; + + // For simplicity, treat all free name lookups in eval scripts as dynamic. + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + // Create the `var` scope. Note that there is also a lexical scope, created + // separately in emitScript(). + auto createScope = [evalsc](ExclusiveContext* cx, HandleScope enclosing) { + ScopeKind scopeKind = evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval; + return EvalScope::create(cx, scopeKind, evalsc->bindings, enclosing); + }; + if (!internBodyScope(bce, createScope)) + return false; + + if (hasEnvironment()) { + if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) + return false; + } else { + // Resolve binding names and emit DEFVAR prologue ops if we don't have + // an environment (i.e., a sloppy eval not in a parameter expression). + // Eval scripts always have their own lexical scope, but non-strict + // scopes may introduce 'var' bindings to the nearest var scope. + // + // TODO: We may optimize strict eval bindings in the future to be on + // the frame. For now, handle everything dynamically. + if (!hasEnvironment() && evalsc->bindings) { + for (DynamicBindingIter bi(evalsc); bi; bi++) { + MOZ_ASSERT(bi.bindingOp() == JSOP_DEFVAR); + + if (bi.isTopLevelFunction()) + continue; + + if (!bce->emitAtomOp(bi.name(), JSOP_DEFVAR)) + return false; + } + } + + // As an optimization, if the eval does not have its own var + // environment and is directly enclosed in a global scope, then all + // free name lookups are global. + if (scope(bce)->enclosing()->is<GlobalScope>()) + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + } + + return true; +} + +bool +BytecodeEmitter::EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedContext* modulesc) +{ + MOZ_ASSERT(this == bce->innermostEmitterScope); + + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) + return false; + + // Resolve body-level bindings, if there are any. + TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; + Maybe<uint32_t> firstLexicalFrameSlot; + if (ModuleScope::Data* bindings = modulesc->bindings) { + BindingIter bi(*bindings); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) + return false; + + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + if (!putNameInCache(bce, bi.name(), loc)) + return false; + + if (BindingKindIsLexical(bi.kind())) { + if (loc.kind() == NameLocation::Kind::FrameSlot && !firstLexicalFrameSlot) + firstLexicalFrameSlot = Some(loc.frameSlot()); + + if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) + return false; + } + } + + updateFrameFixedSlots(bce, bi); + } else { + nextFrameSlot_ = 0; + } + + // Modules are toplevel, so any free names are global. + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + + // Put lexical frame slots in TDZ. Environment slots are poisoned during + // environment creation. + if (firstLexicalFrameSlot) { + if (!deadZoneFrameSlotRange(bce, *firstLexicalFrameSlot, frameSlotEnd())) + return false; + } + + // Create and intern the VM scope. + auto createScope = [modulesc](ExclusiveContext* cx, HandleScope enclosing) { + return ModuleScope::create(cx, modulesc->bindings, modulesc->module(), enclosing); + }; + if (!internBodyScope(bce, createScope)) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +BytecodeEmitter::EmitterScope::enterWith(BytecodeEmitter* bce) +{ + MOZ_ASSERT(this == bce->innermostEmitterScope); + + if (!ensureCache(bce)) + return false; + + // 'with' make all accesses dynamic and unanalyzable. + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + auto createScope = [](ExclusiveContext* cx, HandleScope enclosing) { + return WithScope::create(cx, enclosing); + }; + if (!internScope(bce, createScope)) + return false; + + if (!bce->emitInternedScopeOp(index(), JSOP_ENTERWITH)) + return false; + + if (!appendScopeNote(bce)) + return false; + + return checkEnvironmentChainLength(bce); +} + +bool +BytecodeEmitter::EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal) +{ + // If we aren't leaving the scope due to a non-local jump (e.g., break), + // we must be the innermost scope. + MOZ_ASSERT_IF(!nonLocal, this == bce->innermostEmitterScope); + + ScopeKind kind = scope(bce)->kind(); + switch (kind) { + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + if (!bce->emit1(hasEnvironment() ? JSOP_POPLEXICALENV : JSOP_DEBUGLEAVELEXICALENV)) + return false; + break; + + case ScopeKind::With: + if (!bce->emit1(JSOP_LEAVEWITH)) + return false; + break; + + case ScopeKind::ParameterExpressionVar: + MOZ_ASSERT(hasEnvironment()); + if (!bce->emit1(JSOP_POPVARENV)) + return false; + break; + + case ScopeKind::Function: + case ScopeKind::FunctionBodyVar: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::Eval: + case ScopeKind::StrictEval: + case ScopeKind::Global: + case ScopeKind::NonSyntactic: + case ScopeKind::Module: + break; + } + + // Finish up the scope if we are leaving it in LIFO fashion. + if (!nonLocal) { + // Popping scopes due to non-local jumps generate additional scope + // notes. See NonLocalExitControl::prepareForNonLocalJump. + if (ScopeKindIsInBody(kind)) { + // The extra function var scope is never popped once it's pushed, + // so its scope note extends until the end of any possible code. + uint32_t offset = kind == ScopeKind::FunctionBodyVar ? UINT32_MAX : bce->offset(); + bce->scopeNoteList.recordEnd(noteIndex_, offset, bce->inPrologue()); + } + } + + return true; +} + +Maybe<MaybeCheckTDZ> +BytecodeEmitter::TDZCheckCache::needsTDZCheck(BytecodeEmitter* bce, JSAtom* name) +{ + if (!ensureCache(bce)) + return Nothing(); + + CheckTDZMap::AddPtr p = cache_->lookupForAdd(name); + if (p) + return Some(p->value().wrapped); + + MaybeCheckTDZ rv = CheckTDZ; + for (TDZCheckCache* it = enclosing(); it; it = it->enclosing()) { + if (it->cache_) { + if (CheckTDZMap::Ptr p2 = it->cache_->lookup(name)) { + rv = p2->value(); + break; + } + } + } + + if (!cache_->add(p, name, rv)) { + ReportOutOfMemory(bce->cx); + return Nothing(); + } + + return Some(rv); +} + +bool +BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, + MaybeCheckTDZ check) +{ + if (!ensureCache(bce)) + return false; + + CheckTDZMap::AddPtr p = cache_->lookupForAdd(name); + if (p) { + MOZ_ASSERT(!check, "TDZ only needs to be checked once per binding per basic block."); + p->value() = check; + } else { + if (!cache_->add(p, name, check)) + return false; + } + + return true; +} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, + Parser<FullParseHandler>* parser, 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(parser), + atomIndices(cx->frontendCollectionPool()), + firstLine(lineNum), + maxFixedSlots(0), + maxStackDepth(0), + stackDepth(0), + arrayCompDepth(0), + emitLevel(0), + bodyScopeIndex(UINT32_MAX), + varEmitterScope(nullptr), + innermostNestableControl(nullptr), + innermostEmitterScope(nullptr), + innermostTDZCheckCache(nullptr), + constList(cx), + scopeList(cx), + tryNoteList(cx), + scopeNoteList(cx), + yieldOffsetList(cx), + typesetCount(0), + hasSingletons(false), + hasTryFinally(false), + emittingRunOnceLambda(false), + emitterMode(emitterMode), + functionBodyEndPosSet(false) +{ + MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript); +} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, + Parser<FullParseHandler>* parser, SharedContext* sc, + HandleScript script, Handle<LazyScript*> lazyScript, + TokenPos bodyPosition, EmitterMode emitterMode) + : BytecodeEmitter(parent, parser, sc, script, lazyScript, + parser->tokenStream.srcCoords.lineNum(bodyPosition.begin), + emitterMode) +{ + setFunctionBodyEndPos(bodyPosition); +} + +bool +BytecodeEmitter::init() +{ + return atomIndices.acquire(cx); +} + +template <typename Predicate /* (NestableControl*) -> bool */> +BytecodeEmitter::NestableControl* +BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const +{ + return NestableControl::findNearest(innermostNestableControl, predicate); +} + +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(this, 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(this, name, funScope); +} + +bool +BytecodeEmitter::emitCheck(ptrdiff_t delta, ptrdiff_t* offset) +{ + *offset = code().length(); + + // Start it off moderately large to avoid repeated resizings early on. + // ~98% of cases fit within 1024 bytes. + if (code().capacity() == 0 && !code().reserve(1024)) + return false; + + if (!code().growBy(delta)) { + ReportOutOfMemory(cx); + return false; + } + return true; +} + +void +BytecodeEmitter::updateDepth(ptrdiff_t target) +{ + jsbytecode* pc = code(target); + + int nuses = StackUses(nullptr, pc); + int ndefs = StackDefs(nullptr, 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; +} + +void +JumpList::push(jsbytecode* code, ptrdiff_t jumpOffset) +{ + SET_JUMP_OFFSET(&code[jumpOffset], offset - jumpOffset); + offset = jumpOffset; +} + +void +JumpList::patchAll(jsbytecode* code, JumpTarget target) +{ + ptrdiff_t delta; + for (ptrdiff_t jumpOffset = offset; jumpOffset != -1; jumpOffset += delta) { + jsbytecode* pc = &code[jumpOffset]; + MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)) || JSOp(*pc) == JSOP_LABEL); + delta = GET_JUMP_OFFSET(pc); + MOZ_ASSERT(delta < 0); + ptrdiff_t span = target.offset - jumpOffset; + SET_JUMP_OFFSET(pc, span); + } +} + +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, ParseNode* pn) +{ + if (pn && !updateSourceCoordNotes(pn->pn_pos.begin)) + return false; + return emit3(op, ARGC_HI(argc), ARGC_LO(argc)); +} + +bool +BytecodeEmitter::emitDupAt(unsigned slotFromTop) +{ + MOZ_ASSERT(slotFromTop < unsigned(stackDepth)); + + 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::emitCheckIsObj(CheckIsObjectKind kind) +{ + return emit2(JSOP_CHECKISOBJ, uint8_t(kind)); +} + +static inline unsigned +LengthOfSetLine(unsigned line) +{ + return 1 /* SN_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1); +} + +/* Updates line number notes, not column notes. */ +bool +BytecodeEmitter::updateLineNumberNotes(uint32_t offset) +{ + TokenStream* ts = &parser->tokenStream; + bool onThisLine; + if (!ts->srcCoords.isOnThisLine(offset, currentLine(), &onThisLine)) + return ts->reportError(JSMSG_OUT_OF_MEMORY); + if (!onThisLine) { + unsigned line = ts->srcCoords.lineNum(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->tokenStream.srcCoords.columnIndex(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; +} + +bool +BytecodeEmitter::emitLoopHead(ParseNode* nextpn, JumpTarget* top) +{ + if (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->isKind(PNK_LEXICALSCOPE)) + nextpn = nextpn->scopeBody(); + MOZ_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST)); + if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head) + nextpn = nextpn->pn_head; + if (!updateSourceCoordNotes(nextpn->pn_pos.begin)) + return false; + } + + *top = { offset() }; + return emit1(JSOP_LOOPHEAD); +} + +bool +BytecodeEmitter::emitLoopEntry(ParseNode* nextpn, JumpList entryJump) +{ + if (nextpn) { + /* Update the line number, as for LOOPHEAD. */ + if (nextpn->isKind(PNK_LEXICALSCOPE)) + nextpn = nextpn->scopeBody(); + MOZ_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST)); + if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head) + nextpn = nextpn->pn_head; + if (!updateSourceCoordNotes(nextpn->pn_pos.begin)) + return false; + } + + JumpTarget entry{ offset() }; + patchJumpsToTarget(entryJump, entry); + + LoopControl& loopInfo = innermostNestableControl->as<LoopControl>(); + MOZ_ASSERT(loopInfo.loopDepth() > 0); + + uint8_t loopDepthAndFlags = PackLoopEntryDepthHintAndFlags(loopInfo.loopDepth(), + loopInfo.canIonOsr()); + return emit2(JSOP_LOOPENTRY, loopDepthAndFlags); +} + +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_HI(operand), UINT16_LO(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; +} + +bool +BytecodeEmitter::flushPops(int* npops) +{ + MOZ_ASSERT(*npops != 0); + if (!emitUint16Operand(JSOP_POPN, *npops)) + return false; + + *npops = 0; + return true; +} + +namespace { + +class NonLocalExitControl { + BytecodeEmitter* bce_; + const uint32_t savedScopeNoteIndex_; + const int savedDepth_; + uint32_t openScopeNoteIndex_; + + NonLocalExitControl(const NonLocalExitControl&) = delete; + + MOZ_MUST_USE bool leaveScope(BytecodeEmitter::EmitterScope* scope); + + public: + explicit NonLocalExitControl(BytecodeEmitter* bce) + : bce_(bce), + savedScopeNoteIndex_(bce->scopeNoteList.length()), + savedDepth_(bce->stackDepth), + openScopeNoteIndex_(bce->innermostEmitterScope->noteIndex()) + { } + + ~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(BytecodeEmitter::NestableControl* target); + + MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() { + return prepareForNonLocalJump(nullptr); + } +}; + +bool +NonLocalExitControl::leaveScope(BytecodeEmitter::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(BytecodeEmitter::NestableControl* target) +{ + using NestableControl = BytecodeEmitter::NestableControl; + using EmitterScope = BytecodeEmitter::EmitterScope; + + EmitterScope* es = bce_->innermostEmitterScope; + int npops = 0; + + auto flushPops = [&npops](BytecodeEmitter* bce) { + if (npops && !bce->flushPops(&npops)) + return false; + 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_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) + return false; + } + break; + } + + case StatementKind::ForOfLoop: + npops += 2; + break; + + case StatementKind::ForInLoop: + /* The iterator and the current value are on the stack. */ + npops += 1; + if (!flushPops(bce_)) + return false; + if (!bce_->emit1(JSOP_ENDITER)) + return false; + break; + + default: + break; + } + } + + EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope; + for (; es != targetEmitterScope; es = es->enclosingInFrame()) { + if (!leaveScope(es)) + return false; + } + + return flushPops(bce_); +} + +} // anonymous namespace + +bool +BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType) +{ + NonLocalExitControl nle(this); + + 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); + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_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 emitIndexOp(op, index); +} + +bool +BytecodeEmitter::emitAtomOp(ParseNode* pn, JSOp op) +{ + MOZ_ASSERT(pn->pn_atom != nullptr); + return emitAtomOp(pn->pn_atom, op); +} + +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; +} + +static JSOp +GetIncDecInfo(ParseNodeKind kind, bool* post) +{ + MOZ_ASSERT(kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT || + kind == PNK_POSTDECREMENT || kind == PNK_PREDECREMENT); + *post = kind == PNK_POSTINCREMENT || kind == PNK_POSTDECREMENT; + return (kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT) ? JSOP_ADD : JSOP_SUB; +} + +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) +{ + JS_CHECK_RECURSION(cx, return false); + + restart: + + switch (pn->getKind()) { + // Trivial cases with no side effects. + case PNK_NOP: + case PNK_STRING: + case PNK_TEMPLATE_STRING: + case PNK_REGEXP: + case PNK_TRUE: + case PNK_FALSE: + case PNK_NULL: + case PNK_ELISION: + case PNK_GENERATOR: + case PNK_NUMBER: + case PNK_OBJECT_PROPERTY_NAME: + MOZ_ASSERT(pn->isArity(PN_NULLARY)); + *answer = false; + return true; + + // |this| can throw in derived class constructors, including nested arrow + // functions or eval. + case PNK_THIS: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + *answer = sc->needsThisTDZChecks(); + return true; + + // Trivial binary nodes with more token pos holders. + case PNK_NEWTARGET: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + MOZ_ASSERT(pn->pn_left->isKind(PNK_POSHOLDER)); + MOZ_ASSERT(pn->pn_right->isKind(PNK_POSHOLDER)); + *answer = false; + return true; + + case PNK_BREAK: + case PNK_CONTINUE: + case PNK_DEBUGGER: + MOZ_ASSERT(pn->isArity(PN_NULLARY)); + *answer = true; + return true; + + // Watch out for getters! + case PNK_DOT: + MOZ_ASSERT(pn->isArity(PN_NAME)); + *answer = true; + return true; + + // Unary cases with side effects only if the child has them. + case PNK_TYPEOFEXPR: + case PNK_VOID: + case PNK_NOT: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + return checkSideEffects(pn->pn_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 PNK_COMPUTED_NAME: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + *answer = true; + return true; + + // Looking up or evaluating the associated name could throw. + case PNK_TYPEOFNAME: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + *answer = true; + return true; + + // These unary cases have side effects on the enclosing object/array, + // 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, for an object literal having a mutated prototype + // and an array comprehension containing no other effectful operations + // only produce a value, without affecting anything else. + case PNK_MUTATEPROTO: + case PNK_ARRAYPUSH: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + return checkSideEffects(pn->pn_kid, answer); + + // Unary cases with obvious side effects. + case PNK_PREINCREMENT: + case PNK_POSTINCREMENT: + case PNK_PREDECREMENT: + case PNK_POSTDECREMENT: + case PNK_THROW: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + *answer = true; + return true; + + // These might invoke valueOf/toString, even with a subexpression without + // side effects! Consider |+{ valueOf: null, toString: null }|. + case PNK_BITNOT: + case PNK_POS: + case PNK_NEG: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + *answer = true; + return true; + + // This invokes the (user-controllable) iterator protocol. + case PNK_SPREAD: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + *answer = true; + return true; + + case PNK_YIELD_STAR: + case PNK_YIELD: + case PNK_AWAIT: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + *answer = true; + return true; + + // Deletion generally has side effects, even if isolated cases have none. + case PNK_DELETENAME: + case PNK_DELETEPROP: + case PNK_DELETEELEM: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + *answer = true; + return true; + + // Deletion of a non-Reference expression has side effects only through + // evaluating the expression. + case PNK_DELETEEXPR: { + MOZ_ASSERT(pn->isArity(PN_UNARY)); + ParseNode* expr = pn->pn_kid; + return checkSideEffects(expr, answer); + } + + case PNK_SEMI: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + if (ParseNode* expr = pn->pn_kid) + return checkSideEffects(expr, answer); + *answer = false; + return true; + + // Binary cases with obvious side effects. + case PNK_ASSIGN: + case PNK_ADDASSIGN: + case PNK_SUBASSIGN: + case PNK_BITORASSIGN: + case PNK_BITXORASSIGN: + case PNK_BITANDASSIGN: + case PNK_LSHASSIGN: + case PNK_RSHASSIGN: + case PNK_URSHASSIGN: + case PNK_MULASSIGN: + case PNK_DIVASSIGN: + case PNK_MODASSIGN: + case PNK_POWASSIGN: + case PNK_SETTHIS: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + *answer = true; + return true; + + case PNK_STATEMENTLIST: + case PNK_CATCHLIST: + // Strict equality operations and logical operators are well-behaved and + // perform no conversions. + case PNK_OR: + case PNK_AND: + case PNK_STRICTEQ: + case PNK_STRICTNE: + // Any subexpression of a comma expression could be effectful. + case PNK_COMMA: + MOZ_ASSERT(pn->pn_count > 0); + MOZ_FALLTHROUGH; + // Subcomponents of a literal may be effectful. + case PNK_ARRAY: + case PNK_OBJECT: + MOZ_ASSERT(pn->isArity(PN_LIST)); + for (ParseNode* item = pn->pn_head; item; item = item->pn_next) { + 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 PNK_BITOR: + case PNK_BITXOR: + case PNK_BITAND: + case PNK_EQ: + case PNK_NE: + case PNK_LT: + case PNK_LE: + case PNK_GT: + case PNK_GE: + case PNK_INSTANCEOF: + case PNK_IN: + case PNK_LSH: + case PNK_RSH: + case PNK_URSH: + case PNK_ADD: + case PNK_SUB: + case PNK_STAR: + case PNK_DIV: + case PNK_MOD: + case PNK_POW: + MOZ_ASSERT(pn->isArity(PN_LIST)); + MOZ_ASSERT(pn->pn_count >= 2); + *answer = true; + return true; + + case PNK_COLON: + case PNK_CASE: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + if (!checkSideEffects(pn->pn_left, answer)) + return false; + if (*answer) + return true; + return checkSideEffects(pn->pn_right, answer); + + // More getters. + case PNK_ELEM: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + *answer = true; + return true; + + // These affect visible names in this code, or in other code. + case PNK_IMPORT: + case PNK_EXPORT_FROM: + case PNK_EXPORT_DEFAULT: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + *answer = true; + return true; + + // Likewise. + case PNK_EXPORT: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + *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 PNK_DOWHILE: + case PNK_WHILE: + case PNK_FOR: + case PNK_COMPREHENSIONFOR: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + *answer = true; + return true; + + // Declarations affect the name set of the relevant scope. + case PNK_VAR: + case PNK_CONST: + case PNK_LET: + MOZ_ASSERT(pn->isArity(PN_LIST)); + *answer = true; + return true; + + case PNK_IF: + case PNK_CONDITIONAL: + MOZ_ASSERT(pn->isArity(PN_TERNARY)); + if (!checkSideEffects(pn->pn_kid1, answer)) + return false; + if (*answer) + return true; + if (!checkSideEffects(pn->pn_kid2, answer)) + return false; + if (*answer) + return true; + if ((pn = pn->pn_kid3)) + goto restart; + return true; + + // Function calls can invoke non-local code. + case PNK_NEW: + case PNK_CALL: + case PNK_TAGGED_TEMPLATE: + case PNK_SUPERCALL: + MOZ_ASSERT(pn->isArity(PN_LIST)); + *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 PNK_CLASS: + MOZ_ASSERT(pn->isArity(PN_TERNARY)); + *answer = true; + return true; + + // |with| calls |ToObject| on its expression and so throws if that value + // is null/undefined. + case PNK_WITH: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + *answer = true; + return true; + + case PNK_RETURN: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + *answer = true; + return true; + + case PNK_NAME: + MOZ_ASSERT(pn->isArity(PN_NAME)); + *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 PNK_SHORTHAND: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + *answer = true; + return true; + + case PNK_FUNCTION: + MOZ_ASSERT(pn->isArity(PN_CODE)); + /* + * 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 PNK_MODULE: + *answer = false; + return true; + + // Generator expressions have no side effects on their own. + case PNK_GENEXP: + MOZ_ASSERT(pn->isArity(PN_LIST)); + *answer = false; + return true; + + case PNK_TRY: + MOZ_ASSERT(pn->isArity(PN_TERNARY)); + if (!checkSideEffects(pn->pn_kid1, answer)) + return false; + if (*answer) + return true; + if (ParseNode* catchList = pn->pn_kid2) { + MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST)); + if (!checkSideEffects(catchList, answer)) + return false; + if (*answer) + return true; + } + if (ParseNode* finallyBlock = pn->pn_kid3) { + if (!checkSideEffects(finallyBlock, answer)) + return false; + } + return true; + + case PNK_CATCH: + MOZ_ASSERT(pn->isArity(PN_TERNARY)); + if (!checkSideEffects(pn->pn_kid1, answer)) + return false; + if (*answer) + return true; + if (ParseNode* cond = pn->pn_kid2) { + if (!checkSideEffects(cond, answer)) + return false; + if (*answer) + return true; + } + return checkSideEffects(pn->pn_kid3, answer); + + case PNK_SWITCH: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + if (!checkSideEffects(pn->pn_left, answer)) + return false; + return *answer || checkSideEffects(pn->pn_right, answer); + + case PNK_LABEL: + MOZ_ASSERT(pn->isArity(PN_NAME)); + return checkSideEffects(pn->expr(), answer); + + case PNK_LEXICALSCOPE: + MOZ_ASSERT(pn->isArity(PN_SCOPE)); + return checkSideEffects(pn->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 PNK_TEMPLATE_STRING_LIST: + MOZ_ASSERT(pn->isArity(PN_LIST)); + MOZ_ASSERT(pn->pn_count > 0); + MOZ_ASSERT((pn->pn_count % 2) == 1, + "template strings must alternate template and substitution " + "parts"); + *answer = pn->pn_count > 1; + return true; + + case PNK_ARRAYCOMP: + MOZ_ASSERT(pn->isArity(PN_LIST)); + MOZ_ASSERT(pn->pn_count == 1); + return checkSideEffects(pn->pn_head, answer); + + // This should be unreachable but is left as-is for now. + case PNK_PARAMSBODY: + *answer = true; + return true; + + case PNK_FORIN: // by PNK_FOR/PNK_COMPREHENSIONFOR + case PNK_FOROF: // by PNK_FOR/PNK_COMPREHENSIONFOR + case PNK_FORHEAD: // by PNK_FOR/PNK_COMPREHENSIONFOR + case PNK_CLASSMETHOD: // by PNK_CLASS + case PNK_CLASSNAMES: // by PNK_CLASS + case PNK_CLASSMETHODLIST: // by PNK_CLASS + case PNK_IMPORT_SPEC_LIST: // by PNK_IMPORT + case PNK_IMPORT_SPEC: // by PNK_IMPORT + case PNK_EXPORT_BATCH_SPEC:// by PNK_EXPORT + case PNK_EXPORT_SPEC_LIST: // by PNK_EXPORT + case PNK_EXPORT_SPEC: // by PNK_EXPORT + case PNK_CALLSITEOBJ: // by PNK_TAGGED_TEMPLATE + case PNK_POSHOLDER: // by PNK_NEWTARGET + case PNK_SUPERBASE: // by PNK_ELEM and others + MOZ_CRASH("handled by parent nodes"); + + case PNK_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; +} + +bool +BytecodeEmitter::maybeSetDisplayURL() +{ + if (tokenStream()->hasDisplayURL()) { + if (!parser->ss->setDisplayURL(cx, tokenStream()->displayURL())) + return false; + } + return true; +} + +bool +BytecodeEmitter::maybeSetSourceMap() +{ + if (tokenStream()->hasSourceMapURL()) { + MOZ_ASSERT(!parser->ss->hasSourceMapURL()); + if (!parser->ss->setSourceMapURL(cx, tokenStream()->sourceMapURL())) + return false; + } + + /* + * Source map URLs passed as a compile option (usually via a HTTP source map + * header) override any source map urls passed as comment pragmas. + */ + if (parser->options().sourceMapURL()) { + // Warn about the replacement, but use the new one. + if (parser->ss->hasSourceMapURL()) { + if(!parser->report(ParseWarning, false, nullptr, JSMSG_ALREADY_HAS_PRAGMA, + parser->ss->filename(), "//# sourceMappingURL")) + return false; + } + + if (!parser->ss->setSourceMapURL(cx, parser->options().sourceMapURL())) + return false; + } + + return true; +} + +void +BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext* cx) +{ + // Note: when parsing off thread the resulting scripts need to be handed to + // the debugger after rejoining to the main thread. + if (!cx->isJSContext()) + 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->asJSContext(), script); + } +} + +inline TokenStream* +BytecodeEmitter::tokenStream() +{ + return &parser->tokenStream; +} + +bool +BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) +{ + TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; + + va_list args; + va_start(args, errorNumber); + bool result = tokenStream()->reportCompileErrorNumberVA(pos.begin, JSREPORT_ERROR, + errorNumber, args); + va_end(args); + return result; +} + +bool +BytecodeEmitter::reportStrictWarning(ParseNode* pn, unsigned errorNumber, ...) +{ + TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; + + va_list args; + va_start(args, errorNumber); + bool result = tokenStream()->reportStrictWarningErrorNumberVA(pos.begin, errorNumber, args); + va_end(args); + return result; +} + +bool +BytecodeEmitter::reportStrictModeError(ParseNode* pn, unsigned errorNumber, ...) +{ + TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; + + va_list args; + va_start(args, errorNumber); + bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict(), + errorNumber, args); + va_end(args); + return result; +} + +bool +BytecodeEmitter::emitNewInit(JSProtoKey key) +{ + 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] = jsbytecode(key); + 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, AtomToId(cx->names().value)); + Rooted<jsid> done_id(cx, AtomToId(cx->names().done)); + if (!NativeDefineProperty(cx, obj, value_id, UndefinedHandleValue, nullptr, nullptr, + JSPROP_ENUMERATE)) + { + return false; + } + if (!NativeDefineProperty(cx, obj, done_id, UndefinedHandleValue, nullptr, nullptr, + 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, bool callContext) +{ + switch (loc.kind()) { + case NameLocation::Kind::Dynamic: + if (!emitAtomOp(name, JSOP_GETNAME)) + return false; + break; + + case NameLocation::Kind::Global: + if (!emitAtomOp(name, JSOP_GETGNAME)) + return false; + break; + + case NameLocation::Kind::Intrinsic: + if (!emitAtomOp(name, JSOP_GETINTRINSIC)) + return false; + break; + + case NameLocation::Kind::NamedLambdaCallee: + if (!emit1(JSOP_CALLEE)) + return false; + break; + + case NameLocation::Kind::Import: + if (!emitAtomOp(name, JSOP_GETIMPORT)) + return false; + break; + + case NameLocation::Kind::ArgumentSlot: + if (!emitArgOp(JSOP_GETARG, loc.argumentSlot())) + return false; + break; + + case NameLocation::Kind::FrameSlot: + if (loc.isLexical()) { + if (!emitTDZCheckIfNeeded(name, loc)) + return false; + } + if (!emitLocalOp(JSOP_GETLOCAL, loc.frameSlot())) + return false; + break; + + case NameLocation::Kind::EnvironmentCoordinate: + if (loc.isLexical()) { + if (!emitTDZCheckIfNeeded(name, loc)) + return false; + } + if (!emitEnvCoordOp(JSOP_GETALIASEDVAR, loc.environmentCoordinate())) + return false; + break; + + case NameLocation::Kind::DynamicAnnexBVar: + MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization"); + } + + // Need to provide |this| value for call. + if (callContext) { + switch (loc.kind()) { + case NameLocation::Kind::Dynamic: { + JSOp thisOp = needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS; + if (!emitAtomOp(name, thisOp)) + return false; + break; + } + + case NameLocation::Kind::Global: + if (!emitAtomOp(name, JSOP_GIMPLICITTHIS)) + return false; + break; + + case NameLocation::Kind::Intrinsic: + case NameLocation::Kind::NamedLambdaCallee: + case NameLocation::Kind::Import: + case NameLocation::Kind::ArgumentSlot: + case NameLocation::Kind::FrameSlot: + case NameLocation::Kind::EnvironmentCoordinate: + if (!emit1(JSOP_UNDEFINED)) + return false; + break; + + case NameLocation::Kind::DynamicAnnexBVar: + MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization"); + } + } + + return true; +} + +bool +BytecodeEmitter::emitGetName(ParseNode* pn, bool callContext) +{ + return emitGetName(pn->name(), callContext); +} + +template <typename RHSEmitter> +bool +BytecodeEmitter::emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc, + RHSEmitter emitRhs, bool initialize) +{ + bool emittedBindOp = false; + + switch (loc.kind()) { + case NameLocation::Kind::Dynamic: + case NameLocation::Kind::Import: + case NameLocation::Kind::DynamicAnnexBVar: { + uint32_t atomIndex; + if (!makeAtomIndex(name, &atomIndex)) + return false; + if (loc.kind() == NameLocation::Kind::DynamicAnnexBVar) { + // Annex B vars always go on the nearest variable environment, + // even if lexical environments in between contain same-named + // bindings. + if (!emit1(JSOP_BINDVAR)) + return false; + } else { + if (!emitIndexOp(JSOP_BINDNAME, atomIndex)) + return false; + } + emittedBindOp = true; + if (!emitRhs(this, loc, emittedBindOp)) + return false; + if (!emitIndexOp(strictifySetNameOp(JSOP_SETNAME), atomIndex)) + return false; + break; + } + + case NameLocation::Kind::Global: { + JSOp op; + uint32_t atomIndex; + if (!makeAtomIndex(name, &atomIndex)) + return false; + if (loc.isLexical() && initialize) { + // INITGLEXICAL always gets the global lexical scope. It doesn't + // need a BINDGNAME. + MOZ_ASSERT(innermostScope()->is<GlobalScope>()); + op = JSOP_INITGLEXICAL; + } else { + if (!emitIndexOp(JSOP_BINDGNAME, atomIndex)) + return false; + emittedBindOp = true; + op = strictifySetNameOp(JSOP_SETGNAME); + } + if (!emitRhs(this, loc, emittedBindOp)) + return false; + if (!emitIndexOp(op, atomIndex)) + return false; + break; + } + + case NameLocation::Kind::Intrinsic: + if (!emitRhs(this, loc, emittedBindOp)) + return false; + if (!emitAtomOp(name, JSOP_SETINTRINSIC)) + return false; + break; + + case NameLocation::Kind::NamedLambdaCallee: + if (!emitRhs(this, loc, emittedBindOp)) + return false; + // Assigning to the named lambda is a no-op in sloppy mode but + // throws in strict mode. + if (sc->strict() && !emit1(JSOP_THROWSETCALLEE)) + return false; + break; + + case NameLocation::Kind::ArgumentSlot: { + // If we assign to a positional formal parameter and the arguments + // object is unmapped (strict mode or function with + // default/rest/destructing args), parameters do not alias + // arguments[i], and to make the arguments object reflect initial + // parameter values prior to any mutation we create it eagerly + // whenever parameters are (or might, in the case of calls to eval) + // assigned. + FunctionBox* funbox = sc->asFunctionBox(); + if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj()) + funbox->setDefinitelyNeedsArgsObj(); + + if (!emitRhs(this, loc, emittedBindOp)) + return false; + if (!emitArgOp(JSOP_SETARG, loc.argumentSlot())) + return false; + break; + } + + case NameLocation::Kind::FrameSlot: { + JSOp op = JSOP_SETLOCAL; + if (!emitRhs(this, loc, emittedBindOp)) + return false; + if (loc.isLexical()) { + if (initialize) { + op = JSOP_INITLEXICAL; + } else { + if (loc.isConst()) + op = JSOP_THROWSETCONST; + + if (!emitTDZCheckIfNeeded(name, loc)) + return false; + } + } + if (!emitLocalOp(op, loc.frameSlot())) + return false; + if (op == JSOP_INITLEXICAL) { + if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) + return false; + } + break; + } + + case NameLocation::Kind::EnvironmentCoordinate: { + JSOp op = JSOP_SETALIASEDVAR; + if (!emitRhs(this, loc, emittedBindOp)) + return false; + if (loc.isLexical()) { + if (initialize) { + op = JSOP_INITALIASEDLEXICAL; + } else { + if (loc.isConst()) + op = JSOP_THROWSETALIASEDCONST; + + if (!emitTDZCheckIfNeeded(name, loc)) + return false; + } + } + if (loc.bindingKind() == BindingKind::NamedLambdaCallee) { + // Assigning to the named lambda is a no-op in sloppy mode and throws + // in strict mode. + op = JSOP_THROWSETALIASEDCONST; + if (sc->strict() && !emitEnvCoordOp(op, loc.environmentCoordinate())) + return false; + } else { + if (!emitEnvCoordOp(op, loc.environmentCoordinate())) + return false; + } + if (op == JSOP_INITALIASEDLEXICAL) { + if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) + return false; + } + break; + } + } + + return true; +} + +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(ParseNode* pn) +{ + MOZ_ASSERT(pn->isKind(PNK_DOT)); + MOZ_ASSERT(!pn->as<PropertyAccess>().isSuper()); + + ParseNode* pn2 = pn->pn_expr; + + /* + * If the object operand is also a dotted property reference, reverse the + * list linked via pn_expr temporarily so we can iterate over it from the + * bottom up (reversing again as we go), to avoid excessive recursion. + */ + if (pn2->isKind(PNK_DOT) && !pn2->as<PropertyAccess>().isSuper()) { + ParseNode* pndot = pn2; + ParseNode* pnup = nullptr; + ParseNode* pndown; + for (;;) { + /* Reverse pndot->pn_expr to point up, not down. */ + pndown = pndot->pn_expr; + pndot->pn_expr = pnup; + if (!pndown->isKind(PNK_DOT) || pndown->as<PropertyAccess>().isSuper()) + break; + pnup = pndot; + pndot = pndown; + } + + /* pndown is a primary expression, not a dotted property reference. */ + if (!emitTree(pndown)) + return false; + + do { + /* Walk back up the list, emitting annotated name ops. */ + if (!emitAtomOp(pndot, JSOP_GETPROP)) + return false; + + /* Reverse the pn_expr link again. */ + pnup = pndot->pn_expr; + pndot->pn_expr = pndown; + pndown = pndot; + } while ((pndot = pnup) != nullptr); + return true; + } + + // The non-optimized case. + return emitTree(pn2); +} + +bool +BytecodeEmitter::emitSuperPropLHS(ParseNode* superBase, bool isCall) +{ + if (!emitGetThisForSuperBase(superBase)) + return false; + if (isCall && !emit1(JSOP_DUP)) + return false; + if (!emit1(JSOP_SUPERBASE)) + return false; + return true; +} + +bool +BytecodeEmitter::emitPropOp(ParseNode* pn, JSOp op) +{ + MOZ_ASSERT(pn->isArity(PN_NAME)); + + if (!emitPropLHS(pn)) + return false; + + if (op == JSOP_CALLPROP && !emit1(JSOP_DUP)) + return false; + + if (!emitAtomOp(pn, op)) + return false; + + if (op == JSOP_CALLPROP && !emit1(JSOP_SWAP)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall) +{ + ParseNode* base = &pn->as<PropertyAccess>().expression(); + if (!emitSuperPropLHS(base, isCall)) + return false; + + if (!emitAtomOp(pn, op)) + return false; + + if (isCall && !emit1(JSOP_SWAP)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitPropIncDec(ParseNode* pn) +{ + MOZ_ASSERT(pn->pn_kid->isKind(PNK_DOT)); + + bool post; + bool isSuper = pn->pn_kid->as<PropertyAccess>().isSuper(); + JSOp binop = GetIncDecInfo(pn->getKind(), &post); + + if (isSuper) { + ParseNode* base = &pn->pn_kid->as<PropertyAccess>().expression(); + if (!emitSuperPropLHS(base)) // THIS OBJ + return false; + if (!emit1(JSOP_DUP2)) // THIS OBJ THIS OBJ + return false; + } else { + if (!emitPropLHS(pn->pn_kid)) // OBJ + return false; + if (!emit1(JSOP_DUP)) // OBJ OBJ + return false; + } + if (!emitAtomOp(pn->pn_kid, isSuper? JSOP_GETPROP_SUPER : JSOP_GETPROP)) // OBJ V + return false; + if (!emit1(JSOP_POS)) // OBJ N + return false; + if (post && !emit1(JSOP_DUP)) // OBJ N? N + return false; + if (!emit1(JSOP_ONE)) // OBJ N? N 1 + return false; + if (!emit1(binop)) // OBJ N? N+1 + return false; + + if (post) { + if (!emit2(JSOP_PICK, 2 + isSuper)) // N? N+1 OBJ + return false; + if (!emit1(JSOP_SWAP)) // N? OBJ N+1 + return false; + if (isSuper) { + if (!emit2(JSOP_PICK, 3)) // N THIS N+1 OBJ + return false; + if (!emit1(JSOP_SWAP)) // N THIS OBJ N+1 + return false; + } + } + + JSOp setOp = isSuper ? sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER + : sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; + if (!emitAtomOp(pn->pn_kid, setOp)) // N? N+1 + return false; + if (post && !emit1(JSOP_POP)) // RESULT + return false; + + return true; +} + +bool +BytecodeEmitter::emitNameIncDec(ParseNode* pn) +{ + MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME)); + + bool post; + JSOp binop = GetIncDecInfo(pn->getKind(), &post); + + auto emitRhs = [pn, post, binop](BytecodeEmitter* bce, const NameLocation& loc, + bool emittedBindOp) + { + JSAtom* name = pn->pn_kid->name(); + if (!bce->emitGetNameAtLocation(name, loc, false)) // SCOPE? V + return false; + if (!bce->emit1(JSOP_POS)) // SCOPE? N + return false; + if (post && !bce->emit1(JSOP_DUP)) // SCOPE? N? N + return false; + if (!bce->emit1(JSOP_ONE)) // SCOPE? N? N 1 + return false; + if (!bce->emit1(binop)) // SCOPE? N? N+1 + return false; + + if (post && emittedBindOp) { + if (!bce->emit2(JSOP_PICK, 2)) // N? N+1 SCOPE? + return false; + if (!bce->emit1(JSOP_SWAP)) // N? SCOPE? N+1 + return false; + } + + return true; + }; + + if (!emitSetName(pn->pn_kid, emitRhs)) + return false; + + if (post && !emit1(JSOP_POP)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitElemOperands(ParseNode* pn, EmitElemOption opts) +{ + MOZ_ASSERT(pn->isArity(PN_BINARY)); + + if (!emitTree(pn->pn_left)) + return false; + + if (opts == EmitElemOption::IncDec) { + if (!emit1(JSOP_CHECKOBJCOERCIBLE)) + return false; + } else if (opts == EmitElemOption::Call) { + if (!emit1(JSOP_DUP)) + return false; + } + + if (!emitTree(pn->pn_right)) + return false; + + if (opts == EmitElemOption::Set) { + if (!emit2(JSOP_PICK, 2)) + return false; + } else if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) { + if (!emit1(JSOP_TOID)) + return false; + } + return true; +} + +bool +BytecodeEmitter::emitSuperElemOperands(ParseNode* pn, EmitElemOption opts) +{ + MOZ_ASSERT(pn->isKind(PNK_ELEM) && pn->as<PropertyByValue>().isSuper()); + + // The ordering here is somewhat screwy. We need to evaluate the propval + // first, by spec. Do a little dance to not emit more than one JSOP_THIS. + // Since JSOP_THIS might throw in derived class constructors, we cannot + // just push it earlier as the receiver. We have to swap it down instead. + + if (!emitTree(pn->pn_right)) + return false; + + // We need to convert the key to an object id first, so that we do not do + // it inside both the GETELEM and the SETELEM. + if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) { + if (!emit1(JSOP_TOID)) + return false; + } + + if (!emitGetThisForSuperBase(pn->pn_left)) + return false; + + if (opts == EmitElemOption::Call) { + if (!emit1(JSOP_SWAP)) + return false; + + // We need another |this| on top, also + if (!emitDupAt(1)) + return false; + } + + if (!emit1(JSOP_SUPERBASE)) + return false; + + if (opts == EmitElemOption::Set && !emit2(JSOP_PICK, 3)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitElemOpBase(JSOp op) +{ + if (!emit1(op)) + return false; + + checkTypeSet(op); + return true; +} + +bool +BytecodeEmitter::emitElemOp(ParseNode* pn, JSOp op) +{ + EmitElemOption opts = EmitElemOption::Get; + if (op == JSOP_CALLELEM) + opts = EmitElemOption::Call; + else if (op == JSOP_SETELEM || op == JSOP_STRICTSETELEM) + opts = EmitElemOption::Set; + + return emitElemOperands(pn, opts) && emitElemOpBase(op); +} + +bool +BytecodeEmitter::emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall) +{ + EmitElemOption opts = EmitElemOption::Get; + if (isCall) + opts = EmitElemOption::Call; + else if (op == JSOP_SETELEM_SUPER || op == JSOP_STRICTSETELEM_SUPER) + opts = EmitElemOption::Set; + + if (!emitSuperElemOperands(pn, opts)) + return false; + if (!emitElemOpBase(op)) + return false; + + if (isCall && !emit1(JSOP_SWAP)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitElemIncDec(ParseNode* pn) +{ + MOZ_ASSERT(pn->pn_kid->isKind(PNK_ELEM)); + + bool isSuper = pn->pn_kid->as<PropertyByValue>().isSuper(); + + // We need to convert the key to an object id first, so that we do not do + // it inside both the GETELEM and the SETELEM. This is done by + // emit(Super)ElemOperands. + if (isSuper) { + if (!emitSuperElemOperands(pn->pn_kid, EmitElemOption::IncDec)) + return false; + } else { + if (!emitElemOperands(pn->pn_kid, EmitElemOption::IncDec)) + return false; + } + + bool post; + JSOp binop = GetIncDecInfo(pn->getKind(), &post); + + JSOp getOp; + if (isSuper) { + // There's no such thing as JSOP_DUP3, so we have to be creative. + // Note that pushing things again is no fewer JSOps. + if (!emitDupAt(2)) // KEY THIS OBJ KEY + return false; + if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS + return false; + if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS OBJ + return false; + getOp = JSOP_GETELEM_SUPER; + } else { + // OBJ KEY + if (!emit1(JSOP_DUP2)) // OBJ KEY OBJ KEY + return false; + getOp = JSOP_GETELEM; + } + if (!emitElemOpBase(getOp)) // OBJ KEY V + return false; + if (!emit1(JSOP_POS)) // OBJ KEY N + return false; + if (post && !emit1(JSOP_DUP)) // OBJ KEY N? N + return false; + if (!emit1(JSOP_ONE)) // OBJ KEY N? N 1 + return false; + if (!emit1(binop)) // OBJ KEY N? N+1 + return false; + + if (post) { + if (isSuper) { + // We have one more value to rotate around, because of |this| + // on the stack + if (!emit2(JSOP_PICK, 4)) + return false; + } + if (!emit2(JSOP_PICK, 3 + isSuper)) // KEY N N+1 OBJ + return false; + if (!emit2(JSOP_PICK, 3 + isSuper)) // N N+1 OBJ KEY + return false; + if (!emit2(JSOP_PICK, 2 + isSuper)) // N OBJ KEY N+1 + return false; + } + + JSOp setOp = isSuper ? (sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER) + : (sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM); + if (!emitElemOpBase(setOp)) // N? N+1 + return false; + if (post && !emit1(JSOP_POP)) // RESULT + return false; + + return true; +} + +bool +BytecodeEmitter::emitCallIncDec(ParseNode* incDec) +{ + MOZ_ASSERT(incDec->isKind(PNK_PREINCREMENT) || + incDec->isKind(PNK_POSTINCREMENT) || + incDec->isKind(PNK_PREDECREMENT) || + incDec->isKind(PNK_POSTDECREMENT)); + + MOZ_ASSERT(incDec->pn_kid->isKind(PNK_CALL)); + + ParseNode* call = incDec->pn_kid; + 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 (!constList.append(DoubleValue(dval))) + return false; + + return emitIndex32(JSOP_DOUBLE, constList.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(ParseNode* pn) +{ + ParseNode* cases = pn->pn_right; + MOZ_ASSERT(cases->isKind(PNK_LEXICALSCOPE) || cases->isKind(PNK_STATEMENTLIST)); + + // Emit code for the discriminant. + if (!emitTree(pn->pn_left)) + return false; + + // Enter the scope before pushing the switch BreakableControl since all + // breaks are under this scope. + Maybe<TDZCheckCache> tdzCache; + Maybe<EmitterScope> emitterScope; + if (cases->isKind(PNK_LEXICALSCOPE)) { + if (!cases->isEmptyScope()) { + tdzCache.emplace(this); + emitterScope.emplace(this); + if (!emitterScope->enterLexical(this, ScopeKind::Lexical, cases->scopeBindings())) + return false; + } + + // Advance |cases| to refer to the switch case list. + cases = cases->scopeBody(); + + // 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->pn_xflags & PNX_FUNCDEFS) { + MOZ_ASSERT(emitterScope); + for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) { + if (caseNode->pn_right->pn_xflags & PNX_FUNCDEFS) { + if (!emitHoistedFunctionsInList(caseNode->pn_right)) + return false; + } + } + } + } + + // After entering the scope, push the switch control. + BreakableControl controlInfo(this, StatementKind::Switch); + + ptrdiff_t top = offset(); + + // Switch bytecodes run from here till end of final case. + uint32_t caseCount = cases->pn_count; + if (caseCount > JS_BIT(16)) { + parser->tokenStream.reportError(JSMSG_TOO_MANY_CASES); + return false; + } + + // Try for most optimal, fall back if not dense ints. + JSOp switchOp = JSOP_TABLESWITCH; + uint32_t tableLength = 0; + int32_t low, high; + bool hasDefault = false; + CaseClause* firstCase = cases->pn_head ? &cases->pn_head->as<CaseClause>() : nullptr; + if (caseCount == 0 || + (caseCount == 1 && (hasDefault = firstCase->isDefault()))) + { + caseCount = 0; + low = 0; + high = -1; + } else { + Vector<jsbitmap, 128, SystemAllocPolicy> intmap; + int32_t intmapBitLength = 0; + + low = JSVAL_INT_MAX; + high = JSVAL_INT_MIN; + + for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { + if (caseNode->isDefault()) { + hasDefault = true; + caseCount--; // one of the "cases" was the default + continue; + } + + if (switchOp == JSOP_CONDSWITCH) + continue; + + MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); + + ParseNode* caseValue = caseNode->caseExpression(); + + if (caseValue->getKind() != PNK_NUMBER) { + switchOp = JSOP_CONDSWITCH; + continue; + } + + int32_t i; + if (!NumberIsInt32(caseValue->pn_dval, &i)) { + switchOp = JSOP_CONDSWITCH; + continue; + } + + if (unsigned(i + int(JS_BIT(15))) >= unsigned(JS_BIT(16))) { + switchOp = JSOP_CONDSWITCH; + continue; + } + if (i < low) + low = i; + if (i > high) + high = i; + + // Check for duplicates, which require a JSOP_CONDSWITCH. + // We bias i by 65536 if it's negative, and hope that's a rare + // case (because it requires a malloc'd bitmap). + if (i < 0) + i += JS_BIT(16); + if (i >= intmapBitLength) { + size_t newLength = (i / JS_BITMAP_NBITS) + 1; + if (!intmap.resize(newLength)) + return false; + intmapBitLength = newLength * JS_BITMAP_NBITS; + } + if (JS_TEST_BIT(intmap, i)) { + switchOp = JSOP_CONDSWITCH; + continue; + } + JS_SET_BIT(intmap, i); + } + + // Compute table length and select condswitch instead if overlarge or + // more than half-sparse. + if (switchOp == JSOP_TABLESWITCH) { + tableLength = uint32_t(high - low + 1); + if (tableLength >= JS_BIT(16) || tableLength > 2 * caseCount) + switchOp = JSOP_CONDSWITCH; + } + } + + // The note has one or two offsets: first tells total switch code length; + // second (if condswitch) tells offset to first JSOP_CASE. + unsigned noteIndex; + size_t switchSize; + if (switchOp == JSOP_CONDSWITCH) { + // 0 bytes of immediate for unoptimized switch. + switchSize = 0; + if (!newSrcNote3(SRC_CONDSWITCH, 0, 0, ¬eIndex)) + return false; + } else { + MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); + + // 3 offsets (len, low, high) before the table, 1 per entry. + switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableLength)); + if (!newSrcNote2(SRC_TABLESWITCH, 0, ¬eIndex)) + return false; + } + + // Emit switchOp followed by switchSize bytes of jump or lookup table. + MOZ_ASSERT(top == offset()); + if (!emitN(switchOp, switchSize)) + return false; + + Vector<CaseClause*, 32, SystemAllocPolicy> table; + + JumpList condSwitchDefaultOff; + if (switchOp == JSOP_CONDSWITCH) { + unsigned caseNoteIndex; + bool beforeCases = true; + ptrdiff_t lastCaseOffset = -1; + + // The case conditions need their own TDZ cache since they might not + // all execute. + TDZCheckCache tdzCache(this); + + // Emit code for evaluating cases and jumping to case statements. + for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { + ParseNode* caseValue = caseNode->caseExpression(); + + // If the expression is a literal, suppress line number emission so + // that debugging works more naturally. + if (caseValue) { + if (!emitTree(caseValue, + caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE)) + { + return false; + } + } + + if (!beforeCases) { + // prevCase is the previous JSOP_CASE's bytecode offset. + if (!setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset)) + return false; + } + if (!caseValue) { + // This is the default clause. + continue; + } + + if (!newSrcNote2(SRC_NEXTCASE, 0, &caseNoteIndex)) + return false; + + // The case clauses are produced before any of the case body. The + // JumpList is saved on the parsed tree, then later restored and + // patched when generating the cases body. + JumpList caseJump; + if (!emitJump(JSOP_CASE, &caseJump)) + return false; + caseNode->setOffset(caseJump.offset); + lastCaseOffset = caseJump.offset; + + if (beforeCases) { + // Switch note's second offset is to first JSOP_CASE. + unsigned noteCount = notes().length(); + if (!setSrcNoteOffset(noteIndex, 1, lastCaseOffset - top)) + return false; + unsigned noteCountDelta = notes().length() - noteCount; + if (noteCountDelta != 0) + caseNoteIndex += noteCountDelta; + beforeCases = false; + } + } + + // If we didn't have an explicit default (which could fall in between + // cases, preventing us from fusing this setSrcNoteOffset with the call + // in the loop above), link the last case to the implicit default for + // the benefit of IonBuilder. + if (!hasDefault && + !beforeCases && + !setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset)) + { + return false; + } + + // Emit default even if no explicit default statement. + if (!emitJump(JSOP_DEFAULT, &condSwitchDefaultOff)) + return false; + } else { + MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); + + // skip default offset. + jsbytecode* pc = code(top + JUMP_OFFSET_LEN); + + // Fill in switch bounds, which we know fit in 16-bit offsets. + SET_JUMP_OFFSET(pc, low); + pc += JUMP_OFFSET_LEN; + SET_JUMP_OFFSET(pc, high); + pc += JUMP_OFFSET_LEN; + + if (tableLength != 0) { + if (!table.growBy(tableLength)) + return false; + + for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { + if (ParseNode* caseValue = caseNode->caseExpression()) { + MOZ_ASSERT(caseValue->isKind(PNK_NUMBER)); + + int32_t i = int32_t(caseValue->pn_dval); + MOZ_ASSERT(double(i) == caseValue->pn_dval); + + i -= low; + MOZ_ASSERT(uint32_t(i) < tableLength); + MOZ_ASSERT(!table[i]); + table[i] = caseNode; + } + } + } + } + + JumpTarget defaultOffset{ -1 }; + + // Emit code for each case's statements. + for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { + if (switchOp == JSOP_CONDSWITCH && !caseNode->isDefault()) { + // The case offset got saved in the caseNode structure after + // emitting the JSOP_CASE jump instruction above. + JumpList caseCond; + caseCond.offset = caseNode->offset(); + if (!emitJumpTargetAndPatch(caseCond)) + return false; + } + + JumpTarget here; + if (!emitJumpTarget(&here)) + return false; + if (caseNode->isDefault()) + defaultOffset = here; + + // If this is emitted as a TABLESWITCH, we'll need to know this case's + // offset later when emitting the table. Store it in the node's + // pn_offset (giving the field a different meaning vs. how we used it + // on the immediately preceding line of code). + caseNode->setOffset(here.offset); + + TDZCheckCache tdzCache(this); + + if (!emitTree(caseNode->statementList())) + return false; + } + + if (!hasDefault) { + // If no default case, offset for default is to end of switch. + if (!emitJumpTarget(&defaultOffset)) + return false; + } + MOZ_ASSERT(defaultOffset.offset != -1); + + // Set the default offset (to end of switch if no default). + jsbytecode* pc; + if (switchOp == JSOP_CONDSWITCH) { + pc = nullptr; + patchJumpsToTarget(condSwitchDefaultOff, defaultOffset); + } else { + MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); + pc = code(top); + SET_JUMP_OFFSET(pc, defaultOffset.offset - top); + pc += JUMP_OFFSET_LEN; + } + + // Set the SRC_SWITCH note's offset operand to tell end of switch. + if (!setSrcNoteOffset(noteIndex, 0, lastNonJumpTargetOffset() - top)) + return false; + + if (switchOp == JSOP_TABLESWITCH) { + // Skip over the already-initialized switch bounds. + pc += 2 * JUMP_OFFSET_LEN; + + // Fill in the jump table, if there is one. + for (uint32_t i = 0; i < tableLength; i++) { + CaseClause* caseNode = table[i]; + ptrdiff_t off = caseNode ? caseNode->offset() - top : 0; + SET_JUMP_OFFSET(pc, off); + pc += JUMP_OFFSET_LEN; + } + } + + // Patch breaks before leaving the scope, as all breaks are under the + // lexical scope if it exists. + if (!controlInfo.patchBreaks(this)) + return false; + + if (emitterScope && !emitterScope->leave(this)) + return false; + + return true; +} + +bool +BytecodeEmitter::isRunOnceLambda() +{ + // The run once lambda flags set by the parser are approximate, and we look + // at properties of the function itself before deciding to emit a function + // as a run once lambda. + + if (!(parent && parent->emittingRunOnceLambda) && + (emitterMode != LazyFunction || !lazyScript->treatAsRunOnce())) + { + return false; + } + + FunctionBox* funbox = sc->asFunctionBox(); + return !funbox->argumentsHasLocalBinding() && + !funbox->isGenerator() && + !funbox->function()->name(); +} + +bool +BytecodeEmitter::emitYieldOp(JSOp op) +{ + if (op == JSOP_FINALYIELDRVAL) + return emit1(JSOP_FINALYIELDRVAL); + + MOZ_ASSERT(op == JSOP_INITIALYIELD || op == JSOP_YIELD); + + ptrdiff_t off; + if (!emitN(op, 3, &off)) + return false; + + uint32_t yieldIndex = yieldOffsetList.length(); + if (yieldIndex >= JS_BIT(24)) { + reportError(nullptr, JSMSG_TOO_MANY_YIELDS); + return false; + } + + SET_UINT24(code(off), yieldIndex); + + if (!yieldOffsetList.append(offset())) + return false; + + return emit1(JSOP_DEBUGAFTERYIELD); +} + +bool +BytecodeEmitter::emitSetThis(ParseNode* pn) +{ + // PNK_SETTHIS is used to update |this| after a super() call in a derived + // class constructor. + + MOZ_ASSERT(pn->isKind(PNK_SETTHIS)); + MOZ_ASSERT(pn->pn_left->isKind(PNK_NAME)); + + RootedAtom name(cx, pn->pn_left->name()); + auto emitRhs = [&name, pn](BytecodeEmitter* bce, const NameLocation&, bool) { + // Emit the new |this| value. + if (!bce->emitTree(pn->pn_right)) + return false; + // Get the original |this| and throw if we already initialized + // it. Do *not* use the NameLocation argument, as that's the special + // lexical location below to deal with super() semantics. + if (!bce->emitGetName(name)) + return false; + if (!bce->emit1(JSOP_CHECKTHISREINIT)) + return false; + if (!bce->emit1(JSOP_POP)) + return false; + return true; + }; + + // The 'this' binding is not lexical, but due to super() semantics this + // initialization needs to be treated as a lexical one. + NameLocation loc = lookupName(name); + NameLocation lexicalLoc; + if (loc.kind() == NameLocation::Kind::FrameSlot) { + lexicalLoc = NameLocation::FrameSlot(BindingKind::Let, loc.frameSlot()); + } else if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) { + EnvironmentCoordinate coord = loc.environmentCoordinate(); + uint8_t hops = AssertedCast<uint8_t>(coord.hops()); + lexicalLoc = NameLocation::EnvironmentCoordinate(BindingKind::Let, hops, coord.slot()); + } else { + MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic); + lexicalLoc = loc; + } + + return emitSetOrInitializeNameAtLocation(name, lexicalLoc, emitRhs, true); +} + +bool +BytecodeEmitter::emitScript(ParseNode* body) +{ + TDZCheckCache tdzCache(this); + EmitterScope emitterScope(this); + if (sc->isGlobalContext()) { + switchToPrologue(); + if (!emitterScope.enterGlobal(this, sc->asGlobalContext())) + return false; + switchToMain(); + } else if (sc->isEvalContext()) { + switchToPrologue(); + if (!emitterScope.enterEval(this, sc->asEvalContext())) + return false; + switchToMain(); + } else { + MOZ_ASSERT(sc->isModuleContext()); + if (!emitterScope.enterModule(this, sc->asModuleContext())) + return false; + } + + setFunctionBodyEndPos(body->pn_pos); + + if (sc->isEvalContext() && !sc->strict() && + body->isKind(PNK_LEXICALSCOPE) && !body->isEmptyScope()) + { + // Sloppy eval scripts may need to emit DEFFUNs in the prologue. If there is + // an immediately enclosed lexical scope, we need to enter the lexical + // scope in the prologue for the DEFFUNs to pick up the right + // environment chain. + EmitterScope lexicalEmitterScope(this); + + switchToPrologue(); + if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical, body->scopeBindings())) + return false; + switchToMain(); + + if (!emitLexicalScopeBody(body->scopeBody())) + return false; + + if (!lexicalEmitterScope.leave(this)) + return false; + } else { + if (!emitTree(body)) + return false; + } + + if (!emit1(JSOP_RETRVAL)) + return false; + + if (!emitterScope.leave(this)) + return false; + + if (!JSScript::fullyInitFromEmitter(cx, script, this)) + return false; + + // URL and source map information must be set before firing + // Debugger::onNewScript. + if (!maybeSetDisplayURL() || !maybeSetSourceMap()) + return false; + + tellDebuggerAboutCompiledScript(cx); + + return true; +} + +bool +BytecodeEmitter::emitFunctionScript(ParseNode* body) +{ + FunctionBox* funbox = sc->asFunctionBox(); + + // The ordering of these EmitterScopes is important. The named lambda + // scope needs to enclose the function scope needs to enclose the extra + // var scope. + + Maybe<EmitterScope> namedLambdaEmitterScope; + if (funbox->namedLambdaBindings()) { + namedLambdaEmitterScope.emplace(this); + if (!namedLambdaEmitterScope->enterNamedLambda(this, funbox)) + return false; + } + + /* + * Emit a prologue for run-once scripts which will deoptimize JIT code + * if the script ends up running multiple times via foo.caller related + * shenanigans. + * + * Also mark the script so that initializers created within it may be + * given more precise types. + */ + if (isRunOnceLambda()) { + script->setTreatAsRunOnce(); + MOZ_ASSERT(!script->hasRunOnce()); + + switchToPrologue(); + if (!emit1(JSOP_RUNONCE)) + return false; + switchToMain(); + } + + setFunctionBodyEndPos(body->pn_pos); + if (!emitTree(body)) + return false; + + if (!updateSourceCoordNotes(body->pn_pos.end)) + return false; + + // Always end the script with a JSOP_RETRVAL. Some other parts of the + // codebase depend on this opcode, + // e.g. InterpreterRegs::setToEndOfScript. + if (!emit1(JSOP_RETRVAL)) + return false; + + if (namedLambdaEmitterScope) { + if (!namedLambdaEmitterScope->leave(this)) + return false; + namedLambdaEmitterScope.reset(); + } + + if (!JSScript::fullyInitFromEmitter(cx, script, this)) + return false; + + // URL and source map information must be set before firing + // Debugger::onNewScript. Only top-level functions need this, as compiling + // the outer scripts of nested functions already processed the source. + if (emitterMode != LazyFunction && !parent) { + if (!maybeSetDisplayURL() || !maybeSetSourceMap()) + return false; + + tellDebuggerAboutCompiledScript(cx); + } + + return true; +} + +template <typename NameEmitter> +bool +BytecodeEmitter::emitDestructuringDeclsWithEmitter(ParseNode* pattern, NameEmitter emitName) +{ + if (pattern->isKind(PNK_ARRAY)) { + for (ParseNode* element = pattern->pn_head; element; element = element->pn_next) { + if (element->isKind(PNK_ELISION)) + continue; + ParseNode* target = element; + if (element->isKind(PNK_SPREAD)) { + target = element->pn_kid; + } + if (target->isKind(PNK_ASSIGN)) + target = target->pn_left; + if (target->isKind(PNK_NAME)) { + if (!emitName(this, target)) + return false; + } else { + if (!emitDestructuringDeclsWithEmitter(target, emitName)) + return false; + } + } + return true; + } + + MOZ_ASSERT(pattern->isKind(PNK_OBJECT)); + for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { + MOZ_ASSERT(member->isKind(PNK_MUTATEPROTO) || + member->isKind(PNK_COLON) || + member->isKind(PNK_SHORTHAND)); + + ParseNode* target = member->isKind(PNK_MUTATEPROTO) ? member->pn_kid : member->pn_right; + + if (target->isKind(PNK_ASSIGN)) + target = target->pn_left; + if (target->isKind(PNK_NAME)) { + if (!emitName(this, target)) + return false; + } else { + if (!emitDestructuringDeclsWithEmitter(target, emitName)) + return false; + } + } + return true; +} + +bool +BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav) +{ + // Now emit the lvalue opcode sequence. If the lvalue is a nested + // destructuring initialiser-form, call ourselves to handle it, then pop + // the matched value. Otherwise emit an lvalue bytecode sequence followed + // by an assignment op. + if (target->isKind(PNK_SPREAD)) + target = target->pn_kid; + else if (target->isKind(PNK_ASSIGN)) + target = target->pn_left; + if (target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) { + if (!emitDestructuringOps(target, flav)) + return false; + // Per its post-condition, emitDestructuringOps has left the + // to-be-destructured value on top of the stack. + if (!emit1(JSOP_POP)) + return false; + } else { + switch (target->getKind()) { + case PNK_NAME: { + auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&, + bool emittedBindOp) + { + if (emittedBindOp) { + // This is like ordinary assignment, but with one + // difference. + // + // In `a = b`, we first determine a binding for `a` (using + // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`, + // then a JSOP_SETNAME instruction. + // + // In `[a] = [b]`, per spec, `b` is evaluated first, then + // we determine a binding for `a`. Then we need to do + // assignment-- but the operands are on the stack in the + // wrong order for JSOP_SETPROP, so we have to add a + // JSOP_SWAP. + // + // In the cases where we are emitting a name op, emit a + // swap because of this. + return bce->emit1(JSOP_SWAP); + } + + // In cases of emitting a frame slot or environment slot, + // nothing needs be done. + return true; + }; + + RootedAtom name(cx, target->name()); + switch (flav) { + case DestructuringDeclaration: + if (!emitInitializeName(name, emitSwapScopeAndRhs)) + return false; + break; + + case DestructuringFormalParameterInVarScope: { + // If there's an parameter expression var scope, the + // destructuring declaration needs to initialize the name in + // the function scope. The innermost scope is the var scope, + // and its enclosing scope is the function scope. + EmitterScope* funScope = innermostEmitterScope->enclosingInFrame(); + NameLocation paramLoc = *locationOfNameBoundInScope(name, funScope); + if (!emitSetOrInitializeNameAtLocation(name, paramLoc, emitSwapScopeAndRhs, true)) + return false; + break; + } + + case DestructuringAssignment: + if (!emitSetName(name, emitSwapScopeAndRhs)) + return false; + break; + } + + break; + } + + case PNK_DOT: { + // See the (PNK_NAME, JSOP_SETNAME) case above. + // + // In `a.x = b`, `a` is evaluated first, then `b`, then a + // JSOP_SETPROP instruction. + // + // In `[a.x] = [b]`, per spec, `b` is evaluated before `a`. Then we + // need a property set -- but the operands are on the stack in the + // wrong order for JSOP_SETPROP, so we have to add a JSOP_SWAP. + JSOp setOp; + if (target->as<PropertyAccess>().isSuper()) { + if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression())) + return false; + if (!emit2(JSOP_PICK, 2)) + return false; + setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER; + } else { + if (!emitTree(target->pn_expr)) + return false; + if (!emit1(JSOP_SWAP)) + return false; + setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; + } + if (!emitAtomOp(target, setOp)) + return false; + break; + } + + case PNK_ELEM: { + // See the comment at `case PNK_DOT:` above. This case, + // `[a[x]] = [b]`, is handled much the same way. The JSOP_SWAP + // is emitted by emitElemOperands. + if (target->as<PropertyByValue>().isSuper()) { + JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER; + if (!emitSuperElemOp(target, setOp)) + return false; + } else { + JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; + if (!emitElemOp(target, setOp)) + return false; + } + break; + } + + case PNK_CALL: + MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget " + "rejects function calls as assignment " + "targets in destructuring assignments"); + break; + + default: + MOZ_CRASH("emitDestructuringLHS: bad lhs kind"); + } + + // Pop the assigned value. + if (!emit1(JSOP_POP)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitConditionallyExecutedDestructuringLHS(ParseNode* target, DestructuringFlavor flav) +{ + TDZCheckCache tdzCache(this); + return emitDestructuringLHS(target, flav); +} + +bool +BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) +{ + MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, + ".next() iteration is prohibited in self-hosted code because it " + "can run user-modifiable iteration code"); + + if (!emit1(JSOP_DUP)) // ... ITER ITER + return false; + if (!emitAtomOp(cx->names().next, JSOP_CALLPROP)) // ... ITER NEXT + return false; + if (!emit1(JSOP_SWAP)) // ... NEXT ITER + return false; + if (!emitCall(JSOP_CALL, 0, pn)) // ... RESULT + return false; + if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ... RESULT + return false; + checkTypeSet(JSOP_CALL); + return true; +} + +bool +BytecodeEmitter::emitDefault(ParseNode* defaultExpr) +{ + if (!emit1(JSOP_DUP)) // VALUE VALUE + return false; + if (!emit1(JSOP_UNDEFINED)) // VALUE VALUE UNDEFINED + return false; + if (!emit1(JSOP_STRICTEQ)) // VALUE EQL? + return false; + // Emit source note to enable ion compilation. + if (!newSrcNote(SRC_IF)) + return false; + JumpList jump; + if (!emitJump(JSOP_IFEQ, &jump)) // VALUE + return false; + if (!emit1(JSOP_POP)) // . + return false; + if (!emitConditionallyExecutedTree(defaultExpr)) // DEFAULTVALUE + return false; + if (!emitJumpTargetAndPatch(jump)) + return false; + return true; +} + +class MOZ_STACK_CLASS IfThenElseEmitter +{ + BytecodeEmitter* bce_; + JumpList jumpAroundThen_; + JumpList jumpsAroundElse_; + unsigned noteIndex_; + int32_t thenDepth_; +#ifdef DEBUG + int32_t pushed_; + bool calculatedPushed_; +#endif + enum State { + Start, + If, + Cond, + IfElse, + Else, + End + }; + State state_; + + public: + explicit IfThenElseEmitter(BytecodeEmitter* bce) + : bce_(bce), + noteIndex_(-1), + thenDepth_(0), +#ifdef DEBUG + pushed_(0), + calculatedPushed_(false), +#endif + state_(Start) + {} + + ~IfThenElseEmitter() + {} + + private: + bool emitIf(State nextState) { + MOZ_ASSERT(state_ == Start || state_ == Else); + MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond); + + // Clear jumpAroundThen_ offset that points previous JSOP_IFEQ. + if (state_ == Else) + jumpAroundThen_ = JumpList(); + + // Emit an annotated branch-if-false around the then part. + SrcNoteType type = nextState == If ? SRC_IF : nextState == IfElse ? SRC_IF_ELSE : SRC_COND; + if (!bce_->newSrcNote(type, ¬eIndex_)) + return false; + if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_)) + return false; + + // To restore stack depth in else part, save depth of the then part. +#ifdef DEBUG + // If DEBUG, this is also necessary to calculate |pushed_|. + thenDepth_ = bce_->stackDepth; +#else + if (nextState == IfElse || nextState == Cond) + thenDepth_ = bce_->stackDepth; +#endif + state_ = nextState; + return true; + } + + public: + bool emitIf() { + return emitIf(If); + } + + bool emitCond() { + return emitIf(Cond); + } + + bool emitIfElse() { + return emitIf(IfElse); + } + + bool emitElse() { + MOZ_ASSERT(state_ == IfElse || state_ == Cond); + + calculateOrCheckPushed(); + + // Emit a jump from the end of our then part around the else part. The + // patchJumpsToTarget call at the bottom of this function will fix up + // the offset with jumpsAroundElse value. + if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_)) + return false; + + // Ensure the branch-if-false comes here, then emit the else. + if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) + return false; + + // Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to + // jump, for IonMonkey's benefit. We can't just "back up" from the pc + // of the else clause, because we don't know whether an extended + // jump was required to leap from the end of the then clause over + // the else clause. + if (!bce_->setSrcNoteOffset(noteIndex_, 0, + jumpsAroundElse_.offset - jumpAroundThen_.offset)) + { + return false; + } + + // Restore stack depth of the then part. + bce_->stackDepth = thenDepth_; + state_ = Else; + return true; + } + + bool emitEnd() { + MOZ_ASSERT(state_ == If || state_ == Else); + + calculateOrCheckPushed(); + + if (state_ == If) { + // No else part, fixup the branch-if-false to come here. + if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) + return false; + } + + // Patch all the jumps around else parts. + if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) + return false; + + state_ = End; + return true; + } + + void calculateOrCheckPushed() { +#ifdef DEBUG + if (!calculatedPushed_) { + pushed_ = bce_->stackDepth - thenDepth_; + calculatedPushed_ = true; + } else { + MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_); + } +#endif + } + +#ifdef DEBUG + int32_t pushed() const { + return pushed_; + } + + int32_t popped() const { + return -pushed_; + } +#endif +}; + +bool +BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlavor flav) +{ + MOZ_ASSERT(pattern->isKind(PNK_ARRAY)); + MOZ_ASSERT(pattern->isArity(PN_LIST)); + MOZ_ASSERT(this->stackDepth != 0); + + // Here's pseudo code for |let [a, b, , c=y, ...d] = x;| + // + // let x, y; + // let a, b, c, d; + // let tmp, done, iter, result; // stack values + // + // iter = x[Symbol.iterator](); + // + // // ==== emitted by loop for a ==== + // result = iter.next(); + // done = result.done; + // + // if (done) { + // a = undefined; + // + // result = undefined; + // done = true; + // } else { + // a = result.value; + // + // // Do next element's .next() and .done access here + // result = iter.next(); + // done = result.done; + // } + // + // // ==== emitted by loop for b ==== + // if (done) { + // b = undefined; + // + // result = undefined; + // done = true; + // } else { + // b = result.value; + // + // result = iter.next(); + // done = result.done; + // } + // + // // ==== emitted by loop for elision ==== + // if (done) { + // result = undefined + // done = true + // } else { + // result.value; + // + // result = iter.next(); + // done = result.done; + // } + // + // // ==== emitted by loop for c ==== + // if (done) { + // c = y; + // } else { + // tmp = result.value; + // if (tmp === undefined) + // tmp = y; + // c = tmp; + // + // // Don't do next element's .next() and .done access if + // // this is the last non-spread element. + // } + // + // // ==== emitted by loop for d ==== + // if (done) { + // // Assing empty array when completed + // d = []; + // } else { + // d = [...iter]; + // } + + /* + * Use an iterator to destructure the RHS, instead of index lookup. We + * must leave the *original* value on the stack. + */ + if (!emit1(JSOP_DUP)) // ... OBJ OBJ + return false; + if (!emitIterator()) // ... OBJ? ITER + return false; + bool needToPopIterator = true; + + for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { + bool isHead = member == pattern->pn_head; + if (member->isKind(PNK_SPREAD)) { + IfThenElseEmitter ifThenElse(this); + if (!isHead) { + // If spread is not the first element of the pattern, + // iterator can already be completed. + if (!ifThenElse.emitIfElse()) // ... OBJ? ITER + return false; + + if (!emit1(JSOP_POP)) // ... OBJ? + return false; + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ARRAY + return false; + if (!emitConditionallyExecutedDestructuringLHS(member, flav)) // ... OBJ? + return false; + + if (!ifThenElse.emitElse()) // ... OBJ? ITER + return false; + } + + // If iterator is not completed, create a new array with the rest + // of the iterator. + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ITER ARRAY + return false; + if (!emitNumberOp(0)) // ... OBJ? ITER ARRAY INDEX + return false; + if (!emitSpread()) // ... OBJ? ARRAY INDEX + return false; + if (!emit1(JSOP_POP)) // ... OBJ? ARRAY + return false; + if (!emitConditionallyExecutedDestructuringLHS(member, flav)) // ... OBJ? + return false; + + if (!isHead) { + if (!ifThenElse.emitEnd()) + return false; + MOZ_ASSERT(ifThenElse.popped() == 1); + } + needToPopIterator = false; + MOZ_ASSERT(!member->pn_next); + break; + } + + ParseNode* pndefault = nullptr; + ParseNode* subpattern = member; + if (subpattern->isKind(PNK_ASSIGN)) { + pndefault = subpattern->pn_right; + subpattern = subpattern->pn_left; + } + + bool isElision = subpattern->isKind(PNK_ELISION); + bool hasNextNonSpread = member->pn_next && !member->pn_next->isKind(PNK_SPREAD); + bool hasNextSpread = member->pn_next && member->pn_next->isKind(PNK_SPREAD); + + MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD)); + + auto emitNext = [pattern](ExclusiveContext* cx, BytecodeEmitter* bce) { + if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER ITER + return false; + if (!bce->emitIteratorNext(pattern)) // ... OBJ? ITER RESULT + return false; + if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER RESULT RESULT + return false; + if (!bce->emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ? ITER RESULT DONE? + return false; + return true; + }; + + if (isHead) { + if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE? + return false; + } + + IfThenElseEmitter ifThenElse(this); + if (!ifThenElse.emitIfElse()) // ... OBJ? ITER RESULT + return false; + + if (!emit1(JSOP_POP)) // ... OBJ? ITER + return false; + if (pndefault) { + // Emit only pndefault tree here, as undefined check in emitDefault + // should always be true. + if (!emitConditionallyExecutedTree(pndefault)) // ... OBJ? ITER VALUE + return false; + } else { + if (!isElision) { + if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER UNDEFINED + return false; + if (!emit1(JSOP_NOP_DESTRUCTURING)) + return false; + } + } + if (!isElision) { + if (!emitConditionallyExecutedDestructuringLHS(subpattern, flav)) // ... OBJ? ITER + return false; + } else if (pndefault) { + if (!emit1(JSOP_POP)) // ... OBJ? ITER + return false; + } + + // Setup next element's result when the iterator is done. + if (hasNextNonSpread) { + if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER RESULT + return false; + if (!emit1(JSOP_NOP_DESTRUCTURING)) + return false; + if (!emit1(JSOP_TRUE)) // ... OBJ? ITER RESULT DONE? + return false; + } else if (hasNextSpread) { + if (!emit1(JSOP_TRUE)) // ... OBJ? ITER DONE? + return false; + } + + if (!ifThenElse.emitElse()) // ... OBJ? ITER RESULT + return false; + + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ? ITER VALUE + return false; + + if (pndefault) { + if (!emitDefault(pndefault)) // ... OBJ? ITER VALUE + return false; + } + + if (!isElision) { + if (!emitConditionallyExecutedDestructuringLHS(subpattern, flav)) // ... OBJ? ITER + return false; + } else { + if (!emit1(JSOP_POP)) // ... OBJ? ITER + return false; + } + + // Setup next element's result when the iterator is not done. + if (hasNextNonSpread) { + if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE? + return false; + } else if (hasNextSpread) { + if (!emit1(JSOP_FALSE)) // ... OBJ? ITER DONE? + return false; + } + + if (!ifThenElse.emitEnd()) + return false; + if (hasNextNonSpread) + MOZ_ASSERT(ifThenElse.pushed() == 1); + else if (hasNextSpread) + MOZ_ASSERT(ifThenElse.pushed() == 0); + else + MOZ_ASSERT(ifThenElse.popped() == 1); + } + + if (needToPopIterator) { + if (!emit1(JSOP_POP)) // ... OBJ? + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitComputedPropertyName(ParseNode* computedPropName) +{ + MOZ_ASSERT(computedPropName->isKind(PNK_COMPUTED_NAME)); + return emitTree(computedPropName->pn_kid) && emit1(JSOP_TOID); +} + +bool +BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFlavor flav) +{ + MOZ_ASSERT(pattern->isKind(PNK_OBJECT)); + MOZ_ASSERT(pattern->isArity(PN_LIST)); + + MOZ_ASSERT(this->stackDepth > 0); // ... RHS + + if (!emitRequireObjectCoercible()) // ... RHS + return false; + + for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { + // Duplicate the value being destructured to use as a reference base. + if (!emit1(JSOP_DUP)) // ... RHS RHS + return false; + + // Now push the property name currently being matched, which is the + // current property name "label" on the left of a colon in the object + // initialiser. + bool needsGetElem = true; + + ParseNode* subpattern; + if (member->isKind(PNK_MUTATEPROTO)) { + if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS PROP + return false; + needsGetElem = false; + subpattern = member->pn_kid; + } else { + MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND)); + + ParseNode* key = member->pn_left; + if (key->isKind(PNK_NUMBER)) { + if (!emitNumberOp(key->pn_dval)) // ... RHS RHS KEY + return false; + } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { + PropertyName* name = key->pn_atom->asPropertyName(); + + // The parser already checked for atoms representing indexes and + // used PNK_NUMBER instead, but also watch for ids which TI treats + // as indexes for simplification of downstream analysis. + jsid id = NameToId(name); + if (id != IdToTypeId(id)) { + if (!emitTree(key)) // ... RHS RHS KEY + return false; + } else { + if (!emitAtomOp(name, JSOP_GETPROP)) // ...RHS PROP + return false; + needsGetElem = false; + } + } else { + if (!emitComputedPropertyName(key)) // ... RHS RHS KEY + return false; + } + + subpattern = member->pn_right; + } + + // Get the property value if not done already. + if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS PROP + return false; + + if (subpattern->isKind(PNK_ASSIGN)) { + if (!emitDefault(subpattern->pn_right)) + return false; + subpattern = subpattern->pn_left; + } + + // Destructure PROP per this member's subpattern. + if (!emitDestructuringLHS(subpattern, flav)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitDestructuringOps(ParseNode* pattern, DestructuringFlavor flav) +{ + if (pattern->isKind(PNK_ARRAY)) + return emitDestructuringOpsArray(pattern, flav); + return emitDestructuringOpsObject(pattern, flav); +} + +bool +BytecodeEmitter::emitTemplateString(ParseNode* pn) +{ + MOZ_ASSERT(pn->isArity(PN_LIST)); + + bool pushedString = false; + + for (ParseNode* pn2 = pn->pn_head; pn2 != NULL; pn2 = pn2->pn_next) { + bool isString = (pn2->getKind() == PNK_STRING || pn2->getKind() == PNK_TEMPLATE_STRING); + + // Skip empty strings. These are very common: a template string like + // `${a}${b}` has three empty strings and without this optimization + // we'd emit four JSOP_ADD operations instead of just one. + if (isString && pn2->pn_atom->empty()) + continue; + + if (!isString) { + // We update source notes before emitting the expression + if (!updateSourceCoordNotes(pn2->pn_pos.begin)) + return false; + } + + if (!emitTree(pn2)) + return false; + + if (!isString) { + // We need to convert the expression to a string + if (!emit1(JSOP_TOSTRING)) + return false; + } + + if (pushedString) { + // We've pushed two strings onto the stack. Add them together, leaving just one. + if (!emit1(JSOP_ADD)) + return false; + } else { + pushedString = true; + } + } + + if (!pushedString) { + // All strings were empty, this can happen for something like `${""}`. + // Just push an empty string. + if (!emitAtomOp(cx->names().empty, JSOP_STRING)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitDeclarationList(ParseNode* declList) +{ + MOZ_ASSERT(declList->isArity(PN_LIST)); + + ParseNode* next; + for (ParseNode* decl = declList->pn_head; decl; decl = next) { + if (!updateSourceCoordNotes(decl->pn_pos.begin)) + return false; + next = decl->pn_next; + + if (decl->isKind(PNK_ASSIGN)) { + MOZ_ASSERT(decl->isOp(JSOP_NOP)); + + ParseNode* pattern = decl->pn_left; + MOZ_ASSERT(pattern->isKind(PNK_ARRAY) || pattern->isKind(PNK_OBJECT)); + + if (!emitTree(decl->pn_right)) + return false; + + if (!emitDestructuringOps(pattern, DestructuringDeclaration)) + return false; + + if (!emit1(JSOP_POP)) + return false; + } else { + if (!emitSingleDeclaration(declList, decl, decl->expr())) + return false; + } + } + return true; +} + +bool +BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl, + ParseNode* initializer) +{ + MOZ_ASSERT(decl->isKind(PNK_NAME)); + + // Nothing to do for initializer-less 'var' declarations, as there's no TDZ. + if (!initializer && declList->isKind(PNK_VAR)) + return true; + + auto emitRhs = [initializer, declList](BytecodeEmitter* bce, const NameLocation&, bool) { + if (!initializer) { + // Lexical declarations are initialized to undefined without an + // initializer. + MOZ_ASSERT(declList->isKind(PNK_LET), + "var declarations without initializers handled above, " + "and const declarations must have initializers"); + return bce->emit1(JSOP_UNDEFINED); + } + + MOZ_ASSERT(initializer); + return bce->emitTree(initializer); + }; + + if (!emitInitializeName(decl, emitRhs)) + return false; + + // Pop the RHS. + return emit1(JSOP_POP); +} + +static bool +EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset) +{ + // If there is a RHS tree, emit the tree. + if (rhs) + return bce->emitTree(rhs); + + // Otherwise the RHS value to assign is already on the stack, i.e., the + // next enumeration value in a for-in or for-of loop. Depending on how + // many other values have been pushed on the stack, we need to get the + // already-pushed RHS value. + if (offset != 1 && !bce->emit2(JSOP_PICK, offset - 1)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs) +{ + // Name assignments are handled separately because choosing ops and when + // to emit BINDNAME is involved and should avoid duplication. + if (lhs->isKind(PNK_NAME)) { + auto emitRhs = [op, lhs, rhs](BytecodeEmitter* bce, const NameLocation& lhsLoc, + bool emittedBindOp) + { + // For compound assignments, first get the LHS value, then emit + // the RHS and the op. + if (op != JSOP_NOP) { + if (lhsLoc.kind() == NameLocation::Kind::Dynamic) { + // For dynamic accesses we can do better than a GETNAME + // since the assignment already emitted a BINDNAME on the + // top of the stack. As an optimization, use that to get + // the name. + if (!bce->emit1(JSOP_DUP)) + return false; + if (!bce->emitAtomOp(lhs, JSOP_GETXPROP)) + return false; + } else { + if (!bce->emitGetNameAtLocation(lhs->name(), lhsLoc)) + return false; + } + } + + // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on + // the top of the stack and we need to pick the right RHS value. + if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1)) + return false; + + // Emit the compound assignment op if there is one. + if (op != JSOP_NOP && !bce->emit1(op)) + return false; + + return true; + }; + + return emitSetName(lhs, emitRhs); + } + + // Deal with non-name assignments. + uint32_t atomIndex = (uint32_t) -1; + uint8_t offset = 1; + + switch (lhs->getKind()) { + case PNK_DOT: + if (lhs->as<PropertyAccess>().isSuper()) { + if (!emitSuperPropLHS(&lhs->as<PropertyAccess>().expression())) + return false; + offset += 2; + } else { + if (!emitTree(lhs->expr())) + return false; + offset += 1; + } + if (!makeAtomIndex(lhs->pn_atom, &atomIndex)) + return false; + break; + case PNK_ELEM: { + MOZ_ASSERT(lhs->isArity(PN_BINARY)); + EmitElemOption opt = op == JSOP_NOP ? EmitElemOption::Get : EmitElemOption::CompoundAssign; + if (lhs->as<PropertyByValue>().isSuper()) { + if (!emitSuperElemOperands(lhs, opt)) + return false; + offset += 3; + } else { + if (!emitElemOperands(lhs, opt)) + return false; + offset += 2; + } + break; + } + case PNK_ARRAY: + case PNK_OBJECT: + break; + case PNK_CALL: + if (!emitTree(lhs)) + return false; + + // Assignment to function calls is forbidden, but we have to make the + // call first. Now we can throw. + if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS)) + return false; + + // Rebalance the stack to placate stack-depth assertions. + if (!emit1(JSOP_POP)) + return false; + break; + default: + MOZ_ASSERT(0); + } + + if (op != JSOP_NOP) { + MOZ_ASSERT(rhs); + switch (lhs->getKind()) { + case PNK_DOT: { + JSOp getOp; + if (lhs->as<PropertyAccess>().isSuper()) { + if (!emit1(JSOP_DUP2)) + return false; + getOp = JSOP_GETPROP_SUPER; + } else { + if (!emit1(JSOP_DUP)) + return false; + bool isLength = (lhs->pn_atom == cx->names().length); + getOp = isLength ? JSOP_LENGTH : JSOP_GETPROP; + } + if (!emitIndex32(getOp, atomIndex)) + return false; + break; + } + case PNK_ELEM: { + JSOp elemOp; + if (lhs->as<PropertyByValue>().isSuper()) { + if (!emitDupAt(2)) + return false; + if (!emitDupAt(2)) + return false; + if (!emitDupAt(2)) + return false; + elemOp = JSOP_GETELEM_SUPER; + } else { + if (!emit1(JSOP_DUP2)) + return false; + elemOp = JSOP_GETELEM; + } + if (!emitElemOpBase(elemOp)) + return false; + break; + } + case PNK_CALL: + // We just emitted a JSOP_THROWMSG and popped the call's return + // value. Push a random value to make sure the stack depth is + // correct. + if (!emit1(JSOP_NULL)) + return false; + break; + default:; + } + } + + if (!EmitAssignmentRhs(this, rhs, offset)) + return false; + + /* If += etc., emit the binary operator with a source note. */ + if (op != JSOP_NOP) { + if (!newSrcNote(SRC_ASSIGNOP)) + return false; + if (!emit1(op)) + return false; + } + + /* Finally, emit the specialized assignment bytecode. */ + switch (lhs->getKind()) { + case PNK_DOT: { + JSOp setOp = lhs->as<PropertyAccess>().isSuper() ? + (sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER) : + (sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP); + if (!emitIndexOp(setOp, atomIndex)) + return false; + break; + } + case PNK_CALL: + // We threw above, so nothing to do here. + break; + case PNK_ELEM: { + JSOp setOp = lhs->as<PropertyByValue>().isSuper() ? + sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER : + sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; + if (!emit1(setOp)) + return false; + break; + } + case PNK_ARRAY: + case PNK_OBJECT: + if (!emitDestructuringOps(lhs, DestructuringAssignment)) + return false; + break; + default: + MOZ_ASSERT(0); + } + return true; +} + +bool +ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects, + MutableHandleValue vp, Value* compare, size_t ncompare, + NewObjectKind newKind) +{ + MOZ_ASSERT(newKind == TenuredObject || newKind == SingletonObject); + + switch (getKind()) { + case PNK_NUMBER: + vp.setNumber(pn_dval); + return true; + case PNK_TEMPLATE_STRING: + case PNK_STRING: + vp.setString(pn_atom); + return true; + case PNK_TRUE: + vp.setBoolean(true); + return true; + case PNK_FALSE: + vp.setBoolean(false); + return true; + case PNK_NULL: + vp.setNull(); + return true; + case PNK_CALLSITEOBJ: + case PNK_ARRAY: { + unsigned count; + ParseNode* pn; + + if (allowObjects == DontAllowObjects) { + vp.setMagic(JS_GENERIC_MAGIC); + return true; + } + + ObjectGroup::NewArrayKind arrayKind = ObjectGroup::NewArrayKind::Normal; + if (allowObjects == ForCopyOnWriteArray) { + arrayKind = ObjectGroup::NewArrayKind::CopyOnWrite; + allowObjects = DontAllowObjects; + } + + if (getKind() == PNK_CALLSITEOBJ) { + count = pn_count - 1; + pn = pn_head->pn_next; + } else { + MOZ_ASSERT(isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST)); + count = pn_count; + pn = pn_head; + } + + AutoValueVector values(cx); + if (!values.appendN(MagicValue(JS_ELEMENTS_HOLE), count)) + return false; + size_t idx; + for (idx = 0; pn; idx++, pn = pn->pn_next) { + if (!pn->getConstantValue(cx, allowObjects, values[idx], values.begin(), idx)) + return false; + if (values[idx].isMagic(JS_GENERIC_MAGIC)) { + vp.setMagic(JS_GENERIC_MAGIC); + return true; + } + } + MOZ_ASSERT(idx == count); + + JSObject* obj = ObjectGroup::newArrayObject(cx, values.begin(), values.length(), + newKind, arrayKind); + if (!obj) + return false; + + if (!CombineArrayElementTypes(cx, obj, compare, ncompare)) + return false; + + vp.setObject(*obj); + return true; + } + case PNK_OBJECT: { + MOZ_ASSERT(isOp(JSOP_NEWINIT)); + MOZ_ASSERT(!(pn_xflags & PNX_NONCONST)); + + if (allowObjects == DontAllowObjects) { + vp.setMagic(JS_GENERIC_MAGIC); + return true; + } + MOZ_ASSERT(allowObjects == AllowObjects); + + Rooted<IdValueVector> properties(cx, IdValueVector(cx)); + + RootedValue value(cx), idvalue(cx); + for (ParseNode* pn = pn_head; pn; pn = pn->pn_next) { + if (!pn->pn_right->getConstantValue(cx, allowObjects, &value)) + return false; + if (value.isMagic(JS_GENERIC_MAGIC)) { + vp.setMagic(JS_GENERIC_MAGIC); + return true; + } + + ParseNode* pnid = pn->pn_left; + if (pnid->isKind(PNK_NUMBER)) { + idvalue = NumberValue(pnid->pn_dval); + } else { + MOZ_ASSERT(pnid->isKind(PNK_OBJECT_PROPERTY_NAME) || pnid->isKind(PNK_STRING)); + MOZ_ASSERT(pnid->pn_atom != cx->names().proto); + idvalue = StringValue(pnid->pn_atom); + } + + RootedId id(cx); + if (!ValueToId<CanGC>(cx, idvalue, &id)) + return false; + + if (!properties.append(IdValuePair(id, value))) + return false; + } + + JSObject* obj = ObjectGroup::newPlainObject(cx, properties.begin(), properties.length(), + newKind); + if (!obj) + return false; + + if (!CombinePlainObjectPropertyTypes(cx, obj, compare, ncompare)) + return false; + + vp.setObject(*obj); + return true; + } + default: + MOZ_CRASH("Unexpected node"); + } + return false; +} + +bool +BytecodeEmitter::emitSingletonInitialiser(ParseNode* pn) +{ + NewObjectKind newKind = (pn->getKind() == PNK_OBJECT) ? SingletonObject : TenuredObject; + + RootedValue value(cx); + if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value, nullptr, 0, newKind)) + return false; + + MOZ_ASSERT_IF(newKind == SingletonObject, value.toObject().isSingleton()); + + ObjectBox* objbox = parser->newObjectBox(&value.toObject()); + if (!objbox) + return false; + + return emitObjectOp(objbox, JSOP_OBJECT); +} + +bool +BytecodeEmitter::emitCallSiteObject(ParseNode* pn) +{ + RootedValue value(cx); + if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value)) + return false; + + MOZ_ASSERT(value.isObject()); + + ObjectBox* objbox1 = parser->newObjectBox(&value.toObject()); + if (!objbox1) + return false; + + if (!pn->as<CallSiteNode>().getRawArrayValue(cx, &value)) + return false; + + MOZ_ASSERT(value.isObject()); + + ObjectBox* objbox2 = parser->newObjectBox(&value.toObject()); + if (!objbox2) + return false; + + return emitObjectPairOp(objbox1, objbox2, JSOP_CALLSITEOBJ); +} + +/* See the SRC_FOR source note offsetBias comments later in this file. */ +JS_STATIC_ASSERT(JSOP_NOP_LENGTH == 1); +JS_STATIC_ASSERT(JSOP_POP_LENGTH == 1); + +namespace { + +class EmitLevelManager +{ + BytecodeEmitter* bce; + public: + explicit EmitLevelManager(BytecodeEmitter* bce) : bce(bce) { bce->emitLevel++; } + ~EmitLevelManager() { bce->emitLevel--; } +}; + +} /* anonymous namespace */ + +bool +BytecodeEmitter::emitCatch(ParseNode* pn) +{ + // We must be nested under a try-finally statement. + TryFinallyControl& controlInfo = innermostNestableControl->as<TryFinallyControl>(); + + /* Pick up the pending exception and bind it to the catch variable. */ + if (!emit1(JSOP_EXCEPTION)) + return false; + + /* + * Dup the exception object if there is a guard for rethrowing to use + * it later when rethrowing or in other catches. + */ + if (pn->pn_kid2 && !emit1(JSOP_DUP)) + return false; + + ParseNode* pn2 = pn->pn_kid1; + switch (pn2->getKind()) { + case PNK_ARRAY: + case PNK_OBJECT: + if (!emitDestructuringOps(pn2, DestructuringDeclaration)) + return false; + if (!emit1(JSOP_POP)) + return false; + break; + + case PNK_NAME: + if (!emitLexicalInitialization(pn2)) + return false; + if (!emit1(JSOP_POP)) + return false; + break; + + default: + MOZ_ASSERT(0); + } + + // If there is a guard expression, emit it and arrange to jump to the next + // catch block if the guard expression is false. + if (pn->pn_kid2) { + if (!emitTree(pn->pn_kid2)) + return false; + + // If the guard expression is false, fall through, pop the block scope, + // and jump to the next catch block. Otherwise jump over that code and + // pop the dupped exception. + JumpList guardCheck; + if (!emitJump(JSOP_IFNE, &guardCheck)) + return false; + + { + NonLocalExitControl nle(this); + + // Move exception back to cx->exception to prepare for + // the next catch. + if (!emit1(JSOP_THROWING)) + return false; + + // Leave the scope for this catch block. + if (!nle.prepareForNonLocalJump(&controlInfo)) + return false; + + // Jump to the next handler added by emitTry. + if (!emitJump(JSOP_GOTO, &controlInfo.guardJump)) + return false; + } + + // Back to normal control flow. + if (!emitJumpTargetAndPatch(guardCheck)) + return false; + + // Pop duplicated exception object as we no longer need it. + if (!emit1(JSOP_POP)) + return false; + } + + /* Emit the catch body. */ + return emitTree(pn->pn_kid3); +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the +// comment on EmitSwitch. +MOZ_NEVER_INLINE bool +BytecodeEmitter::emitTry(ParseNode* pn) +{ + // Track jumps-over-catches and gosubs-to-finally for later fixup. + // + // When a finally block is active, non-local jumps (including + // jumps-over-catches) result in a GOSUB being written into the bytecode + // stream and fixed-up later. + // + TryFinallyControl controlInfo(this, pn->pn_kid3 ? StatementKind::Finally : StatementKind::Try); + + // Since an exception can be thrown at any place inside the try block, + // we need to restore the stack and the scope chain before we transfer + // the control to the exception handler. + // + // For that we store in a try note associated with the catch or + // finally block the stack depth upon the try entry. The interpreter + // uses this depth to properly unwind the stack and the scope chain. + // + int depth = stackDepth; + + // Record the try location, then emit the try block. + unsigned noteIndex; + if (!newSrcNote(SRC_TRY, ¬eIndex)) + return false; + if (!emit1(JSOP_TRY)) + return false; + + ptrdiff_t tryStart = offset(); + if (!emitTree(pn->pn_kid1)) + return false; + MOZ_ASSERT(depth == stackDepth); + + // GOSUB to finally, if present. + if (pn->pn_kid3) { + if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) + return false; + } + + // Source note points to the jump at the end of the try block. + if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart + JSOP_TRY_LENGTH)) + return false; + + // Emit jump over catch and/or finally. + JumpList catchJump; + if (!emitJump(JSOP_GOTO, &catchJump)) + return false; + + JumpTarget tryEnd; + if (!emitJumpTarget(&tryEnd)) + return false; + + // If this try has a catch block, emit it. + ParseNode* catchList = pn->pn_kid2; + if (catchList) { + MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST)); + + // The emitted code for a catch block looks like: + // + // [pushlexicalenv] only if any local aliased + // exception + // if there is a catchguard: + // dup + // setlocal 0; pop assign or possibly destructure exception + // if there is a catchguard: + // < catchguard code > + // ifne POST + // debugleaveblock + // [poplexicalenv] only if any local aliased + // throwing pop exception to cx->exception + // goto <next catch block> + // POST: pop + // < catch block contents > + // debugleaveblock + // [poplexicalenv] only if any local aliased + // goto <end of catch blocks> non-local; finally applies + // + // If there's no catch block without a catchguard, the last <next catch + // block> points to rethrow code. This code will [gosub] to the finally + // code if appropriate, and is also used for the catch-all trynote for + // capturing exceptions thrown from catch{} blocks. + // + for (ParseNode* pn3 = catchList->pn_head; pn3; pn3 = pn3->pn_next) { + MOZ_ASSERT(this->stackDepth == depth); + + // Clear the frame's return value that might have been set by the + // try block: + // + // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 + if (!emit1(JSOP_UNDEFINED)) + return false; + if (!emit1(JSOP_SETRVAL)) + return false; + + // Emit the lexical scope and catch body. + MOZ_ASSERT(pn3->isKind(PNK_LEXICALSCOPE)); + if (!emitTree(pn3)) + return false; + + // gosub <finally>, if required. + if (pn->pn_kid3) { + if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) + return false; + MOZ_ASSERT(this->stackDepth == depth); + } + + // Jump over the remaining catch blocks. This will get fixed + // up to jump to after catch/finally. + if (!emitJump(JSOP_GOTO, &catchJump)) + return false; + + // If this catch block had a guard clause, patch the guard jump to + // come here. + if (controlInfo.guardJump.offset != -1) { + if (!emitJumpTargetAndPatch(controlInfo.guardJump)) + return false; + controlInfo.guardJump.offset = -1; + + // If this catch block is the last one, rethrow, delegating + // execution of any finally block to the exception handler. + if (!pn3->pn_next) { + if (!emit1(JSOP_EXCEPTION)) + return false; + if (!emit1(JSOP_THROW)) + return false; + } + } + } + } + + MOZ_ASSERT(this->stackDepth == depth); + + // Emit the finally handler, if there is one. + JumpTarget finallyStart{ 0 }; + if (pn->pn_kid3) { + if (!emitJumpTarget(&finallyStart)) + return false; + + // Fix up the gosubs that might have been emitted before non-local + // jumps to the finally code. + patchJumpsToTarget(controlInfo.gosubs, finallyStart); + + // Indicate that we're emitting a subroutine body. + controlInfo.setEmittingSubroutine(); + if (!updateSourceCoordNotes(pn->pn_kid3->pn_pos.begin)) + return false; + if (!emit1(JSOP_FINALLY)) + return false; + if (!emit1(JSOP_GETRVAL)) + return false; + + // Clear the frame's return value to make break/continue return + // correct value even if there's no other statement before them: + // + // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 + if (!emit1(JSOP_UNDEFINED)) + return false; + if (!emit1(JSOP_SETRVAL)) + return false; + + if (!emitTree(pn->pn_kid3)) + return false; + if (!emit1(JSOP_SETRVAL)) + return false; + if (!emit1(JSOP_RETSUB)) + return false; + hasTryFinally = true; + MOZ_ASSERT(this->stackDepth == depth); + } + + // ReconstructPCStack needs a NOP here to mark the end of the last catch block. + if (!emit1(JSOP_NOP)) + return false; + + // Fix up the end-of-try/catch jumps to come here. + if (!emitJumpTargetAndPatch(catchJump)) + return false; + + // Add the try note last, to let post-order give us the right ordering + // (first to last for a given nesting level, inner to outer by level). + if (catchList && !tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd.offset)) + return false; + + // If we've got a finally, mark try+catch region with additional + // trynote to catch exceptions (re)thrown from a catch block or + // for the try{}finally{} case. + if (pn->pn_kid3 && !tryNoteList.append(JSTRY_FINALLY, depth, tryStart, finallyStart.offset)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitIf(ParseNode* pn) +{ + IfThenElseEmitter ifThenElse(this); + + if_again: + /* Emit code for the condition before pushing stmtInfo. */ + if (!emitConditionallyExecutedTree(pn->pn_kid1)) + return false; + + ParseNode* elseNode = pn->pn_kid3; + if (elseNode) { + if (!ifThenElse.emitIfElse()) + return false; + } else { + if (!ifThenElse.emitIf()) + return false; + } + + /* Emit code for the then part. */ + if (!emitConditionallyExecutedTree(pn->pn_kid2)) + return false; + + if (elseNode) { + if (!ifThenElse.emitElse()) + return false; + + if (elseNode->isKind(PNK_IF)) { + pn = elseNode; + goto if_again; + } + + /* Emit code for the else part. */ + if (!emitConditionallyExecutedTree(elseNode)) + return false; + } + + if (!ifThenElse.emitEnd()) + return false; + + return true; +} + +bool +BytecodeEmitter::emitHoistedFunctionsInList(ParseNode* list) +{ + MOZ_ASSERT(list->pn_xflags & PNX_FUNCDEFS); + + for (ParseNode* pn = list->pn_head; pn; pn = pn->pn_next) { + ParseNode* maybeFun = pn; + + if (!sc->strict()) { + while (maybeFun->isKind(PNK_LABEL)) + maybeFun = maybeFun->as<LabeledStatement>().statement(); + } + + if (maybeFun->isKind(PNK_FUNCTION) && maybeFun->functionIsHoisted()) { + if (!emitTree(maybeFun)) + return false; + } + } + + return true; +} + +bool +BytecodeEmitter::emitLexicalScopeBody(ParseNode* body, EmitLineNumberNote emitLineNote) +{ + if (body->isKind(PNK_STATEMENTLIST) && body->pn_xflags & PNX_FUNCDEFS) { + // This block contains function statements whose definitions are + // hoisted to the top of the block. Emit these as a separate pass + // before the rest of the block. + if (!emitHoistedFunctionsInList(body)) + return false; + } + + // Line notes were updated by emitLexicalScope. + return emitTree(body, emitLineNote); +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See +// the comment on emitSwitch. +MOZ_NEVER_INLINE bool +BytecodeEmitter::emitLexicalScope(ParseNode* pn) +{ + MOZ_ASSERT(pn->isKind(PNK_LEXICALSCOPE)); + + TDZCheckCache tdzCache(this); + + ParseNode* body = pn->scopeBody(); + if (pn->isEmptyScope()) + return emitLexicalScopeBody(body); + + // Update line number notes before emitting TDZ poison in + // EmitterScope::enterLexical to avoid spurious pausing on seemingly + // non-effectful lines in Debugger. + // + // For example, consider the following code. + // + // L1: { + // L2: let x = 42; + // L3: } + // + // If line number notes were not updated before the TDZ poison, the TDZ + // poison bytecode sequence of 'uninitialized; initlexical' will have line + // number L1, and the Debugger will pause there. + if (!ParseNodeRequiresSpecialLineNumberNotes(body)) { + ParseNode* pnForPos = body; + if (body->isKind(PNK_STATEMENTLIST) && body->pn_head) + pnForPos = body->pn_head; + if (!updateLineNumberNotes(pnForPos->pn_pos.begin)) + return false; + } + + EmitterScope emitterScope(this); + ScopeKind kind; + if (body->isKind(PNK_CATCH)) + kind = body->pn_kid1->isKind(PNK_NAME) ? ScopeKind::SimpleCatch : ScopeKind::Catch; + else + kind = ScopeKind::Lexical; + + if (!emitterScope.enterLexical(this, kind, pn->scopeBindings())) + return false; + + if (body->isKind(PNK_FOR)) { + // for loops need to emit {FRESHEN,RECREATE}LEXICALENV if there are + // lexical declarations in the head. Signal this by passing a + // non-nullptr lexical scope. + if (!emitFor(body, &emitterScope)) + return false; + } else { + if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) + return false; + } + + return emitterScope.leave(this); +} + +bool +BytecodeEmitter::emitWith(ParseNode* pn) +{ + if (!emitTree(pn->pn_left)) + return false; + + EmitterScope emitterScope(this); + if (!emitterScope.enterWith(this)) + return false; + + if (!emitTree(pn->pn_right)) + return false; + + return emitterScope.leave(this); +} + +bool +BytecodeEmitter::emitRequireObjectCoercible() +{ + // For simplicity, handle this in self-hosted code, at cost of 13 bytes of + // bytecode versus 1 byte for a dedicated opcode. As more places need this + // behavior, we may want to reconsider this tradeoff. + +#ifdef DEBUG + auto depth = this->stackDepth; +#endif + MOZ_ASSERT(depth > 0); // VAL + if (!emit1(JSOP_DUP)) // VAL VAL + return false; + + // Note that "intrinsic" is a misnomer: we're calling a *self-hosted* + // function that's not an intrinsic! But it nonetheless works as desired. + if (!emitAtomOp(cx->names().RequireObjectCoercible, + JSOP_GETINTRINSIC)) // VAL VAL REQUIREOBJECTCOERCIBLE + { + return false; + } + if (!emit1(JSOP_UNDEFINED)) // VAL VAL REQUIREOBJECTCOERCIBLE UNDEFINED + return false; + if (!emit2(JSOP_PICK, 2)) // VAL REQUIREOBJECTCOERCIBLE UNDEFINED VAL + return false; + if (!emitCall(JSOP_CALL, 1)) // VAL IGNORED + return false; + checkTypeSet(JSOP_CALL); + + if (!emit1(JSOP_POP)) // VAL + return false; + + MOZ_ASSERT(depth == this->stackDepth); + return true; +} + +bool +BytecodeEmitter::emitIterator() +{ + // Convert iterable to iterator. + if (!emit1(JSOP_DUP)) // OBJ OBJ + return false; + if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::iterator))) // OBJ OBJ @@ITERATOR + return false; + if (!emitElemOpBase(JSOP_CALLELEM)) // OBJ ITERFN + return false; + if (!emit1(JSOP_SWAP)) // ITERFN OBJ + return false; + if (!emitCall(JSOP_CALLITER, 0)) // ITER + return false; + checkTypeSet(JSOP_CALLITER); + if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) // ITER + return false; + return true; +} + +bool +BytecodeEmitter::emitSpread(bool allowSelfHosted) +{ + LoopControl loopInfo(this, StatementKind::Spread); + + // Jump down to the loop condition to minimize overhead assuming at least + // one iteration, as the other loop forms do. Annotate so IonMonkey can + // find the loop-closing jump. + unsigned noteIndex; + if (!newSrcNote(SRC_FOR_OF, ¬eIndex)) + return false; + + // Jump down to the loop condition to minimize overhead, assuming at least + // one iteration. (This is also what we do for loops; whether this + // assumption holds for spreads is an unanswered question.) + JumpList initialJump; + if (!emitJump(JSOP_GOTO, &initialJump)) // ITER ARR I (during the goto) + return false; + + JumpTarget top{ -1 }; + if (!emitLoopHead(nullptr, &top)) // ITER ARR I + return false; + + // When we enter the goto above, we have ITER ARR I on the stack. But when + // we reach this point on the loop backedge (if spreading produces at least + // one value), we've additionally pushed a RESULT iteration value. + // Increment manually to reflect this. + this->stackDepth++; + + JumpList beq; + JumpTarget breakTarget{ -1 }; + { +#ifdef DEBUG + auto loopDepth = this->stackDepth; +#endif + + // Emit code to assign result.value to the iteration variable. + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER ARR I VALUE + return false; + if (!emit1(JSOP_INITELEM_INC)) // ITER ARR (I+1) + return false; + + MOZ_ASSERT(this->stackDepth == loopDepth - 1); + + // Spread operations can't contain |continue|, so don't bother setting loop + // and enclosing "update" offsets, as we do with for-loops. + + // COME FROM the beginning of the loop to here. + if (!emitLoopEntry(nullptr, initialJump)) // ITER ARR I + return false; + + if (!emitDupAt(2)) // ITER ARR I ITER + return false; + if (!emitIteratorNext(nullptr, allowSelfHosted)) // ITER ARR I RESULT + return false; + if (!emit1(JSOP_DUP)) // ITER ARR I RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER ARR I RESULT DONE? + return false; + + if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER ARR I RESULT + return false; + + MOZ_ASSERT(this->stackDepth == loopDepth); + } + + // Let Ion know where the closing jump of this loop is. + if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset)) + return false; + + // No breaks or continues should occur in spreads. + MOZ_ASSERT(loopInfo.breaks.offset == -1); + MOZ_ASSERT(loopInfo.continues.offset == -1); + + if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset)) + return false; + + if (!emit2(JSOP_PICK, 3)) // ARR FINAL_INDEX RESULT ITER + return false; + + return emitUint16Operand(JSOP_POPN, 2); // ARR FINAL_INDEX +} + +bool +BytecodeEmitter::emitInitializeForInOrOfTarget(ParseNode* forHead) +{ + MOZ_ASSERT(forHead->isKind(PNK_FORIN) || forHead->isKind(PNK_FOROF)); + MOZ_ASSERT(forHead->isArity(PN_TERNARY)); + + MOZ_ASSERT(this->stackDepth >= 1, + "must have a per-iteration value for initializing"); + + ParseNode* target = forHead->pn_kid1; + MOZ_ASSERT(!forHead->pn_kid2); + + // If the for-in/of loop didn't have a variable declaration, per-loop + // initialization is just assigning the iteration value to a target + // expression. + if (!parser->handler.isDeclarationList(target)) + return emitAssignment(target, JSOP_NOP, nullptr); // ... ITERVAL + + // Otherwise, per-loop initialization is (possibly) declaration + // initialization. If the declaration is a lexical declaration, it must be + // initialized. If the declaration is a variable declaration, an + // assignment to that name (which does *not* necessarily assign to the + // variable!) must be generated. + + if (!updateSourceCoordNotes(target->pn_pos.begin)) + return false; + + MOZ_ASSERT(target->isForLoopDeclaration()); + target = parser->handler.singleBindingFromDeclaration(target); + + if (target->isKind(PNK_NAME)) { + auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&, + bool emittedBindOp) + { + if (emittedBindOp) { + // Per-iteration initialization in for-in/of loops computes the + // iteration value *before* initializing. Thus the + // initializing value may be buried under a bind-specific value + // on the stack. Swap it to the top of the stack. + MOZ_ASSERT(bce->stackDepth >= 2); + return bce->emit1(JSOP_SWAP); + } + + // In cases of emitting a frame slot or environment slot, + // nothing needs be done. + MOZ_ASSERT(bce->stackDepth >= 1); + return true; + }; + + // The caller handles removing the iteration value from the stack. + return emitInitializeName(target, emitSwapScopeAndRhs); + } + + MOZ_ASSERT(!target->isKind(PNK_ASSIGN), + "for-in/of loop destructuring declarations can't have initializers"); + + MOZ_ASSERT(target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)); + return emitDestructuringOps(target, DestructuringDeclaration); +} + +bool +BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitterScope) +{ + MOZ_ASSERT(forOfLoop->isKind(PNK_FOR)); + MOZ_ASSERT(forOfLoop->isArity(PN_BINARY)); + + ParseNode* forOfHead = forOfLoop->pn_left; + MOZ_ASSERT(forOfHead->isKind(PNK_FOROF)); + MOZ_ASSERT(forOfHead->isArity(PN_TERNARY)); + + // Evaluate the expression being iterated. + ParseNode* forHeadExpr = forOfHead->pn_kid3; + if (!emitTree(forHeadExpr)) // ITERABLE + return false; + if (!emitIterator()) // ITER + return false; + + // For-of loops have both the iterator and the value on the stack. Push + // undefined to balance the stack. + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT + return false; + + LoopControl loopInfo(this, StatementKind::ForOfLoop); + + // Annotate so IonMonkey can find the loop-closing jump. + unsigned noteIndex; + if (!newSrcNote(SRC_FOR_OF, ¬eIndex)) + return false; + + JumpList initialJump; + if (!emitJump(JSOP_GOTO, &initialJump)) // ITER RESULT + return false; + + JumpTarget top{ -1 }; + if (!emitLoopHead(nullptr, &top)) // ITER RESULT + return false; + + // If the loop had an escaping lexical declaration, replace the current + // environment with an dead zoned one to implement TDZ semantics. + if (headLexicalEmitterScope) { + // The environment chain only includes an environment for the for-of + // loop head *if* a scope binding is captured, thereby requiring + // recreation each iteration. If a lexical scope exists for the head, + // it must be the innermost one. If that scope has closed-over + // bindings inducing an environment, recreate the current environment. + DebugOnly<ParseNode*> forOfTarget = forOfHead->pn_kid1; + MOZ_ASSERT(forOfTarget->isKind(PNK_LET) || forOfTarget->isKind(PNK_CONST)); + MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); + MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); + + if (headLexicalEmitterScope->hasEnvironment()) { + if (!emit1(JSOP_RECREATELEXICALENV)) // ITER RESULT + return false; + } + + // For uncaptured bindings, put them back in TDZ. + if (!headLexicalEmitterScope->deadZoneFrameSlots(this)) + return false; + } + + JumpList beq; + JumpTarget breakTarget{ -1 }; + { +#ifdef DEBUG + auto loopDepth = this->stackDepth; +#endif + + // Emit code to assign result.value to the iteration variable. + if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + return false; + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE + return false; + + if (!emitInitializeForInOrOfTarget(forOfHead)) // ITER RESULT VALUE + return false; + + if (!emit1(JSOP_POP)) // ITER RESULT + return false; + + MOZ_ASSERT(this->stackDepth == loopDepth, + "the stack must be balanced around the initializing " + "operation"); + + // Perform the loop body. + ParseNode* forBody = forOfLoop->pn_right; + if (!emitTree(forBody)) // ITER RESULT + return false; + + // Set offset for continues. + loopInfo.continueTarget = { offset() }; + + if (!emitLoopEntry(forHeadExpr, initialJump)) // ITER RESULT + return false; + + if (!emit1(JSOP_POP)) // ITER + return false; + if (!emit1(JSOP_DUP)) // ITER ITER + return false; + + if (!emitIteratorNext(forOfHead)) // ITER RESULT + return false; + if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE? + return false; + + if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) + return false; // ITER RESULT + + MOZ_ASSERT(this->stackDepth == loopDepth); + } + + // Let Ion know where the closing jump of this loop is. + if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset)) + return false; + + if (!loopInfo.patchBreaksAndContinues(this)) + return false; + + if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset)) + return false; + + return emitUint16Operand(JSOP_POPN, 2); // +} + +bool +BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitterScope) +{ + MOZ_ASSERT(forInLoop->isKind(PNK_FOR)); + MOZ_ASSERT(forInLoop->isArity(PN_BINARY)); + MOZ_ASSERT(forInLoop->isOp(JSOP_ITER)); + + ParseNode* forInHead = forInLoop->pn_left; + MOZ_ASSERT(forInHead->isKind(PNK_FORIN)); + MOZ_ASSERT(forInHead->isArity(PN_TERNARY)); + + // Annex B: Evaluate the var-initializer expression if present. + // |for (var i = initializer in expr) { ... }| + ParseNode* forInTarget = forInHead->pn_kid1; + if (parser->handler.isDeclarationList(forInTarget)) { + ParseNode* decl = parser->handler.singleBindingFromDeclaration(forInTarget); + if (decl->isKind(PNK_NAME)) { + if (ParseNode* initializer = decl->expr()) { + MOZ_ASSERT(forInTarget->isKind(PNK_VAR), + "for-in initializers are only permitted for |var| declarations"); + + if (!updateSourceCoordNotes(decl->pn_pos.begin)) + return false; + + auto emitRhs = [initializer](BytecodeEmitter* bce, const NameLocation&, bool) { + return bce->emitTree(initializer); + }; + + if (!emitInitializeName(decl, emitRhs)) + return false; + + // Pop the initializer. + if (!emit1(JSOP_POP)) + return false; + } + } + } + + // Evaluate the expression being iterated. + ParseNode* expr = forInHead->pn_kid3; + if (!emitTree(expr)) // EXPR + return false; + + // Convert the value to the appropriate sort of iterator object for the + // loop variant (for-in, for-each-in, or destructuring for-in). + unsigned iflags = forInLoop->pn_iflags; + MOZ_ASSERT(0 == (iflags & ~(JSITER_FOREACH | JSITER_ENUMERATE))); + if (!emit2(JSOP_ITER, AssertedCast<uint8_t>(iflags))) // ITER + return false; + + // For-in loops have both the iterator and the value on the stack. Push + // undefined to balance the stack. + if (!emit1(JSOP_UNDEFINED)) // ITER ITERVAL + return false; + + LoopControl loopInfo(this, StatementKind::ForInLoop); + + /* Annotate so IonMonkey can find the loop-closing jump. */ + unsigned noteIndex; + if (!newSrcNote(SRC_FOR_IN, ¬eIndex)) + return false; + + // Jump down to the loop condition to minimize overhead (assuming at least + // one iteration, just like the other loop forms). + JumpList initialJump; + if (!emitJump(JSOP_GOTO, &initialJump)) // ITER ITERVAL + return false; + + JumpTarget top{ -1 }; + if (!emitLoopHead(nullptr, &top)) // ITER ITERVAL + return false; + + // If the loop had an escaping lexical declaration, replace the current + // environment with an dead zoned one to implement TDZ semantics. + if (headLexicalEmitterScope) { + // The environment chain only includes an environment for the for-in + // loop head *if* a scope binding is captured, thereby requiring + // recreation each iteration. If a lexical scope exists for the head, + // it must be the innermost one. If that scope has closed-over + // bindings inducing an environment, recreate the current environment. + MOZ_ASSERT(forInTarget->isKind(PNK_LET) || forInTarget->isKind(PNK_CONST)); + MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); + MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); + + if (headLexicalEmitterScope->hasEnvironment()) { + if (!emit1(JSOP_RECREATELEXICALENV)) // ITER ITERVAL + return false; + } + + // For uncaptured bindings, put them back in TDZ. + if (!headLexicalEmitterScope->deadZoneFrameSlots(this)) + return false; + } + + { +#ifdef DEBUG + auto loopDepth = this->stackDepth; +#endif + MOZ_ASSERT(loopDepth >= 2); + + if (!emitInitializeForInOrOfTarget(forInHead)) // ITER ITERVAL + return false; + + MOZ_ASSERT(this->stackDepth == loopDepth, + "iterator and iterval must be left on the stack"); + } + + // Perform the loop body. + ParseNode* forBody = forInLoop->pn_right; + if (!emitTree(forBody)) // ITER ITERVAL + return false; + + // Set offset for continues. + loopInfo.continueTarget = { offset() }; + + if (!emitLoopEntry(nullptr, initialJump)) // ITER ITERVAL + return false; + if (!emit1(JSOP_POP)) // ITER + return false; + if (!emit1(JSOP_MOREITER)) // ITER NEXTITERVAL? + return false; + if (!emit1(JSOP_ISNOITER)) // ITER NEXTITERVAL? ISNOITER + return false; + + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) + return false; // ITER NEXTITERVAL + + // Set the srcnote offset so we can find the closing jump. + if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset)) + return false; + + if (!loopInfo.patchBreaksAndContinues(this)) + return false; + + // Pop the enumeration value. + if (!emit1(JSOP_POP)) // ITER + return false; + + if (!tryNoteList.append(JSTRY_FOR_IN, this->stackDepth, top.offset, offset())) + return false; + + return emit1(JSOP_ENDITER); // +} + +/* C-style `for (init; cond; update) ...` loop. */ +bool +BytecodeEmitter::emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope) +{ + LoopControl loopInfo(this, StatementKind::ForLoop); + + ParseNode* forHead = pn->pn_left; + ParseNode* forBody = pn->pn_right; + + // If the head of this for-loop declared any lexical variables, the parser + // wrapped this PNK_FOR node in a PNK_LEXICALSCOPE representing the + // implicit scope of those variables. By the time we get here, we have + // already entered that scope. So far, so good. + // + // ### Scope freshening + // + // Each iteration of a `for (let V...)` loop creates a fresh loop variable + // binding for V, even if the loop is a C-style `for(;;)` loop: + // + // var funcs = []; + // for (let i = 0; i < 2; i++) + // funcs.push(function() { return i; }); + // assertEq(funcs[0](), 0); // the two closures capture... + // assertEq(funcs[1](), 1); // ...two different `i` bindings + // + // This is implemented by "freshening" the implicit block -- changing the + // scope chain to a fresh clone of the instantaneous block object -- each + // iteration, just before evaluating the "update" in for(;;) loops. + // + // No freshening occurs in `for (const ...;;)` as there's no point: you + // can't reassign consts. This is observable through the Debugger API. (The + // ES6 spec also skips cloning the environment in this case.) + bool forLoopRequiresFreshening = false; + if (ParseNode* init = forHead->pn_kid1) { + // Emit the `init` clause, whether it's an expression or a variable + // declaration. (The loop variables were hoisted into an enclosing + // scope, but we still need to emit code for the initializers.) + if (!updateSourceCoordNotes(init->pn_pos.begin)) + return false; + if (!emitTree(init)) + return false; + + if (!init->isForLoopDeclaration()) { + // 'init' is an expression, not a declaration. emitTree left its + // value on the stack. + if (!emit1(JSOP_POP)) + return false; + } + + // ES 13.7.4.8 step 2. The initial freshening. + // + // If an initializer let-declaration may be captured during loop iteration, + // the current scope has an environment. If so, freshen the current + // environment to expose distinct bindings for each loop iteration. + forLoopRequiresFreshening = init->isKind(PNK_LET) && headLexicalEmitterScope; + if (forLoopRequiresFreshening) { + // The environment chain only includes an environment for the for(;;) + // loop head's let-declaration *if* a scope binding is captured, thus + // requiring a fresh environment each iteration. If a lexical scope + // exists for the head, it must be the innermost one. If that scope + // has closed-over bindings inducing an environment, recreate the + // current environment. + MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); + MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); + + if (headLexicalEmitterScope->hasEnvironment()) { + if (!emit1(JSOP_FRESHENLEXICALENV)) + return false; + } + } + } + + /* + * NB: the SRC_FOR note has offsetBias 1 (JSOP_NOP_LENGTH). + * Use tmp to hold the biased srcnote "top" offset, which differs + * from the top local variable by the length of the JSOP_GOTO + * emitted in between tmp and top if this loop has a condition. + */ + unsigned noteIndex; + if (!newSrcNote(SRC_FOR, ¬eIndex)) + return false; + if (!emit1(JSOP_NOP)) + return false; + ptrdiff_t tmp = offset(); + + JumpList jmp; + if (forHead->pn_kid2) { + /* Goto the loop condition, which branches back to iterate. */ + if (!emitJump(JSOP_GOTO, &jmp)) + return false; + } + + /* Emit code for the loop body. */ + JumpTarget top{ -1 }; + if (!emitLoopHead(forBody, &top)) + return false; + if (jmp.offset == -1 && !emitLoopEntry(forBody, jmp)) + return false; + + if (!emitConditionallyExecutedTree(forBody)) + return false; + + // Set loop and enclosing "update" offsets, for continue. Note that we + // continue to immediately *before* the block-freshening: continuing must + // refresh the block. + if (!emitJumpTarget(&loopInfo.continueTarget)) + return false; + + // ES 13.7.4.8 step 3.e. The per-iteration freshening. + if (forLoopRequiresFreshening) { + MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); + MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); + + if (headLexicalEmitterScope->hasEnvironment()) { + if (!emit1(JSOP_FRESHENLEXICALENV)) + return false; + } + } + + // Check for update code to do before the condition (if any). + // The update code may not be executed at all; it needs its own TDZ cache. + if (ParseNode* update = forHead->pn_kid3) { + TDZCheckCache tdzCache(this); + + if (!updateSourceCoordNotes(update->pn_pos.begin)) + return false; + if (!emitTree(update)) + return false; + if (!emit1(JSOP_POP)) + return false; + + /* Restore the absolute line number for source note readers. */ + uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.end); + if (currentLine() != lineNum) { + if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(lineNum))) + return false; + current->currentLine = lineNum; + current->lastColumn = 0; + } + } + + ptrdiff_t tmp3 = offset(); + + if (forHead->pn_kid2) { + /* Fix up the goto from top to target the loop condition. */ + MOZ_ASSERT(jmp.offset >= 0); + if (!emitLoopEntry(forHead->pn_kid2, jmp)) + return false; + + if (!emitTree(forHead->pn_kid2)) + return false; + } else if (!forHead->pn_kid3) { + // If there is no condition clause and no update clause, mark + // the loop-ending "goto" with the location of the "for". + // This ensures that the debugger will stop on each loop + // iteration. + if (!updateSourceCoordNotes(pn->pn_pos.begin)) + return false; + } + + /* Set the first note offset so we can find the loop condition. */ + if (!setSrcNoteOffset(noteIndex, 0, tmp3 - tmp)) + return false; + if (!setSrcNoteOffset(noteIndex, 1, loopInfo.continueTarget.offset - tmp)) + return false; + + /* If no loop condition, just emit a loop-closing jump. */ + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(forHead->pn_kid2 ? JSOP_IFNE : JSOP_GOTO, top, &beq, &breakTarget)) + return false; + + /* The third note offset helps us find the loop-closing jump. */ + if (!setSrcNoteOffset(noteIndex, 2, beq.offset - tmp)) + return false; + + if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset, breakTarget.offset)) + return false; + + if (!loopInfo.patchBreaksAndContinues(this)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope) +{ + MOZ_ASSERT(pn->isKind(PNK_FOR)); + + if (pn->pn_left->isKind(PNK_FORHEAD)) + return emitCStyleFor(pn, headLexicalEmitterScope); + + if (!updateLineNumberNotes(pn->pn_pos.begin)) + return false; + + if (pn->pn_left->isKind(PNK_FORIN)) + return emitForIn(pn, headLexicalEmitterScope); + + MOZ_ASSERT(pn->pn_left->isKind(PNK_FOROF)); + return emitForOf(pn, headLexicalEmitterScope); +} + +bool +BytecodeEmitter::emitComprehensionForInOrOfVariables(ParseNode* pn, bool* lexicalScope) +{ + // ES6 specifies that lexical for-loop variables get a fresh binding each + // iteration, and that evaluation of the expression looped over occurs with + // these variables dead zoned. But these rules only apply to *standard* + // for-in/of loops, and we haven't extended these requirements to + // comprehension syntax. + + *lexicalScope = pn->isKind(PNK_LEXICALSCOPE); + if (*lexicalScope) { + // This is initially-ES7-tracked syntax, now with considerably murkier + // outlook. The scope work is done by the caller by instantiating an + // EmitterScope. There's nothing to do here. + } else { + // This is legacy comprehension syntax. We'll have PNK_LET here, using + // a lexical scope provided by/for the entire comprehension. Name + // analysis assumes declarations initialize lets, but as we're handling + // this declaration manually, we must also initialize manually to avoid + // triggering dead zone checks. + MOZ_ASSERT(pn->isKind(PNK_LET)); + MOZ_ASSERT(pn->pn_count == 1); + + if (!emitDeclarationList(pn)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) +{ + MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR)); + + ParseNode* forHead = pn->pn_left; + MOZ_ASSERT(forHead->isKind(PNK_FOROF)); + + ParseNode* forHeadExpr = forHead->pn_kid3; + ParseNode* forBody = pn->pn_right; + + ParseNode* loopDecl = forHead->pn_kid1; + bool lexicalScope = false; + if (!emitComprehensionForInOrOfVariables(loopDecl, &lexicalScope)) + return false; + + // For-of loops run with two values on the stack: the iterator and the + // current result object. + + // Evaluate the expression to the right of 'of'. + if (!emitTree(forHeadExpr)) // EXPR + return false; + if (!emitIterator()) // ITER + return false; + + // Push a dummy result so that we properly enter iteration midstream. + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT + return false; + + // Enter the block before the loop body, after evaluating the obj. + // Initialize let bindings with undefined when entering, as the name + // assigned to is a plain assignment. + TDZCheckCache tdzCache(this); + Maybe<EmitterScope> emitterScope; + ParseNode* loopVariableName; + if (lexicalScope) { + loopVariableName = parser->handler.singleBindingFromDeclaration(loopDecl->pn_expr); + emitterScope.emplace(this); + if (!emitterScope->enterComprehensionFor(this, loopDecl->scopeBindings())) + return false; + } else { + loopVariableName = parser->handler.singleBindingFromDeclaration(loopDecl); + } + + LoopControl loopInfo(this, StatementKind::ForOfLoop); + + // Jump down to the loop condition to minimize overhead assuming at least + // one iteration, as the other loop forms do. Annotate so IonMonkey can + // find the loop-closing jump. + unsigned noteIndex; + if (!newSrcNote(SRC_FOR_OF, ¬eIndex)) + return false; + JumpList jmp; + if (!emitJump(JSOP_GOTO, &jmp)) + return false; + + JumpTarget top{ -1 }; + if (!emitLoopHead(nullptr, &top)) + return false; + +#ifdef DEBUG + int loopDepth = this->stackDepth; +#endif + + // Emit code to assign result.value to the iteration variable. + if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + return false; + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE + return false; + if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER RESULT VALUE + return false; + if (!emit1(JSOP_POP)) // ITER RESULT + return false; + + // The stack should be balanced around the assignment opcode sequence. + MOZ_ASSERT(this->stackDepth == loopDepth); + + // Emit code for the loop body. + if (!emitTree(forBody)) + return false; + + // Set offset for continues. + loopInfo.continueTarget = { offset() }; + + if (!emitLoopEntry(forHeadExpr, jmp)) + return false; + + if (!emit1(JSOP_POP)) // ITER + return false; + if (!emit1(JSOP_DUP)) // ITER ITER + return false; + if (!emitIteratorNext(forHead)) // ITER RESULT + return false; + if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE? + return false; + + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT + return false; + + MOZ_ASSERT(this->stackDepth == loopDepth); + + // Let Ion know where the closing jump of this loop is. + if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset)) + return false; + + if (!loopInfo.patchBreaksAndContinues(this)) + return false; + + if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset)) + return false; + + if (emitterScope) { + if (!emitterScope->leave(this)) + return false; + emitterScope.reset(); + } + + // Pop the result and the iter. + return emitUint16Operand(JSOP_POPN, 2); // +} + +bool +BytecodeEmitter::emitComprehensionForIn(ParseNode* pn) +{ + MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR)); + + ParseNode* forHead = pn->pn_left; + MOZ_ASSERT(forHead->isKind(PNK_FORIN)); + + ParseNode* forBody = pn->pn_right; + + ParseNode* loopDecl = forHead->pn_kid1; + bool lexicalScope = false; + if (loopDecl && !emitComprehensionForInOrOfVariables(loopDecl, &lexicalScope)) + return false; + + // Evaluate the expression to the right of 'in'. + if (!emitTree(forHead->pn_kid3)) + return false; + + /* + * Emit a bytecode to convert top of stack value to the iterator + * object depending on the loop variant (for-in, for-each-in, or + * destructuring for-in). + */ + MOZ_ASSERT(pn->isOp(JSOP_ITER)); + if (!emit2(JSOP_ITER, (uint8_t) pn->pn_iflags)) + return false; + + // For-in loops have both the iterator and the value on the stack. Push + // undefined to balance the stack. + if (!emit1(JSOP_UNDEFINED)) + return false; + + // Enter the block before the loop body, after evaluating the obj. + // Initialize let bindings with undefined when entering, as the name + // assigned to is a plain assignment. + TDZCheckCache tdzCache(this); + Maybe<EmitterScope> emitterScope; + if (lexicalScope) { + emitterScope.emplace(this); + if (!emitterScope->enterComprehensionFor(this, loopDecl->scopeBindings())) + return false; + } + + LoopControl loopInfo(this, StatementKind::ForInLoop); + + /* Annotate so IonMonkey can find the loop-closing jump. */ + unsigned noteIndex; + if (!newSrcNote(SRC_FOR_IN, ¬eIndex)) + return false; + + /* + * Jump down to the loop condition to minimize overhead assuming at + * least one iteration, as the other loop forms do. + */ + JumpList jmp; + if (!emitJump(JSOP_GOTO, &jmp)) + return false; + + JumpTarget top{ -1 }; + if (!emitLoopHead(nullptr, &top)) + return false; + +#ifdef DEBUG + int loopDepth = this->stackDepth; +#endif + + // Emit code to assign the enumeration value to the left hand side, but + // also leave it on the stack. + if (!emitAssignment(forHead->pn_kid2, JSOP_NOP, nullptr)) + return false; + + /* The stack should be balanced around the assignment opcode sequence. */ + MOZ_ASSERT(this->stackDepth == loopDepth); + + /* Emit code for the loop body. */ + if (!emitTree(forBody)) + return false; + + // Set offset for continues. + loopInfo.continueTarget = { offset() }; + + if (!emitLoopEntry(nullptr, jmp)) + return false; + if (!emit1(JSOP_POP)) + return false; + if (!emit1(JSOP_MOREITER)) + return false; + if (!emit1(JSOP_ISNOITER)) + return false; + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) + return false; + + /* Set the srcnote offset so we can find the closing jump. */ + if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset)) + return false; + + if (!loopInfo.patchBreaksAndContinues(this)) + return false; + + // Pop the enumeration value. + if (!emit1(JSOP_POP)) + return false; + + JumpTarget endIter{ offset() }; + if (!tryNoteList.append(JSTRY_FOR_IN, this->stackDepth, top.offset, endIter.offset)) + return false; + if (!emit1(JSOP_ENDITER)) + return false; + + if (emitterScope) { + if (!emitterScope->leave(this)) + return false; + emitterScope.reset(); + } + + return true; +} + +bool +BytecodeEmitter::emitComprehensionFor(ParseNode* compFor) +{ + MOZ_ASSERT(compFor->pn_left->isKind(PNK_FORIN) || + compFor->pn_left->isKind(PNK_FOROF)); + + if (!updateLineNumberNotes(compFor->pn_pos.begin)) + return false; + + return compFor->pn_left->isKind(PNK_FORIN) + ? emitComprehensionForIn(compFor) + : emitComprehensionForOf(compFor); +} + +MOZ_NEVER_INLINE bool +BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) +{ + FunctionBox* funbox = pn->pn_funbox; + RootedFunction fun(cx, funbox->function()); + RootedAtom name(cx, fun->name()); + MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); + MOZ_ASSERT_IF(pn->isOp(JSOP_FUNWITHPROTO), needsProto); + + /* + * Set the |wasEmitted| flag in the funbox once the function has been + * emitted. Function definitions that need hoisting to the top of the + * function will be seen by emitFunction in two places. + */ + if (funbox->wasEmitted && pn->functionIsHoisted()) { + // Annex B block-scoped functions are hoisted like any other + // block-scoped function to the top of their scope. When their + // definitions are seen for the second time, we need to emit the + // assignment that assigns the function to the outer 'var' binding. + if (funbox->isAnnexB) { + auto emitRhs = [&name](BytecodeEmitter* bce, const NameLocation&, bool) { + // The RHS is the value of the lexically bound name in the + // innermost scope. + return bce->emitGetName(name); + }; + + // Get the location of the 'var' binding in the body scope. The + // name must be found, else there is a bug in the Annex B handling + // in Parser. + // + // In sloppy eval contexts, this location is dynamic. + Maybe<NameLocation> lhsLoc = locationOfNameBoundInScope(name, varEmitterScope); + + // If there are parameter expressions, the var name could be a + // parameter. + if (!lhsLoc && sc->isFunctionBox() && sc->asFunctionBox()->hasExtraBodyVarScope()) + lhsLoc = locationOfNameBoundInScope(name, varEmitterScope->enclosingInFrame()); + + if (!lhsLoc) { + lhsLoc = Some(NameLocation::DynamicAnnexBVar()); + } else { + MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var || + lhsLoc->bindingKind() == BindingKind::FormalParameter || + (lhsLoc->bindingKind() == BindingKind::Let && + sc->asFunctionBox()->hasParameterExprs)); + } + + if (!emitSetOrInitializeNameAtLocation(name, *lhsLoc, emitRhs, false)) + return false; + if (!emit1(JSOP_POP)) + return false; + } + + MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript()); + MOZ_ASSERT(pn->functionIsHoisted()); + return true; + } + + funbox->wasEmitted = true; + + /* + * Mark as singletons any function which will only be executed once, or + * which is inner to a lambda we only expect to run once. In the latter + * case, if the lambda runs multiple times then CloneFunctionObject will + * make a deep clone of its contents. + */ + if (fun->isInterpreted()) { + bool singleton = checkRunOnceContext(); + if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton)) + return false; + + SharedContext* outersc = sc; + if (fun->isInterpretedLazy()) { + // We need to update the static scope chain regardless of whether + // the LazyScript has already been initialized, due to the case + // where we previously successfully compiled an inner function's + // lazy script but failed to compile the outer script after the + // fact. If we attempt to compile the outer script again, the + // static scope chain will be newly allocated and will mismatch + // the previously compiled LazyScript's. + ScriptSourceObject* source = &script->sourceObject()->as<ScriptSourceObject>(); + fun->lazyScript()->setEnclosingScopeAndSource(innermostScope(), source); + if (emittingRunOnceLambda) + fun->lazyScript()->setTreatAsRunOnce(); + } else { + MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript); + + // Inherit most things (principals, version, etc) from the + // parent. Use default values for the rest. + Rooted<JSScript*> parent(cx, script); + MOZ_ASSERT(parent->getVersion() == parser->options().version); + MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors()); + const TransitiveCompileOptions& transitiveOptions = parser->options(); + CompileOptions options(cx, transitiveOptions); + + Rooted<JSObject*> sourceObject(cx, script->sourceObject()); + Rooted<JSScript*> script(cx, JSScript::Create(cx, options, sourceObject, + funbox->bufStart, funbox->bufEnd)); + if (!script) + return false; + + BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, + pn->pn_pos, emitterMode); + if (!bce2.init()) + return false; + + /* We measured the max scope depth when we parsed the function. */ + if (!bce2.emitFunctionScript(pn->pn_body)) + return false; + + if (funbox->isLikelyConstructorWrapper()) + script->setLikelyConstructorWrapper(); + } + + if (outersc->isFunctionBox()) + outersc->asFunctionBox()->setHasInnerFunctions(); + } else { + MOZ_ASSERT(IsAsmJSModule(fun)); + } + + /* Make the function object a literal in the outer script's pool. */ + unsigned index = objectList.add(pn->pn_funbox); + + /* Non-hoisted functions simply emit their respective op. */ + if (!pn->functionIsHoisted()) { + /* JSOP_LAMBDA_ARROW is always preceded by a new.target */ + MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW)); + if (funbox->isAsync()) { + MOZ_ASSERT(!needsProto); + return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow()); + } + + if (fun->isArrow()) { + if (sc->allowNewTarget()) { + if (!emit1(JSOP_NEWTARGET)) + return false; + } else { + if (!emit1(JSOP_NULL)) + return false; + } + } + + if (needsProto) { + MOZ_ASSERT(pn->getOp() == JSOP_FUNWITHPROTO || pn->getOp() == JSOP_LAMBDA); + pn->setOp(JSOP_FUNWITHPROTO); + } + + if (pn->getOp() == JSOP_DEFFUN) { + if (!emitIndex32(JSOP_LAMBDA, index)) + return false; + return emit1(JSOP_DEFFUN); + } + + return emitIndex32(pn->getOp(), index); + } + + MOZ_ASSERT(!needsProto); + + bool topLevelFunction; + if (sc->isFunctionBox() || (sc->isEvalContext() && sc->strict())) { + // No nested functions inside other functions are top-level. + topLevelFunction = false; + } else { + // In sloppy eval scripts, top-level functions in are accessed + // dynamically. In global and module scripts, top-level functions are + // those bound in the var scope. + NameLocation loc = lookupName(name); + topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic || + loc.bindingKind() == BindingKind::Var; + } + + if (topLevelFunction) { + if (sc->isModuleContext()) { + // For modules, we record the function and instantiate the binding + // during ModuleDeclarationInstantiation(), before the script is run. + + RootedModuleObject module(cx, sc->asModuleContext()->module()); + if (!module->noteFunctionDeclaration(cx, name, fun)) + return false; + } else { + MOZ_ASSERT(sc->isGlobalContext() || sc->isEvalContext()); + MOZ_ASSERT(pn->getOp() == JSOP_NOP); + switchToPrologue(); + if (funbox->isAsync()) { + if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow())) + return false; + } else { + if (!emitIndex32(JSOP_LAMBDA, index)) + return false; + } + if (!emit1(JSOP_DEFFUN)) + return false; + if (!updateSourceCoordNotes(pn->pn_pos.begin)) + return false; + switchToMain(); + } + } else { + // For functions nested within functions and blocks, make a lambda and + // initialize the binding name of the function in the current scope. + + bool isAsync = funbox->isAsync(); + auto emitLambda = [index, isAsync](BytecodeEmitter* bce, const NameLocation&, bool) { + if (isAsync) { + return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false, + /* isArrow = */ false); + } + return bce->emitIndexOp(JSOP_LAMBDA, index); + }; + + if (!emitInitializeName(name, emitLambda)) + return false; + if (!emit1(JSOP_POP)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitAsyncWrapperLambda(unsigned index, bool isArrow) { + if (isArrow) { + if (sc->allowNewTarget()) { + if (!emit1(JSOP_NEWTARGET)) + return false; + } else { + if (!emit1(JSOP_NULL)) + return false; + } + if (!emitIndex32(JSOP_LAMBDA_ARROW, index)) + return false; + } else { + if (!emitIndex32(JSOP_LAMBDA, index)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow) +{ + // needsHomeObject can be true for propertyList for extended class. + // In that case push both unwrapped and wrapped function, in order to + // initialize home object of unwrapped function, and set wrapped function + // as a property. + // + // lambda // unwrapped + // dup // unwrapped unwrapped + // toasync // unwrapped wrapped + // + // Emitted code is surrounded by the following code. + // + // // classObj classCtor classProto + // (emitted code) // classObj classCtor classProto unwrapped wrapped + // swap // classObj classCtor classProto wrapped unwrapped + // inithomeobject 1 // classObj classCtor classProto wrapped unwrapped + // // initialize the home object of unwrapped + // // with classProto here + // pop // classObj classCtor classProto wrapped + // inithiddenprop // classObj classCtor classProto wrapped + // // initialize the property of the classProto + // // with wrapped function here + // pop // classObj classCtor classProto + // + // needsHomeObject is false for other cases, push wrapped function only. + if (!emitAsyncWrapperLambda(index, isArrow)) + return false; + if (needsHomeObject) { + if (!emit1(JSOP_DUP)) + return false; + } + if (!emit1(JSOP_TOASYNC)) + return false; + return true; +} + +bool +BytecodeEmitter::emitDo(ParseNode* pn) +{ + /* Emit an annotated nop so IonBuilder can recognize the 'do' loop. */ + unsigned noteIndex; + if (!newSrcNote(SRC_WHILE, ¬eIndex)) + return false; + if (!emit1(JSOP_NOP)) + return false; + + unsigned noteIndex2; + if (!newSrcNote(SRC_WHILE, ¬eIndex2)) + return false; + + /* Compile the loop body. */ + JumpTarget top; + if (!emitLoopHead(pn->pn_left, &top)) + return false; + + LoopControl loopInfo(this, StatementKind::DoLoop); + + JumpList empty; + if (!emitLoopEntry(nullptr, empty)) + return false; + + if (!emitTree(pn->pn_left)) + return false; + + // Set the offset for continues. + if (!emitJumpTarget(&loopInfo.continueTarget)) + return false; + + /* Compile the loop condition, now that continues know where to go. */ + if (!emitTree(pn->pn_right)) + return false; + + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_IFNE, top, &beq, &breakTarget)) + return false; + + if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset, breakTarget.offset)) + return false; + + /* + * Update the annotations with the update and back edge positions, for + * IonBuilder. + * + * Be careful: We must set noteIndex2 before noteIndex in case the noteIndex + * note gets bigger. + */ + if (!setSrcNoteOffset(noteIndex2, 0, beq.offset - top.offset)) + return false; + if (!setSrcNoteOffset(noteIndex, 0, 1 + (loopInfo.continueTarget.offset - top.offset))) + return false; + + if (!loopInfo.patchBreaksAndContinues(this)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitWhile(ParseNode* pn) +{ + /* + * Minimize bytecodes issued for one or more iterations by jumping to + * the condition below the body and closing the loop if the condition + * is true with a backward branch. For iteration count i: + * + * i test at the top test at the bottom + * = =============== ================== + * 0 ifeq-pass goto; ifne-fail + * 1 ifeq-fail; goto; ifne-pass goto; ifne-pass; ifne-fail + * 2 2*(ifeq-fail; goto); ifeq-pass goto; 2*ifne-pass; ifne-fail + * . . . + * N N*(ifeq-fail; goto); ifeq-pass goto; N*ifne-pass; ifne-fail + */ + + // If we have a single-line while, like "while (x) ;", we want to + // emit the line note before the initial goto, so that the + // debugger sees a single entry point. This way, if there is a + // breakpoint on the line, it will only fire once; and "next"ing + // will skip the whole loop. However, for the multi-line case we + // want to emit the line note after the initial goto, so that + // "cont" stops on each iteration -- but without a stop before the + // first iteration. + if (parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin) == + parser->tokenStream.srcCoords.lineNum(pn->pn_pos.end) && + !updateSourceCoordNotes(pn->pn_pos.begin)) + return false; + + JumpTarget top{ -1 }; + if (!emitJumpTarget(&top)) + return false; + + LoopControl loopInfo(this, StatementKind::WhileLoop); + loopInfo.continueTarget = top; + + unsigned noteIndex; + if (!newSrcNote(SRC_WHILE, ¬eIndex)) + return false; + + JumpList jmp; + if (!emitJump(JSOP_GOTO, &jmp)) + return false; + + if (!emitLoopHead(pn->pn_right, &top)) + return false; + + if (!emitConditionallyExecutedTree(pn->pn_right)) + return false; + + if (!emitLoopEntry(pn->pn_left, jmp)) + return false; + if (!emitTree(pn->pn_left)) + return false; + + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_IFNE, top, &beq, &breakTarget)) + return false; + + if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset, breakTarget.offset)) + return false; + + if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset)) + return false; + + if (!loopInfo.patchBreaksAndContinues(this)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitBreak(PropertyName* label) +{ + BreakableControl* target; + SrcNoteType noteType; + if (label) { + // Any statement with the matching label may be the break target. + auto hasSameLabel = [label](LabelControl* labelControl) { + return labelControl->label() == label; + }; + target = findInnermostNestableControl<LabelControl>(hasSameLabel); + noteType = SRC_BREAK2LABEL; + } else { + auto isNotLabel = [](BreakableControl* control) { + return !control->is<LabelControl>(); + }; + target = findInnermostNestableControl<BreakableControl>(isNotLabel); + noteType = (target->kind() == StatementKind::Switch) ? SRC_SWITCHBREAK : SRC_BREAK; + } + + return emitGoto(target, &target->breaks, noteType); +} + +bool +BytecodeEmitter::emitContinue(PropertyName* label) +{ + LoopControl* target = nullptr; + if (label) { + // Find the loop statement enclosed by the matching label. + NestableControl* control = innermostNestableControl; + while (!control->is<LabelControl>() || control->as<LabelControl>().label() != label) { + if (control->is<LoopControl>()) + target = &control->as<LoopControl>(); + control = control->enclosing(); + } + } else { + target = findInnermostNestableControl<LoopControl>(); + } + return emitGoto(target, &target->continues, SRC_CONTINUE); +} + +bool +BytecodeEmitter::emitGetFunctionThis(ParseNode* pn) +{ + MOZ_ASSERT(sc->thisBinding() == ThisBinding::Function); + MOZ_ASSERT(pn->isKind(PNK_NAME)); + MOZ_ASSERT(pn->name() == cx->names().dotThis); + + if (!emitTree(pn)) + return false; + if (sc->needsThisTDZChecks() && !emit1(JSOP_CHECKTHIS)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitGetThisForSuperBase(ParseNode* pn) +{ + MOZ_ASSERT(pn->isKind(PNK_SUPERBASE)); + return emitGetFunctionThis(pn->pn_kid); +} + +bool +BytecodeEmitter::emitThisLiteral(ParseNode* pn) +{ + MOZ_ASSERT(pn->isKind(PNK_THIS)); + + if (ParseNode* thisName = pn->pn_kid) + return emitGetFunctionThis(thisName); + + if (sc->thisBinding() == ThisBinding::Module) + return emit1(JSOP_UNDEFINED); + + MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global); + return emit1(JSOP_GLOBALTHIS); +} + +bool +BytecodeEmitter::emitCheckDerivedClassConstructorReturn() +{ + MOZ_ASSERT(lookupName(cx->names().dotThis).hasKnownSlot()); + if (!emitGetName(cx->names().dotThis)) + return false; + if (!emit1(JSOP_CHECKRETURN)) + return false; + return true; +} + +bool +BytecodeEmitter::emitReturn(ParseNode* pn) +{ + if (!updateSourceCoordNotes(pn->pn_pos.begin)) + return false; + + if (sc->isFunctionBox() && sc->asFunctionBox()->isStarGenerator()) { + if (!emitPrepareIteratorResult()) + return false; + } + + /* Push a return value */ + if (ParseNode* pn2 = pn->pn_kid) { + if (!emitTree(pn2)) + return false; + } else { + /* No explicit return value provided */ + if (!emit1(JSOP_UNDEFINED)) + return false; + } + + if (sc->isFunctionBox() && sc->asFunctionBox()->isStarGenerator()) { + if (!emitFinishIteratorResult(true)) + return false; + } + + // We know functionBodyEndPos is set because "return" is only + // valid in a function, and so we've passed through + // emitFunctionScript. + MOZ_ASSERT(functionBodyEndPosSet); + if (!updateSourceCoordNotes(functionBodyEndPos)) + return false; + + /* + * EmitNonLocalJumpFixup may add fixup bytecode to close open try + * blocks having finally clauses and to exit intermingled let blocks. + * We can't simply transfer control flow to our caller in that case, + * because we must gosub to those finally clauses from inner to outer, + * with the correct stack pointer (i.e., after popping any with, + * for/in, etc., slots nested inside the finally's try). + * + * In this case we mutate JSOP_RETURN into JSOP_SETRVAL and add an + * extra JSOP_RETRVAL after the fixups. + */ + ptrdiff_t top = offset(); + + bool isGenerator = sc->isFunctionBox() && sc->asFunctionBox()->isGenerator(); + bool isDerivedClassConstructor = + sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); + + if (!emit1((isGenerator || isDerivedClassConstructor) ? JSOP_SETRVAL : JSOP_RETURN)) + return false; + + // Make sure that we emit this before popping the blocks in prepareForNonLocalJump, + // to ensure that the error is thrown while the scope-chain is still intact. + if (isDerivedClassConstructor) { + if (!emitCheckDerivedClassConstructorReturn()) + return false; + } + + NonLocalExitControl nle(this); + + if (!nle.prepareForNonLocalJumpToOutermost()) + return false; + + if (isGenerator) { + // We know that .generator is on the function scope, as we just exited + // all nested scopes. + NameLocation loc = + *locationOfNameBoundInFunctionScope(cx->names().dotGenerator, varEmitterScope); + if (!emitGetNameAtLocation(cx->names().dotGenerator, loc)) + return false; + if (!emitYieldOp(JSOP_FINALYIELDRVAL)) + return false; + } else if (isDerivedClassConstructor) { + MOZ_ASSERT(code()[top] == JSOP_SETRVAL); + if (!emit1(JSOP_RETRVAL)) + return false; + } else if (top + static_cast<ptrdiff_t>(JSOP_RETURN_LENGTH) != offset()) { + code()[top] = JSOP_SETRVAL; + if (!emit1(JSOP_RETRVAL)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitYield(ParseNode* pn) +{ + MOZ_ASSERT(sc->isFunctionBox()); + + if (pn->getOp() == JSOP_YIELD) { + if (sc->asFunctionBox()->isStarGenerator()) { + if (!emitPrepareIteratorResult()) + return false; + } + if (pn->pn_left) { + if (!emitTree(pn->pn_left)) + return false; + } else { + if (!emit1(JSOP_UNDEFINED)) + return false; + } + if (sc->asFunctionBox()->isStarGenerator()) { + if (!emitFinishIteratorResult(false)) + return false; + } + } else { + MOZ_ASSERT(pn->getOp() == JSOP_INITIALYIELD); + } + + if (!emitTree(pn->pn_right)) + return false; + + if (!emitYieldOp(pn->getOp())) + return false; + + if (pn->getOp() == JSOP_INITIALYIELD && !emit1(JSOP_POP)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) +{ + MOZ_ASSERT(sc->isFunctionBox()); + MOZ_ASSERT(sc->asFunctionBox()->isStarGenerator()); + + if (!emitTree(iter)) // ITERABLE + return false; + if (!emitIterator()) // ITER + return false; + + // Initial send value is undefined. + if (!emit1(JSOP_UNDEFINED)) // ITER RECEIVED + return false; + + int depth = stackDepth; + MOZ_ASSERT(depth >= 2); + + JumpList send; + if (!emitJump(JSOP_GOTO, &send)) // goto send + return false; + + // Try prologue. // ITER RESULT + unsigned noteIndex; + if (!newSrcNote(SRC_TRY, ¬eIndex)) + return false; + JumpTarget tryStart{ offset() }; + if (!emit1(JSOP_TRY)) // tryStart: + return false; + MOZ_ASSERT(this->stackDepth == depth); + + // Load the generator object. + if (!emitTree(gen)) // ITER RESULT GENOBJ + return false; + + // Yield RESULT as-is, without re-boxing. + if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED + return false; + + // Try epilogue. + if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart.offset)) + return false; + if (!emitJump(JSOP_GOTO, &send)) // goto send + return false; + + JumpTarget tryEnd; + if (!emitJumpTarget(&tryEnd)) // tryEnd: + return false; + + // Catch location. + stackDepth = uint32_t(depth); // ITER RESULT + if (!emit1(JSOP_POP)) // ITER + return false; + // THROW? = 'throw' in ITER + if (!emit1(JSOP_EXCEPTION)) // ITER EXCEPTION + return false; + if (!emit1(JSOP_SWAP)) // EXCEPTION ITER + return false; + if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER + return false; + if (!emitAtomOp(cx->names().throw_, JSOP_STRING)) // EXCEPTION ITER ITER "throw" + return false; + if (!emit1(JSOP_SWAP)) // EXCEPTION ITER "throw" ITER + return false; + if (!emit1(JSOP_IN)) // EXCEPTION ITER THROW? + return false; + // if (THROW?) goto delegate + JumpList checkThrow; + if (!emitJump(JSOP_IFNE, &checkThrow)) // EXCEPTION ITER + return false; + if (!emit1(JSOP_POP)) // EXCEPTION + return false; + if (!emit1(JSOP_THROW)) // throw EXCEPTION + return false; + + if (!emitJumpTargetAndPatch(checkThrow)) // delegate: + return false; + // RESULT = ITER.throw(EXCEPTION) // EXCEPTION ITER + stackDepth = uint32_t(depth); + if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER + return false; + if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER ITER + return false; + if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // EXCEPTION ITER ITER THROW + return false; + if (!emit1(JSOP_SWAP)) // EXCEPTION ITER THROW ITER + return false; + if (!emit2(JSOP_PICK, 3)) // ITER THROW ITER EXCEPTION + return false; + if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT + return false; + checkTypeSet(JSOP_CALL); + MOZ_ASSERT(this->stackDepth == depth); + JumpList checkResult; + if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult + return false; + + // Catch epilogue. + + // This is a peace offering to ReconstructPCStack. See the note in EmitTry. + if (!emit1(JSOP_NOP)) + return false; + if (!tryNoteList.append(JSTRY_CATCH, depth, tryStart.offset + JSOP_TRY_LENGTH, tryEnd.offset)) + return false; + + // After the try/catch block: send the received value to the iterator. + if (!emitJumpTargetAndPatch(send)) // send: + return false; + + // Send location. + // result = iter.next(received) // ITER RECEIVED + if (!emit1(JSOP_SWAP)) // RECEIVED ITER + return false; + if (!emit1(JSOP_DUP)) // RECEIVED ITER ITER + return false; + if (!emit1(JSOP_DUP)) // RECEIVED ITER ITER ITER + return false; + if (!emitAtomOp(cx->names().next, JSOP_CALLPROP)) // RECEIVED ITER ITER NEXT + return false; + if (!emit1(JSOP_SWAP)) // RECEIVED ITER NEXT ITER + return false; + if (!emit2(JSOP_PICK, 3)) // ITER NEXT ITER RECEIVED + return false; + if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT + return false; + if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ITER RESULT + return false; + checkTypeSet(JSOP_CALL); + MOZ_ASSERT(this->stackDepth == depth); + + if (!emitJumpTargetAndPatch(checkResult)) // checkResult: + return false; + + // if (!result.done) goto tryStart; // ITER RESULT + if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE + return false; + // if (!DONE) goto tryStart; + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq, &breakTarget)) // ITER RESULT + return false; + + // result.value + if (!emit1(JSOP_SWAP)) // RESULT ITER + return false; + if (!emit1(JSOP_POP)) // RESULT + return false; + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // VALUE + return false; + + MOZ_ASSERT(this->stackDepth == depth - 1); + + return true; +} + +bool +BytecodeEmitter::emitStatementList(ParseNode* pn) +{ + MOZ_ASSERT(pn->isArity(PN_LIST)); + for (ParseNode* pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + if (!emitTree(pn2)) + return false; + } + return true; +} + +bool +BytecodeEmitter::emitStatement(ParseNode* pn) +{ + MOZ_ASSERT(pn->isKind(PNK_SEMI)); + + ParseNode* pn2 = pn->pn_kid; + if (!pn2) + return true; + + if (!updateSourceCoordNotes(pn->pn_pos.begin)) + return false; + + /* + * Top-level or called-from-a-native JS_Execute/EvaluateScript, + * debugger, and eval frames may need the value of the ultimate + * expression statement as the script's result, despite the fact + * that it appears useless to the compiler. + * + * API users may also set the JSOPTION_NO_SCRIPT_RVAL option when + * calling JS_Compile* to suppress JSOP_SETRVAL. + */ + bool wantval = false; + bool useful = false; + if (sc->isFunctionBox()) + MOZ_ASSERT(!script->noScriptRval()); + else + useful = wantval = !script->noScriptRval(); + + /* Don't eliminate expressions with side effects. */ + if (!useful) { + if (!checkSideEffects(pn2, &useful)) + return false; + + /* + * Don't eliminate apparently useless expressions if they are labeled + * expression statements. The startOffset() test catches the case + * where we are nesting in emitTree for a labeled compound statement. + */ + if (innermostNestableControl && + innermostNestableControl->is<LabelControl>() && + innermostNestableControl->as<LabelControl>().startOffset() >= offset()) + { + useful = true; + } + } + + if (useful) { + JSOp op = wantval ? JSOP_SETRVAL : JSOP_POP; + MOZ_ASSERT_IF(pn2->isKind(PNK_ASSIGN), pn2->isOp(JSOP_NOP)); + if (!emitTree(pn2)) + return false; + if (!emit1(op)) + return false; + } else if (pn->isDirectivePrologueMember()) { + // Don't complain about directive prologue members; just don't emit + // their code. + } else { + if (JSAtom* atom = pn->isStringExprStatement()) { + // Warn if encountering a non-directive prologue member string + // expression statement, that is inconsistent with the current + // directive prologue. That is, a script *not* starting with + // "use strict" should warn for any "use strict" statements seen + // later in the script, because such statements are misleading. + const char* directive = nullptr; + if (atom == cx->names().useStrict) { + if (!sc->strictScript) + directive = js_useStrict_str; + } else if (atom == cx->names().useAsm) { + if (sc->isFunctionBox()) { + if (IsAsmJSModule(sc->asFunctionBox()->function())) + directive = js_useAsm_str; + } + } + + if (directive) { + if (!reportStrictWarning(pn2, JSMSG_CONTRARY_NONDIRECTIVE, directive)) + return false; + } + } else { + current->currentLine = parser->tokenStream.srcCoords.lineNum(pn2->pn_pos.begin); + current->lastColumn = 0; + if (!reportStrictWarning(pn2, JSMSG_USELESS_EXPR)) + return false; + } + } + + return true; +} + +bool +BytecodeEmitter::emitDeleteName(ParseNode* node) +{ + MOZ_ASSERT(node->isKind(PNK_DELETENAME)); + MOZ_ASSERT(node->isArity(PN_UNARY)); + + ParseNode* nameExpr = node->pn_kid; + MOZ_ASSERT(nameExpr->isKind(PNK_NAME)); + + return emitAtomOp(nameExpr, JSOP_DELNAME); +} + +bool +BytecodeEmitter::emitDeleteProperty(ParseNode* node) +{ + MOZ_ASSERT(node->isKind(PNK_DELETEPROP)); + MOZ_ASSERT(node->isArity(PN_UNARY)); + + ParseNode* propExpr = node->pn_kid; + MOZ_ASSERT(propExpr->isKind(PNK_DOT)); + + if (propExpr->as<PropertyAccess>().isSuper()) { + // Still have to calculate the base, even though we are are going + // to throw unconditionally, as calculating the base could also + // throw. + if (!emit1(JSOP_SUPERBASE)) + return false; + + return emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER); + } + + JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP; + return emitPropOp(propExpr, delOp); +} + +bool +BytecodeEmitter::emitDeleteElement(ParseNode* node) +{ + MOZ_ASSERT(node->isKind(PNK_DELETEELEM)); + MOZ_ASSERT(node->isArity(PN_UNARY)); + + ParseNode* elemExpr = node->pn_kid; + MOZ_ASSERT(elemExpr->isKind(PNK_ELEM)); + + if (elemExpr->as<PropertyByValue>().isSuper()) { + // Still have to calculate everything, even though we're gonna throw + // since it may have side effects + if (!emitTree(elemExpr->pn_right)) + return false; + + if (!emit1(JSOP_SUPERBASE)) + return false; + if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) + return false; + + // Another wrinkle: Balance the stack from the emitter's point of view. + // Execution will not reach here, as the last bytecode threw. + return emit1(JSOP_POP); + } + + JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM; + return emitElemOp(elemExpr, delOp); +} + +bool +BytecodeEmitter::emitDeleteExpression(ParseNode* node) +{ + MOZ_ASSERT(node->isKind(PNK_DELETEEXPR)); + MOZ_ASSERT(node->isArity(PN_UNARY)); + + ParseNode* expression = node->pn_kid; + + // If useless, just emit JSOP_TRUE; otherwise convert |delete <expr>| to + // effectively |<expr>, true|. + bool useful = false; + if (!checkSideEffects(expression, &useful)) + return false; + + if (useful) { + if (!emitTree(expression)) + return false; + if (!emit1(JSOP_POP)) + return false; + } + + return emit1(JSOP_TRUE); +} + +static const char * +SelfHostedCallFunctionName(JSAtom* name, ExclusiveContext* cx) +{ + if (name == cx->names().callFunction) + return "callFunction"; + if (name == cx->names().callContentFunction) + return "callContentFunction"; + if (name == cx->names().constructContentFunction) + return "constructContentFunction"; + + MOZ_CRASH("Unknown self-hosted call function name"); +} + +bool +BytecodeEmitter::emitSelfHostedCallFunction(ParseNode* pn) +{ + // Special-casing of callFunction to emit bytecode that directly + // invokes the callee with the correct |this| object and arguments. + // callFunction(fun, thisArg, arg0, arg1) thus becomes: + // - emit lookup for fun + // - emit lookup for thisArg + // - emit lookups for arg0, arg1 + // + // argc is set to the amount of actually emitted args and the + // emitting of args below is disabled by setting emitArgs to false. + ParseNode* pn2 = pn->pn_head; + const char* errorName = SelfHostedCallFunctionName(pn2->name(), cx); + + if (pn->pn_count < 3) { + reportError(pn, JSMSG_MORE_ARGS_NEEDED, errorName, "2", "s"); + return false; + } + + JSOp callOp = pn->getOp(); + if (callOp != JSOP_CALL) { + reportError(pn, JSMSG_NOT_CONSTRUCTOR, errorName); + return false; + } + + bool constructing = pn2->name() == cx->names().constructContentFunction; + ParseNode* funNode = pn2->pn_next; + if (constructing) + callOp = JSOP_NEW; + else if (funNode->getKind() == PNK_NAME && funNode->name() == cx->names().std_Function_apply) + callOp = JSOP_FUNAPPLY; + + if (!emitTree(funNode)) + return false; + +#ifdef DEBUG + if (emitterMode == BytecodeEmitter::SelfHosting && + pn2->name() == cx->names().callFunction) + { + if (!emit1(JSOP_DEBUGCHECKSELFHOSTED)) + return false; + } +#endif + + ParseNode* thisOrNewTarget = funNode->pn_next; + if (constructing) { + // Save off the new.target value, but here emit a proper |this| for a + // constructing call. + if (!emit1(JSOP_IS_CONSTRUCTING)) + return false; + } else { + // It's |this|, emit it. + if (!emitTree(thisOrNewTarget)) + return false; + } + + for (ParseNode* argpn = thisOrNewTarget->pn_next; argpn; argpn = argpn->pn_next) { + if (!emitTree(argpn)) + return false; + } + + if (constructing) { + if (!emitTree(thisOrNewTarget)) + return false; + } + + uint32_t argc = pn->pn_count - 3; + if (!emitCall(callOp, argc)) + return false; + + checkTypeSet(callOp); + return true; +} + +bool +BytecodeEmitter::emitSelfHostedResumeGenerator(ParseNode* pn) +{ + // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'close') + if (pn->pn_count != 4) { + reportError(pn, JSMSG_MORE_ARGS_NEEDED, "resumeGenerator", "1", "s"); + return false; + } + + ParseNode* funNode = pn->pn_head; // The resumeGenerator node. + + ParseNode* genNode = funNode->pn_next; + if (!emitTree(genNode)) + return false; + + ParseNode* valNode = genNode->pn_next; + if (!emitTree(valNode)) + return false; + + ParseNode* kindNode = valNode->pn_next; + MOZ_ASSERT(kindNode->isKind(PNK_STRING)); + uint16_t operand = GeneratorObject::getResumeKind(cx, kindNode->pn_atom); + MOZ_ASSERT(!kindNode->pn_next); + + if (!emitCall(JSOP_RESUME, operand)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitSelfHostedForceInterpreter(ParseNode* pn) +{ + if (!emit1(JSOP_FORCEINTERPRETER)) + return false; + if (!emit1(JSOP_UNDEFINED)) + return false; + return true; +} + +bool +BytecodeEmitter::emitSelfHostedAllowContentSpread(ParseNode* pn) +{ + if (pn->pn_count != 2) { + reportError(pn, JSMSG_MORE_ARGS_NEEDED, "allowContentSpread", "1", ""); + return false; + } + + // We're just here as a sentinel. Pass the value through directly. + return emitTree(pn->pn_head->pn_next); +} + +bool +BytecodeEmitter::isRestParameter(ParseNode* pn, bool* result) +{ + if (!sc->isFunctionBox()) { + *result = false; + return true; + } + + FunctionBox* funbox = sc->asFunctionBox(); + RootedFunction fun(cx, funbox->function()); + if (!fun->hasRest()) { + *result = false; + return true; + } + + if (!pn->isKind(PNK_NAME)) { + if (emitterMode == BytecodeEmitter::SelfHosting && pn->isKind(PNK_CALL)) { + ParseNode* pn2 = pn->pn_head; + if (pn2->getKind() == PNK_NAME && pn2->name() == cx->names().allowContentSpread) + return isRestParameter(pn2->pn_next, result); + } + *result = false; + return true; + } + + JSAtom* name = pn->name(); + Maybe<NameLocation> paramLoc = locationOfNameBoundInFunctionScope(name); + if (paramLoc && lookupName(name) == *paramLoc) { + FunctionScope::Data* bindings = funbox->functionScopeBindings(); + if (bindings->nonPositionalFormalStart > 0) { + // |paramName| can be nullptr when the rest destructuring syntax is + // used: `function f(...[]) {}`. + JSAtom* paramName = bindings->names[bindings->nonPositionalFormalStart - 1].name(); + *result = paramName && name == paramName; + return true; + } + } + + return true; +} + +bool +BytecodeEmitter::emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitted) +{ + // Emit a pereparation code to optimize the spread call with a rest + // parameter: + // + // function f(...args) { + // g(...args); + // } + // + // If the spread operand is a rest parameter and it's optimizable array, + // skip spread operation and pass it directly to spread call operation. + // See the comment in OptimizeSpreadCall in Interpreter.cpp for the + // optimizable conditons. + bool result = false; + if (!isRestParameter(arg0, &result)) + return false; + + if (!result) { + *emitted = false; + return true; + } + + if (!emitTree(arg0)) + return false; + + if (!emit1(JSOP_OPTIMIZE_SPREADCALL)) + return false; + + if (!emitJump(JSOP_IFNE, jmp)) + return false; + + if (!emit1(JSOP_POP)) + return false; + + *emitted = true; + return true; +} + +bool +BytecodeEmitter::emitCallOrNew(ParseNode* pn) +{ + bool callop = pn->isKind(PNK_CALL) || pn->isKind(PNK_TAGGED_TEMPLATE); + /* + * Emit callable invocation or operator new (constructor call) code. + * First, emit code for the left operand to evaluate the callable or + * constructable object expression. + * + * For operator new, we emit JSOP_GETPROP instead of JSOP_CALLPROP, etc. + * This is necessary to interpose the lambda-initialized method read + * barrier -- see the code in jsinterp.cpp for JSOP_LAMBDA followed by + * JSOP_{SET,INIT}PROP. + * + * Then (or in a call case that has no explicit reference-base + * object) we emit JSOP_UNDEFINED to produce the undefined |this| + * value required for calls (which non-strict mode functions + * will box into the global object). + */ + uint32_t argc = pn->pn_count - 1; + + if (argc >= ARGC_LIMIT) { + parser->tokenStream.reportError(callop + ? JSMSG_TOO_MANY_FUN_ARGS + : JSMSG_TOO_MANY_CON_ARGS); + return false; + } + + ParseNode* pn2 = pn->pn_head; + bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE; + switch (pn2->getKind()) { + case PNK_NAME: + if (emitterMode == BytecodeEmitter::SelfHosting && !spread) { + // Calls to "forceInterpreter", "callFunction", + // "callContentFunction", or "resumeGenerator" in self-hosted + // code generate inline bytecode. + if (pn2->name() == cx->names().callFunction || + pn2->name() == cx->names().callContentFunction || + pn2->name() == cx->names().constructContentFunction) + { + return emitSelfHostedCallFunction(pn); + } + if (pn2->name() == cx->names().resumeGenerator) + return emitSelfHostedResumeGenerator(pn); + if (pn2->name() == cx->names().forceInterpreter) + return emitSelfHostedForceInterpreter(pn); + if (pn2->name() == cx->names().allowContentSpread) + return emitSelfHostedAllowContentSpread(pn); + // Fall through. + } + if (!emitGetName(pn2, callop)) + return false; + break; + case PNK_DOT: + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + if (pn2->as<PropertyAccess>().isSuper()) { + if (!emitSuperPropOp(pn2, JSOP_GETPROP_SUPER, /* isCall = */ callop)) + return false; + } else { + if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP)) + return false; + } + + break; + case PNK_ELEM: + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + if (pn2->as<PropertyByValue>().isSuper()) { + if (!emitSuperElemOp(pn2, JSOP_GETELEM_SUPER, /* isCall = */ callop)) + return false; + } else { + if (!emitElemOp(pn2, callop ? JSOP_CALLELEM : JSOP_GETELEM)) + return false; + if (callop) { + if (!emit1(JSOP_SWAP)) + return false; + } + } + + break; + case PNK_FUNCTION: + /* + * Top level lambdas which are immediately invoked should be + * treated as only running once. Every time they execute we will + * create new types and scripts for their contents, to increase + * the quality of type information within them and enable more + * backend optimizations. Note that this does not depend on the + * lambda being invoked at most once (it may be named or be + * accessed via foo.caller indirection), as multiple executions + * will just cause the inner scripts to be repeatedly cloned. + */ + MOZ_ASSERT(!emittingRunOnceLambda); + if (checkRunOnceContext()) { + emittingRunOnceLambda = true; + if (!emitTree(pn2)) + return false; + emittingRunOnceLambda = false; + } else { + if (!emitTree(pn2)) + return false; + } + callop = false; + break; + case PNK_SUPERBASE: + MOZ_ASSERT(pn->isKind(PNK_SUPERCALL)); + MOZ_ASSERT(parser->handler.isSuperBase(pn2)); + if (!emit1(JSOP_SUPERFUN)) + return false; + break; + default: + if (!emitTree(pn2)) + return false; + callop = false; /* trigger JSOP_UNDEFINED after */ + break; + } + + bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW || + pn->getOp() == JSOP_SUPERCALL || pn->getOp() == JSOP_SPREADSUPERCALL; + + + // Emit room for |this|. + if (!callop) { + if (isNewOp) { + if (!emit1(JSOP_IS_CONSTRUCTING)) + return false; + } else { + if (!emit1(JSOP_UNDEFINED)) + return false; + } + } + + /* + * Emit code for each argument in order, then emit the JSOP_*CALL or + * JSOP_NEW bytecode with a two-byte immediate telling how many args + * were pushed on the operand stack. + */ + if (!spread) { + for (ParseNode* pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) { + if (!emitTree(pn3)) + return false; + } + + if (isNewOp) { + if (pn->isKind(PNK_SUPERCALL)) { + if (!emit1(JSOP_NEWTARGET)) + return false; + } else { + // Repush the callee as new.target + if (!emitDupAt(argc + 1)) + return false; + } + } + } else { + ParseNode* args = pn2->pn_next; + JumpList jmp; + bool optCodeEmitted = false; + if (argc == 1) { + if (!emitOptimizeSpread(args->pn_kid, &jmp, &optCodeEmitted)) + return false; + } + + if (!emitArray(args, argc, JSOP_SPREADCALLARRAY)) + return false; + + if (optCodeEmitted) { + if (!emitJumpTargetAndPatch(jmp)) + return false; + } + + if (isNewOp) { + if (pn->isKind(PNK_SUPERCALL)) { + if (!emit1(JSOP_NEWTARGET)) + return false; + } else { + if (!emitDupAt(2)) + return false; + } + } + } + + if (!spread) { + if (!emitCall(pn->getOp(), argc, pn)) + return false; + } else { + if (!emit1(pn->getOp())) + return false; + } + checkTypeSet(pn->getOp()); + if (pn->isOp(JSOP_EVAL) || + pn->isOp(JSOP_STRICTEVAL) || + pn->isOp(JSOP_SPREADEVAL) || + pn->isOp(JSOP_STRICTSPREADEVAL)) + { + uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin); + if (!emitUint32Operand(JSOP_LINENO, lineNum)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitRightAssociative(ParseNode* pn) +{ + // ** is the only right-associative operator. + MOZ_ASSERT(pn->isKind(PNK_POW)); + MOZ_ASSERT(pn->isArity(PN_LIST)); + + // Right-associative operator chain. + for (ParseNode* subexpr = pn->pn_head; subexpr; subexpr = subexpr->pn_next) { + if (!emitTree(subexpr)) + return false; + } + for (uint32_t i = 0; i < pn->pn_count - 1; i++) { + if (!emit1(JSOP_POW)) + return false; + } + return true; +} + +bool +BytecodeEmitter::emitLeftAssociative(ParseNode* pn) +{ + MOZ_ASSERT(pn->isArity(PN_LIST)); + + // Left-associative operator chain. + if (!emitTree(pn->pn_head)) + return false; + JSOp op = pn->getOp(); + ParseNode* nextExpr = pn->pn_head->pn_next; + do { + if (!emitTree(nextExpr)) + return false; + if (!emit1(op)) + return false; + } while ((nextExpr = nextExpr->pn_next)); + return true; +} + +bool +BytecodeEmitter::emitLogical(ParseNode* pn) +{ + MOZ_ASSERT(pn->isArity(PN_LIST)); + + /* + * JSOP_OR converts the operand on the stack to boolean, leaves the original + * value on the stack and jumps if true; otherwise it falls into the next + * bytecode, which pops the left operand and then evaluates the right operand. + * The jump goes around the right operand evaluation. + * + * JSOP_AND converts the operand on the stack to boolean and jumps if false; + * otherwise it falls into the right operand's bytecode. + */ + + TDZCheckCache tdzCache(this); + + /* Left-associative operator chain: avoid too much recursion. */ + ParseNode* pn2 = pn->pn_head; + if (!emitTree(pn2)) + return false; + JSOp op = pn->getOp(); + JumpList jump; + if (!emitJump(op, &jump)) + return false; + if (!emit1(JSOP_POP)) + return false; + + /* Emit nodes between the head and the tail. */ + while ((pn2 = pn2->pn_next)->pn_next) { + if (!emitTree(pn2)) + return false; + if (!emitJump(op, &jump)) + return false; + if (!emit1(JSOP_POP)) + return false; + } + if (!emitTree(pn2)) + return false; + + if (!emitJumpTargetAndPatch(jump)) + return false; + return true; +} + +bool +BytecodeEmitter::emitSequenceExpr(ParseNode* pn) +{ + for (ParseNode* child = pn->pn_head; ; child = child->pn_next) { + if (!updateSourceCoordNotes(child->pn_pos.begin)) + return false; + if (!emitTree(child)) + return false; + if (!child->pn_next) + break; + if (!emit1(JSOP_POP)) + return false; + } + return true; +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See +// the comment on emitSwitch. +MOZ_NEVER_INLINE bool +BytecodeEmitter::emitIncOrDec(ParseNode* pn) +{ + switch (pn->pn_kid->getKind()) { + case PNK_DOT: + return emitPropIncDec(pn); + case PNK_ELEM: + return emitElemIncDec(pn); + case PNK_CALL: + return emitCallIncDec(pn); + default: + return emitNameIncDec(pn); + } + + return true; +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See +// the comment on emitSwitch. +MOZ_NEVER_INLINE bool +BytecodeEmitter::emitLabeledStatement(const LabeledStatement* pn) +{ + /* + * Emit a JSOP_LABEL instruction. The argument is the offset to the statement + * following the labeled statement. + */ + uint32_t index; + if (!makeAtomIndex(pn->label(), &index)) + return false; + + JumpList top; + if (!emitJump(JSOP_LABEL, &top)) + return false; + + /* Emit code for the labeled statement. */ + LabelControl controlInfo(this, pn->label(), offset()); + + if (!emitTree(pn->statement())) + return false; + + /* Patch the JSOP_LABEL offset. */ + JumpTarget brk{ lastNonJumpTargetOffset() }; + patchJumpsToTarget(top, brk); + + if (!controlInfo.patchBreaks(this)) + return false; + + return true; +} + +bool +BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional) +{ + /* Emit the condition, then branch if false to the else part. */ + if (!emitTree(&conditional.condition())) + return false; + + IfThenElseEmitter ifThenElse(this); + if (!ifThenElse.emitCond()) + return false; + + if (!emitConditionallyExecutedTree(&conditional.thenExpression())) + return false; + + if (!ifThenElse.emitElse()) + return false; + + if (!emitConditionallyExecutedTree(&conditional.elseExpression())) + return false; + + if (!ifThenElse.emitEnd()) + return false; + MOZ_ASSERT(ifThenElse.pushed() == 1); + + return true; +} + +bool +BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, PropListType type) +{ + for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) { + if (!updateSourceCoordNotes(propdef->pn_pos.begin)) + return false; + + // Handle __proto__: v specially because *only* this form, and no other + // involving "__proto__", performs [[Prototype]] mutation. + if (propdef->isKind(PNK_MUTATEPROTO)) { + MOZ_ASSERT(type == ObjectLiteral); + if (!emitTree(propdef->pn_kid)) + return false; + objp.set(nullptr); + if (!emit1(JSOP_MUTATEPROTO)) + return false; + continue; + } + + bool extraPop = false; + if (type == ClassBody && propdef->as<ClassMethod>().isStatic()) { + extraPop = true; + if (!emit1(JSOP_DUP2)) + return false; + if (!emit1(JSOP_POP)) + return false; + } + + /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */ + ParseNode* key = propdef->pn_left; + bool isIndex = false; + if (key->isKind(PNK_NUMBER)) { + if (!emitNumberOp(key->pn_dval)) + return false; + isIndex = true; + } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { + // EmitClass took care of constructor already. + if (type == ClassBody && key->pn_atom == cx->names().constructor && + !propdef->as<ClassMethod>().isStatic()) + { + continue; + } + + // The parser already checked for atoms representing indexes and + // used PNK_NUMBER instead, but also watch for ids which TI treats + // as indexes for simpliciation of downstream analysis. + jsid id = NameToId(key->pn_atom->asPropertyName()); + if (id != IdToTypeId(id)) { + if (!emitTree(key)) + return false; + isIndex = true; + } + } else { + if (!emitComputedPropertyName(key)) + return false; + isIndex = true; + } + + /* Emit code for the property initializer. */ + if (!emitTree(propdef->pn_right)) + return false; + + JSOp op = propdef->getOp(); + MOZ_ASSERT(op == JSOP_INITPROP || + op == JSOP_INITPROP_GETTER || + op == JSOP_INITPROP_SETTER); + + if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) + objp.set(nullptr); + + if (propdef->pn_right->isKind(PNK_FUNCTION) && + propdef->pn_right->pn_funbox->needsHomeObject()) + { + MOZ_ASSERT(propdef->pn_right->pn_funbox->function()->allowSuperProperty()); + bool isAsync = propdef->pn_right->pn_funbox->isAsync(); + if (isAsync) { + if (!emit1(JSOP_SWAP)) + return false; + } + if (!emit2(JSOP_INITHOMEOBJECT, isIndex + isAsync)) + return false; + if (isAsync) { + if (!emit1(JSOP_POP)) + return false; + } + } + + // Class methods are not enumerable. + if (type == ClassBody) { + switch (op) { + case JSOP_INITPROP: op = JSOP_INITHIDDENPROP; break; + case JSOP_INITPROP_GETTER: op = JSOP_INITHIDDENPROP_GETTER; break; + case JSOP_INITPROP_SETTER: op = JSOP_INITHIDDENPROP_SETTER; break; + default: MOZ_CRASH("Invalid op"); + } + } + + if (isIndex) { + objp.set(nullptr); + switch (op) { + case JSOP_INITPROP: op = JSOP_INITELEM; break; + case JSOP_INITHIDDENPROP: op = JSOP_INITHIDDENELEM; break; + case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break; + case JSOP_INITHIDDENPROP_GETTER: op = JSOP_INITHIDDENELEM_GETTER; break; + case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break; + case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break; + default: MOZ_CRASH("Invalid op"); + } + if (!emit1(op)) + return false; + } else { + MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)); + + uint32_t index; + if (!makeAtomIndex(key->pn_atom, &index)) + return false; + + if (objp) { + MOZ_ASSERT(type == ObjectLiteral); + MOZ_ASSERT(!IsHiddenInitOp(op)); + MOZ_ASSERT(!objp->inDictionaryMode()); + Rooted<jsid> id(cx, AtomToId(key->pn_atom)); + RootedValue undefinedValue(cx, UndefinedValue()); + if (!NativeDefineProperty(cx, objp, id, undefinedValue, nullptr, nullptr, + JSPROP_ENUMERATE)) + { + return false; + } + if (objp->inDictionaryMode()) + objp.set(nullptr); + } + + if (!emitIndex32(op, index)) + return false; + } + + if (extraPop) { + if (!emit1(JSOP_POP)) + return false; + } + } + return true; +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See +// the comment on emitSwitch. +MOZ_NEVER_INLINE bool +BytecodeEmitter::emitObject(ParseNode* pn) +{ + if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && checkSingletonContext()) + return emitSingletonInitialiser(pn); + + /* + * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing + * a new object and defining (in source order) each property on the object + * (or mutating the object's [[Prototype]], in the case of __proto__). + */ + ptrdiff_t offset = this->offset(); + if (!emitNewInit(JSProto_Object)) + return false; + + /* + * Try to construct the shape of the object as we go, so we can emit a + * JSOP_NEWOBJECT with the final shape instead. + */ + RootedPlainObject obj(cx); + // 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(pn->pn_count); + obj = NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject); + if (!obj) + return false; + + if (!emitPropertyList(pn, &obj, ObjectLiteral)) + return false; + + if (obj) { + /* + * The object survived and has a predictable shape: update the original + * bytecode. + */ + ObjectBox* objbox = parser->newObjectBox(obj); + if (!objbox) + return false; + + static_assert(JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH, + "newinit and newobject must have equal length to edit in-place"); + + uint32_t index = objectList.add(objbox); + jsbytecode* code = this->code(offset); + code[0] = JSOP_NEWOBJECT; + code[1] = jsbytecode(index >> 24); + code[2] = jsbytecode(index >> 16); + code[3] = jsbytecode(index >> 8); + code[4] = jsbytecode(index); + } + + return true; +} + +bool +BytecodeEmitter::emitArrayComp(ParseNode* pn) +{ + if (!emitNewInit(JSProto_Array)) + return false; + + /* + * Pass the new array's stack index to the PNK_ARRAYPUSH case via + * arrayCompDepth, then simply traverse the PNK_FOR node and + * its kids under pn2 to generate this comprehension. + */ + MOZ_ASSERT(stackDepth > 0); + uint32_t saveDepth = arrayCompDepth; + arrayCompDepth = (uint32_t) (stackDepth - 1); + if (!emitTree(pn->pn_head)) + return false; + arrayCompDepth = saveDepth; + + return true; +} + +bool +BytecodeEmitter::emitArrayLiteral(ParseNode* pn) +{ + if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head) { + if (checkSingletonContext()) { + // Bake in the object entirely if it will only be created once. + return emitSingletonInitialiser(pn); + } + + // If the array consists entirely of primitive values, make a + // template object with copy on write elements that can be reused + // every time the initializer executes. + if (emitterMode != BytecodeEmitter::SelfHosting && pn->pn_count != 0) { + RootedValue value(cx); + if (!pn->getConstantValue(cx, ParseNode::ForCopyOnWriteArray, &value)) + return false; + if (!value.isMagic(JS_GENERIC_MAGIC)) { + // Note: the group of the template object might not yet reflect + // that the object has copy on write elements. When the + // interpreter or JIT compiler fetches the template, it should + // use ObjectGroup::getOrFixupCopyOnWriteObject to make sure the + // group for the template is accurate. We don't do this here as we + // want to use ObjectGroup::allocationSiteGroup, which requires a + // finished script. + JSObject* obj = &value.toObject(); + MOZ_ASSERT(obj->is<ArrayObject>() && + obj->as<ArrayObject>().denseElementsAreCopyOnWrite()); + + ObjectBox* objbox = parser->newObjectBox(obj); + if (!objbox) + return false; + + return emitObjectOp(objbox, JSOP_NEWARRAY_COPYONWRITE); + } + } + } + + return emitArray(pn->pn_head, pn->pn_count, JSOP_NEWARRAY); +} + +bool +BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) +{ + + /* + * Emit code for [a, b, c] that is equivalent to constructing a new + * array and in source order evaluating each element value and adding + * it to the array, without invoking latent setters. We use the + * JSOP_NEWINIT and JSOP_INITELEM_ARRAY bytecodes to ignore setters and + * to avoid dup'ing and popping the array as each element is added, as + * JSOP_SETELEM/JSOP_SETPROP would do. + */ + MOZ_ASSERT(op == JSOP_NEWARRAY || op == JSOP_SPREADCALLARRAY); + + uint32_t nspread = 0; + for (ParseNode* elt = pn; elt; elt = elt->pn_next) { + if (elt->isKind(PNK_SPREAD)) + nspread++; + } + + // Array literal's length is limited to NELEMENTS_LIMIT in parser. + static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX, + "array literals' maximum length must not exceed limits " + "required by BaselineCompiler::emit_JSOP_NEWARRAY, " + "BaselineCompiler::emit_JSOP_INITELEM_ARRAY, " + "and DoSetElemFallback's handling of JSOP_INITELEM_ARRAY"); + MOZ_ASSERT(count >= nspread); + MOZ_ASSERT(count <= NativeObject::MAX_DENSE_ELEMENTS_COUNT, + "the parser must throw an error if the array exceeds maximum " + "length"); + + // For arrays with spread, this is a very pessimistic allocation, the + // minimum possible final size. + if (!emitUint32Operand(op, count - nspread)) // ARRAY + return false; + + ParseNode* pn2 = pn; + uint32_t index; + bool afterSpread = false; + for (index = 0; pn2; index++, pn2 = pn2->pn_next) { + if (!afterSpread && pn2->isKind(PNK_SPREAD)) { + afterSpread = true; + if (!emitNumberOp(index)) // ARRAY INDEX + return false; + } + if (!updateSourceCoordNotes(pn2->pn_pos.begin)) + return false; + + bool allowSelfHostedSpread = false; + if (pn2->isKind(PNK_ELISION)) { + if (!emit1(JSOP_HOLE)) + return false; + } else { + ParseNode* expr; + if (pn2->isKind(PNK_SPREAD)) { + expr = pn2->pn_kid; + + if (emitterMode == BytecodeEmitter::SelfHosting && + expr->isKind(PNK_CALL) && + expr->pn_head->name() == cx->names().allowContentSpread) + { + allowSelfHostedSpread = true; + } + } else { + expr = pn2; + } + if (!emitTree(expr)) // ARRAY INDEX? VALUE + return false; + } + if (pn2->isKind(PNK_SPREAD)) { + if (!emitIterator()) // ARRAY INDEX ITER + return false; + if (!emit2(JSOP_PICK, 2)) // INDEX ITER ARRAY + return false; + if (!emit2(JSOP_PICK, 2)) // ITER ARRAY INDEX + return false; + if (!emitSpread(allowSelfHostedSpread)) // ARRAY INDEX + return false; + } else if (afterSpread) { + if (!emit1(JSOP_INITELEM_INC)) + return false; + } else { + if (!emitUint32Operand(JSOP_INITELEM_ARRAY, index)) + return false; + } + } + MOZ_ASSERT(index == count); + if (afterSpread) { + if (!emit1(JSOP_POP)) // ARRAY + return false; + } + return true; +} + +bool +BytecodeEmitter::emitUnary(ParseNode* pn) +{ + if (!updateSourceCoordNotes(pn->pn_pos.begin)) + return false; + + /* Unary op, including unary +/-. */ + JSOp op = pn->getOp(); + ParseNode* pn2 = pn->pn_kid; + + if (!emitTree(pn2)) + return false; + + return emit1(op); +} + +bool +BytecodeEmitter::emitTypeof(ParseNode* node, JSOp op) +{ + MOZ_ASSERT(op == JSOP_TYPEOF || op == JSOP_TYPEOFEXPR); + + if (!updateSourceCoordNotes(node->pn_pos.begin)) + return false; + + if (!emitTree(node->pn_kid)) + return false; + + return emit1(op); +} + +bool +BytecodeEmitter::emitFunctionFormalParametersAndBody(ParseNode *pn) +{ + MOZ_ASSERT(pn->isKind(PNK_PARAMSBODY)); + + ParseNode* funBody = pn->last(); + FunctionBox* funbox = sc->asFunctionBox(); + + TDZCheckCache tdzCache(this); + + if (funbox->hasParameterExprs) { + EmitterScope funEmitterScope(this); + if (!funEmitterScope.enterFunction(this, funbox)) + return false; + + if (!emitInitializeFunctionSpecialNames()) + return false; + + if (!emitFunctionFormalParameters(pn)) + return false; + + { + Maybe<EmitterScope> extraVarEmitterScope; + + if (funbox->hasExtraBodyVarScope()) { + extraVarEmitterScope.emplace(this); + if (!extraVarEmitterScope->enterFunctionExtraBodyVar(this, funbox)) + return false; + + // After emitting expressions for all parameters, copy over any + // formal parameters which have been redeclared as vars. For + // example, in the following, the var y in the body scope is 42: + // + // function f(x, y = 42) { var y; } + // + RootedAtom name(cx); + if (funbox->extraVarScopeBindings() && funbox->functionScopeBindings()) { + for (BindingIter bi(*funbox->functionScopeBindings(), true); bi; bi++) { + name = bi.name(); + + // There may not be a var binding of the same name. + if (!locationOfNameBoundInScope(name, extraVarEmitterScope.ptr())) + continue; + + // The '.this' and '.generator' function special + // bindings should never appear in the extra var + // scope. 'arguments', however, may. + MOZ_ASSERT(name != cx->names().dotThis && + name != cx->names().dotGenerator); + + NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope); + auto emitRhs = [&name, ¶mLoc](BytecodeEmitter* bce, + const NameLocation&, bool) + { + return bce->emitGetNameAtLocation(name, paramLoc); + }; + + if (!emitInitializeName(name, emitRhs)) + return false; + if (!emit1(JSOP_POP)) + return false; + } + } + } + + if (!emitFunctionBody(funBody)) + return false; + + if (extraVarEmitterScope && !extraVarEmitterScope->leave(this)) + return false; + } + + return funEmitterScope.leave(this); + } + + // No parameter expressions. Enter the function body scope and emit + // everything. + // + // One caveat is that Debugger considers ops in the prologue to be + // unreachable (i.e. cannot set a breakpoint on it). If there are no + // parameter exprs, any unobservable environment ops (like pushing the + // call object, setting '.this', etc) need to go in the prologue, else it + // messes up breakpoint tests. + EmitterScope emitterScope(this); + + switchToPrologue(); + if (!emitterScope.enterFunction(this, funbox)) + return false; + + if (!emitInitializeFunctionSpecialNames()) + return false; + switchToMain(); + + if (!emitFunctionFormalParameters(pn)) + return false; + + if (!emitFunctionBody(funBody)) + return false; + + return emitterScope.leave(this); +} + +bool +BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) +{ + ParseNode* funBody = pn->last(); + FunctionBox* funbox = sc->asFunctionBox(); + EmitterScope* funScope = innermostEmitterScope; + + bool hasParameterExprs = funbox->hasParameterExprs; + bool hasRest = funbox->function()->hasRest(); + + uint16_t argSlot = 0; + for (ParseNode* arg = pn->pn_head; arg != funBody; arg = arg->pn_next, argSlot++) { + ParseNode* bindingElement = arg; + ParseNode* initializer = nullptr; + if (arg->isKind(PNK_ASSIGN)) { + bindingElement = arg->pn_left; + initializer = arg->pn_right; + } + + // Left-hand sides are either simple names or destructuring patterns. + MOZ_ASSERT(bindingElement->isKind(PNK_NAME) || + bindingElement->isKind(PNK_ARRAY) || + bindingElement->isKind(PNK_ARRAYCOMP) || + bindingElement->isKind(PNK_OBJECT)); + + // The rest parameter doesn't have an initializer. + bool isRest = hasRest && arg->pn_next == funBody; + MOZ_ASSERT_IF(isRest, !initializer); + + bool isDestructuring = !bindingElement->isKind(PNK_NAME); + + // ES 14.1.19 says if BindingElement contains an expression in the + // production FormalParameter : BindingElement, it is evaluated in a + // new var environment. This is needed to prevent vars from escaping + // direct eval in parameter expressions. + Maybe<EmitterScope> paramExprVarScope; + if (funbox->hasDirectEvalInParameterExpr && (isDestructuring || initializer)) { + paramExprVarScope.emplace(this); + if (!paramExprVarScope->enterParameterExpressionVar(this)) + return false; + } + + // First push the RHS if there is a default expression or if it is + // rest. + + if (initializer) { + // If we have an initializer, emit the initializer and assign it + // to the argument slot. TDZ is taken care of afterwards. + MOZ_ASSERT(hasParameterExprs); + if (!emitArgOp(JSOP_GETARG, argSlot)) + return false; + if (!emit1(JSOP_DUP)) + return false; + if (!emit1(JSOP_UNDEFINED)) + return false; + if (!emit1(JSOP_STRICTEQ)) + return false; + // Emit source note to enable Ion compilation. + if (!newSrcNote(SRC_IF)) + return false; + JumpList jump; + if (!emitJump(JSOP_IFEQ, &jump)) + return false; + if (!emit1(JSOP_POP)) + return false; + if (!emitConditionallyExecutedTree(initializer)) + return false; + if (!emitJumpTargetAndPatch(jump)) + return false; + } else if (isRest) { + if (!emit1(JSOP_REST)) + return false; + checkTypeSet(JSOP_REST); + } + + // Initialize the parameter name. + + if (isDestructuring) { + // If we had an initializer or the rest parameter, the value is + // already on the stack. + if (!initializer && !isRest && !emitArgOp(JSOP_GETARG, argSlot)) + return false; + + // If there's an parameter expression var scope, the destructuring + // declaration needs to initialize the name in the function scope, + // which is not the innermost scope. + if (!emitDestructuringOps(bindingElement, + paramExprVarScope + ? DestructuringFormalParameterInVarScope + : DestructuringDeclaration)) + { + return false; + } + + if (!emit1(JSOP_POP)) + return false; + } else { + RootedAtom paramName(cx, bindingElement->name()); + NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope); + + if (hasParameterExprs) { + auto emitRhs = [argSlot, initializer, isRest](BytecodeEmitter* bce, + const NameLocation&, bool) + { + // If we had an initializer or a rest parameter, the value is + // already on the stack. + if (!initializer && !isRest) + return bce->emitArgOp(JSOP_GETARG, argSlot); + return true; + }; + + if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, emitRhs, true)) + return false; + if (!emit1(JSOP_POP)) + return false; + } else if (isRest) { + // The rest value is already on top of the stack. + auto nop = [](BytecodeEmitter*, const NameLocation&, bool) { + return true; + }; + + if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, nop, true)) + return false; + if (!emit1(JSOP_POP)) + return false; + } + } + + if (paramExprVarScope) { + if (!paramExprVarScope->leave(this)) + return false; + } + } + + return true; +} + +bool +BytecodeEmitter::emitInitializeFunctionSpecialNames() +{ + FunctionBox* funbox = sc->asFunctionBox(); + + auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, HandlePropertyName name, + JSOp op) + { + // A special name must be slotful, either on the frame or on the + // call environment. + MOZ_ASSERT(bce->lookupName(name).hasKnownSlot()); + + auto emitInitial = [op](BytecodeEmitter* bce, const NameLocation&, bool) { + return bce->emit1(op); + }; + + if (!bce->emitInitializeName(name, emitInitial)) + return false; + if (!bce->emit1(JSOP_POP)) + return false; + + return true; + }; + + // Do nothing if the function doesn't have an arguments binding. + if (funbox->argumentsHasLocalBinding()) { + if (!emitInitializeFunctionSpecialName(this, cx->names().arguments, JSOP_ARGUMENTS)) + return false; + } + + // Do nothing if the function doesn't have a this-binding (this + // happens for instance if it doesn't use this/eval or if it's an + // arrow function). + if (funbox->hasThisBinding()) { + if (!emitInitializeFunctionSpecialName(this, cx->names().dotThis, JSOP_FUNCTIONTHIS)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitFunctionBody(ParseNode* funBody) +{ + FunctionBox* funbox = sc->asFunctionBox(); + + if (!emitTree(funBody)) + return false; + + if (funbox->isGenerator()) { + // If we fall off the end of a generator, do a final yield. + if (funbox->isStarGenerator() && !emitPrepareIteratorResult()) + return false; + + if (!emit1(JSOP_UNDEFINED)) + return false; + + if (sc->asFunctionBox()->isStarGenerator() && !emitFinishIteratorResult(true)) + return false; + + if (!emit1(JSOP_SETRVAL)) + return false; + + NameLocation loc = *locationOfNameBoundInFunctionScope(cx->names().dotGenerator); + if (!emitGetNameAtLocation(cx->names().dotGenerator, loc)) + return false; + + // No need to check for finally blocks, etc as in EmitReturn. + if (!emitYieldOp(JSOP_FINALYIELDRVAL)) + return false; + } else { + // Non-generator functions just return |undefined|. The + // JSOP_RETRVAL emitted below will do that, except if the + // script has a finally block: there can be a non-undefined + // value in the return value slot. Make sure the return value + // is |undefined|. + if (hasTryFinally) { + if (!emit1(JSOP_UNDEFINED)) + return false; + if (!emit1(JSOP_SETRVAL)) + return false; + } + } + + if (funbox->isDerivedClassConstructor()) { + if (!emitCheckDerivedClassConstructorReturn()) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitLexicalInitialization(ParseNode* pn) +{ + // The caller has pushed the RHS to the top of the stack. Assert that the + // name is lexical and no BIND[G]NAME ops were emitted. + auto assertLexical = [](BytecodeEmitter*, const NameLocation& loc, bool emittedBindOp) { + MOZ_ASSERT(loc.isLexical()); + MOZ_ASSERT(!emittedBindOp); + return true; + }; + return emitInitializeName(pn, assertLexical); +} + +// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 +// (BindingClassDeclarationEvaluation). +bool +BytecodeEmitter::emitClass(ParseNode* pn) +{ + ClassNode& classNode = pn->as<ClassNode>(); + + ClassNames* names = classNode.names(); + + ParseNode* heritageExpression = classNode.heritage(); + + ParseNode* classMethods = classNode.methodList(); + ParseNode* constructor = nullptr; + for (ParseNode* mn = classMethods->pn_head; mn; mn = mn->pn_next) { + ClassMethod& method = mn->as<ClassMethod>(); + ParseNode& methodName = method.name(); + if (!method.isStatic() && + (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) && + methodName.pn_atom == cx->names().constructor) + { + constructor = &method.method(); + break; + } + } + + bool savedStrictness = sc->setLocalStrictMode(true); + + Maybe<TDZCheckCache> tdzCache; + Maybe<EmitterScope> emitterScope; + if (names) { + tdzCache.emplace(this); + emitterScope.emplace(this); + if (!emitterScope->enterLexical(this, ScopeKind::Lexical, classNode.scopeBindings())) + return false; + } + + // This is kind of silly. In order to the get the home object defined on + // the constructor, we have to make it second, but we want the prototype + // on top for EmitPropertyList, because we expect static properties to be + // rarer. The result is a few more swaps than we would like. Such is life. + if (heritageExpression) { + if (!emitTree(heritageExpression)) + return false; + if (!emit1(JSOP_CLASSHERITAGE)) + return false; + if (!emit1(JSOP_OBJWITHPROTO)) + return false; + + // JSOP_CLASSHERITAGE leaves both protos on the stack. After + // creating the prototype, swap it to the bottom to make the + // constructor. + if (!emit1(JSOP_SWAP)) + return false; + } else { + if (!emitNewInit(JSProto_Object)) + return false; + } + + if (constructor) { + if (!emitFunction(constructor, !!heritageExpression)) + return false; + if (constructor->pn_funbox->needsHomeObject()) { + if (!emit2(JSOP_INITHOMEOBJECT, 0)) + return false; + } + } else { + JSAtom *name = names ? names->innerBinding()->pn_atom : cx->names().empty; + if (heritageExpression) { + if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) + return false; + } else { + if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) + return false; + } + } + + if (!emit1(JSOP_SWAP)) + return false; + + if (!emit1(JSOP_DUP2)) + return false; + if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) + return false; + if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) + return false; + + RootedPlainObject obj(cx); + if (!emitPropertyList(classMethods, &obj, ClassBody)) + return false; + + if (!emit1(JSOP_POP)) + return false; + + if (names) { + ParseNode* innerName = names->innerBinding(); + if (!emitLexicalInitialization(innerName)) + return false; + + // Pop the inner scope. + if (!emitterScope->leave(this)) + return false; + emitterScope.reset(); + + ParseNode* outerName = names->outerBinding(); + if (outerName) { + if (!emitLexicalInitialization(outerName)) + return false; + // Only class statements make outer bindings, and they do not leave + // themselves on the stack. + if (!emit1(JSOP_POP)) + return false; + } + } + + MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness)); + + return true; +} + +bool +BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) +{ + JS_CHECK_RECURSION(cx, return false); + + EmitLevelManager elm(this); + + /* Emit notes to tell the current bytecode's source line number. + However, a couple trees require special treatment; see the + relevant emitter functions for details. */ + if (emitLineNote == EMIT_LINENOTE && !ParseNodeRequiresSpecialLineNumberNotes(pn)) { + if (!updateLineNumberNotes(pn->pn_pos.begin)) + return false; + } + + switch (pn->getKind()) { + case PNK_FUNCTION: + if (!emitFunction(pn)) + return false; + break; + + case PNK_PARAMSBODY: + if (!emitFunctionFormalParametersAndBody(pn)) + return false; + break; + + case PNK_IF: + if (!emitIf(pn)) + return false; + break; + + case PNK_SWITCH: + if (!emitSwitch(pn)) + return false; + break; + + case PNK_WHILE: + if (!emitWhile(pn)) + return false; + break; + + case PNK_DOWHILE: + if (!emitDo(pn)) + return false; + break; + + case PNK_FOR: + if (!emitFor(pn)) + return false; + break; + + case PNK_COMPREHENSIONFOR: + if (!emitComprehensionFor(pn)) + return false; + break; + + case PNK_BREAK: + if (!emitBreak(pn->as<BreakStatement>().label())) + return false; + break; + + case PNK_CONTINUE: + if (!emitContinue(pn->as<ContinueStatement>().label())) + return false; + break; + + case PNK_WITH: + if (!emitWith(pn)) + return false; + break; + + case PNK_TRY: + if (!emitTry(pn)) + return false; + break; + + case PNK_CATCH: + if (!emitCatch(pn)) + return false; + break; + + case PNK_VAR: + if (!emitDeclarationList(pn)) + return false; + break; + + case PNK_RETURN: + if (!emitReturn(pn)) + return false; + break; + + case PNK_YIELD_STAR: + if (!emitYieldStar(pn->pn_left, pn->pn_right)) + return false; + break; + + case PNK_GENERATOR: + if (!emit1(JSOP_GENERATOR)) + return false; + break; + + case PNK_YIELD: + case PNK_AWAIT: + if (!emitYield(pn)) + return false; + break; + + case PNK_STATEMENTLIST: + if (!emitStatementList(pn)) + return false; + break; + + case PNK_SEMI: + if (!emitStatement(pn)) + return false; + break; + + case PNK_LABEL: + if (!emitLabeledStatement(&pn->as<LabeledStatement>())) + return false; + break; + + case PNK_COMMA: + if (!emitSequenceExpr(pn)) + return false; + break; + + case PNK_ASSIGN: + case PNK_ADDASSIGN: + case PNK_SUBASSIGN: + case PNK_BITORASSIGN: + case PNK_BITXORASSIGN: + case PNK_BITANDASSIGN: + case PNK_LSHASSIGN: + case PNK_RSHASSIGN: + case PNK_URSHASSIGN: + case PNK_MULASSIGN: + case PNK_DIVASSIGN: + case PNK_MODASSIGN: + case PNK_POWASSIGN: + if (!emitAssignment(pn->pn_left, pn->getOp(), pn->pn_right)) + return false; + break; + + case PNK_CONDITIONAL: + if (!emitConditionalExpression(pn->as<ConditionalExpression>())) + return false; + break; + + case PNK_OR: + case PNK_AND: + if (!emitLogical(pn)) + return false; + break; + + case PNK_ADD: + case PNK_SUB: + case PNK_BITOR: + case PNK_BITXOR: + case PNK_BITAND: + case PNK_STRICTEQ: + case PNK_EQ: + case PNK_STRICTNE: + case PNK_NE: + case PNK_LT: + case PNK_LE: + case PNK_GT: + case PNK_GE: + case PNK_IN: + case PNK_INSTANCEOF: + case PNK_LSH: + case PNK_RSH: + case PNK_URSH: + case PNK_STAR: + case PNK_DIV: + case PNK_MOD: + if (!emitLeftAssociative(pn)) + return false; + break; + + case PNK_POW: + if (!emitRightAssociative(pn)) + return false; + break; + + case PNK_TYPEOFNAME: + if (!emitTypeof(pn, JSOP_TYPEOF)) + return false; + break; + + case PNK_TYPEOFEXPR: + if (!emitTypeof(pn, JSOP_TYPEOFEXPR)) + return false; + break; + + case PNK_THROW: + case PNK_VOID: + case PNK_NOT: + case PNK_BITNOT: + case PNK_POS: + case PNK_NEG: + if (!emitUnary(pn)) + return false; + break; + + case PNK_PREINCREMENT: + case PNK_PREDECREMENT: + case PNK_POSTINCREMENT: + case PNK_POSTDECREMENT: + if (!emitIncOrDec(pn)) + return false; + break; + + case PNK_DELETENAME: + if (!emitDeleteName(pn)) + return false; + break; + + case PNK_DELETEPROP: + if (!emitDeleteProperty(pn)) + return false; + break; + + case PNK_DELETEELEM: + if (!emitDeleteElement(pn)) + return false; + break; + + case PNK_DELETEEXPR: + if (!emitDeleteExpression(pn)) + return false; + break; + + case PNK_DOT: + if (pn->as<PropertyAccess>().isSuper()) { + if (!emitSuperPropOp(pn, JSOP_GETPROP_SUPER)) + return false; + } else { + if (!emitPropOp(pn, JSOP_GETPROP)) + return false; + } + break; + + case PNK_ELEM: + if (pn->as<PropertyByValue>().isSuper()) { + if (!emitSuperElemOp(pn, JSOP_GETELEM_SUPER)) + return false; + } else { + if (!emitElemOp(pn, JSOP_GETELEM)) + return false; + } + break; + + case PNK_NEW: + case PNK_TAGGED_TEMPLATE: + case PNK_CALL: + case PNK_GENEXP: + case PNK_SUPERCALL: + if (!emitCallOrNew(pn)) + return false; + break; + + case PNK_LEXICALSCOPE: + if (!emitLexicalScope(pn)) + return false; + break; + + case PNK_CONST: + case PNK_LET: + if (!emitDeclarationList(pn)) + return false; + break; + + case PNK_IMPORT: + MOZ_ASSERT(sc->isModuleContext()); + break; + + case PNK_EXPORT: + MOZ_ASSERT(sc->isModuleContext()); + if (pn->pn_kid->getKind() != PNK_EXPORT_SPEC_LIST) { + if (!emitTree(pn->pn_kid)) + return false; + } + break; + + case PNK_EXPORT_DEFAULT: + MOZ_ASSERT(sc->isModuleContext()); + if (!emitTree(pn->pn_kid)) + return false; + if (pn->pn_right) { + if (!emitLexicalInitialization(pn->pn_right)) + return false; + if (!emit1(JSOP_POP)) + return false; + } + break; + + case PNK_EXPORT_FROM: + MOZ_ASSERT(sc->isModuleContext()); + break; + + case PNK_ARRAYPUSH: + /* + * The array object's stack index is in arrayCompDepth. See below + * under the array initialiser code generator for array comprehension + * special casing. + */ + if (!emitTree(pn->pn_kid)) + return false; + if (!emitDupAt(this->stackDepth - 1 - arrayCompDepth)) + return false; + if (!emit1(JSOP_ARRAYPUSH)) + return false; + break; + + case PNK_CALLSITEOBJ: + if (!emitCallSiteObject(pn)) + return false; + break; + + case PNK_ARRAY: + if (!emitArrayLiteral(pn)) + return false; + break; + + case PNK_ARRAYCOMP: + if (!emitArrayComp(pn)) + return false; + break; + + case PNK_OBJECT: + if (!emitObject(pn)) + return false; + break; + + case PNK_NAME: + if (!emitGetName(pn)) + return false; + break; + + case PNK_TEMPLATE_STRING_LIST: + if (!emitTemplateString(pn)) + return false; + break; + + case PNK_TEMPLATE_STRING: + case PNK_STRING: + if (!emitAtomOp(pn, JSOP_STRING)) + return false; + break; + + case PNK_NUMBER: + if (!emitNumberOp(pn->pn_dval)) + return false; + break; + + case PNK_REGEXP: + if (!emitRegExp(objectList.add(pn->as<RegExpLiteral>().objbox()))) + return false; + break; + + case PNK_TRUE: + case PNK_FALSE: + case PNK_NULL: + if (!emit1(pn->getOp())) + return false; + break; + + case PNK_THIS: + if (!emitThisLiteral(pn)) + return false; + break; + + case PNK_DEBUGGER: + if (!updateSourceCoordNotes(pn->pn_pos.begin)) + return false; + if (!emit1(JSOP_DEBUGGER)) + return false; + break; + + case PNK_NOP: + MOZ_ASSERT(pn->getArity() == PN_NULLARY); + break; + + case PNK_CLASS: + if (!emitClass(pn)) + return false; + break; + + case PNK_NEWTARGET: + if (!emit1(JSOP_NEWTARGET)) + return false; + break; + + case PNK_SETTHIS: + if (!emitSetThis(pn)) + return false; + break; + + case PNK_POSHOLDER: + MOZ_FALLTHROUGH_ASSERT("Should never try to emit PNK_POSHOLDER"); + + default: + MOZ_ASSERT(0); + } + + /* bce->emitLevel == 1 means we're last on the stack, so finish up. */ + if (emitLevel == 1) { + if (!updateSourceCoordNotes(pn->pn_pos.end)) + return false; + } + return true; +} + +bool +BytecodeEmitter::emitConditionallyExecutedTree(ParseNode* pn) +{ + // Code that may be conditionally executed always need their own TDZ + // cache. + TDZCheckCache tdzCache(this); + return emitTree(pn); +} + +static bool +AllocSrcNote(ExclusiveContext* cx, SrcNotesVector& notes, unsigned* index) +{ + // Start it off moderately large to avoid repeated resizings early on. + // ~99% of cases fit within 256 bytes. + if (notes.capacity() == 0 && !notes.reserve(256)) + return false; + + if (!notes.growBy(1)) { + ReportOutOfMemory(cx); + return false; + } + + *index = notes.length() - 1; + return true; +} + +bool +BytecodeEmitter::newSrcNote(SrcNoteType type, unsigned* indexp) +{ + SrcNotesVector& notes = this->notes(); + unsigned index; + if (!AllocSrcNote(cx, notes, &index)) + return false; + + /* + * Compute delta from the last annotated bytecode's offset. If it's too + * big to fit in sn, allocate one or more xdelta notes and reset sn. + */ + ptrdiff_t offset = this->offset(); + ptrdiff_t delta = offset - lastNoteOffset(); + current->lastNoteOffset = offset; + if (delta >= SN_DELTA_LIMIT) { + do { + ptrdiff_t xdelta = Min(delta, SN_XDELTA_MASK); + SN_MAKE_XDELTA(¬es[index], xdelta); + delta -= xdelta; + if (!AllocSrcNote(cx, notes, &index)) + return false; + } while (delta >= SN_DELTA_LIMIT); + } + + /* + * Initialize type and delta, then allocate the minimum number of notes + * needed for type's arity. Usually, we won't need more, but if an offset + * does take two bytes, setSrcNoteOffset will grow notes. + */ + SN_MAKE_NOTE(¬es[index], type, delta); + for (int n = (int)js_SrcNoteSpec[type].arity; n > 0; n--) { + if (!newSrcNote(SRC_NULL)) + return false; + } + + if (indexp) + *indexp = index; + return true; +} + +bool +BytecodeEmitter::newSrcNote2(SrcNoteType type, ptrdiff_t offset, unsigned* indexp) +{ + unsigned index; + if (!newSrcNote(type, &index)) + return false; + if (!setSrcNoteOffset(index, 0, offset)) + return false; + if (indexp) + *indexp = index; + return true; +} + +bool +BytecodeEmitter::newSrcNote3(SrcNoteType type, ptrdiff_t offset1, ptrdiff_t offset2, + unsigned* indexp) +{ + unsigned index; + if (!newSrcNote(type, &index)) + return false; + if (!setSrcNoteOffset(index, 0, offset1)) + return false; + if (!setSrcNoteOffset(index, 1, offset2)) + return false; + if (indexp) + *indexp = index; + return true; +} + +bool +BytecodeEmitter::addToSrcNoteDelta(jssrcnote* sn, ptrdiff_t delta) +{ + /* + * Called only from finishTakingSrcNotes to add to main script note + * deltas, and only by a small positive amount. + */ + MOZ_ASSERT(current == &main); + MOZ_ASSERT((unsigned) delta < (unsigned) SN_XDELTA_LIMIT); + + ptrdiff_t base = SN_DELTA(sn); + ptrdiff_t limit = SN_IS_XDELTA(sn) ? SN_XDELTA_LIMIT : SN_DELTA_LIMIT; + ptrdiff_t newdelta = base + delta; + if (newdelta < limit) { + SN_SET_DELTA(sn, newdelta); + } else { + jssrcnote xdelta; + SN_MAKE_XDELTA(&xdelta, delta); + if (!main.notes.insert(sn, xdelta)) + return false; + } + return true; +} + +bool +BytecodeEmitter::setSrcNoteOffset(unsigned index, unsigned which, ptrdiff_t offset) +{ + if (!SN_REPRESENTABLE_OFFSET(offset)) { + parser->tokenStream.reportError(JSMSG_NEED_DIET, js_script_str); + return false; + } + + SrcNotesVector& notes = this->notes(); + + /* Find the offset numbered which (i.e., skip exactly which offsets). */ + jssrcnote* sn = ¬es[index]; + MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA); + MOZ_ASSERT((int) which < js_SrcNoteSpec[SN_TYPE(sn)].arity); + for (sn++; which; sn++, which--) { + if (*sn & SN_4BYTE_OFFSET_FLAG) + sn += 3; + } + + /* + * See if the new offset requires four bytes either by being too big or if + * the offset has already been inflated (in which case, we need to stay big + * to not break the srcnote encoding if this isn't the last srcnote). + */ + if (offset > (ptrdiff_t)SN_4BYTE_OFFSET_MASK || (*sn & SN_4BYTE_OFFSET_FLAG)) { + /* Maybe this offset was already set to a four-byte value. */ + if (!(*sn & SN_4BYTE_OFFSET_FLAG)) { + /* Insert three dummy bytes that will be overwritten shortly. */ + jssrcnote dummy = 0; + if (!(sn = notes.insert(sn, dummy)) || + !(sn = notes.insert(sn, dummy)) || + !(sn = notes.insert(sn, dummy))) + { + ReportOutOfMemory(cx); + return false; + } + } + *sn++ = (jssrcnote)(SN_4BYTE_OFFSET_FLAG | (offset >> 24)); + *sn++ = (jssrcnote)(offset >> 16); + *sn++ = (jssrcnote)(offset >> 8); + } + *sn = (jssrcnote)offset; + return true; +} + +bool +BytecodeEmitter::finishTakingSrcNotes(uint32_t* out) +{ + MOZ_ASSERT(current == &main); + + unsigned prologueCount = prologue.notes.length(); + if (prologueCount && prologue.currentLine != firstLine) { + switchToPrologue(); + if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(firstLine))) + return false; + switchToMain(); + } else { + /* + * Either no prologue srcnotes, or no line number change over prologue. + * We don't need a SRC_SETLINE, but we may need to adjust the offset + * of the first main note, by adding to its delta and possibly even + * prepending SRC_XDELTA notes to it to account for prologue bytecodes + * that came at and after the last annotated bytecode. + */ + ptrdiff_t offset = prologueOffset() - prologue.lastNoteOffset; + MOZ_ASSERT(offset >= 0); + if (offset > 0 && main.notes.length() != 0) { + /* NB: Use as much of the first main note's delta as we can. */ + jssrcnote* sn = main.notes.begin(); + ptrdiff_t delta = SN_IS_XDELTA(sn) + ? SN_XDELTA_MASK - (*sn & SN_XDELTA_MASK) + : SN_DELTA_MASK - (*sn & SN_DELTA_MASK); + if (offset < delta) + delta = offset; + for (;;) { + if (!addToSrcNoteDelta(sn, delta)) + return false; + offset -= delta; + if (offset == 0) + break; + delta = Min(offset, SN_XDELTA_MASK); + sn = main.notes.begin(); + } + } + } + + // The prologue count might have changed, so we can't reuse prologueCount. + // The + 1 is to account for the final SN_MAKE_TERMINATOR that is appended + // when the notes are copied to their final destination by CopySrcNotes. + *out = prologue.notes.length() + main.notes.length() + 1; + return true; +} + +void +BytecodeEmitter::copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes) +{ + unsigned prologueCount = prologue.notes.length(); + unsigned mainCount = main.notes.length(); + unsigned totalCount = prologueCount + mainCount; + MOZ_ASSERT(totalCount == nsrcnotes - 1); + if (prologueCount) + PodCopy(destination, prologue.notes.begin(), prologueCount); + PodCopy(destination + prologueCount, main.notes.begin(), mainCount); + SN_MAKE_TERMINATOR(&destination[totalCount]); +} + +void +CGConstList::finish(ConstArray* array) +{ + MOZ_ASSERT(length() == array->length); + + for (unsigned i = 0; i < length(); i++) + array->vector[i] = list[i]; +} + +bool +CGObjectList::isAdded(ObjectBox* objbox) +{ + // An objbox added to CGObjectList as non-first element has non-null + // emitLink member. The first element has null emitLink. + // Check for firstbox to cover the first element. + return objbox->emitLink || objbox == firstbox; +} + +/* + * Find the index of the given object for code generator. + * + * Since the emitter refers to each parsed object only once, for the index we + * use the number of already indexed objects. We also add the object to a list + * to convert the list to a fixed-size array when we complete code generation, + * see js::CGObjectList::finish below. + */ +unsigned +CGObjectList::add(ObjectBox* objbox) +{ + if (isAdded(objbox)) + return indexOf(objbox->object); + + objbox->emitLink = lastbox; + lastbox = objbox; + + // See the comment in CGObjectList::isAdded. + if (!firstbox) + firstbox = objbox; + return length++; +} + +unsigned +CGObjectList::indexOf(JSObject* obj) +{ + MOZ_ASSERT(length > 0); + unsigned index = length - 1; + for (ObjectBox* box = lastbox; box->object != obj; box = box->emitLink) + index--; + return index; +} + +void +CGObjectList::finish(ObjectArray* array) +{ + MOZ_ASSERT(length <= INDEX_LIMIT); + MOZ_ASSERT(length == array->length); + + js::GCPtrObject* cursor = array->vector + array->length; + ObjectBox* objbox = lastbox; + do { + --cursor; + MOZ_ASSERT(!*cursor); + MOZ_ASSERT(objbox->object->isTenured()); + *cursor = objbox->object; + + ObjectBox* tmp = objbox->emitLink; + // Clear emitLink for CGObjectList::isAdded. + objbox->emitLink = nullptr; + objbox = tmp; + } while (objbox != nullptr); + MOZ_ASSERT(cursor == array->vector); +} + +ObjectBox* +CGObjectList::find(uint32_t index) +{ + MOZ_ASSERT(index < length); + ObjectBox* box = lastbox; + for (unsigned n = length - 1; n > index; n--) + box = box->emitLink; + return box; +} + +void +CGScopeList::finish(ScopeArray* array) +{ + MOZ_ASSERT(length() <= INDEX_LIMIT); + MOZ_ASSERT(length() == array->length); + for (uint32_t i = 0; i < length(); i++) + array->vector[i].init(vector[i]); +} + +bool +CGTryNoteList::append(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end) +{ + MOZ_ASSERT(start <= end); + MOZ_ASSERT(size_t(uint32_t(start)) == start); + MOZ_ASSERT(size_t(uint32_t(end)) == end); + + JSTryNote note; + note.kind = kind; + note.stackDepth = stackDepth; + note.start = uint32_t(start); + note.length = uint32_t(end - start); + + return list.append(note); +} + +void +CGTryNoteList::finish(TryNoteArray* array) +{ + MOZ_ASSERT(length() == array->length); + + for (unsigned i = 0; i < length(); i++) + array->vector[i] = list[i]; +} + +bool +CGScopeNoteList::append(uint32_t scopeIndex, uint32_t offset, bool inPrologue, + uint32_t parent) +{ + CGScopeNote note; + mozilla::PodZero(¬e); + + note.index = scopeIndex; + note.start = offset; + note.parent = parent; + note.startInPrologue = inPrologue; + + return list.append(note); +} + +void +CGScopeNoteList::recordEnd(uint32_t index, uint32_t offset, bool inPrologue) +{ + MOZ_ASSERT(index < length()); + MOZ_ASSERT(list[index].length == 0); + list[index].end = offset; + list[index].endInPrologue = inPrologue; +} + +void +CGScopeNoteList::finish(ScopeNoteArray* array, uint32_t prologueLength) +{ + MOZ_ASSERT(length() == array->length); + + for (unsigned i = 0; i < length(); i++) { + if (!list[i].startInPrologue) + list[i].start += prologueLength; + if (!list[i].endInPrologue && list[i].end != UINT32_MAX) + list[i].end += prologueLength; + MOZ_ASSERT(list[i].end >= list[i].start); + list[i].length = list[i].end - list[i].start; + array->vector[i] = list[i]; + } +} + +void +CGYieldOffsetList::finish(YieldOffsetArray& array, uint32_t prologueLength) +{ + MOZ_ASSERT(length() == array.length()); + + for (unsigned i = 0; i < length(); i++) + array[i] = prologueLength + list[i]; +} + +/* + * We should try to get rid of offsetBias (always 0 or 1, where 1 is + * JSOP_{NOP,POP}_LENGTH), which is used only by SRC_FOR. + */ +const JSSrcNoteSpec js_SrcNoteSpec[] = { +#define DEFINE_SRC_NOTE_SPEC(sym, name, arity) { name, arity }, + FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_SPEC) +#undef DEFINE_SRC_NOTE_SPEC +}; + +static int +SrcNoteArity(jssrcnote* sn) +{ + MOZ_ASSERT(SN_TYPE(sn) < SRC_LAST); + return js_SrcNoteSpec[SN_TYPE(sn)].arity; +} + +JS_FRIEND_API(unsigned) +js::SrcNoteLength(jssrcnote* sn) +{ + unsigned arity; + jssrcnote* base; + + arity = SrcNoteArity(sn); + for (base = sn++; arity; sn++, arity--) { + if (*sn & SN_4BYTE_OFFSET_FLAG) + sn += 3; + } + return sn - base; +} + +JS_FRIEND_API(ptrdiff_t) +js::GetSrcNoteOffset(jssrcnote* sn, unsigned which) +{ + /* Find the offset numbered which (i.e., skip exactly which offsets). */ + MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA); + MOZ_ASSERT((int) which < SrcNoteArity(sn)); + for (sn++; which; sn++, which--) { + if (*sn & SN_4BYTE_OFFSET_FLAG) + sn += 3; + } + if (*sn & SN_4BYTE_OFFSET_FLAG) { + return (ptrdiff_t)(((uint32_t)(sn[0] & SN_4BYTE_OFFSET_MASK) << 24) + | (sn[1] << 16) + | (sn[2] << 8) + | sn[3]); + } + return (ptrdiff_t)*sn; +} |