From 81691afbc33a053582b6ecf1e811935ccef8bd2e Mon Sep 17 00:00:00 2001 From: Martok Date: Thu, 23 Mar 2023 23:24:00 +0100 Subject: [PATCH] Issue #2155 - Split Nestable to ds/ and EmitterScope to EmitterScope.{cpp.h} Based-on: m-c 1351107/17, m-c 1460489/2 --- js/src/ds/Nestable.h | 66 ++ js/src/frontend/BytecodeEmitter.cpp | 1172 +-------------------------- js/src/frontend/BytecodeEmitter.h | 2 +- js/src/frontend/EmitterScope.cpp | 1092 +++++++++++++++++++++++++ js/src/frontend/EmitterScope.h | 155 ++++ js/src/frontend/Parser.h | 1 + js/src/frontend/SharedContext.h | 50 -- js/src/frontend/TDZCheckCache.h | 2 +- js/src/moz.build | 1 + 9 files changed, 1323 insertions(+), 1218 deletions(-) create mode 100644 js/src/ds/Nestable.h create mode 100644 js/src/frontend/EmitterScope.cpp create mode 100644 js/src/frontend/EmitterScope.h diff --git a/js/src/ds/Nestable.h b/js/src/ds/Nestable.h new file mode 100644 index 0000000000..50712cb7f0 --- /dev/null +++ b/js/src/ds/Nestable.h @@ -0,0 +1,66 @@ +/* -*- 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/. */ + +#ifndef ds_Nestable_h +#define ds_Nestable_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" + +namespace js { + +// A base class for nestable structures. +template +class MOZ_STACK_CLASS Nestable +{ + Concrete** stack_; + Concrete* enclosing_; + + protected: + explicit Nestable(Concrete** stack) + : stack_(stack), + enclosing_(*stack) + { + *stack_ = static_cast(this); + } + + // These method are protected. Some derived classes, such as ParseContext, + // do not expose the ability to walk the stack. + Concrete* enclosing() const { + return enclosing_; + } + + template bool */> + static Concrete* findNearest(Concrete* it, Predicate predicate) { + while (it && !predicate(it)) + it = it->enclosing(); + return it; + } + + template + static T* findNearest(Concrete* it) { + while (it && !it->template is()) + it = it->enclosing(); + return it ? &it->template as() : nullptr; + } + + template bool */> + static T* findNearest(Concrete* it, Predicate predicate) { + while (it && (!it->template is() || !predicate(&it->template as()))) + it = it->enclosing(); + return it ? &it->template as() : nullptr; + } + + public: + ~Nestable() { + MOZ_ASSERT(*stack_ == static_cast(this)); + *stack_ = enclosing_; + } +}; + +} // namespace js + +#endif /* ds_Nestable_h */ diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 4b80a99669..d309b4d6b2 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -27,8 +27,10 @@ #include "jstypes.h" #include "jsutil.h" +#include "ds/Nestable.h" #include "frontend/CallOrNewEmitter.h" #include "frontend/ElemOpEmitter.h" +#include "frontend/EmitterScope.h" #include "frontend/IfEmitter.h" #include "frontend/NameOpEmitter.h" #include "frontend/Parser.h" @@ -303,1165 +305,6 @@ class TryFinallyControl : public BytecodeEmitter::NestableControl } }; -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) -{ - TrailingNamesArray& names = data.trailingNames; - 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 -{ - // 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 nameCache_; - - // If this scope's cache does not include free names, such as the - // global scope, the NameLocation to return. - Maybe 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 - 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(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()->isStarGenerator() || - bce->sc->asFunctionBox()->isLegacyGenerator() || - bce->sc->asFunctionBox()->isAsync()), - 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 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)->innermostEmitterScopeNoCheck(); - } - - 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 - MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope); - template - 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(&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 bindings); - MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox); - MOZ_MUST_USE bool enterComprehensionFor(BytecodeEmitter* bce, - Handle 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::enclosing(); - } - - NameLocation lookup(BytecodeEmitter* bce, JSAtom* name) { - if (Maybe loc = lookupInCache(bce, name)) - return *loc; - return searchAndCache(bce, name); - } - - Maybe 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 -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 -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().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().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()) - 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 loc; - uint8_t hops = hasEnvironment() ? 1 : 0; - DebugOnly 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 -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 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 bindings) -{ - MOZ_ASSERT(kind != ScopeKind::NamedLambda && kind != ScopeKind::StrictNamedLambda); - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - - 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->innermostEmitterScopeNoCheck()); - 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 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(); - NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) - return false; - if (!noe.emitAssignment()) - return false; - } - - if (!bce->emit1(JSOP_POP)) - return false; - - return true; -} - -bool -BytecodeEmitter::EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce) -{ - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - - 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->innermostEmitterScopeNoCheck()); - - // 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 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->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->innermostEmitterScopeNoCheck()); - - // 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->innermostEmitterScopeNoCheck()); - - 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->innermostEmitterScopeNoCheck()); - - 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()) - fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); - } - - return true; -} - -bool -BytecodeEmitter::EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedContext* modulesc) -{ - MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); - - bce->setVarEmitterScope(this); - - if (!ensureCache(bce)) - return false; - - // Resolve body-level bindings, if there are any. - TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; - Maybe 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->innermostEmitterScopeNoCheck()); - - 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->innermostEmitterScopeNoCheck()); - - 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; -} - class MOZ_STACK_CLASS TryEmitter { public: @@ -1867,8 +710,6 @@ class MOZ_RAII OptionalEmitter class ForOfLoopControl : public LoopControl { - using EmitterScope = BytecodeEmitter::EmitterScope; - // The stack depth of the iterator. int32_t iterDepth_; @@ -2147,7 +988,7 @@ BytecodeEmitter::lookupName(JSAtom* name) Maybe BytecodeEmitter::locationOfNameBoundInScope(JSAtom* name, EmitterScope* target) { - return innermostEmitterScope()->locationBoundInScope(this, name, target); + return innermostEmitterScope()->locationBoundInScope(name, target); } Maybe @@ -2156,7 +997,7 @@ BytecodeEmitter::locationOfNameBoundInFunctionScope(JSAtom* name, EmitterScope* EmitterScope* funScope = source; while (!funScope->scope(this)->is()) funScope = funScope->enclosingInFrame(); - return source->locationBoundInScope(this, name, funScope); + return source->locationBoundInScope(name, funScope); } bool @@ -2605,7 +1446,7 @@ class NonLocalExitControl NonLocalExitControl(const NonLocalExitControl&) = delete; - MOZ_MUST_USE bool leaveScope(BytecodeEmitter::EmitterScope* scope); + MOZ_MUST_USE bool leaveScope(EmitterScope* scope); public: NonLocalExitControl(BytecodeEmitter* bce, Kind kind) @@ -2630,7 +1471,7 @@ class NonLocalExitControl }; bool -NonLocalExitControl::leaveScope(BytecodeEmitter::EmitterScope* es) +NonLocalExitControl::leaveScope(EmitterScope* es) { if (!es->leave(bce_, /* nonLocal = */ true)) return false; @@ -2656,7 +1497,6 @@ bool NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* target) { using NestableControl = BytecodeEmitter::NestableControl; - using EmitterScope = BytecodeEmitter::EmitterScope; EmitterScope* es = bce_->innermostEmitterScope(); int npops = 0; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 47400658dc..eae080b2b5 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -122,13 +122,13 @@ typedef Vector SrcNotesVector; class CallOrNewEmitter; class ElemOpEmitter; +class EmitterScope; class PropOpEmitter; class TDZCheckCache; struct MOZ_STACK_CLASS BytecodeEmitter { class NestableControl; - class EmitterScope; SharedContext* const sc; /* context shared between parsing and bytecode generation */ diff --git a/js/src/frontend/EmitterScope.cpp b/js/src/frontend/EmitterScope.cpp new file mode 100644 index 0000000000..d6ebfe265f --- /dev/null +++ b/js/src/frontend/EmitterScope.cpp @@ -0,0 +1,1092 @@ +/* -*- 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/. */ + +#include "frontend/EmitterScope.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/NameOpEmitter.h" +#include "frontend/TDZCheckCache.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +EmitterScope::EmitterScope(BytecodeEmitter* bce) + : Nestable(&bce->innermostEmitterScope_), + nameCache_(bce->cx->frontendCollectionPool()), + hasEnvironment_(false), + environmentChainLength_(0), + nextFrameSlot_(0), + scopeIndex_(ScopeNote::NoScopeIndex), + noteIndex_(ScopeNote::NoScopeNoteIndex) +{} + +static inline void +MarkAllBindingsClosedOver(LexicalScope::Data& data) +{ + TrailingNamesArray& names = data.trailingNames; + for (uint32_t i = 0; i < data.length; i++) + names[i] = BindingName(names[i].name(), true); +} + +static bool +ScopeKindIsInBody(ScopeKind kind) +{ + return kind == ScopeKind::Lexical || + kind == ScopeKind::SimpleCatch || + kind == ScopeKind::Catch || + kind == ScopeKind::With || + kind == ScopeKind::FunctionBodyVar || + kind == ScopeKind::ParameterExpressionVar; +} + +bool +EmitterScope::ensureCache(BytecodeEmitter* bce) +{ + return nameCache_.acquire(bce->cx); +} + +template +bool +EmitterScope::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; +} + +bool +EmitterScope::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(hops + 1); + return true; +} + +void +EmitterScope::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()->isStarGenerator() || + bce->sc->asFunctionBox()->isLegacyGenerator() || + bce->sc->asFunctionBox()->isAsync()), + bce->maxFixedSlots == 0); +} + +bool +EmitterScope::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 +EmitterScope::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(); +} + +EmitterScope* +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)->innermostEmitterScopeNoCheck(); + } + + return nullptr; +} + +Scope* +EmitterScope::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 +EmitterScope::nameCanBeFree(BytecodeEmitter* bce, JSAtom* name) +{ + // '.generator' cannot be accessed by name. + return name != bce->cx->names().dotGenerator; +} + +#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().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 +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().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()) + 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 +EmitterScope::searchAndCache(BytecodeEmitter* bce, JSAtom* name) +{ + Maybe loc; + uint8_t hops = hasEnvironment() ? 1 : 0; + DebugOnly 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; +} + +template +bool +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 +bool +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 +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); +} + +bool +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; +} + +void +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"); +} + +bool +EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind, + Handle bindings) +{ + MOZ_ASSERT(kind != ScopeKind::NamedLambda && kind != ScopeKind::StrictNamedLambda); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + 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 +EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + 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 +EmitterScope::enterComprehensionFor(BytecodeEmitter* bce, + Handle 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(); + NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) + return false; + if (!noe.emitAssignment()) + return false; + } + + if (!bce->emit1(JSOP_POP)) + return false; + + return true; +} + +bool +EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + // 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 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->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 +EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox) +{ + MOZ_ASSERT(funbox->hasParameterExprs); + MOZ_ASSERT(funbox->extraVarScopeBindings() || + funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings()); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + // 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); +} + +bool +EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + 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); +} + +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 +EmitterScope::enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + 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 +EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + 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()) + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + } + + return true; +} + +bool +EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedContext* modulesc) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) + return false; + + // Resolve body-level bindings, if there are any. + TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; + Maybe 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 +EmitterScope::enterWith(BytecodeEmitter* bce) +{ + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + 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 +EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) +{ + return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd()); +} + +bool +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->innermostEmitterScopeNoCheck()); + + 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; +} + +Scope* +EmitterScope::scope(const BytecodeEmitter* bce) const +{ + return bce->scopeList.vector[index()]; +} + +NameLocation +EmitterScope::lookup(BytecodeEmitter* bce, JSAtom* name) +{ + if (Maybe loc = lookupInCache(bce, name)) + return *loc; + return searchAndCache(bce, name); +} + +Maybe +EmitterScope::locationBoundInScope(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 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; +} diff --git a/js/src/frontend/EmitterScope.h b/js/src/frontend/EmitterScope.h new file mode 100644 index 0000000000..cfee86a214 --- /dev/null +++ b/js/src/frontend/EmitterScope.h @@ -0,0 +1,155 @@ +/* -*- 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/. */ + +#ifndef frontend_EmitterScope_h +#define frontend_EmitterScope_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include + +#include "ds/Nestable.h" +#include "frontend/NameAnalysisTypes.h" +#include "frontend/NameCollections.h" +#include "frontend/SharedContext.h" +#include "jstypes.h" +#include "vm/Scope.h" + +namespace js { +namespace frontend { + +// A scope that introduces bindings. +class EmitterScope : public Nestable +{ + // 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 nameCache_; + + // If this scope's cache does not include free names, such as the + // global scope, the NameLocation to return. + mozilla::Maybe 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); + + template + MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce, const BindingIter& bi); + + MOZ_MUST_USE bool checkEnvironmentChainLength(BytecodeEmitter* bce); + + void updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi); + + MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name, NameLocation loc); + + mozilla::Maybe lookupInCache(BytecodeEmitter* bce, JSAtom* name); + + EmitterScope* enclosing(BytecodeEmitter** bce) const; + + Scope* enclosingScope(BytecodeEmitter* bce) const; + + static bool nameCanBeFree(BytecodeEmitter* bce, JSAtom* name); + + static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops); + NameLocation searchAndCache(BytecodeEmitter* bce, JSAtom* name); + + template + MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope); + template + 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); + + void dump(BytecodeEmitter* bce); + + MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind, + Handle bindings); + MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox); + MOZ_MUST_USE bool enterComprehensionFor(BytecodeEmitter* bce, + Handle 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; + + 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::enclosing(); + } + + NameLocation lookup(BytecodeEmitter* bce, JSAtom* name); + + mozilla::Maybe locationBoundInScope(JSAtom* name, EmitterScope* target); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_EmitterScope_h */ diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 1e9028cbee..54eb7f4168 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -14,6 +14,7 @@ #include "jsiter.h" #include "jspubtd.h" +#include "ds/Nestable.h" #include "frontend/BytecodeCompiler.h" #include "frontend/FullParseHandler.h" #include "frontend/NameAnalysisTypes.h" diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index d9cdbcaf95..ab90953194 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -60,56 +60,6 @@ StatementKindIsUnlabeledBreakTarget(StatementKind kind) return StatementKindIsLoop(kind) || kind == StatementKind::Switch; } -// A base class for nestable structures in the frontend, such as statements -// and scopes. -template -class MOZ_STACK_CLASS Nestable -{ - Concrete** stack_; - Concrete* enclosing_; - - protected: - explicit Nestable(Concrete** stack) - : stack_(stack), - enclosing_(*stack) - { - *stack_ = static_cast(this); - } - - // These method are protected. Some derived classes, such as ParseContext, - // do not expose the ability to walk the stack. - Concrete* enclosing() const { - return enclosing_; - } - - template bool */> - static Concrete* findNearest(Concrete* it, Predicate predicate) { - while (it && !predicate(it)) - it = it->enclosing(); - return it; - } - - template - static T* findNearest(Concrete* it) { - while (it && !it->template is()) - it = it->enclosing(); - return it ? &it->template as() : nullptr; - } - - template bool */> - static T* findNearest(Concrete* it, Predicate predicate) { - while (it && (!it->template is() || !predicate(&it->template as()))) - it = it->enclosing(); - return it ? &it->template as() : nullptr; - } - - public: - ~Nestable() { - MOZ_ASSERT(*stack_ == static_cast(this)); - *stack_ = enclosing_; - } -}; - // These flags apply to both global and function contexts. class AnyContextFlags { diff --git a/js/src/frontend/TDZCheckCache.h b/js/src/frontend/TDZCheckCache.h index 0789cbeeaf..29ccefda14 100644 --- a/js/src/frontend/TDZCheckCache.h +++ b/js/src/frontend/TDZCheckCache.h @@ -10,7 +10,7 @@ #include "mozilla/Attributes.h" #include "mozilla/Maybe.h" -#include "frontend/SharedContext.h" // for Nestable +#include "ds/Nestable.h" #include "frontend/NameCollections.h" #include "js/TypeDecls.h" #include "vm/Stack.h" diff --git a/js/src/moz.build b/js/src/moz.build index 1754f1d2cd..59038c7230 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -142,6 +142,7 @@ UNIFIED_SOURCES += [ 'frontend/BytecodeEmitter.cpp', 'frontend/CallOrNewEmitter.cpp', 'frontend/ElemOpEmitter.cpp', + 'frontend/EmitterScope.cpp', 'frontend/FoldConstants.cpp', 'frontend/IfEmitter.cpp', 'frontend/JumpList.cpp',