summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/BytecodeEmitter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/frontend/BytecodeEmitter.cpp')
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp10170
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, &noteIndex))
+ 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, &noteIndex))
+ 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, &noteIndex_))
+ 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, &noteIndex))
+ 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, &noteIndex))
+ 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, &noteIndex))
+ 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, &noteIndex))
+ 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, &noteIndex))
+ 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, &noteIndex))
+ 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, &noteIndex))
+ 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, &noteIndex))
+ return false;
+ if (!emit1(JSOP_NOP))
+ return false;
+
+ unsigned noteIndex2;
+ if (!newSrcNote(SRC_WHILE, &noteIndex2))
+ 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, &noteIndex))
+ 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, &noteIndex))
+ 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, &paramLoc](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(&notes[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(&notes[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 = &notes[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(&note);
+
+ 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;
+}