Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "frontend/Stencil.h"
#include "mozilla/AlreadyAddRefed.h" // already_AddRefed
#include "mozilla/Assertions.h" // MOZ_RELEASE_ASSERT
#include "mozilla/Maybe.h" // mozilla::Maybe
#include "mozilla/OperatorNewExtensions.h" // mozilla::KnownNotNull
#include "mozilla/PodOperations.h" // mozilla::PodCopy
#include "mozilla/RefPtr.h" // RefPtr
#include "mozilla/ScopeExit.h" // mozilla::ScopeExit
#include "mozilla/Sprintf.h" // SprintfLiteral
#include <algorithm> // std::fill
#include <string.h> // strlen
#include "ds/LifoAlloc.h" // LifoAlloc
#include "frontend/AbstractScopePtr.h" // ScopeIndex
#include "frontend/BytecodeCompiler.h" // CompileGlobalScriptToStencil, InstantiateStencils, CanLazilyParse, ParseModuleToStencil
#include "frontend/BytecodeSection.h" // EmitScriptThingsVector
#include "frontend/CompilationStencil.h" // CompilationStencil, CompilationState, ExtensibleCompilationStencil, CompilationGCOutput, CompilationStencilMerger
#include "frontend/FrontendContext.h"
#include "frontend/NameAnalysisTypes.h" // EnvironmentCoordinate
#include "frontend/ParserAtom.h" // ParserAtom, ParserAtomIndex, TaggedParserAtomIndex, ParserAtomsTable, Length{1,2,3}StaticParserString, InstantiateMarkedAtoms, InstantiateMarkedAtomsAsPermanent, GetWellKnownAtom
#include "frontend/ScopeBindingCache.h" // ScopeBindingCache
#include "frontend/SharedContext.h"
#include "frontend/StencilXdr.h" // XDRStencilEncoder, XDRStencilDecoder
#include "gc/AllocKind.h" // gc::AllocKind
#include "gc/Tracer.h" // TraceNullableRoot
#include "js/CallArgs.h" // JSNative
#include "js/CompileOptions.h" // JS::DecodeOptions, JS::ReadOnlyDecodeOptions
#include "js/experimental/JSStencil.h" // JS::Stencil
#include "js/GCAPI.h" // JS::AutoCheckCannotGC
#include "js/Printer.h" // js::Fprinter
#include "js/RealmOptions.h" // JS::RealmBehaviors
#include "js/RootingAPI.h" // Rooted
#include "js/Transcoding.h" // JS::TranscodeBuffer
#include "js/Utility.h" // js_malloc, js_calloc, js_free
#include "js/Value.h" // ObjectValue
#include "js/WasmModule.h" // JS::WasmModule
#include "vm/BigIntType.h" // ParseBigIntLiteral, BigIntLiteralIsZero
#include "vm/BindingKind.h" // BindingKind
#include "vm/EnvironmentObject.h"
#include "vm/GeneratorAndAsyncKind.h" // GeneratorKind, FunctionAsyncKind
#include "vm/JSContext.h" // JSContext
#include "vm/JSFunction.h" // JSFunction, GetFunctionPrototype, NewFunctionWithProto
#include "vm/JSObject.h" // JSObject, TenuredObject
#include "vm/JSONPrinter.h" // js::JSONPrinter
#include "vm/JSScript.h" // BaseScript, JSScript
#include "vm/Realm.h" // JS::Realm
#include "vm/RegExpObject.h" // js::RegExpObject
#include "vm/Scope.h" // Scope, *Scope, ScopeKind::*, ScopeKindString, ScopeIter, ScopeKindIsCatch, BindingIter, GetScopeDataTrailingNames, SizeOfParserScopeData
#include "vm/ScopeKind.h" // ScopeKind
#include "vm/SelfHosting.h" // SetClonedSelfHostedFunctionName
#include "vm/StaticStrings.h"
#include "vm/StencilEnums.h" // ImmutableScriptFlagsEnum
#include "vm/StringType.h" // JSAtom, js::CopyChars
#include "wasm/AsmJS.h" // InstantiateAsmJS
#include "vm/EnvironmentObject-inl.h" // JSObject::enclosingEnvironment
#include "vm/JSFunction-inl.h" // JSFunction::create
using namespace js;
using namespace js::frontend;
// These 2 functions are used to write the same code with lambda using auto
// arguments. The auto argument type is set by the Variant.match function of the
// InputScope variant. Thus dispatching to either a Scope* or to a
// ScopeStencilRef. This function can then be used as a way to specialize the
// code within the lambda without duplicating the code.
//
// Identically, an InputName is constructed using the scope type and the
// matching binding name type. This way, functions which are called by this
// lambda can manipulate an InputName and do not have to be duplicated.
//
// for (InputScopeIter si(...); si; si++) {
// si.scope().match([](auto& scope) {
// for (auto bi = InputBindingIter(scope); bi; bi++) {
// InputName name(scope, bi.name());
// }
// });
// }
static js::BindingIter InputBindingIter(Scope* ptr) {
return js::BindingIter(ptr);
}
static ParserBindingIter InputBindingIter(const ScopeStencilRef& ref) {
return ParserBindingIter(ref);
}
static ParserBindingIter InputBindingIter(const FakeStencilGlobalScope&) {
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("No bindings on empty global.");
}
InputName InputScript::displayAtom() const {
return script_.match(
[](BaseScript* ptr) {
return InputName(ptr, ptr->function()->fullDisplayAtom());
},
[](const ScriptStencilRef& ref) {
return InputName(ref, ref.scriptData().functionAtom);
});
}
TaggedParserAtomIndex InputName::internInto(FrontendContext* fc,
ParserAtomsTable& parserAtoms,
CompilationAtomCache& atomCache) {
return variant_.match(
[&](JSAtom* ptr) -> TaggedParserAtomIndex {
return parserAtoms.internJSAtom(fc, atomCache, ptr);
},
[&](NameStencilRef& ref) -> TaggedParserAtomIndex {
return parserAtoms.internExternalParserAtomIndex(fc, ref.context_,
ref.atomIndex_);
});
}
bool InputName::isEqualTo(FrontendContext* fc, ParserAtomsTable& parserAtoms,
CompilationAtomCache& atomCache,
TaggedParserAtomIndex other,
JSAtom** otherCached) const {
return variant_.match(
[&](const JSAtom* ptr) -> bool {
if (ptr->hash() != parserAtoms.hash(other)) {
return false;
}
// JSAtom variant is used only on the main thread delazification,
// where JSContext is always available.
JSContext* cx = fc->maybeCurrentJSContext();
MOZ_ASSERT(cx);
if (!*otherCached) {
// 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 the enclosing scopes are snapshotted.
//
AutoEnterOOMUnsafeRegion oomUnsafe;
*otherCached = parserAtoms.toJSAtom(cx, fc, other, atomCache);
if (!*otherCached) {
oomUnsafe.crash("InputName::isEqualTo");
}
} else {
MOZ_ASSERT(atomCache.getExistingAtomAt(cx, other) == *otherCached);
}
return ptr == *otherCached;
},
[&](const NameStencilRef& ref) -> bool {
return parserAtoms.isEqualToExternalParserAtomIndex(other, ref.context_,
ref.atomIndex_);
});
}
GenericAtom::GenericAtom(FrontendContext* fc, ParserAtomsTable& parserAtoms,
CompilationAtomCache& atomCache,
TaggedParserAtomIndex index)
: ref(EmitterName(fc, parserAtoms, atomCache, index)) {
hash = parserAtoms.hash(index);
}
GenericAtom::GenericAtom(const CompilationStencil& context,
TaggedParserAtomIndex index)
: ref(StencilName{context, index}) {
if (index.isParserAtomIndex()) {
ParserAtom* atom = context.parserAtomData[index.toParserAtomIndex()];
hash = atom->hash();
} else {
hash = index.staticOrWellKnownHash();
}
}
GenericAtom::GenericAtom(ScopeStencilRef& scope, TaggedParserAtomIndex index)
: GenericAtom(scope.context_, index) {}
BindingHasher<TaggedParserAtomIndex>::Lookup::Lookup(ScopeStencilRef& scope_ref,
const GenericAtom& other)
: keyStencil(scope_ref.context_), other(other) {}
bool GenericAtom::operator==(const GenericAtom& other) const {
return ref.match(
[&other](const EmitterName& name) -> bool {
return other.ref.match(
[&name](const EmitterName& other) -> bool {
// We never have multiple Emitter context at the same time.
MOZ_ASSERT(name.fc == other.fc);
MOZ_ASSERT(&name.parserAtoms == &other.parserAtoms);
MOZ_ASSERT(&name.atomCache == &other.atomCache);
return name.index == other.index;
},
[&name](const StencilName& other) -> bool {
return name.parserAtoms.isEqualToExternalParserAtomIndex(
name.index, other.stencil, other.index);
},
[&name](JSAtom* other) -> bool {
// JSAtom variant is used only on the main thread delazification,
// where JSContext is always available.
JSContext* cx = name.fc->maybeCurrentJSContext();
MOZ_ASSERT(cx);
AutoEnterOOMUnsafeRegion oomUnsafe;
JSAtom* namePtr = name.parserAtoms.toJSAtom(
cx, name.fc, name.index, name.atomCache);
if (!namePtr) {
oomUnsafe.crash("GenericAtom(EmitterName == JSAtom*)");
}
return namePtr == other;
});
},
[&other](const StencilName& name) -> bool {
return other.ref.match(
[&name](const EmitterName& other) -> bool {
return other.parserAtoms.isEqualToExternalParserAtomIndex(
other.index, name.stencil, name.index);
},
[&name](const StencilName& other) -> bool {
// Technically it is possible to have multiple stencils, but in
// this particular case let's assume we never encounter a case
// where we are comparing names from different stencils.
//
// The reason this assumption is safe today is that we are only
// using this in the context of a stencil-delazification, where
// the only StencilNames are coming from the CompilationStencil
// provided to CompilationInput::initFromStencil.
MOZ_ASSERT(&name.stencil == &other.stencil);
return name.index == other.index;
},
[](JSAtom* other) -> bool {
MOZ_CRASH("Never used.");
return false;
});
},
[&other](JSAtom* name) -> bool {
return other.ref.match(
[&name](const EmitterName& other) -> bool {
// JSAtom variant is used only on the main thread delazification,
// where JSContext is always available.
JSContext* cx = other.fc->maybeCurrentJSContext();
MOZ_ASSERT(cx);
AutoEnterOOMUnsafeRegion oomUnsafe;
JSAtom* otherPtr = other.parserAtoms.toJSAtom(
cx, other.fc, other.index, other.atomCache);
if (!otherPtr) {
oomUnsafe.crash("GenericAtom(JSAtom* == EmitterName)");
}
return name == otherPtr;
},
[](const StencilName& other) -> bool {
MOZ_CRASH("Never used.");
return false;
},
[&name](JSAtom* other) -> bool { return name == other; });
});
}
#ifdef DEBUG
template <typename SpanT, typename VecT>
void AssertBorrowingSpan(const SpanT& span, const VecT& vec) {
MOZ_ASSERT(span.size() == vec.length());
MOZ_ASSERT(span.data() == vec.begin());
}
#endif
bool ScopeBindingCache::canCacheFor(Scope* ptr) {
MOZ_CRASH("Unexpected scope chain type: Scope*");
}
bool ScopeBindingCache::canCacheFor(ScopeStencilRef ref) {
MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
}
bool ScopeBindingCache::canCacheFor(const FakeStencilGlobalScope& ref) {
MOZ_CRASH("Unexpected scope chain type: FakeStencilGlobalScope");
}
BindingMap<JSAtom*>* ScopeBindingCache::createCacheFor(Scope* ptr) {
MOZ_CRASH("Unexpected scope chain type: Scope*");
}
BindingMap<JSAtom*>* ScopeBindingCache::lookupScope(Scope* ptr,
CacheGeneration gen) {
MOZ_CRASH("Unexpected scope chain type: Scope*");
}
BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::createCacheFor(
ScopeStencilRef ref) {
MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
}
BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::lookupScope(
ScopeStencilRef ref, CacheGeneration gen) {
MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
}
BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::createCacheFor(
const FakeStencilGlobalScope& ref) {
MOZ_CRASH("Unexpected scope chain type: FakeStencilGlobalScope");
}
BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::lookupScope(
const FakeStencilGlobalScope& ref, CacheGeneration gen) {
MOZ_CRASH("Unexpected scope chain type: FakeStencilGlobalScope");
}
bool NoScopeBindingCache::canCacheFor(Scope* ptr) { return false; }
bool NoScopeBindingCache::canCacheFor(ScopeStencilRef ref) { return false; }
bool NoScopeBindingCache::canCacheFor(const FakeStencilGlobalScope& ref) {
return false;
}
bool RuntimeScopeBindingCache::canCacheFor(Scope* ptr) { return true; }
BindingMap<JSAtom*>* RuntimeScopeBindingCache::createCacheFor(Scope* ptr) {
BaseScopeData* dataPtr = ptr->rawData();
BindingMap<JSAtom*> bindingCache;
if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) {
return nullptr;
}
return lookupScope(ptr, cacheGeneration);
}
BindingMap<JSAtom*>* RuntimeScopeBindingCache::lookupScope(
Scope* ptr, CacheGeneration gen) {
MOZ_ASSERT(gen == cacheGeneration);
BaseScopeData* dataPtr = ptr->rawData();
auto valuePtr = scopeMap.lookup(dataPtr);
if (!valuePtr) {
return nullptr;
}
return &valuePtr->value();
}
bool StencilScopeBindingCache::canCacheFor(ScopeStencilRef ref) { return true; }
BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::createCacheFor(
ScopeStencilRef ref) {
#ifdef DEBUG
AssertBorrowingSpan(ref.context_.scopeNames, merger_.getResult().scopeNames);
#endif
auto* dataPtr = ref.context_.scopeNames[ref.scopeIndex_];
BindingMap<TaggedParserAtomIndex> bindingCache;
if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) {
return nullptr;
}
return lookupScope(ref, 1);
}
BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::lookupScope(
ScopeStencilRef ref, CacheGeneration gen) {
#ifdef DEBUG
AssertBorrowingSpan(ref.context_.scopeNames, merger_.getResult().scopeNames);
#endif
auto* dataPtr = ref.context_.scopeNames[ref.scopeIndex_];
auto ptr = scopeMap.lookup(dataPtr);
if (!ptr) {
return nullptr;
}
return &ptr->value();
}
static AbstractBaseScopeData<TaggedParserAtomIndex>
moduleGlobalAbstractScopeData;
bool StencilScopeBindingCache::canCacheFor(const FakeStencilGlobalScope& ref) {
return true;
}
BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::createCacheFor(
const FakeStencilGlobalScope& ref) {
auto* dataPtr = &moduleGlobalAbstractScopeData;
BindingMap<TaggedParserAtomIndex> bindingCache;
if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) {
return nullptr;
}
return lookupScope(ref, 1);
}
BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::lookupScope(
const FakeStencilGlobalScope& ref, CacheGeneration gen) {
auto* dataPtr = &moduleGlobalAbstractScopeData;
auto ptr = scopeMap.lookup(dataPtr);
if (!ptr) {
return nullptr;
}
return &ptr->value();
}
bool ScopeContext::init(FrontendContext* fc, CompilationInput& input,
ParserAtomsTable& parserAtoms,
ScopeBindingCache* scopeCache, InheritThis inheritThis,
JSObject* enclosingEnv) {
// Record the scopeCache to be used while looking up NameLocation bindings.
this->scopeCache = scopeCache;
scopeCacheGen = scopeCache->getCurrentGeneration();
InputScope maybeNonDefaultEnclosingScope(
input.maybeNonDefaultEnclosingScope());
// If this eval is in response to Debugger.Frame.eval, we may have an
// incomplete scope chain. In order to provide a better debugging experience,
// we inspect the (optional) environment chain to determine it's enclosing
// FunctionScope if there is one. If there is no such scope, we use the
// orignal scope provided.
//
// NOTE: This is used to compute the ThisBinding kind and to allow access to
// private fields and methods, while other contextual information only
// uses the actual scope passed to the compile.
auto effectiveScope =
determineEffectiveScope(maybeNonDefaultEnclosingScope, enclosingEnv);
if (inheritThis == InheritThis::Yes) {
computeThisBinding(effectiveScope);
computeThisEnvironment(maybeNonDefaultEnclosingScope);
}
computeInScope(maybeNonDefaultEnclosingScope);
cacheEnclosingScope(input.enclosingScope);
if (input.target == CompilationInput::CompilationTarget::Eval) {
if (!cacheEnclosingScopeBindingForEval(fc, input, parserAtoms)) {
return false;
}
if (!cachePrivateFieldsForEval(fc, input, enclosingEnv, effectiveScope,
parserAtoms)) {
return false;
}
}
return true;
}
void ScopeContext::computeThisEnvironment(const InputScope& enclosingScope) {
uint32_t envCount = 0;
for (InputScopeIter si(enclosingScope); si; si++) {
if (si.kind() == ScopeKind::Function) {
// Arrow function inherit the "this" environment of the enclosing script,
// so continue ignore them.
if (!si.scope().isArrow()) {
allowNewTarget = true;
if (si.scope().allowSuperProperty()) {
allowSuperProperty = true;
enclosingThisEnvironmentHops = envCount;
}
if (si.scope().isClassConstructor()) {
memberInitializers =
si.scope().useMemberInitializers()
? mozilla::Some(si.scope().getMemberInitializers())
: mozilla::Some(MemberInitializers::Empty());
MOZ_ASSERT(memberInitializers->valid);
} else {
if (si.scope().isSyntheticFunction()) {
allowArguments = false;
}
}
if (si.scope().isDerivedClassConstructor()) {
allowSuperCall = true;
}
// Found the effective "this" environment, so stop.
return;
}
}
if (si.scope().hasEnvironment()) {
envCount++;
}
}
}
void ScopeContext::computeThisBinding(const InputScope& scope) {
// Inspect the scope-chain.
for (InputScopeIter si(scope); si; si++) {
if (si.kind() == ScopeKind::Module) {
thisBinding = ThisBinding::Module;
return;
}
if (si.kind() == ScopeKind::Function) {
// Arrow functions don't have their own `this` binding.
if (si.scope().isArrow()) {
continue;
}
// Derived class constructors (and their nested arrow functions and evals)
// use ThisBinding::DerivedConstructor, which ensures TDZ checks happen
// when accessing |this|.
if (si.scope().isDerivedClassConstructor()) {
thisBinding = ThisBinding::DerivedConstructor;
} else {
thisBinding = ThisBinding::Function;
}
return;
}
}
thisBinding = ThisBinding::Global;
}
void ScopeContext::computeInScope(const InputScope& enclosingScope) {
for (InputScopeIter si(enclosingScope); si; si++) {
if (si.kind() == ScopeKind::ClassBody) {
inClass = true;
}
if (si.kind() == ScopeKind::With) {
inWith = true;
}
}
}
void ScopeContext::cacheEnclosingScope(const InputScope& enclosingScope) {
if (enclosingScope.isNull()) {
return;
}
enclosingScopeEnvironmentChainLength =
enclosingScope.environmentChainLength();
enclosingScopeKind = enclosingScope.kind();
if (enclosingScopeKind == ScopeKind::Function) {
enclosingScopeIsArrow = enclosingScope.isArrow();
}
enclosingScopeHasEnvironment = enclosingScope.hasEnvironment();
#ifdef DEBUG
hasNonSyntacticScopeOnChain =
enclosingScope.hasOnChain(ScopeKind::NonSyntactic);
// This computes a general answer for the query "does the enclosing scope
// have a function scope that needs a home object?", but it's only asserted
// if the parser parses eval body that contains `super` that needs a home
// object.
for (InputScopeIter si(enclosingScope); si; si++) {
if (si.kind() == ScopeKind::Function) {
if (si.scope().isArrow()) {
continue;
}
if (si.scope().allowSuperProperty() && si.scope().needsHomeObject()) {
hasFunctionNeedsHomeObjectOnChain = true;
}
break;
}
}
#endif
// Pre-fill the scope cache by iterating over all the names. Stop iterating
// as soon as we find a scope which already has a filled scope cache.
AutoEnterOOMUnsafeRegion oomUnsafe;
for (InputScopeIter si(enclosingScope); si; si++) {
// If the current scope already exists, then there is no need to go deeper
// as the scope which are encoded after this one should already be present
// in the cache.
bool hasScopeCache = si.scope().match([&](auto& scope_ref) -> bool {
MOZ_ASSERT(scopeCache->canCacheFor(scope_ref));
return scopeCache->lookupScope(scope_ref, scopeCacheGen);
});
if (hasScopeCache) {
return;
}
bool hasEnv = si.hasSyntacticEnvironment();
auto setCatchAll = [&](NameLocation loc) {
return si.scope().match([&](auto& scope_ref) {
using BindingMapPtr = decltype(scopeCache->createCacheFor(scope_ref));
BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
if (!bindingMapPtr) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: scopeCache->createCacheFor");
return;
}
bindingMapPtr->catchAll.emplace(loc);
});
};
auto createEmpty = [&]() {
return si.scope().match([&](auto& scope_ref) {
using BindingMapPtr = decltype(scopeCache->createCacheFor(scope_ref));
BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
if (!bindingMapPtr) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: scopeCache->createCacheFor");
return;
}
});
};
switch (si.kind()) {
case ScopeKind::Function:
if (hasEnv) {
if (si.scope().funHasExtensibleScope()) {
setCatchAll(NameLocation::Dynamic());
return;
}
si.scope().match([&](auto& scope_ref) {
using BindingMapPtr =
decltype(scopeCache->createCacheFor(scope_ref));
using Lookup =
typename std::remove_pointer_t<BindingMapPtr>::Lookup;
BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
if (!bindingMapPtr) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: "
"scopeCache->createCacheFor");
return;
}
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
NameLocation loc = bi.nameLocation();
if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
continue;
}
auto ctxFreeKey = bi.name();
GenericAtom ctxKey(scope_ref, ctxFreeKey);
Lookup ctxLookup(scope_ref, ctxKey);
if (!bindingMapPtr->hashMap.put(ctxLookup, ctxFreeKey, loc)) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
return;
}
}
});
} else {
createEmpty();
}
break;
case ScopeKind::StrictEval:
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) {
si.scope().match([&](auto& scope_ref) {
using BindingMapPtr =
decltype(scopeCache->createCacheFor(scope_ref));
using Lookup =
typename std::remove_pointer_t<BindingMapPtr>::Lookup;
BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
if (!bindingMapPtr) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: "
"scopeCache->createCacheFor");
return;
}
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
NameLocation loc = bi.nameLocation();
if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
continue;
}
auto ctxFreeKey = bi.name();
GenericAtom ctxKey(scope_ref, ctxFreeKey);
Lookup ctxLookup(scope_ref, ctxKey);
if (!bindingMapPtr->hashMap.putNew(ctxLookup, ctxFreeKey, loc)) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
return;
}
}
});
} else {
createEmpty();
}
break;
case ScopeKind::Module:
// This case is used only when delazifying a function inside
// module.
// Initial compilation of module doesn't have enlcosing scope.
if (hasEnv) {
si.scope().match([&](auto& scope_ref) {
using BindingMapPtr =
decltype(scopeCache->createCacheFor(scope_ref));
using Lookup =
typename std::remove_pointer_t<BindingMapPtr>::Lookup;
BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
if (!bindingMapPtr) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: "
"scopeCache->createCacheFor");
return;
}
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
// Imports are on the environment but are indirect
// bindings and must be accessed dynamically instead of
// using an EnvironmentCoordinate.
NameLocation loc = bi.nameLocation();
if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate &&
loc.kind() != NameLocation::Kind::Import) {
continue;
}
auto ctxFreeKey = bi.name();
GenericAtom ctxKey(scope_ref, ctxFreeKey);
Lookup ctxLookup(scope_ref, ctxKey);
if (!bindingMapPtr->hashMap.putNew(ctxLookup, ctxFreeKey, loc)) {
oomUnsafe.crash(
"ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
return;
}
}
});
} else {
createEmpty();
}
break;
case ScopeKind::Eval:
// 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) {
ScopeKind kind = si.scope().enclosing().kind();
if (kind == ScopeKind::Global || kind == ScopeKind::NonSyntactic) {
setCatchAll(NameLocation::Global(BindingKind::Var));
return;
}
}
setCatchAll(NameLocation::Dynamic());
return;
case ScopeKind::Global:
setCatchAll(NameLocation::Global(BindingKind::Var));
return;
case ScopeKind::With:
case ScopeKind::NonSyntactic:
setCatchAll(NameLocation::Dynamic());
return;
case ScopeKind::WasmInstance:
case ScopeKind::WasmFunction:
MOZ_CRASH("No direct eval inside wasm functions");
}
}
MOZ_CRASH("Malformed scope chain");
}
// Given an input scope, possibly refine this to a more precise scope.
// This is used during eval in the debugger to provide the appropriate scope and
// ThisBinding kind and environment, which is key to making private field eval
// work correctly.
//
// The trick here is that an eval may have a non-syntatic scope but nevertheless
// have an 'interesting' environment which can be traversed to find the
// appropriate scope the the eval to function as desired. See the diagram below.
//
// Eval Scope Eval Env Frame Env Frame Scope
// ============ ============= ========= =============
//
// NonSyntactic
// |
// v
// null DebugEnvProxy LexicalScope
// | |
// v v
// DebugEnvProxy --> CallObj --> FunctionScope
// | | |
// v v v
// ... ... ...
//
InputScope ScopeContext::determineEffectiveScope(InputScope& scope,
JSObject* environment) {
MOZ_ASSERT(effectiveScopeHops == 0);
// If the scope-chain is non-syntactic, we may still determine a more precise
// effective-scope to use instead.
if (environment && scope.hasOnChain(ScopeKind::NonSyntactic)) {
JSObject* env = environment;
while (env) {
// Look at target of any DebugEnvironmentProxy, but be sure to use
// enclosingEnvironment() of the proxy itself.
JSObject* unwrapped = env;
if (env->is<DebugEnvironmentProxy>()) {
unwrapped = &env->as<DebugEnvironmentProxy>().environment();
#ifdef DEBUG
enclosingEnvironmentIsDebugProxy_ = true;
#endif
}
if (unwrapped->is<CallObject>()) {
JSFunction* callee = &unwrapped->as<CallObject>().callee();
return InputScope(callee->nonLazyScript()->bodyScope());
}
env = env->enclosingEnvironment();
effectiveScopeHops++;
}
}
return scope;
}
static uint32_t DepthOfNearestVarScopeForDirectEval(const InputScope& scope) {
uint32_t depth = 0;
if (scope.isNull()) {
return depth;
}
for (InputScopeIter si(scope); si; si++) {
depth++;
switch (si.scope().kind()) {
case ScopeKind::Function:
case ScopeKind::FunctionBodyVar:
case ScopeKind::Global:
case ScopeKind::NonSyntactic:
return depth;
default:
break;
}
}
return depth;
}
bool ScopeContext::cacheEnclosingScopeBindingForEval(
FrontendContext* fc, CompilationInput& input,
ParserAtomsTable& parserAtoms) {
enclosingLexicalBindingCache_.emplace();
uint32_t varScopeDepth =
DepthOfNearestVarScopeForDirectEval(input.enclosingScope);
uint32_t depth = 0;
for (InputScopeIter si(input.enclosingScope); si; si++) {
bool success = si.scope().match([&](auto& scope_ref) {
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
switch (bi.kind()) {
case BindingKind::Let: {
// Annex B.3.5 allows redeclaring simple (non-destructured)
// catch parameters with var declarations.
bool annexB35Allowance = si.kind() == ScopeKind::SimpleCatch;
if (!annexB35Allowance) {
auto kind = ScopeKindIsCatch(si.kind())
? EnclosingLexicalBindingKind::CatchParameter
: EnclosingLexicalBindingKind::Let;
InputName binding(scope_ref, bi.name());
if (!addToEnclosingLexicalBindingCache(
fc, parserAtoms, input.atomCache, binding, kind)) {
return false;
}
}
break;
}
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
case BindingKind::Using:
break;
#endif
case BindingKind::Const: {
InputName binding(scope_ref, bi.name());
if (!addToEnclosingLexicalBindingCache(
fc, parserAtoms, input.atomCache, binding,
EnclosingLexicalBindingKind::Const)) {
return false;
}
break;
}
case BindingKind::Synthetic: {
InputName binding(scope_ref, bi.name());
if (!addToEnclosingLexicalBindingCache(
fc, parserAtoms, input.atomCache, binding,
EnclosingLexicalBindingKind::Synthetic)) {
return false;
}
break;
}
case BindingKind::PrivateMethod: {
InputName binding(scope_ref, bi.name());
if (!addToEnclosingLexicalBindingCache(
fc, parserAtoms, input.atomCache, binding,
EnclosingLexicalBindingKind::PrivateMethod)) {
return false;
}
break;
}
case BindingKind::Import:
case BindingKind::FormalParameter:
case BindingKind::Var:
case BindingKind::NamedLambdaCallee:
break;
}
}
return true;
});
if (!success) {
return false;
}
if (++depth == varScopeDepth) {
break;
}
}
return true;
}
bool ScopeContext::addToEnclosingLexicalBindingCache(
FrontendContext* fc, ParserAtomsTable& parserAtoms,
CompilationAtomCache& atomCache, InputName& name,
EnclosingLexicalBindingKind kind) {
TaggedParserAtomIndex parserName =
name.internInto(fc, parserAtoms, atomCache);
if (!parserName) {
return false;
}
// Same lexical binding can appear multiple times across scopes.
//
// enclosingLexicalBindingCache_ map is used for detecting conflicting
// `var` binding, and inner binding should be reported in the error.
//
// cacheEnclosingScopeBindingForEval iterates from inner scope, and
// inner-most binding is added to the map first.
//
// Do not overwrite the value with outer bindings.
auto p = enclosingLexicalBindingCache_->lookupForAdd(parserName);
if (!p) {
if (!enclosingLexicalBindingCache_->add(p, parserName, kind)) {
ReportOutOfMemory(fc);
return false;
}
}
return true;
}
static bool IsPrivateField(Scope*, JSAtom* atom) {
MOZ_ASSERT(atom->length() > 0);
JS::AutoCheckCannotGC nogc;
if (atom->hasLatin1Chars()) {
return atom->latin1Chars(nogc)[0] == '#';
}
return atom->twoByteChars(nogc)[0] == '#';
}
static bool IsPrivateField(ScopeStencilRef& scope, TaggedParserAtomIndex atom) {
if (atom.isParserAtomIndex()) {
const CompilationStencil& context = scope.context_;
ParserAtom* parserAtom = context.parserAtomData[atom.toParserAtomIndex()];
return parserAtom->isPrivateName();
}
#ifdef DEBUG
if (atom.isWellKnownAtomId()) {
const auto& info = GetWellKnownAtomInfo(atom.toWellKnownAtomId());
// #constructor is a well-known term, but it is invalid private name.
MOZ_ASSERT(!(info.length > 1 && info.content[0] == '#'));
} else if (atom.isLength2StaticParserString()) {
char content[2];
ParserAtomsTable::getLength2Content(atom.toLength2StaticParserString(),
content);
// # character is not part of the allowed character of static strings.
MOZ_ASSERT(content[0] != '#');
}
#endif
return false;
}
static bool IsPrivateField(const FakeStencilGlobalScope&,
TaggedParserAtomIndex) {
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("No private fields on empty global.");
}
bool ScopeContext::cachePrivateFieldsForEval(FrontendContext* fc,
CompilationInput& input,
JSObject* enclosingEnvironment,
const InputScope& effectiveScope,
ParserAtomsTable& parserAtoms) {
effectiveScopePrivateFieldCache_.emplace();
// We compute an environment coordinate relative to the effective scope
// environment. In order to safely consume these environment coordinates,
// we re-map them to include the hops to get the to the effective scope:
// see EmitterScope::lookupPrivate
uint32_t hops = effectiveScopeHops;
for (InputScopeIter si(effectiveScope); si; si++) {
if (si.scope().kind() == ScopeKind::ClassBody) {
uint32_t slots = 0;
bool success = si.scope().match([&](auto& scope_ref) {
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
if (bi.kind() == BindingKind::PrivateMethod ||
(bi.kind() == BindingKind::Synthetic &&
IsPrivateField(scope_ref, bi.name()))) {
InputName binding(scope_ref, bi.name());
auto parserName =
binding.internInto(fc, parserAtoms, input.atomCache);
if (!parserName) {
return false;
}
NameLocation loc = NameLocation::DebugEnvironmentCoordinate(
bi.kind(), hops, slots);
if (!effectiveScopePrivateFieldCache_->put(parserName, loc)) {
ReportOutOfMemory(fc);
return false;
}
}
slots++;
}
return true;
});
if (!success) {
return false;
}
}
// Hops is only consumed by GetAliasedDebugVar, which uses this to
// traverse the debug environment chain. See the [SMDOC] for Debug
// Environment Chain, which explains why we don't check for
// isEnvironment when computing hops here (basically, debug proxies
// pretend all scopes have environments, even if they were actually
// optimized out).
hops++;
}
return true;
}
#ifdef DEBUG
static bool NameIsOnEnvironment(FrontendContext* fc,
ParserAtomsTable& parserAtoms,
CompilationAtomCache& atomCache,
InputScope& scope, TaggedParserAtomIndex name) {
JSAtom* jsname = nullptr;
return scope.match([&](auto& scope_ref) {
if (std::is_same_v<decltype(scope_ref), FakeStencilGlobalScope&>) {
// This condition is added to handle the FakeStencilGlobalScope which is
// used to emulate the global object when delazifying while executing, and
// which is not provided by the Stencil.
return true;
}
for (auto bi = InputBindingIter(scope_ref); 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.
InputName binding(scope_ref, bi.name());
if (binding.isEqualTo(fc, parserAtoms, atomCache, name, &jsname)) {
BindingLocation::Kind kind = bi.location().kind();
if (bi.hasArgumentSlot()) {
// The following is equivalent to
// functionScope.script()->functionAllowsParameterRedeclaration()
if (scope.hasMappedArgsObj()) {
// Check for duplicate positional formal parameters.
using InputBindingIter = decltype(bi);
for (InputBindingIter bi2(bi); bi2 && bi2.hasArgumentSlot();
bi2++) {
InputName binding2(scope_ref, bi2.name());
if (binding2.isEqualTo(fc, parserAtoms, atomCache, name,
&jsname)) {
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
NameLocation ScopeContext::searchInEnclosingScope(FrontendContext* fc,
CompilationInput& input,
ParserAtomsTable& parserAtoms,
TaggedParserAtomIndex name) {
MOZ_ASSERT(input.target ==
CompilationInput::CompilationTarget::Delazification ||
input.target == CompilationInput::CompilationTarget::Eval);
MOZ_ASSERT(scopeCache);
if (scopeCacheGen != scopeCache->getCurrentGeneration()) {
return searchInEnclosingScopeNoCache(fc, input, parserAtoms, name);
}
#ifdef DEBUG
// Catch assertion failures in the NoCache variant before looking at the
// cached content.
NameLocation expect =
searchInEnclosingScopeNoCache(fc, input, parserAtoms, name);
#endif
NameLocation found =
searchInEnclosingScopeWithCache(fc, input, parserAtoms, name);
MOZ_ASSERT(expect == found);
return found;
}
NameLocation ScopeContext::searchInEnclosingScopeWithCache(
FrontendContext* fc, CompilationInput& input, ParserAtomsTable& parserAtoms,
TaggedParserAtomIndex name) {
MOZ_ASSERT(input.target ==
CompilationInput::CompilationTarget::Delazification ||
input.target == CompilationInput::CompilationTarget::Eval);
// Generic atom of the looked up name.
GenericAtom genName(fc, parserAtoms, input.atomCache, name);
mozilla::Maybe<NameLocation> found;
// Number of enclosing scope we walked over.
uint8_t hops = 0;
for (InputScopeIter si(input.enclosingScope); si; si++) {
MOZ_ASSERT(NameIsOnEnvironment(fc, parserAtoms, input.atomCache, si.scope(),
name));
// If the result happens to be in the cached content of the scope that we
// are iterating over, then return it.
si.scope().match([&](auto& scope_ref) {
using BindingMapPtr =
decltype(scopeCache->lookupScope(scope_ref, scopeCacheGen));
BindingMapPtr bindingMapPtr =
scopeCache->lookupScope(scope_ref, scopeCacheGen);
MOZ_ASSERT(bindingMapPtr);
auto& bindingMap = *bindingMapPtr;
if (bindingMap.catchAll.isSome()) {
found = bindingMap.catchAll;
return;
}
// The scope_ref is given as argument to know where to lookup the key
// index of the hash table if the names have to be compared.
using Lookup = typename std::remove_pointer_t<BindingMapPtr>::Lookup;
Lookup ctxName(scope_ref, genName);
auto ptr = bindingMap.hashMap.lookup(ctxName);
if (!ptr) {
return;
}
found.emplace(ptr->value());
});
if (found.isSome()) {
// Cached entries do not store the number of hops, as it might be reused
// by multiple inner functions, which might different number of hops.
found = found.map([&hops](NameLocation loc) {
if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
return loc;
}
return loc.addHops(hops);
});
return found.value();
}
bool hasEnv = si.hasSyntacticEnvironment();
if (hasEnv) {
MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
hops++;
}
}
MOZ_CRASH("Malformed scope chain");
}
NameLocation ScopeContext::searchInEnclosingScopeNoCache(
FrontendContext* fc, CompilationInput& input, ParserAtomsTable& parserAtoms,
TaggedParserAtomIndex name) {
MOZ_ASSERT(input.target ==
CompilationInput::CompilationTarget::Delazification ||
input.target == CompilationInput::CompilationTarget::Eval);
// Cached JSAtom equivalent of the TaggedParserAtomIndex `name` argument.
JSAtom* jsname = nullptr;
// NameLocation which contains relative locations to access `name`.
mozilla::Maybe<NameLocation> result;
// Number of enclosing scoep we walked over.
uint8_t hops = 0;
for (InputScopeIter si(input.enclosingScope); si; si++) {
MOZ_ASSERT(NameIsOnEnvironment(fc, parserAtoms, input.atomCache, si.scope(),
name));
bool hasEnv = si.hasSyntacticEnvironment();
switch (si.kind()) {
case ScopeKind::Function:
if (hasEnv) {
if (si.scope().funHasExtensibleScope()) {
return NameLocation::Dynamic();
}
si.scope().match([&](auto& scope_ref) {
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
InputName binding(scope_ref, bi.name());
if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name,
&jsname)) {
continue;
}
BindingLocation bindLoc = bi.location();
// hasMappedArgsObj == script.functionAllowsParameterRedeclaration
if (bi.hasArgumentSlot() && si.scope().hasMappedArgsObj()) {
// Check for duplicate positional formal parameters.
using InputBindingIter = decltype(bi);
for (InputBindingIter bi2(bi); bi2 && bi2.hasArgumentSlot();
bi2++) {
if (bi.name() == bi2.name()) {
bindLoc = bi2.location();
}
}
}
MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
result.emplace(NameLocation::EnvironmentCoordinate(
bi.kind(), hops, bindLoc.slot()));
return;
}
});
}
break;
case ScopeKind::StrictEval:
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) {
si.scope().match([&](auto& scope_ref) {
for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
InputName binding(scope_ref, bi.name());
if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name,
&jsname)) {