summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/EmitterScope.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/frontend/EmitterScope.cpp
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--js/src/frontend/EmitterScope.cpp1119
1 files changed, 1119 insertions, 0 deletions
diff --git a/js/src/frontend/EmitterScope.cpp b/js/src/frontend/EmitterScope.cpp
new file mode 100644
index 0000000000..b27bc1cf2a
--- /dev/null
+++ b/js/src/frontend/EmitterScope.cpp
@@ -0,0 +1,1119 @@
+/* -*- 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<EmitterScope>(&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<uint8_t>(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<NameLocation> 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<ScopeIndex> 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<FunctionScope>().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<FunctionScope>().script();
+ if (script->funHasExtensibleScope()) {
+ return NameLocation::Dynamic();
+ }
+
+ for (BindingIter bi(si.scope()); bi; bi++) {
+ if (bi.name() != name) {
+ continue;
+ }
+
+ BindingLocation bindLoc = bi.location();
+ if (bi.hasArgumentSlot() &&
+ script->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<GlobalScope>()) {
+ return NameLocation::Global(BindingKind::Var);
+ }
+ return NameLocation::Dynamic();
+
+ case ScopeKind::Global:
+ return NameLocation::Global(BindingKind::Var);
+
+ case ScopeKind::With:
+ case ScopeKind::NonSyntactic:
+ return NameLocation::Dynamic();
+
+ 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<NameLocation> loc;
+ uint8_t hops = hasEnvironment() ? 1 : 0;
+ DebugOnly<bool> inCurrentScript = enclosingInFrame();
+
+ // Start searching in the current compilation.
+ for (EmitterScope* es = enclosing(&bce); es; es = es->enclosing(&bce)) {
+ loc = es->lookupInCache(bce, name);
+ if (loc) {
+ if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate) {
+ *loc = loc->addHops(hops);
+ }
+ break;
+ }
+
+ if (es->hasEnvironment()) {
+ hops++;
+ }
+
+#ifdef DEBUG
+ if (!es->enclosingInFrame()) {
+ inCurrentScript = false;
+ }
+#endif
+ }
+
+ // If the name is not found in the current compilation, walk the Scope
+ // chain encompassing the compilation.
+ if (!loc) {
+ // 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 <typename ScopeCreator>
+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 <typename ScopeCreator>
+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<ScopeIndex> 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<ScopeIndex> 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<ScopeIndex> 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<ScopeIndex> 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<ScopeIndex> 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<ScopeIndex> 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<GlobalScope>()) {
+ 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<uint32_t> 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<ScopeIndex> 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<ScopeIndex> 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<ScopeIndex> EmitterScope::scopeIndex(
+ const BytecodeEmitter* bce) const {
+ return bce->perScriptData().gcThingList().getScopeIndex(index());
+}
+
+NameLocation EmitterScope::lookup(BytecodeEmitter* bce,
+ const ParserAtom* name) {
+ if (Maybe<NameLocation> loc = lookupInCache(bce, name)) {
+ return *loc;
+ }
+ return searchAndCache(bce, name);
+}
+
+Maybe<NameLocation> 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<NameLocation> loc;
+ if (NameLocationMap::Ptr p = target->nameCache_->lookup(name)) {
+ NameLocation l = p->value().wrapped;
+ if (l.kind() == NameLocation::Kind::EnvironmentCoordinate) {
+ loc = Some(l.addHops(extraHops));
+ } else {
+ loc = Some(l);
+ }
+ }
+ return loc;
+}