diff options
Diffstat (limited to 'js/src/vm/Scope.h')
-rw-r--r-- | js/src/vm/Scope.h | 1892 |
1 files changed, 1892 insertions, 0 deletions
diff --git a/js/src/vm/Scope.h b/js/src/vm/Scope.h new file mode 100644 index 0000000000..a914a14f28 --- /dev/null +++ b/js/src/vm/Scope.h @@ -0,0 +1,1892 @@ +/* -*- 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/. */ + +#ifndef vm_Scope_h +#define vm_Scope_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_ASSERT_IF +#include "mozilla/Attributes.h" // MOZ_IMPLICIT, MOZ_INIT_OUTSIDE_CTOR, MOZ_STACK_CLASS +#include "mozilla/Casting.h" // mozilla::AssertedCast +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf +#include "mozilla/Span.h" // mozilla::Span + +#include <algorithm> // std::fill_n +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t, uint16_t, uint32_t, uintptr_t +#include <type_traits> // std::is_same_v, std::is_base_of_v + +#include "builtin/ModuleObject.h" // ModuleObject, Handle<ModuleObject*> +#include "frontend/ParserAtom.h" // frontend::TaggedParserAtomIndex +#include "gc/Barrier.h" // HeapPtr +#include "gc/Cell.h" // TenuredCellWithNonGCPointer +#include "js/GCPolicyAPI.h" // GCPolicy, IgnoreGCPolicy +#include "js/HeapAPI.h" // CellFlagBitsReservedForGC +#include "js/RootingAPI.h" // Handle, MutableHandle +#include "js/TraceKind.h" // JS::TraceKind +#include "js/TypeDecls.h" // HandleFunction +#include "js/UbiNode.h" // ubi::* +#include "js/UniquePtr.h" // UniquePtr +#include "util/Poison.h" // AlwaysPoison, JS_SCOPE_DATA_TRAILING_NAMES_PATTERN, MemCheckKind +#include "vm/JSFunction.h" // JSFunction +#include "vm/ScopeKind.h" // ScopeKind +#include "vm/Shape.h" // Shape +#include "wasm/WasmJS.h" // WasmInstanceObject + +class JSAtom; +class JSScript; +class JSTracer; +struct JSContext; + +namespace js { + +class JS_PUBLIC_API GenericPrinter; + +namespace frontend { +class ScopeStencil; +struct ScopeStencilRef; +class RuntimeScopeBindingCache; +} // namespace frontend + +template <typename NameT> +class AbstractBaseScopeData; + +template <typename NameT> +class BaseAbstractBindingIter; + +template <typename NameT> +class AbstractBindingIter; + +template <typename NameT> +class AbstractPositionalFormalParameterIter; + +using BindingIter = AbstractBindingIter<JSAtom>; + +class AbstractScopePtr; + +static inline bool ScopeKindIsCatch(ScopeKind kind) { + return kind == ScopeKind::SimpleCatch || kind == ScopeKind::Catch; +} + +static inline bool ScopeKindIsInBody(ScopeKind kind) { + return kind == ScopeKind::Lexical || kind == ScopeKind::SimpleCatch || + kind == ScopeKind::Catch || kind == ScopeKind::With || + kind == ScopeKind::FunctionLexical || + kind == ScopeKind::FunctionBodyVar || kind == ScopeKind::ClassBody; +} + +const char* BindingKindString(BindingKind kind); +const char* ScopeKindString(ScopeKind kind); + +template <typename NameT> +class AbstractBindingName; + +template <> +class AbstractBindingName<JSAtom> { + public: + using NameT = JSAtom; + using NamePointerT = NameT*; + + private: + // A JSAtom* with its low bit used as a tag for the: + // * whether it is closed over (i.e., exists in the environment shape) + // * whether it is a top-level function binding in global or eval scope, + // instead of var binding (both are in the same range in Scope data) + uintptr_t bits_; + + static constexpr uintptr_t ClosedOverFlag = 0x1; + // TODO: We should reuse this bit for let vs class distinction to + // show the better redeclaration error message (bug 1428672). + static constexpr uintptr_t TopLevelFunctionFlag = 0x2; + static constexpr uintptr_t FlagMask = 0x3; + + public: + AbstractBindingName() : bits_(0) {} + + AbstractBindingName(NameT* name, bool closedOver, + bool isTopLevelFunction = false) + : bits_(uintptr_t(name) | (closedOver ? ClosedOverFlag : 0x0) | + (isTopLevelFunction ? TopLevelFunctionFlag : 0x0)) {} + + NamePointerT name() const { + return reinterpret_cast<NameT*>(bits_ & ~FlagMask); + } + + bool closedOver() const { return bits_ & ClosedOverFlag; } + + private: + friend class BaseAbstractBindingIter<NameT>; + + // This method should be called only for binding names in `vars` range in + // BindingIter. + bool isTopLevelFunction() const { return bits_ & TopLevelFunctionFlag; } + + public: + void trace(JSTracer* trc) { + if (JSAtom* atom = name()) { + TraceManuallyBarrieredEdge(trc, &atom, "binding name"); + } + } +}; + +template <> +class AbstractBindingName<frontend::TaggedParserAtomIndex> { + uint32_t bits_; + + using TaggedParserAtomIndex = frontend::TaggedParserAtomIndex; + + public: + using NameT = TaggedParserAtomIndex; + using NamePointerT = NameT; + + private: + static constexpr size_t TaggedIndexBit = TaggedParserAtomIndex::IndexBit + 2; + + static constexpr size_t FlagShift = TaggedIndexBit; + static constexpr size_t FlagBit = 2; + static constexpr uint32_t FlagMask = BitMask(FlagBit) << FlagShift; + + static constexpr uint32_t ClosedOverFlag = 1 << FlagShift; + static constexpr uint32_t TopLevelFunctionFlag = 2 << FlagShift; + + public: + AbstractBindingName() : bits_(TaggedParserAtomIndex::NullTag) { + // TaggedParserAtomIndex's tags shouldn't overlap with flags. + static_assert((TaggedParserAtomIndex::NullTag & FlagMask) == 0); + static_assert((TaggedParserAtomIndex::ParserAtomIndexTag & FlagMask) == 0); + static_assert((TaggedParserAtomIndex::WellKnownTag & FlagMask) == 0); + } + + AbstractBindingName(TaggedParserAtomIndex name, bool closedOver, + bool isTopLevelFunction = false) + : bits_(name.rawData() | (closedOver ? ClosedOverFlag : 0x0) | + (isTopLevelFunction ? TopLevelFunctionFlag : 0x0)) {} + + public: + NamePointerT name() const { + return TaggedParserAtomIndex::fromRaw(bits_ & ~FlagMask); + } + + bool closedOver() const { return bits_ & ClosedOverFlag; } + + AbstractBindingName<JSAtom> copyWithNewAtom(JSAtom* newName) const { + return AbstractBindingName<JSAtom>(newName, closedOver(), + isTopLevelFunction()); + } + + void updateNameAfterStencilMerge(TaggedParserAtomIndex name) { + bits_ = (bits_ & FlagMask) | name.rawData(); + } + + private: + friend class BaseAbstractBindingIter<TaggedParserAtomIndex>; + friend class frontend::ScopeStencil; + + // This method should be called only for binding names in `vars` range in + // BindingIter. + bool isTopLevelFunction() const { return bits_ & TopLevelFunctionFlag; } +}; + +using BindingName = AbstractBindingName<JSAtom>; + +static inline void TraceBindingNames(JSTracer* trc, BindingName* names, + uint32_t length) { + for (uint32_t i = 0; i < length; i++) { + JSAtom* name = names[i].name(); + MOZ_ASSERT(name); + TraceManuallyBarrieredEdge(trc, &name, "scope name"); + } +}; +static inline void TraceNullableBindingNames(JSTracer* trc, BindingName* names, + uint32_t length) { + for (uint32_t i = 0; i < length; i++) { + if (JSAtom* name = names[i].name()) { + TraceManuallyBarrieredEdge(trc, &name, "scope name"); + } + } +}; + +const size_t ScopeDataAlignBytes = size_t(1) << gc::CellFlagBitsReservedForGC; + +/** + * Base class for scope {Runtime,Parser}Data classes to inherit from. + * + * `js::Scope` stores a pointer to RuntimeData classes in their first word, so + * they must be suitably aligned to allow storing GC flags in the low bits. + */ +template <typename NameT> +class AbstractBaseScopeData { + public: + using NameType = NameT; + + // The length of names after specialized ScopeData subclasses. + uint32_t length = 0; +}; + +template <typename ScopeDataT> +static inline void AssertDerivedScopeData() { + static_assert( + !std::is_same_v<ScopeDataT, + AbstractBaseScopeData<typename ScopeDataT::NameType>>, + "ScopeDataT shouldn't be AbstractBaseScopeData"); + static_assert( + std::is_base_of_v<AbstractBaseScopeData<typename ScopeDataT::NameType>, + ScopeDataT>, + "ScopeDataT should be subclass of AbstractBaseScopeData"); +} + +template <typename ScopeDataT> +static inline size_t GetOffsetOfScopeDataTrailingNames() { + AssertDerivedScopeData<ScopeDataT>(); + return sizeof(ScopeDataT); +} + +template <typename ScopeDataT> +static inline AbstractBindingName<typename ScopeDataT::NameType>* +GetScopeDataTrailingNamesPointer(ScopeDataT* data) { + AssertDerivedScopeData<ScopeDataT>(); + return reinterpret_cast<AbstractBindingName<typename ScopeDataT::NameType>*>( + data + 1); +} + +template <typename ScopeDataT> +static inline const AbstractBindingName<typename ScopeDataT::NameType>* +GetScopeDataTrailingNamesPointer(const ScopeDataT* data) { + AssertDerivedScopeData<ScopeDataT>(); + return reinterpret_cast< + const AbstractBindingName<typename ScopeDataT::NameType>*>(data + 1); +} + +template <typename ScopeDataT> +static inline mozilla::Span<AbstractBindingName<typename ScopeDataT::NameType>> +GetScopeDataTrailingNames(ScopeDataT* data) { + return mozilla::Span(GetScopeDataTrailingNamesPointer(data), data->length); +} + +template <typename ScopeDataT> +static inline mozilla::Span< + const AbstractBindingName<typename ScopeDataT::NameType>> +GetScopeDataTrailingNames(const ScopeDataT* data) { + return mozilla::Span(GetScopeDataTrailingNamesPointer(data), data->length); +} + +using BaseScopeData = AbstractBaseScopeData<JSAtom>; + +inline void PoisonNames(AbstractBindingName<JSAtom>* data, uint32_t length) { + AlwaysPoison(data, JS_SCOPE_DATA_TRAILING_NAMES_PATTERN, + sizeof(AbstractBindingName<JSAtom>) * length, + MemCheckKind::MakeUndefined); +} + +// frontend::TaggedParserAtomIndex doesn't require poison value. +// Fill with null value instead. +inline void PoisonNames( + AbstractBindingName<frontend::TaggedParserAtomIndex>* data, + uint32_t length) { + std::fill_n(data, length, + AbstractBindingName<frontend::TaggedParserAtomIndex>()); +} + +template <typename ScopeDataT> +static inline void PoisonNames(ScopeDataT* data, uint32_t length) { + if (length) { + PoisonNames(GetScopeDataTrailingNamesPointer(data), length); + } +} + +// +// Allow using is<T> and as<T> on Rooted<Scope*> and Handle<Scope*>. +// +template <typename Wrapper> +class WrappedPtrOperations<Scope*, Wrapper> { + public: + template <class U> + JS::Handle<U*> as() const { + const Wrapper& self = *static_cast<const Wrapper*>(this); + MOZ_ASSERT_IF(self, self->template is<U>()); + return Handle<U*>::fromMarkedLocation( + reinterpret_cast<U* const*>(self.address())); + } +}; + +// +// The base class of all Scopes. +// +class Scope : public gc::TenuredCellWithNonGCPointer<BaseScopeData> { + friend class GCMarker; + friend class frontend::ScopeStencil; + friend class js::AbstractBindingIter<JSAtom>; + friend class js::frontend::RuntimeScopeBindingCache; + friend class gc::CellAllocator; + + protected: + // The raw data pointer, stored in the cell header. + BaseScopeData* rawData() { return headerPtr(); } + const BaseScopeData* rawData() const { return headerPtr(); } + + // The kind determines data_. + const ScopeKind kind_; + + // If there are any aliased bindings, the shape for the + // EnvironmentObject. Otherwise nullptr. + const HeapPtr<SharedShape*> environmentShape_; + + // The enclosing scope or nullptr. + HeapPtr<Scope*> enclosingScope_; + + Scope(ScopeKind kind, Scope* enclosing, SharedShape* environmentShape) + : TenuredCellWithNonGCPointer(nullptr), + kind_(kind), + environmentShape_(environmentShape), + enclosingScope_(enclosing) {} + + static Scope* create(JSContext* cx, ScopeKind kind, Handle<Scope*> enclosing, + Handle<SharedShape*> envShape); + + template <typename ConcreteScope> + void initData( + MutableHandle<UniquePtr<typename ConcreteScope::RuntimeData>> data); + + template <typename F> + void applyScopeDataTyped(F&& f); + + static void updateEnvShapeIfRequired(mozilla::Maybe<uint32_t>* envShape, + bool needsEnvironment); + + public: + template <typename ConcreteScope> + static ConcreteScope* create( + JSContext* cx, ScopeKind kind, Handle<Scope*> enclosing, + Handle<SharedShape*> envShape, + MutableHandle<UniquePtr<typename ConcreteScope::RuntimeData>> data); + + static const JS::TraceKind TraceKind = JS::TraceKind::Scope; + + template <typename T> + bool is() const { + return kind_ == T::classScopeKind_; + } + + template <typename T> + T& as() { + MOZ_ASSERT(this->is<T>()); + return *static_cast<T*>(this); + } + + template <typename T> + const T& as() const { + MOZ_ASSERT(this->is<T>()); + return *static_cast<const T*>(this); + } + + ScopeKind kind() const { return kind_; } + + bool isNamedLambda() const { + return kind() == ScopeKind::NamedLambda || + kind() == ScopeKind::StrictNamedLambda; + } + + SharedShape* environmentShape() const { return environmentShape_; } + + Scope* enclosing() const { return enclosingScope_; } + + static bool hasEnvironment(ScopeKind kind, bool hasEnvironmentShape = false) { + switch (kind) { + case ScopeKind::With: + case ScopeKind::Global: + case ScopeKind::NonSyntactic: + return true; + default: + // If there's a shape, an environment must be created for this scope. + return hasEnvironmentShape; + } + } + + bool hasEnvironment() const { + return hasEnvironment(kind_, !!environmentShape()); + } + + uint32_t firstFrameSlot() const; + + uint32_t chainLength() const; + uint32_t environmentChainLength() const; + + template <typename T> + bool hasOnChain() const { + for (const Scope* it = this; it; it = it->enclosing()) { + if (it->is<T>()) { + return true; + } + } + return false; + } + + bool hasOnChain(ScopeKind kind) const { + for (const Scope* it = this; it; it = it->enclosing()) { + if (it->kind() == kind) { + return true; + } + } + return false; + } + + void traceChildren(JSTracer* trc); + void finalize(JS::GCContext* gcx); + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + void dump(); +#if defined(DEBUG) || defined(JS_JITSPEW) + static bool dumpForDisassemble(JSContext* cx, JS::Handle<Scope*> scope, + GenericPrinter& out, const char* indent); +#endif /* defined(DEBUG) || defined(JS_JITSPEW) */ +}; + +template <class DataT> +inline size_t SizeOfScopeData(uint32_t length) { + using BindingT = AbstractBindingName<typename DataT::NameType>; + return GetOffsetOfScopeDataTrailingNames<DataT>() + length * sizeof(BindingT); +} + +// +// A useful typedef for selecting between a gc-aware wrappers +// around pointers to BaseScopeData-derived types, and around raw +// pointer wrappers around BaseParserScopeData-derived types. +// +template <typename ScopeT, typename AtomT> +using AbstractScopeData = typename ScopeT::template AbstractData<AtomT>; + +// Binding names are stored from `this+1`. +// Make sure the class aligns the binding name size. +template <typename SlotInfo> +struct alignas(alignof(AbstractBindingName<frontend::TaggedParserAtomIndex>)) + ParserScopeData + : public AbstractBaseScopeData<frontend::TaggedParserAtomIndex> { + SlotInfo slotInfo; + + explicit ParserScopeData(size_t length) { PoisonNames(this, length); } + ParserScopeData() = delete; +}; + +// RuntimeScopeData has 2 requirements: +// * It aligns with `BindingName`, that is stored after `this+1` +// * It aligns with ScopeDataAlignBytes, in order to put it in the first +// word of `js::Scope` +static_assert(alignof(BindingName) <= ScopeDataAlignBytes); +template <typename SlotInfo> +struct alignas(ScopeDataAlignBytes) RuntimeScopeData + : public AbstractBaseScopeData<JSAtom> { + SlotInfo slotInfo; + + explicit RuntimeScopeData(size_t length) { PoisonNames(this, length); } + RuntimeScopeData() = delete; + + void trace(JSTracer* trc); +}; + +// +// A lexical scope that holds let and const bindings. There are 4 kinds of +// LexicalScopes. +// +// Lexical +// A plain lexical scope. +// +// SimpleCatch +// Holds the single catch parameter of a catch block. +// +// Catch +// Holds the catch parameters (and only the catch parameters) of a catch +// block. +// +// NamedLambda +// StrictNamedLambda +// Holds the single name of the callee for a named lambda expression. +// +// All kinds of LexicalScopes correspond to LexicalEnvironmentObjects on the +// environment chain. +// +class LexicalScope : public Scope { + friend class Scope; + friend class AbstractBindingIter<JSAtom>; + friend class GCMarker; + friend class frontend::ScopeStencil; + + public: + struct SlotInfo { + // Frame slots [0, nextFrameSlot) are live when this is the innermost + // scope. + uint32_t nextFrameSlot = 0; + + // Bindings are sorted by kind in both frames and environments. + // + // lets - [0, constStart) + // consts - [constStart, length) + uint32_t constStart = 0; + }; + + using RuntimeData = RuntimeScopeData<SlotInfo>; + using ParserData = ParserScopeData<SlotInfo>; + + template <typename NameT> + using AbstractData = + typename std::conditional_t<std::is_same<NameT, JSAtom>::value, + RuntimeData, ParserData>; + + private: + static void prepareForScopeCreation(ScopeKind kind, uint32_t firstFrameSlot, + LexicalScope::ParserData* data, + mozilla::Maybe<uint32_t>* envShape); + + RuntimeData& data() { return *static_cast<RuntimeData*>(rawData()); } + const RuntimeData& data() const { + return *static_cast<const RuntimeData*>(rawData()); + } + + public: + static uint32_t nextFrameSlot(Scope* scope); + + uint32_t nextFrameSlot() const { return data().slotInfo.nextFrameSlot; } + + // Returns an empty shape for extensible global and non-syntactic lexical + // scopes. + static SharedShape* getEmptyExtensibleEnvironmentShape(JSContext* cx); +}; + +template <> +inline bool Scope::is<LexicalScope>() const { + return kind_ == ScopeKind::Lexical || kind_ == ScopeKind::SimpleCatch || + kind_ == ScopeKind::Catch || kind_ == ScopeKind::NamedLambda || + kind_ == ScopeKind::StrictNamedLambda || + kind_ == ScopeKind::FunctionLexical; +} + +// The body scope of a JS class, containing only synthetic bindings for private +// class members. (The binding for the class name, `C` in the example below, is +// in another scope, a `LexicalScope`, that encloses the `ClassBodyScope`.) +// Example: +// +// class C { +// #f = 0; +// #m() { +// return this.#f++; +// } +// } +// +// This class has a ClassBodyScope with four synthetic bindings: +// - `#f` (private name) +// - `#m` (private name) +// - `#m.method` (function object) +// - `.privateBrand` (the class's private brand) +class ClassBodyScope : public Scope { + friend class Scope; + friend class AbstractBindingIter<JSAtom>; + friend class GCMarker; + friend class frontend::ScopeStencil; + friend class AbstractScopePtr; + + static const ScopeKind classScopeKind_ = ScopeKind::ClassBody; + + public: + struct SlotInfo { + // Frame slots [0, nextFrameSlot) are live when this is the innermost + // scope. + uint32_t nextFrameSlot = 0; + + // Bindings are sorted by kind in both frames and environments. + // + // synthetic - [0, privateMethodStart) + // privateMethod - [privateMethodStart, length) + uint32_t privateMethodStart = 0; + }; + + using RuntimeData = RuntimeScopeData<SlotInfo>; + using ParserData = ParserScopeData<SlotInfo>; + + template <typename NameT> + using AbstractData = + typename std::conditional_t<std::is_same<NameT, JSAtom>::value, + RuntimeData, ParserData>; + + private: + static void prepareForScopeCreation(ScopeKind kind, uint32_t firstFrameSlot, + ClassBodyScope::ParserData* data, + mozilla::Maybe<uint32_t>* envShape); + + RuntimeData& data() { return *static_cast<RuntimeData*>(rawData()); } + const RuntimeData& data() const { + return *static_cast<const RuntimeData*>(rawData()); + } + + public: + static uint32_t nextFrameSlot(Scope* scope); + + uint32_t nextFrameSlot() const { return data().slotInfo.nextFrameSlot; } + + // Returns an empty shape for extensible global and non-syntactic lexical + // scopes. + static SharedShape* getEmptyExtensibleEnvironmentShape(JSContext* cx); +}; + +// +// Scope corresponding to a function. Holds formal parameter names, special +// internal names (see FunctionScope::isSpecialName), and, if the function +// parameters contain no expressions that might possibly be evaluated, the +// function's var bindings. For example, in these functions, the FunctionScope +// will store a/b/c bindings but not d/e/f bindings: +// +// function f1(a, b) { +// var c; +// let e; +// const f = 3; +// } +// function f2([a], b = 4, ...c) { +// var d, e, f; // stored in VarScope +// } +// +// Corresponds to CallObject on environment chain. +// +class FunctionScope : public Scope { + friend class GCMarker; + friend class AbstractBindingIter<JSAtom>; + friend class AbstractPositionalFormalParameterIter<JSAtom>; + friend class Scope; + friend class AbstractScopePtr; + static const ScopeKind classScopeKind_ = ScopeKind::Function; + + public: + struct SlotInfo { + // Frame slots [0, nextFrameSlot) are live when this is the innermost + // scope. + uint32_t nextFrameSlot = 0; + + // Flag bits. + // This uses uint32_t in order to make this struct packed. + uint32_t flags = 0; + + // If parameter expressions are present, parameters act like lexical + // bindings. + static constexpr uint32_t HasParameterExprsFlag = 1; + + // Bindings are sorted by kind in both frames and environments. + // + // Positional formal parameter names are those that are not + // destructured. They may be referred to by argument slots if + // !script()->hasParameterExprs(). + // + // An argument slot that needs to be skipped due to being destructured + // or having defaults will have a nullptr name in the name array to + // advance the argument slot. + // + // Rest parameter binding is also included in positional formals. + // This also becomes nullptr if destructuring. + // + // The number of positional formals is equal to function.length if + // there's no rest, function.length+1 otherwise. + // + // Destructuring parameters and destructuring rest are included in + // "other formals" below. + // + // "vars" contains the following: + // * function's top level vars if !script()->hasParameterExprs() + // * special internal names (arguments, .this, .generator) if + // they're used. + // + // positional formals - [0, nonPositionalFormalStart) + // other formals - [nonPositionalParamStart, varStart) + // vars - [varStart, length) + uint16_t nonPositionalFormalStart = 0; + uint16_t varStart = 0; + + bool hasParameterExprs() const { return flags & HasParameterExprsFlag; } + void setHasParameterExprs() { flags |= HasParameterExprsFlag; } + }; + + struct alignas(ScopeDataAlignBytes) RuntimeData + : public AbstractBaseScopeData<JSAtom> { + SlotInfo slotInfo; + // The canonical function of the scope, as during a scope walk we + // often query properties of the JSFunction (e.g., is the function an + // arrow). + HeapPtr<JSFunction*> canonicalFunction = {}; + + explicit RuntimeData(size_t length) { PoisonNames(this, length); } + RuntimeData() = delete; + + void trace(JSTracer* trc); + }; + + using ParserData = ParserScopeData<SlotInfo>; + + template <typename NameT> + using AbstractData = + typename std::conditional_t<std::is_same<NameT, JSAtom>::value, + RuntimeData, ParserData>; + + static void prepareForScopeCreation(FunctionScope::ParserData* data, + bool hasParameterExprs, + bool needsEnvironment, + mozilla::Maybe<uint32_t>* envShape); + + private: + RuntimeData& data() { return *static_cast<RuntimeData*>(rawData()); } + + const RuntimeData& data() const { + return *static_cast<const RuntimeData*>(rawData()); + } + + public: + uint32_t nextFrameSlot() const { return data().slotInfo.nextFrameSlot; } + + JSFunction* canonicalFunction() const { return data().canonicalFunction; } + void initCanonicalFunction(JSFunction* fun) { + data().canonicalFunction.init(fun); + } + + JSScript* script() const; + + bool hasParameterExprs() const { return data().slotInfo.hasParameterExprs(); } + + uint32_t numPositionalFormalParameters() const { + return data().slotInfo.nonPositionalFormalStart; + } + + static bool isSpecialName(frontend::TaggedParserAtomIndex name); +}; + +// +// Scope holding only vars. There is a single kind of VarScopes. +// +// FunctionBodyVar +// Corresponds to the extra var scope present in functions with parameter +// expressions. See examples in comment above FunctionScope. +// +// Corresponds to VarEnvironmentObject on environment chain. +// +class VarScope : public Scope { + friend class GCMarker; + friend class AbstractBindingIter<JSAtom>; + friend class Scope; + friend class frontend::ScopeStencil; + + public: + struct SlotInfo { + // Frame slots [0, nextFrameSlot) are live when this is the innermost + // scope. + uint32_t nextFrameSlot = 0; + + // All bindings are vars. + // + // vars - [0, length) + }; + + using RuntimeData = RuntimeScopeData<SlotInfo>; + using ParserData = ParserScopeData<SlotInfo>; + + template <typename NameT> + using AbstractData = + typename std::conditional_t<std::is_same<NameT, JSAtom>::value, + RuntimeData, ParserData>; + + private: + static void prepareForScopeCreation(ScopeKind kind, + VarScope::ParserData* data, + uint32_t firstFrameSlot, + bool needsEnvironment, + mozilla::Maybe<uint32_t>* envShape); + + RuntimeData& data() { return *static_cast<RuntimeData*>(rawData()); } + + const RuntimeData& data() const { + return *static_cast<const RuntimeData*>(rawData()); + } + + public: + uint32_t nextFrameSlot() const { return data().slotInfo.nextFrameSlot; } +}; + +template <> +inline bool Scope::is<VarScope>() const { + return kind_ == ScopeKind::FunctionBodyVar; +} + +// +// Scope corresponding to both the global object scope and the global lexical +// scope. +// +// Both are extensible and are singletons across <script> tags, so these +// scopes are a fragment of the names in global scope. In other words, two +// global scripts may have two different GlobalScopes despite having the same +// GlobalObject. +// +// There are 2 kinds of GlobalScopes. +// +// Global +// Corresponds to a GlobalObject and its GlobalLexicalEnvironmentObject on +// the environment chain. +// +// NonSyntactic +// Corresponds to a non-GlobalObject created by the embedding on the +// environment chain. This distinction is important for optimizations. +// +class GlobalScope : public Scope { + friend class Scope; + friend class AbstractBindingIter<JSAtom>; + friend class GCMarker; + + public: + struct SlotInfo { + // Bindings are sorted by kind. + // `vars` includes top-level functions which is distinguished by a bit + // on the BindingName. + // + // vars - [0, letStart) + // lets - [letStart, constStart) + // consts - [constStart, length) + uint32_t letStart = 0; + uint32_t constStart = 0; + }; + + using RuntimeData = RuntimeScopeData<SlotInfo>; + using ParserData = ParserScopeData<SlotInfo>; + + template <typename NameT> + using AbstractData = + typename std::conditional_t<std::is_same<NameT, JSAtom>::value, + RuntimeData, ParserData>; + + static GlobalScope* createEmpty(JSContext* cx, ScopeKind kind); + + private: + static GlobalScope* createWithData( + JSContext* cx, ScopeKind kind, + MutableHandle<UniquePtr<RuntimeData>> data); + + RuntimeData& data() { return *static_cast<RuntimeData*>(rawData()); } + + const RuntimeData& data() const { + return *static_cast<const RuntimeData*>(rawData()); + } + + public: + bool isSyntactic() const { return kind() != ScopeKind::NonSyntactic; } + + bool hasBindings() const { return data().length > 0; } +}; + +template <> +inline bool Scope::is<GlobalScope>() const { + return kind_ == ScopeKind::Global || kind_ == ScopeKind::NonSyntactic; +} + +// +// Scope of a 'with' statement. Has no bindings. +// +// Corresponds to a WithEnvironmentObject on the environment chain. +class WithScope : public Scope { + friend class Scope; + friend class AbstractScopePtr; + static const ScopeKind classScopeKind_ = ScopeKind::With; + + public: + static WithScope* create(JSContext* cx, Handle<Scope*> enclosing); +}; + +// +// Scope of an eval. Holds var bindings. There are 2 kinds of EvalScopes. +// +// StrictEval +// A strict eval. Corresponds to a VarEnvironmentObject, where its var +// bindings lives. +// +// Eval +// A sloppy eval. This is an empty scope, used only in the frontend, to +// detect redeclaration errors. It has no Environment. Any `var`s declared +// in the eval code are bound on the nearest enclosing var environment. +// +class EvalScope : public Scope { + friend class Scope; + friend class AbstractBindingIter<JSAtom>; + friend class GCMarker; + friend class frontend::ScopeStencil; + + public: + struct SlotInfo { + // Frame slots [0, nextFrameSlot) are live when this is the innermost + // scope. + uint32_t nextFrameSlot = 0; + + // All bindings in an eval script are 'var' bindings. The implicit + // lexical scope around the eval is present regardless of strictness + // and is its own LexicalScope. + // `vars` includes top-level functions which is distinguished by a bit + // on the BindingName. + // + // vars - [0, length) + }; + + using RuntimeData = RuntimeScopeData<SlotInfo>; + using ParserData = ParserScopeData<SlotInfo>; + + template <typename NameT> + using AbstractData = + typename std::conditional_t<std::is_same<NameT, JSAtom>::value, + RuntimeData, ParserData>; + + private: + static void prepareForScopeCreation(ScopeKind scopeKind, + EvalScope::ParserData* data, + mozilla::Maybe<uint32_t>* envShape); + + RuntimeData& data() { return *static_cast<RuntimeData*>(rawData()); } + + const RuntimeData& data() const { + return *static_cast<const RuntimeData*>(rawData()); + } + + public: + // Starting a scope, the nearest var scope that a direct eval can + // introduce vars on. + static Scope* nearestVarScopeForDirectEval(Scope* scope); + + uint32_t nextFrameSlot() const { return data().slotInfo.nextFrameSlot; } + + bool strict() const { return kind() == ScopeKind::StrictEval; } + + bool hasBindings() const { return data().length > 0; } + + bool isNonGlobal() const { + if (strict()) { + return true; + } + return !nearestVarScopeForDirectEval(enclosing())->is<GlobalScope>(); + } +}; + +template <> +inline bool Scope::is<EvalScope>() const { + return kind_ == ScopeKind::Eval || kind_ == ScopeKind::StrictEval; +} + +// +// Scope corresponding to the toplevel script in an ES module. +// +// Like GlobalScopes, these scopes contain both vars and lexical bindings, as +// the treating of imports and exports requires putting them in one scope. +// +// Corresponds to a ModuleEnvironmentObject on the environment chain. +// +class ModuleScope : public Scope { + friend class GCMarker; + friend class AbstractBindingIter<JSAtom>; + friend class Scope; + friend class AbstractScopePtr; + friend class frontend::ScopeStencil; + static const ScopeKind classScopeKind_ = ScopeKind::Module; + + public: + struct SlotInfo { + // Frame slots [0, nextFrameSlot) are live when this is the innermost + // scope. + uint32_t nextFrameSlot = 0; + + // Bindings are sorted by kind. + // + // imports - [0, varStart) + // vars - [varStart, letStart) + // lets - [letStart, constStart) + // consts - [constStart, length) + uint32_t varStart = 0; + uint32_t letStart = 0; + uint32_t constStart = 0; + }; + + struct alignas(ScopeDataAlignBytes) RuntimeData + : public AbstractBaseScopeData<JSAtom> { + SlotInfo slotInfo; + // The module of the scope. + HeapPtr<ModuleObject*> module = {}; + + explicit RuntimeData(size_t length); + RuntimeData() = delete; + + void trace(JSTracer* trc); + }; + + using ParserData = ParserScopeData<SlotInfo>; + + template <typename NameT> + using AbstractData = + typename std::conditional_t<std::is_same<NameT, JSAtom>::value, + RuntimeData, ParserData>; + + private: + static void prepareForScopeCreation(ModuleScope::ParserData* data, + mozilla::Maybe<uint32_t>* envShape); + + RuntimeData& data() { return *static_cast<RuntimeData*>(rawData()); } + + const RuntimeData& data() const { + return *static_cast<const RuntimeData*>(rawData()); + } + + public: + uint32_t nextFrameSlot() const { return data().slotInfo.nextFrameSlot; } + + ModuleObject* module() const { return data().module; } + void initModule(ModuleObject* mod) { return data().module.init(mod); } + + // Off-thread compilation needs to calculate environmentChainLength for + // an emptyGlobalScope where the global may not be available. + static const size_t EnclosingEnvironmentChainLength = 1; +}; + +class WasmInstanceScope : public Scope { + friend class AbstractBindingIter<JSAtom>; + friend class Scope; + friend class GCMarker; + friend class AbstractScopePtr; + static const ScopeKind classScopeKind_ = ScopeKind::WasmInstance; + + public: + struct SlotInfo { + // Frame slots [0, nextFrameSlot) are live when this is the innermost + // scope. + uint32_t nextFrameSlot = 0; + + // Bindings list the WASM memories and globals. + // + // memories - [0, globalsStart) + // globals - [globalsStart, length) + uint32_t memoriesStart = 0; + uint32_t globalsStart = 0; + }; + + struct alignas(ScopeDataAlignBytes) RuntimeData + : public AbstractBaseScopeData<JSAtom> { + SlotInfo slotInfo; + // The wasm instance of the scope. + HeapPtr<WasmInstanceObject*> instance = {}; + + explicit RuntimeData(size_t length); + RuntimeData() = delete; + + void trace(JSTracer* trc); + }; + + using ParserData = ParserScopeData<SlotInfo>; + + template <typename NameT> + using AbstractData = + typename std::conditional_t<std::is_same<NameT, JSAtom>::value, + RuntimeData, ParserData>; + + static WasmInstanceScope* create(JSContext* cx, WasmInstanceObject* instance); + + private: + RuntimeData& data() { return *static_cast<RuntimeData*>(rawData()); } + + const RuntimeData& data() const { + return *static_cast<const RuntimeData*>(rawData()); + } + + public: + WasmInstanceObject* instance() const { return data().instance; } + + uint32_t memoriesStart() const { return data().slotInfo.memoriesStart; } + + uint32_t globalsStart() const { return data().slotInfo.globalsStart; } + + uint32_t namesCount() const { return data().length; } +}; + +// Scope corresponding to the wasm function. A WasmFunctionScope is used by +// Debugger only, and not for wasm execution. +// +class WasmFunctionScope : public Scope { + friend class AbstractBindingIter<JSAtom>; + friend class Scope; + friend class GCMarker; + friend class AbstractScopePtr; + static const ScopeKind classScopeKind_ = ScopeKind::WasmFunction; + + public: + struct SlotInfo { + // Frame slots [0, nextFrameSlot) are live when this is the innermost + // scope. + uint32_t nextFrameSlot = 0; + + // Bindings are the local variable names. + // + // vars - [0, length) + }; + + using RuntimeData = RuntimeScopeData<SlotInfo>; + using ParserData = ParserScopeData<SlotInfo>; + + template <typename NameT> + using AbstractData = + typename std::conditional_t<std::is_same<NameT, JSAtom>::value, + RuntimeData, ParserData>; + + static WasmFunctionScope* create(JSContext* cx, Handle<Scope*> enclosing, + uint32_t funcIndex); + + private: + RuntimeData& data() { return *static_cast<RuntimeData*>(rawData()); } + + const RuntimeData& data() const { + return *static_cast<const RuntimeData*>(rawData()); + } +}; + +template <typename F> +void Scope::applyScopeDataTyped(F&& f) { + switch (kind()) { + case ScopeKind::Function: { + f(&as<FunctionScope>().data()); + break; + case ScopeKind::FunctionBodyVar: + f(&as<VarScope>().data()); + break; + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::FunctionLexical: + f(&as<LexicalScope>().data()); + break; + case ScopeKind::ClassBody: + f(&as<ClassBodyScope>().data()); + break; + case ScopeKind::With: + // With scopes do not have data. + break; + case ScopeKind::Eval: + case ScopeKind::StrictEval: + f(&as<EvalScope>().data()); + break; + case ScopeKind::Global: + case ScopeKind::NonSyntactic: + f(&as<GlobalScope>().data()); + break; + case ScopeKind::Module: + f(&as<ModuleScope>().data()); + break; + case ScopeKind::WasmInstance: + f(&as<WasmInstanceScope>().data()); + break; + case ScopeKind::WasmFunction: + f(&as<WasmFunctionScope>().data()); + break; + } + } +} + +// +// An iterator for a Scope's bindings. This is the source of truth for frame +// and environment object layout. +// +// It may be placed in GC containers; for example: +// +// for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) { +// use(bi); +// SomeMayGCOperation(); +// use(bi); +// } +// +template <typename NameT> +class BaseAbstractBindingIter { + protected: + // Bindings are sorted by kind. Because different Scopes have differently + // laid out {Runtime,Parser}Data for packing, BindingIter must handle all + // binding kinds. + // + // Kind ranges: + // + // imports - [0, positionalFormalStart) + // positional formals - [positionalFormalStart, nonPositionalFormalStart) + // other formals - [nonPositionalParamStart, varStart) + // vars - [varStart, letStart) + // lets - [letStart, constStart) + // consts - [constStart, syntheticStart) + // synthetic - [syntheticStart, privateMethodStart) + // private methods = [privateMethodStart, length) + // + // Access method when not closed over: + // + // imports - name + // positional formals - argument slot + // other formals - frame slot + // vars - frame slot + // lets - frame slot + // consts - frame slot + // synthetic - frame slot + // private methods - frame slot + // + // Access method when closed over: + // + // imports - name + // positional formals - environment slot or name + // other formals - environment slot or name + // vars - environment slot or name + // lets - environment slot or name + // consts - environment slot or name + // synthetic - environment slot or name + // private methods - environment slot or name + MOZ_INIT_OUTSIDE_CTOR uint32_t positionalFormalStart_; + MOZ_INIT_OUTSIDE_CTOR uint32_t nonPositionalFormalStart_; + MOZ_INIT_OUTSIDE_CTOR uint32_t varStart_; + MOZ_INIT_OUTSIDE_CTOR uint32_t letStart_; + MOZ_INIT_OUTSIDE_CTOR uint32_t constStart_; + MOZ_INIT_OUTSIDE_CTOR uint32_t syntheticStart_; + MOZ_INIT_OUTSIDE_CTOR uint32_t privateMethodStart_; + MOZ_INIT_OUTSIDE_CTOR uint32_t length_; + + MOZ_INIT_OUTSIDE_CTOR uint32_t index_; + + enum Flags : uint8_t { + CannotHaveSlots = 0, + CanHaveArgumentSlots = 1 << 0, + CanHaveFrameSlots = 1 << 1, + CanHaveEnvironmentSlots = 1 << 2, + + // See comment in settle below. + HasFormalParameterExprs = 1 << 3, + IgnoreDestructuredFormalParameters = 1 << 4, + + // Truly I hate named lambdas. + IsNamedLambda = 1 << 5 + }; + + static const uint8_t CanHaveSlotsMask = 0x7; + + MOZ_INIT_OUTSIDE_CTOR uint8_t flags_; + MOZ_INIT_OUTSIDE_CTOR uint16_t argumentSlot_; + MOZ_INIT_OUTSIDE_CTOR uint32_t frameSlot_; + MOZ_INIT_OUTSIDE_CTOR uint32_t environmentSlot_; + + MOZ_INIT_OUTSIDE_CTOR AbstractBindingName<NameT>* names_; + + void init(uint32_t positionalFormalStart, uint32_t nonPositionalFormalStart, + uint32_t varStart, uint32_t letStart, uint32_t constStart, + uint32_t syntheticStart, uint32_t privateMethodStart, uint8_t flags, + uint32_t firstFrameSlot, uint32_t firstEnvironmentSlot, + mozilla::Span<AbstractBindingName<NameT>> names) { + positionalFormalStart_ = positionalFormalStart; + nonPositionalFormalStart_ = nonPositionalFormalStart; + varStart_ = varStart; + letStart_ = letStart; + constStart_ = constStart; + syntheticStart_ = syntheticStart; + privateMethodStart_ = privateMethodStart; + length_ = names.size(); + + index_ = 0; + flags_ = flags; + argumentSlot_ = 0; + frameSlot_ = firstFrameSlot; + environmentSlot_ = firstEnvironmentSlot; + names_ = names.data(); + + settle(); + } + + void init(LexicalScope::AbstractData<NameT>& data, uint32_t firstFrameSlot, + uint8_t flags); + + void init(ClassBodyScope::AbstractData<NameT>& data, uint32_t firstFrameSlot); + void init(FunctionScope::AbstractData<NameT>& data, uint8_t flags); + + void init(VarScope::AbstractData<NameT>& data, uint32_t firstFrameSlot); + void init(GlobalScope::AbstractData<NameT>& data); + void init(EvalScope::AbstractData<NameT>& data, bool strict); + void init(ModuleScope::AbstractData<NameT>& data); + void init(WasmInstanceScope::AbstractData<NameT>& data); + void init(WasmFunctionScope::AbstractData<NameT>& data); + + bool hasFormalParameterExprs() const { + return flags_ & HasFormalParameterExprs; + } + + bool ignoreDestructuredFormalParameters() const { + return flags_ & IgnoreDestructuredFormalParameters; + } + + bool isNamedLambda() const { return flags_ & IsNamedLambda; } + + void increment() { + MOZ_ASSERT(!done()); + if (flags_ & CanHaveSlotsMask) { + if (canHaveArgumentSlots()) { + if (index_ < nonPositionalFormalStart_) { + MOZ_ASSERT(index_ >= positionalFormalStart_); + argumentSlot_++; + } + } + if (closedOver()) { + // Imports must not be given known slots. They are + // indirect bindings. + MOZ_ASSERT(kind() != BindingKind::Import); + MOZ_ASSERT(canHaveEnvironmentSlots()); + environmentSlot_++; + } else if (canHaveFrameSlots()) { + // Usually positional formal parameters don't have frame + // slots, except when there are parameter expressions, in + // which case they act like lets. + if (index_ >= nonPositionalFormalStart_ || + (hasFormalParameterExprs() && name())) { + frameSlot_++; + } + } + } + index_++; + } + + void settle() { + if (ignoreDestructuredFormalParameters()) { + while (!done() && !name()) { + increment(); + } + } + } + + BaseAbstractBindingIter() = default; + + public: + BaseAbstractBindingIter(LexicalScope::AbstractData<NameT>& data, + uint32_t firstFrameSlot, bool isNamedLambda) { + init(data, firstFrameSlot, isNamedLambda ? IsNamedLambda : 0); + } + + BaseAbstractBindingIter(ClassBodyScope::AbstractData<NameT>& data, + uint32_t firstFrameSlot) { + init(data, firstFrameSlot); + } + + BaseAbstractBindingIter(FunctionScope::AbstractData<NameT>& data, + bool hasParameterExprs) { + init(data, IgnoreDestructuredFormalParameters | + (hasParameterExprs ? HasFormalParameterExprs : 0)); + } + + BaseAbstractBindingIter(VarScope::AbstractData<NameT>& data, + uint32_t firstFrameSlot) { + init(data, firstFrameSlot); + } + + explicit BaseAbstractBindingIter(GlobalScope::AbstractData<NameT>& data) { + init(data); + } + + explicit BaseAbstractBindingIter(ModuleScope::AbstractData<NameT>& data) { + init(data); + } + + explicit BaseAbstractBindingIter( + WasmFunctionScope::AbstractData<NameT>& data) { + init(data); + } + + BaseAbstractBindingIter(EvalScope::AbstractData<NameT>& data, bool strict) { + init(data, strict); + } + + MOZ_IMPLICIT BaseAbstractBindingIter( + const BaseAbstractBindingIter<NameT>& bi) = default; + + bool done() const { return index_ == length_; } + + explicit operator bool() const { return !done(); } + + void operator++(int) { + increment(); + settle(); + } + + bool isLast() const { + MOZ_ASSERT(!done()); + return index_ + 1 == length_; + } + + bool canHaveArgumentSlots() const { return flags_ & CanHaveArgumentSlots; } + + bool canHaveFrameSlots() const { return flags_ & CanHaveFrameSlots; } + + bool canHaveEnvironmentSlots() const { + return flags_ & CanHaveEnvironmentSlots; + } + + typename AbstractBindingName<NameT>::NamePointerT name() const { + MOZ_ASSERT(!done()); + return names_[index_].name(); + } + + bool closedOver() const { + MOZ_ASSERT(!done()); + return names_[index_].closedOver(); + } + + BindingLocation location() const { + MOZ_ASSERT(!done()); + if (!(flags_ & CanHaveSlotsMask)) { + return BindingLocation::Global(); + } + if (index_ < positionalFormalStart_) { + return BindingLocation::Import(); + } + if (closedOver()) { + MOZ_ASSERT(canHaveEnvironmentSlots()); + return BindingLocation::Environment(environmentSlot_); + } + if (index_ < nonPositionalFormalStart_ && canHaveArgumentSlots()) { + return BindingLocation::Argument(argumentSlot_); + } + if (canHaveFrameSlots()) { + return BindingLocation::Frame(frameSlot_); + } + MOZ_ASSERT(isNamedLambda()); + return BindingLocation::NamedLambdaCallee(); + } + + BindingKind kind() const { + MOZ_ASSERT(!done()); + if (index_ < positionalFormalStart_) { + return BindingKind::Import; + } + if (index_ < varStart_) { + // When the parameter list has expressions, the parameters act + // like lexical bindings and have TDZ. + if (hasFormalParameterExprs()) { + return BindingKind::Let; + } + return BindingKind::FormalParameter; + } + if (index_ < letStart_) { + return BindingKind::Var; + } + if (index_ < constStart_) { + return BindingKind::Let; + } + if (index_ < syntheticStart_) { + return isNamedLambda() ? BindingKind::NamedLambdaCallee + : BindingKind::Const; + } + if (index_ < privateMethodStart_) { + return BindingKind::Synthetic; + } + return BindingKind::PrivateMethod; + } + + js::frontend::NameLocation nameLocation() const { + using js::frontend::NameLocation; + + BindingKind bindKind = kind(); + BindingLocation bl = location(); + switch (bl.kind()) { + case BindingLocation::Kind::Global: + return NameLocation::Global(bindKind); + case BindingLocation::Kind::Argument: + return NameLocation::ArgumentSlot(bl.argumentSlot()); + case BindingLocation::Kind::Frame: + return NameLocation::FrameSlot(bindKind, bl.slot()); + case BindingLocation::Kind::Environment: + return NameLocation::EnvironmentCoordinate(bindKind, 0, bl.slot()); + case BindingLocation::Kind::Import: + return NameLocation::Import(); + case BindingLocation::Kind::NamedLambdaCallee: + return NameLocation::NamedLambdaCallee(); + } + MOZ_CRASH("Bad BindingKind"); + } + + bool isTopLevelFunction() const { + MOZ_ASSERT(!done()); + bool result = names_[index_].isTopLevelFunction(); + MOZ_ASSERT_IF(result, kind() == BindingKind::Var); + return result; + } + + bool hasArgumentSlot() const { + MOZ_ASSERT(!done()); + if (hasFormalParameterExprs()) { + return false; + } + return index_ >= positionalFormalStart_ && + index_ < nonPositionalFormalStart_; + } + + uint16_t argumentSlot() const { + MOZ_ASSERT(canHaveArgumentSlots()); + return mozilla::AssertedCast<uint16_t>(index_); + } + + uint32_t nextFrameSlot() const { + MOZ_ASSERT(canHaveFrameSlots()); + return frameSlot_; + } + + uint32_t nextEnvironmentSlot() const { + MOZ_ASSERT(canHaveEnvironmentSlots()); + return environmentSlot_; + } +}; + +template <typename NameT> +class AbstractBindingIter; + +template <> +class AbstractBindingIter<JSAtom> : public BaseAbstractBindingIter<JSAtom> { + using Base = BaseAbstractBindingIter<JSAtom>; + + public: + AbstractBindingIter(ScopeKind kind, BaseScopeData* data, + uint32_t firstFrameSlot); + + explicit AbstractBindingIter(Scope* scope); + explicit AbstractBindingIter(JSScript* script); + + using Base::Base; + + inline void trace(JSTracer* trc) { + TraceNullableBindingNames(trc, names_, length_); + } +}; + +template <> +class AbstractBindingIter<frontend::TaggedParserAtomIndex> + : public BaseAbstractBindingIter<frontend::TaggedParserAtomIndex> { + using Base = BaseAbstractBindingIter<frontend::TaggedParserAtomIndex>; + + public: + explicit AbstractBindingIter(const frontend::ScopeStencilRef& ref); + + using Base::Base; +}; + +void DumpBindings(JSContext* cx, Scope* scope); +JSAtom* FrameSlotName(JSScript* script, jsbytecode* pc); + +SharedShape* EmptyEnvironmentShape(JSContext* cx, const JSClass* cls, + uint32_t numSlots, ObjectFlags objectFlags); + +template <class T> +SharedShape* EmptyEnvironmentShape(JSContext* cx) { + return EmptyEnvironmentShape(cx, &T::class_, T::RESERVED_SLOTS, + T::OBJECT_FLAGS); +} + +// +// PositionalFormalParameterIter is a refinement BindingIter that only iterates +// over positional formal parameters of a function. +// +template <typename NameT> +class BasePositionalFormalParamterIter : public AbstractBindingIter<NameT> { + using Base = AbstractBindingIter<NameT>; + + protected: + void settle() { + if (this->index_ >= this->nonPositionalFormalStart_) { + this->index_ = this->length_; + } + } + + public: + using Base::Base; + + void operator++(int) { + Base::operator++(1); + settle(); + } + + bool isDestructured() const { return !this->name(); } +}; + +template <typename NameT> +class AbstractPositionalFormalParameterIter; + +template <> +class AbstractPositionalFormalParameterIter<JSAtom> + : public BasePositionalFormalParamterIter<JSAtom> { + using Base = BasePositionalFormalParamterIter<JSAtom>; + + public: + explicit AbstractPositionalFormalParameterIter(Scope* scope); + explicit AbstractPositionalFormalParameterIter(JSScript* script); + + using Base::Base; +}; + +template <> +class AbstractPositionalFormalParameterIter<frontend::TaggedParserAtomIndex> + : public BasePositionalFormalParamterIter<frontend::TaggedParserAtomIndex> { + using Base = + BasePositionalFormalParamterIter<frontend::TaggedParserAtomIndex>; + + public: + AbstractPositionalFormalParameterIter( + FunctionScope::AbstractData<frontend::TaggedParserAtomIndex>& data, + bool hasParameterExprs) + : Base(data, hasParameterExprs) { + settle(); + } + + using Base::Base; +}; + +using PositionalFormalParameterIter = + AbstractPositionalFormalParameterIter<JSAtom>; + +// +// Iterator for walking the scope chain. +// +// It may be placed in GC containers; for example: +// +// for (Rooted<ScopeIter> si(cx, ScopeIter(scope)); si; si++) { +// use(si); +// SomeMayGCOperation(); +// use(si); +// } +// +class MOZ_STACK_CLASS ScopeIter { + Scope* scope_; + + public: + explicit ScopeIter(Scope* scope) : scope_(scope) {} + + explicit ScopeIter(JSScript* script); + + explicit ScopeIter(const ScopeIter& si) = default; + + bool done() const { return !scope_; } + + explicit operator bool() const { return !done(); } + + void operator++(int) { + MOZ_ASSERT(!done()); + scope_ = scope_->enclosing(); + } + + Scope* scope() const { + MOZ_ASSERT(!done()); + return scope_; + } + + ScopeKind kind() const { + MOZ_ASSERT(!done()); + return scope_->kind(); + } + + // Returns the shape of the environment if it is known. It is possible to + // hasSyntacticEnvironment and to have no known shape, e.g., eval. + SharedShape* environmentShape() const { return scope()->environmentShape(); } + + // Returns whether this scope has a syntactic environment (i.e., an + // Environment that isn't a non-syntactic With or NonSyntacticVariables) + // on the environment chain. + bool hasSyntacticEnvironment() const; + + void trace(JSTracer* trc) { + if (scope_) { + TraceRoot(trc, &scope_, "scope iter scope"); + } + } +}; + +// +// Specializations of Rooted containers for the iterators. +// + +template <typename Wrapper> +class WrappedPtrOperations<BindingIter, Wrapper> { + const BindingIter& iter() const { + return static_cast<const Wrapper*>(this)->get(); + } + + public: + bool done() const { return iter().done(); } + explicit operator bool() const { return !done(); } + bool isLast() const { return iter().isLast(); } + bool canHaveArgumentSlots() const { return iter().canHaveArgumentSlots(); } + bool canHaveFrameSlots() const { return iter().canHaveFrameSlots(); } + bool canHaveEnvironmentSlots() const { + return iter().canHaveEnvironmentSlots(); + } + JSAtom* name() const { return iter().name(); } + bool closedOver() const { return iter().closedOver(); } + BindingLocation location() const { return iter().location(); } + BindingKind kind() const { return iter().kind(); } + bool isTopLevelFunction() const { return iter().isTopLevelFunction(); } + bool hasArgumentSlot() const { return iter().hasArgumentSlot(); } + uint16_t argumentSlot() const { return iter().argumentSlot(); } + uint32_t nextFrameSlot() const { return iter().nextFrameSlot(); } + uint32_t nextEnvironmentSlot() const { return iter().nextEnvironmentSlot(); } +}; + +template <typename Wrapper> +class MutableWrappedPtrOperations<BindingIter, Wrapper> + : public WrappedPtrOperations<BindingIter, Wrapper> { + BindingIter& iter() { return static_cast<Wrapper*>(this)->get(); } + + public: + void operator++(int) { iter().operator++(1); } +}; + +template <typename Wrapper> +class WrappedPtrOperations<ScopeIter, Wrapper> { + const ScopeIter& iter() const { + return static_cast<const Wrapper*>(this)->get(); + } + + public: + bool done() const { return iter().done(); } + explicit operator bool() const { return !done(); } + Scope* scope() const { return iter().scope(); } + ScopeKind kind() const { return iter().kind(); } + SharedShape* environmentShape() const { return iter().environmentShape(); } + bool hasSyntacticEnvironment() const { + return iter().hasSyntacticEnvironment(); + } +}; + +template <typename Wrapper> +class MutableWrappedPtrOperations<ScopeIter, Wrapper> + : public WrappedPtrOperations<ScopeIter, Wrapper> { + ScopeIter& iter() { return static_cast<Wrapper*>(this)->get(); } + + public: + void operator++(int) { iter().operator++(1); } +}; + +SharedShape* CreateEnvironmentShape(JSContext* cx, BindingIter& bi, + const JSClass* cls, uint32_t numSlots, + ObjectFlags objectFlags); + +SharedShape* EmptyEnvironmentShape(JSContext* cx, const JSClass* cls, + uint32_t numSlots, ObjectFlags objectFlags); + +static inline size_t GetOffsetOfParserScopeDataTrailingNames(ScopeKind kind) { + switch (kind) { + // FunctionScope + case ScopeKind::Function: + return GetOffsetOfScopeDataTrailingNames<FunctionScope::ParserData>(); + + // VarScope + case ScopeKind::FunctionBodyVar: + return GetOffsetOfScopeDataTrailingNames<VarScope::ParserData>(); + + // LexicalScope + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::FunctionLexical: + return GetOffsetOfScopeDataTrailingNames<LexicalScope::ParserData>(); + + // ClassBodyScope + case ScopeKind::ClassBody: + return GetOffsetOfScopeDataTrailingNames<ClassBodyScope::ParserData>(); + + // EvalScope + case ScopeKind::Eval: + case ScopeKind::StrictEval: + return GetOffsetOfScopeDataTrailingNames<EvalScope::ParserData>(); + + // GlobalScope + case ScopeKind::Global: + case ScopeKind::NonSyntactic: + return GetOffsetOfScopeDataTrailingNames<GlobalScope::ParserData>(); + + // ModuleScope + case ScopeKind::Module: + return GetOffsetOfScopeDataTrailingNames<ModuleScope::ParserData>(); + + // WasmInstanceScope + case ScopeKind::WasmInstance: + return GetOffsetOfScopeDataTrailingNames<WasmInstanceScope::ParserData>(); + + // WasmFunctionScope + case ScopeKind::WasmFunction: + return GetOffsetOfScopeDataTrailingNames<WasmFunctionScope::ParserData>(); + + // WithScope doesn't have ScopeData. + case ScopeKind::With: + default: + MOZ_CRASH("Unexpected ScopeKind"); + } + + return 0; +} + +inline size_t SizeOfParserScopeData(ScopeKind kind, uint32_t length) { + return GetOffsetOfParserScopeDataTrailingNames(kind) + + sizeof(AbstractBindingName<frontend::TaggedParserAtomIndex>) * length; +} + +inline mozilla::Span<AbstractBindingName<frontend::TaggedParserAtomIndex>> +GetParserScopeDataTrailingNames( + ScopeKind kind, + AbstractBaseScopeData<frontend::TaggedParserAtomIndex>* data) { + return mozilla::Span( + reinterpret_cast<AbstractBindingName<frontend::TaggedParserAtomIndex>*>( + uintptr_t(data) + GetOffsetOfParserScopeDataTrailingNames(kind)), + data->length); +} + +} // namespace js + +namespace JS { + +template <> +struct GCPolicy<js::ScopeKind> : public IgnoreGCPolicy<js::ScopeKind> {}; + +template <typename T> +struct ScopeDataGCPolicy : public NonGCPointerPolicy<T> {}; + +#define DEFINE_SCOPE_DATA_GCPOLICY(Data) \ + template <> \ + struct MapTypeToRootKind<Data*> { \ + static const RootKind kind = RootKind::Traceable; \ + }; \ + template <> \ + struct GCPolicy<Data*> : public ScopeDataGCPolicy<Data*> {} + +DEFINE_SCOPE_DATA_GCPOLICY(js::LexicalScope::RuntimeData); +DEFINE_SCOPE_DATA_GCPOLICY(js::ClassBodyScope::RuntimeData); +DEFINE_SCOPE_DATA_GCPOLICY(js::FunctionScope::RuntimeData); +DEFINE_SCOPE_DATA_GCPOLICY(js::VarScope::RuntimeData); +DEFINE_SCOPE_DATA_GCPOLICY(js::GlobalScope::RuntimeData); +DEFINE_SCOPE_DATA_GCPOLICY(js::EvalScope::RuntimeData); +DEFINE_SCOPE_DATA_GCPOLICY(js::ModuleScope::RuntimeData); +DEFINE_SCOPE_DATA_GCPOLICY(js::WasmFunctionScope::RuntimeData); + +#undef DEFINE_SCOPE_DATA_GCPOLICY + +namespace ubi { + +template <> +class Concrete<js::Scope> : TracerConcrete<js::Scope> { + protected: + explicit Concrete(js::Scope* ptr) : TracerConcrete<js::Scope>(ptr) {} + + public: + static void construct(void* storage, js::Scope* ptr) { + new (storage) Concrete(ptr); + } + + CoarseType coarseType() const final { return CoarseType::Script; } + + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +} // namespace ubi +} // namespace JS + +#endif // vm_Scope_h |