/* -*- 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/GlobalObject.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) {} bool EmitterScope::ensureCache(BytecodeEmitter* bce) { return nameCache_.acquire(bce->cx); } 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->stencil.input.enclosingScope) { hops = bce->stencil.input.enclosingScope->environmentChainLength(); } 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, js_function_str); 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, const ParserAtom* 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, const ParserAtom* 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, const ParserAtom* name) { // '.generator' cannot be accessed by name. return name != bce->cx->parserNames().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->functionAllowsParameterRedeclaration()) { // 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) { MOZ_ASSERT(scope); // TODO-Stencil // This needs to be handled properly by snapshotting enclosing scopes. 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->functionAllowsParameterRedeclaration()) { // 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::Lexical: case ScopeKind::NamedLambda: case ScopeKind::StrictNamedLambda: case ScopeKind::SimpleCatch: case ScopeKind::Catch: case ScopeKind::FunctionLexical: case ScopeKind::ClassBody: 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(); case ScopeKind::WasmInstance: case ScopeKind::WasmFunction: MOZ_CRASH("No direct eval inside wasm functions"); } if (hasEnv) { MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1); hops++; } } MOZ_CRASH("Malformed scope chain"); } NameLocation EmitterScope::searchAndCache(BytecodeEmitter* bce, const ParserAtom* 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) { // TODO-Stencil // Here, we convert our name into a JSAtom*, and hard-crash on failure // to allocate. This conversion should not be required as we should be // able to iterate up snapshotted scope chains that use parser atoms. // // This will be fixed when parser scopes are snapshotted, and // `searchInEnclosingScope` changes to accepting a `const ParserAtom*` // instead of a `JSAtom*`. // // See bug 1660275. AutoEnterOOMUnsafeRegion oomUnsafe; JSAtom* jsname = name->toJSAtom(bce->cx, bce->stencil.input.atomCache); if (!jsname) { oomUnsafe.crash("EmitterScope::searchAndCache"); } inCurrentScript = false; loc = Some(searchInEnclosingScope(jsname, bce->stencil.input.enclosingScope, 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; } 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); Scope* scope = &bce->cx->global()->emptyGlobalScope(); hasEnvironment_ = scope->hasEnvironment(); bce->bodyScopeIndex = GCThingIndex(bce->perScriptData().gcThingList().length()); return bce->perScriptData().gcThingList().appendEmptyGlobalScope( &scopeIndex_); } template bool EmitterScope::internScopeCreationData(BytecodeEmitter* bce, ScopeCreator createScope) { ScopeIndex index; if (!createScope(bce->cx, enclosingScopeIndex(bce), &index)) { return false; } ScopeStencil& scope = bce->compilationState.scopeData[index.index]; hasEnvironment_ = scope.hasEnvironment(); return bce->perScriptData().gcThingList().append(index, &scopeIndex_); } template bool EmitterScope::internBodyScopeCreationData(BytecodeEmitter* bce, ScopeCreator createScope) { MOZ_ASSERT(bce->bodyScopeIndex == ScopeNote::NoScopeIndex, "There can be only one body scope"); bce->bodyScopeIndex = GCThingIndex(bce->perScriptData().gcThingList().length()); return internScopeCreationData(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->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(); UniqueChars bytes = ParserAtomToPrintableString(bce->cx, r.front().key()); if (!bytes) { 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::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 = NameLocation::fromBinding(bi.kind(), bi.location()); if (!putNameInCache( bce, bce->compilationState.getParserAtomAt(bce->cx, bi.name()), loc)) { return false; } if (!tdzCache->noteTDZCheck( bce, bce->compilationState.getParserAtomAt(bce->cx, bi.name()), CheckTDZ)) { return false; } } updateFrameFixedSlots(bce, bi); auto createScope = [kind, bindings, firstFrameSlot, bce]( JSContext* cx, mozilla::Maybe enclosing, ScopeIndex* index) { return ScopeStencil::createForLexicalScope( cx, bce->stencil, bce->compilationState, kind, bindings, firstFrameSlot, enclosing, index); }; if (!internScopeCreationData(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; } 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 = NameLocation::fromBinding(bi.kind(), bi.location()); if (!putNameInCache(bce, bce->compilationState.getParserAtomAt(bce->cx, 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; auto createScope = [funbox, scopeKind, bce]( JSContext* cx, mozilla::Maybe enclosing, ScopeIndex* index) { return ScopeStencil::createForLexicalScope( cx, bce->stencil, bce->compilationState, scopeKind, funbox->namedLambdaBindings(), LOCALNO_LIMIT, enclosing, index); }; if (!internScopeCreationData(bce, createScope)) { 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 = NameLocation::fromBinding(bi.kind(), bi.location()); NameLocationMap::AddPtr p = cache.lookupForAdd( bce->compilationState.getParserAtomAt(bce->cx, 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, bce->compilationState.getParserAtomAt(bce->cx, 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, 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()); } // 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 = 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; } } auto createScope = [funbox, bce](JSContext* cx, mozilla::Maybe enclosing, ScopeIndex* index) { return ScopeStencil::createForFunctionScope( cx, bce->stencil, bce->compilationState, funbox->functionScopeBindings(), funbox->hasParameterExprs, funbox->needsCallObjectRegardlessOfBindings(), funbox->index(), funbox->isArrow(), enclosing, index); }; if (!internBodyScopeCreationData(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()) { ParserBindingIter bi(*bindings, firstFrameSlot); for (; bi; bi++) { if (!checkSlotLimits(bce, bi)) { return false; } NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); MOZ_ASSERT(bi.kind() == BindingKind::Var); if (!putNameInCache( bce, bce->compilationState.getParserAtomAt(bce->cx, 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. auto createScope = [funbox, firstFrameSlot, bce]( JSContext* cx, mozilla::Maybe enclosing, ScopeIndex* index) { return ScopeStencil::createForVarScope( cx, bce->stencil, bce->compilationState, ScopeKind::FunctionBodyVar, funbox->extraVarScopeBindings(), firstFrameSlot, funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(), enclosing, index); }; if (!internScopeCreationData(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::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); } auto createScope = [globalsc, bce](JSContext* cx, mozilla::Maybe enclosing, ScopeIndex* index) { MOZ_ASSERT(enclosing.isNothing()); return ScopeStencil::createForGlobalScope( cx, bce->stencil, bce->compilationState, globalsc->scopeKind(), globalsc->bindings, index); }; if (!internBodyScopeCreationData(bce, createScope)) { 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 = NameLocation::fromBinding(bi.kind(), bi.location()); const ParserAtom* name = bce->compilationState.getParserAtomAt(bce->cx, bi.name()); if (!putNameInCache(bce, 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; } // 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, bce](JSContext* cx, mozilla::Maybe enclosing, ScopeIndex* index) { ScopeKind scopeKind = evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval; return ScopeStencil::createForEvalScope(cx, bce->stencil, bce->compilationState, scopeKind, evalsc->bindings, enclosing, index); }; if (!internBodyScopeCreationData(bce, createScope)) { return false; } 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 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::ParserData* bindings = modulesc->bindings) { ParserBindingIter bi(*bindings); for (; bi; bi++) { if (!checkSlotLimits(bce, bi)) { return false; } NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); if (!putNameInCache( bce, bce->compilationState.getParserAtomAt(bce->cx, bi.name()), loc)) { return false; } if (BindingKindIsLexical(bi.kind())) { if (loc.kind() == NameLocation::Kind::FrameSlot && !firstLexicalFrameSlot) { firstLexicalFrameSlot = Some(loc.frameSlot()); } if (!tdzCache->noteTDZCheck( bce, bce->compilationState.getParserAtomAt(bce->cx, 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. auto createScope = [modulesc, bce](JSContext* cx, mozilla::Maybe enclosing, ScopeIndex* index) { return ScopeStencil::createForModuleScope( cx, bce->stencil, bce->compilationState, modulesc->bindings, enclosing, index); }; if (!internBodyScopeCreationData(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 = [bce](JSContext* cx, mozilla::Maybe enclosing, ScopeIndex* index) { return ScopeStencil::createForWithScope( cx, bce->stencil, bce->compilationState, enclosing, index); }; if (!internScopeCreationData(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) 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, const ParserAtom* name) { if (Maybe loc = lookupInCache(bce, name)) { return *loc; } return searchAndCache(bce, name); } Maybe EmitterScope::locationBoundInScope(const ParserAtom* 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; }