From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/frontend/EmitterScope.cpp | 1116 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1116 insertions(+) create mode 100644 js/src/frontend/EmitterScope.cpp (limited to 'js/src/frontend/EmitterScope.cpp') diff --git a/js/src/frontend/EmitterScope.cpp b/js/src/frontend/EmitterScope.cpp new file mode 100644 index 0000000000..840c8c361e --- /dev/null +++ b/js/src/frontend/EmitterScope.cpp @@ -0,0 +1,1116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "frontend/EmitterScope.h" + +#include "frontend/AbstractScopePtr.h" +#include "frontend/BytecodeEmitter.h" +#include "frontend/ModuleSharedContext.h" +#include "frontend/TDZCheckCache.h" +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "vm/EnvironmentObject.h" // ClassBodyLexicalEnvironmentObject + +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->fc->nameCollectionPool()), + hasEnvironment_(false), + environmentChainLength_(0), + nextFrameSlot_(0), + scopeIndex_(ScopeNote::NoScopeIndex), + noteIndex_(ScopeNote::NoScopeNoteIndex) {} + +bool EmitterScope::ensureCache(BytecodeEmitter* bce) { + return nameCache_.acquire(bce->fc); +} + +bool EmitterScope::checkSlotLimits(BytecodeEmitter* bce, + const ParserBindingIter& bi) { + if (bi.nextFrameSlot() >= LOCALNO_LIMIT || + bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT) { + bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); + return false; + } + return true; +} + +bool EmitterScope::checkEnvironmentChainLength(BytecodeEmitter* bce) { + uint32_t hops; + if (EmitterScope* emitterScope = enclosing(&bce)) { + hops = emitterScope->environmentChainLength_; + } else if (!bce->compilationState.input.enclosingScope.isNull()) { + hops = + bce->compilationState.scopeContext.enclosingScopeEnvironmentChainLength; + } else { + // If we're compiling module, enclosingScope is nullptr and it means empty + // global scope. + // See also the assertion in CompilationStencil::instantiateStencils. + // + // Global script also uses enclosingScope == nullptr, but it shouldn't call + // checkEnvironmentChainLength. + MOZ_ASSERT(bce->sc->isModule()); + hops = ModuleScope::EnclosingEnvironmentChainLength; + } + + if (hops >= ENVCOORD_HOPS_LIMIT - 1) { + bce->reportError(nullptr, JSMSG_TOO_DEEP, "function"); + return false; + } + + environmentChainLength_ = mozilla::AssertedCast(hops + 1); + return true; +} + +void EmitterScope::updateFrameFixedSlots(BytecodeEmitter* bce, + const ParserBindingIter& bi) { + nextFrameSlot_ = bi.nextFrameSlot(); + if (nextFrameSlot_ > bce->maxFixedSlots) { + bce->maxFixedSlots = nextFrameSlot_; + } +} + +bool EmitterScope::putNameInCache(BytecodeEmitter* bce, + TaggedParserAtomIndex name, + NameLocation loc) { + NameLocationMap& cache = *nameCache_; + NameLocationMap::AddPtr p = cache.lookupForAdd(name); + MOZ_ASSERT(!p); + if (!cache.add(p, name, loc)) { + ReportOutOfMemory(bce->fc); + return false; + } + return true; +} + +Maybe EmitterScope::lookupInCache(BytecodeEmitter* bce, + TaggedParserAtomIndex 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; +} + +mozilla::Maybe EmitterScope::enclosingScopeIndex( + BytecodeEmitter* bce) const { + if (EmitterScope* es = enclosing(&bce)) { + // NOTE: A value of Nothing for the ScopeIndex will occur when the enclosing + // scope is the empty-global-scope. This is only allowed for self-hosting + // code. + MOZ_ASSERT_IF(es->scopeIndex(bce).isNothing(), + bce->emitterMode == BytecodeEmitter::SelfHosting); + return es->scopeIndex(bce); + } + + // The enclosing script is already compiled or the current script is the + // global script. + return mozilla::Nothing(); +} + +/* static */ +bool EmitterScope::nameCanBeFree(BytecodeEmitter* bce, + TaggedParserAtomIndex name) { + // '.generator' cannot be accessed by name. + return name != TaggedParserAtomIndex::WellKnown::dot_generator_(); +} + +NameLocation EmitterScope::searchAndCache(BytecodeEmitter* bce, + TaggedParserAtomIndex 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) { + MOZ_ASSERT(bce->compilationState.input.target == + CompilationInput::CompilationTarget::Delazification || + bce->compilationState.input.target == + CompilationInput::CompilationTarget::Eval); + inCurrentScript = false; + loc = Some(bce->compilationState.scopeContext.searchInEnclosingScope( + bce->fc, bce->compilationState.input, bce->parserAtoms(), name)); + if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate) { + *loc = loc->addHops(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->fc->recoverFromOutOfMemory(); + } + + return *loc; +} + +bool EmitterScope::internEmptyGlobalScopeAsBody(BytecodeEmitter* bce) { + // Only the self-hosted top-level script uses this. If this changes, you must + // update ScopeStencil::enclosing. + MOZ_ASSERT(bce->emitterMode == BytecodeEmitter::SelfHosting); + + hasEnvironment_ = Scope::hasEnvironment(ScopeKind::Global); + + bce->bodyScopeIndex = + GCThingIndex(bce->perScriptData().gcThingList().length()); + return bce->perScriptData().gcThingList().appendEmptyGlobalScope( + &scopeIndex_); +} + +bool EmitterScope::internScopeStencil(BytecodeEmitter* bce, + ScopeIndex scopeIndex) { + ScopeStencil& scope = bce->compilationState.scopeData[scopeIndex.index]; + hasEnvironment_ = scope.hasEnvironment(); + return bce->perScriptData().gcThingList().append(scopeIndex, &scopeIndex_); +} + +bool EmitterScope::internBodyScopeStencil(BytecodeEmitter* bce, + ScopeIndex scopeIndex) { + MOZ_ASSERT(bce->bodyScopeIndex == ScopeNote::NoScopeIndex, + "There can be only one body scope"); + bce->bodyScopeIndex = + GCThingIndex(bce->perScriptData().gcThingList().length()); + return internScopeStencil(bce, scopeIndex); +} + +bool EmitterScope::appendScopeNote(BytecodeEmitter* bce) { + MOZ_ASSERT(ScopeKindIsInBody(scope(bce).kind()) && enclosingInFrame(), + "Scope notes are not needed for body-level scopes."); + noteIndex_ = bce->bytecodeSection().scopeNoteList().length(); + return bce->bytecodeSection().scopeNoteList().append( + index(), bce->bytecodeSection().offset(), + enclosingInFrame() ? enclosingInFrame()->noteIndex() + : ScopeNote::NoScopeNoteIndex); +} + +bool EmitterScope::clearFrameSlotRange(BytecodeEmitter* bce, JSOp opcode, + uint32_t slotStart, + uint32_t slotEnd) const { + MOZ_ASSERT(opcode == JSOp::Uninitialized || opcode == JSOp::Undefined); + + // 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. + // + // This code is also used to reset `var`s to `undefined` when entering an + // extra body var scope; and to clear slots when leaving a block, in + // generators and async functions, to avoid keeping garbage alive + // indefinitely. + if (slotStart != slotEnd) { + if (!bce->emit1(opcode)) { + 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(); + + auto atom = r.front().key(); + UniqueChars bytes = bce->parserAtoms().toPrintableString(atom); + if (!bytes) { + ReportOutOfMemory(bce->fc); + return; + } + if (l.kind() != NameLocation::Kind::Dynamic) { + fprintf(stdout, " %s %s ", BindingKindString(l.bindingKind()), + bytes.get()); + } else { + fprintf(stdout, " %s ", bytes.get()); + } + + 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::DebugEnvironmentCoordinate: + fprintf(stdout, "debugEnvironment 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, + LexicalScope::ParserData* bindings) { + MOZ_ASSERT(kind != ScopeKind::NamedLambda && + kind != ScopeKind::StrictNamedLambda); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + if (!ensureCache(bce)) { + return false; + } + + // Resolve bindings. + TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; + uint32_t firstFrameSlot = frameSlotStart(); + ParserBindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + + if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) { + return false; + } + } + + updateFrameFixedSlots(bce, bi); + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForLexicalScope( + bce->fc, bce->compilationState, kind, bindings, firstFrameSlot, + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internScopeStencil(bce, scopeIndex)) { + 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::enterClassBody(BytecodeEmitter* bce, ScopeKind kind, + ClassBodyScope::ParserData* bindings) { + MOZ_ASSERT(kind == ScopeKind::ClassBody); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + if (!ensureCache(bce)) { + return false; + } + + // Resolve bindings. + TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; + uint32_t firstFrameSlot = frameSlotStart(); + ParserBindingIter bi(*bindings, firstFrameSlot); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + + if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) { + return false; + } + } + + updateFrameFixedSlots(bce, bi); + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForClassBodyScope( + bce->fc, bce->compilationState, kind, bindings, firstFrameSlot, + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internScopeStencil(bce, scopeIndex)) { + return false; + } + + if (ScopeKindIsInBody(kind) && hasEnvironment()) { + // After interning the VM scope we can get the scope index. + // + // ClassBody uses PushClassBodyEnv, however, PopLexicalEnv supports both + // cases and doesn't need extra specialization. + if (!bce->emitInternedScopeOp(index(), JSOp::PushClassBodyEnv)) { + return false; + } + } + + // Lexical scopes need notes to be mapped from a pc. + if (!appendScopeNote(bce)) { + 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; + } + + ParserBindingIter 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 = bi.nameLocation(); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + + bi++; + MOZ_ASSERT(!bi, "There should be exactly one binding in a NamedLambda scope"); + + ScopeKind scopeKind = + funbox->strict() ? ScopeKind::StrictNamedLambda : ScopeKind::NamedLambda; + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForLexicalScope( + bce->fc, bce->compilationState, scopeKind, + funbox->namedLambdaBindings(), LOCALNO_LIMIT, + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internScopeStencil(bce, scopeIndex)) { + return false; + } + + return checkEnvironmentChainLength(bce); +} + +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->functionHasExtraBodyVarScope()) { + bce->setVarEmitterScope(this); + } + + if (!ensureCache(bce)) { + return false; + } + + // Resolve body-level bindings, if there are any. + auto bindings = funbox->functionScopeBindings(); + if (bindings) { + NameLocationMap& cache = *nameCache_; + + ParserBindingIter bi(*bindings, funbox->hasParameterExprs); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + 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->fc); + return false; + } + } + + updateFrameFixedSlots(bce, bi); + } else { + nextFrameSlot_ = 0; + } + + // If the function's scope may be extended at runtime due to sloppy direct + // eval, 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->funHasExtensibleScope()) { + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + } else if (funbox->isStandalone) { + // If the function is standalone, the enclosing scope is either an empty + // global or non-syntactic scope, and there's no static bindings. + if (bce->compilationState.input.target == + CompilationInput::CompilationTarget:: + StandaloneFunctionInNonSyntacticScope) { + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + } else { + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + } + } + + // In case of parameter expressions, the parameters are lexical + // bindings and have TDZ. + if (funbox->hasParameterExprs && nextFrameSlot_) { + uint32_t paramFrameSlotEnd = 0; + for (ParserBindingIter bi(*bindings, true); bi; bi++) { + if (!BindingKindIsLexical(bi.kind())) { + break; + } + + NameLocation loc = bi.nameLocation(); + if (loc.kind() == NameLocation::Kind::FrameSlot) { + MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot()); + paramFrameSlotEnd = loc.frameSlot() + 1; + } + } + + if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd)) { + return false; + } + } + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForFunctionScope( + bce->fc, bce->compilationState, funbox->functionScopeBindings(), + funbox->hasParameterExprs, + funbox->needsCallObjectRegardlessOfBindings(), funbox->index(), + funbox->isArrow(), enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internBodyScopeStencil(bce, scopeIndex)) { + 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()) { + ParserBindingIter bi(*bindings, firstFrameSlot); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + MOZ_ASSERT(bi.kind() == BindingKind::Var); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + } + + uint32_t priorEnd = bce->maxFixedSlots; + updateFrameFixedSlots(bce, bi); + + // If any of the bound slots were previously used, reset them to undefined. + // This doesn't break TDZ for let/const/class bindings because there aren't + // any in extra body var scopes. We assert above that bi.kind() is Var. + uint32_t end = std::min(priorEnd, nextFrameSlot_); + if (firstFrameSlot < end) { + if (!clearFrameSlotRange(bce, JSOp::Undefined, firstFrameSlot, end)) { + return false; + } + } + } 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->funHasExtensibleScope()) { + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + } + + // Create and intern the VM scope. + ScopeIndex scopeIndex; + if (!ScopeStencil::createForVarScope( + bce->fc, bce->compilationState, ScopeKind::FunctionBodyVar, + funbox->extraVarScopeBindings(), firstFrameSlot, + funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(), + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internScopeStencil(bce, scopeIndex)) { + 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::enterGlobal(BytecodeEmitter* bce, + GlobalSharedContext* globalsc) { + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + // TODO-Stencil + // This is another snapshot-sensitive location. + // The incoming atoms from the global scope object should be snapshotted. + // For now, converting them to ParserAtoms here individually. + + 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()); + + return internEmptyGlobalScopeAsBody(bce); + } + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForGlobalScope(bce->fc, bce->compilationState, + globalsc->scopeKind(), + globalsc->bindings, &scopeIndex)) { + return false; + } + if (!internBodyScopeStencil(bce, scopeIndex)) { + return false; + } + + // See: JSScript::outermostScope. + MOZ_ASSERT(bce->bodyScopeIndex == GCThingIndex::outermostScopeIndex(), + "Global scope must be index 0"); + + // Resolve binding names. + // + // NOTE: BytecodeEmitter::emitDeclarationInstantiation will emit the + // redeclaration check and initialize these bindings. + if (globalsc->bindings) { + for (ParserBindingIter bi(*globalsc->bindings); bi; bi++) { + NameLocation loc = bi.nameLocation(); + if (!putNameInCache(bce, bi.name(), loc)) { + 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()); + } + + return true; +} + +bool EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc) { + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) { + return false; + } + + // Create the `var` scope. Note that there is also a lexical scope, created + // separately in emitScript(). + ScopeKind scopeKind = + evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval; + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForEvalScope( + bce->fc, bce->compilationState, scopeKind, evalsc->bindings, + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internBodyScopeStencil(bce, scopeIndex)) { + return false; + } + + if (evalsc->strict()) { + if (evalsc->bindings) { + ParserBindingIter bi(*evalsc->bindings, true); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + } + + updateFrameFixedSlots(bce, bi); + } + } else { + // For simplicity, treat all free name lookups in nonstrict eval scripts as + // dynamic. + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + } + + if (hasEnvironment()) { + if (!bce->emitInternedScopeOp(index(), JSOp::PushVarEnv)) { + return false; + } + } else { + // NOTE: BytecodeEmitter::emitDeclarationInstantiation will emit the + // redeclaration check and initialize these bindings for sloppy + // eval. + + // 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 checkEnvironmentChainLength(bce); +} + +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::ParserData* bindings = modulesc->bindings) { + ParserBindingIter bi(*bindings); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + 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 creation data. + ScopeIndex scopeIndex; + if (!ScopeStencil::createForModuleScope( + bce->fc, bce->compilationState, modulesc->bindings, + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internBodyScopeStencil(bce, scopeIndex)) { + 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()); + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForWithScope(bce->fc, bce->compilationState, + enclosingScopeIndex(bce), + &scopeIndex)) { + return false; + } + + if (!internScopeStencil(bce, scopeIndex)) { + return false; + } + + if (!bce->emitInternedScopeOp(index(), JSOp::EnterWith)) { + return false; + } + + if (!appendScopeNote(bce)) { + return false; + } + + return checkEnvironmentChainLength(bce); +} + +bool EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) const { + 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: + case ScopeKind::FunctionLexical: + case ScopeKind::ClassBody: + if (bce->sc->isFunctionBox() && + bce->sc->asFunctionBox()->needsClearSlotsOnExit()) { + if (!deadZoneFrameSlots(bce)) { + return false; + } + } + if (!bce->emit1(hasEnvironment() ? JSOp::PopLexicalEnv + : JSOp::DebugLeaveLexicalEnv)) { + return false; + } + break; + + case ScopeKind::With: + if (!bce->emit1(JSOp::LeaveWith)) { + 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; + + case ScopeKind::WasmInstance: + case ScopeKind::WasmFunction: + MOZ_CRASH("No wasm function scopes in JS"); + } + + // 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)) { + if (kind == ScopeKind::FunctionBodyVar) { + // The extra function var scope is never popped once it's pushed, + // so its scope note extends until the end of any possible code. + bce->bytecodeSection().scopeNoteList().recordEndFunctionBodyVar( + noteIndex_); + } else { + bce->bytecodeSection().scopeNoteList().recordEnd( + noteIndex_, bce->bytecodeSection().offset()); + } + } + } + + return true; +} + +AbstractScopePtr EmitterScope::scope(const BytecodeEmitter* bce) const { + return bce->perScriptData().gcThingList().getScope(index()); +} + +mozilla::Maybe EmitterScope::scopeIndex( + const BytecodeEmitter* bce) const { + return bce->perScriptData().gcThingList().getScopeIndex(index()); +} + +NameLocation EmitterScope::lookup(BytecodeEmitter* bce, + TaggedParserAtomIndex name) { + if (Maybe loc = lookupInCache(bce, name)) { + return *loc; + } + return searchAndCache(bce, name); +} + +/* static */ +uint32_t EmitterScope::CountEnclosingCompilationEnvironments( + BytecodeEmitter* bce, EmitterScope* emitterScope) { + uint32_t environments = emitterScope->hasEnvironment() ? 1 : 0; + while ((emitterScope = emitterScope->enclosing(&bce))) { + if (emitterScope->hasEnvironment()) { + environments++; + } + } + return environments; +} + +void EmitterScope::lookupPrivate(BytecodeEmitter* bce, + TaggedParserAtomIndex name, NameLocation& loc, + mozilla::Maybe& brandLoc) { + loc = lookup(bce, name); + + // Private Brand checking relies on the ability to construct a new + // environment coordinate for a name at a fixed offset, which will + // correspond to the private brand for that class. + // + // If our name lookup isn't a fixed location, we must construct a + // new environment coordinate, using information available from our private + // field cache. (See cachePrivateFieldsForEval, and Bug 1638309). + // + // This typically involves a DebugEnvironmentProxy, so we need to use a + // DebugEnvironmentCoordinate. + // + // See also Bug 793345 which argues that we should remove the + // DebugEnvironmentProxy. + if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate && + loc.kind() != NameLocation::Kind::FrameSlot) { + MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic || + loc.kind() == NameLocation::Kind::Global); + // Private fields don't require brand checking and can be correctly + // code-generated with dynamic name lookup bytecode we have today. However, + // for that to happen we first need to figure out if we have a Private + // method or private field, which we cannot disambiguate based on the + // dynamic lookup. + // + // However, this is precisely the case that the private field eval case can + // help us handle. It knows the truth about these private bindings. + mozilla::Maybe cacheEntry = + bce->compilationState.scopeContext.getPrivateFieldLocation(name); + MOZ_ASSERT(cacheEntry); + + if (cacheEntry->bindingKind() == BindingKind::PrivateMethod) { + MOZ_ASSERT(cacheEntry->kind() == + NameLocation::Kind::DebugEnvironmentCoordinate); + // To construct the brand check there are two hop values required: + // + // 1. Compilation Hops: The number of environment hops required to get to + // the compilation enclosing environment. + // 2. "external hops", to get from compilation enclosing debug environment + // to the environment that actually contains our brand. This is + // determined by the cacheEntry. This traversal will bypass a Debug + // environment proxy, which is why need to use + // DebugEnvironmentCoordinate. + + uint32_t compilation_hops = + CountEnclosingCompilationEnvironments(bce, this); + + uint32_t external_hops = cacheEntry->environmentCoordinate().hops(); + + brandLoc = Some(NameLocation::DebugEnvironmentCoordinate( + BindingKind::Synthetic, compilation_hops + external_hops, + ClassBodyLexicalEnvironmentObject::privateBrandSlot())); + } else { + brandLoc = Nothing(); + } + return; + } + + if (loc.bindingKind() == BindingKind::PrivateMethod) { + uint32_t hops = 0; + if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) { + hops = loc.environmentCoordinate().hops(); + } else { + // If we have a FrameSlot, then our innermost emitter scope must be a + // class body scope, and we can generate an environment coordinate with + // hops=0 to find the associated brand location. + MOZ_ASSERT(bce->innermostScope().is()); + } + + brandLoc = Some(NameLocation::EnvironmentCoordinate( + BindingKind::Synthetic, hops, + ClassBodyLexicalEnvironmentObject::privateBrandSlot())); + } else { + brandLoc = Nothing(); + } +} + +Maybe EmitterScope::locationBoundInScope( + TaggedParserAtomIndex 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; +} -- cgit v1.2.3