/* -*- 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 // std::fill_n #include // size_t #include // uint8_t, uint16_t, uint32_t, uintptr_t #include // std::is_same_v, std::is_base_of_v #include "builtin/ModuleObject.h" // ModuleObject, Handle #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 class AbstractBaseScopeData; template class BaseAbstractBindingIter; template class AbstractBindingIter; template class AbstractPositionalFormalParameterIter; using BindingIter = AbstractBindingIter; 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 class AbstractBindingName; template <> class AbstractBindingName { 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(bits_ & ~FlagMask); } bool closedOver() const { return bits_ & ClosedOverFlag; } private: friend class BaseAbstractBindingIter; // 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 { 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 copyWithNewAtom(JSAtom* newName) const { return AbstractBindingName(newName, closedOver(), isTopLevelFunction()); } void updateNameAfterStencilMerge(TaggedParserAtomIndex name) { bits_ = (bits_ & FlagMask) | name.rawData(); } private: friend class BaseAbstractBindingIter; 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; 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 class AbstractBaseScopeData { public: using NameType = NameT; // The length of names after specialized ScopeData subclasses. uint32_t length = 0; }; template static inline void AssertDerivedScopeData() { static_assert( !std::is_same_v>, "ScopeDataT shouldn't be AbstractBaseScopeData"); static_assert( std::is_base_of_v, ScopeDataT>, "ScopeDataT should be subclass of AbstractBaseScopeData"); } template static inline size_t GetOffsetOfScopeDataTrailingNames() { AssertDerivedScopeData(); return sizeof(ScopeDataT); } template static inline AbstractBindingName* GetScopeDataTrailingNamesPointer(ScopeDataT* data) { AssertDerivedScopeData(); return reinterpret_cast*>( data + 1); } template static inline const AbstractBindingName* GetScopeDataTrailingNamesPointer(const ScopeDataT* data) { AssertDerivedScopeData(); return reinterpret_cast< const AbstractBindingName*>(data + 1); } template static inline mozilla::Span> GetScopeDataTrailingNames(ScopeDataT* data) { return mozilla::Span(GetScopeDataTrailingNamesPointer(data), data->length); } template static inline mozilla::Span< const AbstractBindingName> GetScopeDataTrailingNames(const ScopeDataT* data) { return mozilla::Span(GetScopeDataTrailingNamesPointer(data), data->length); } using BaseScopeData = AbstractBaseScopeData; inline void PoisonNames(AbstractBindingName* data, uint32_t length) { AlwaysPoison(data, JS_SCOPE_DATA_TRAILING_NAMES_PATTERN, sizeof(AbstractBindingName) * length, MemCheckKind::MakeUndefined); } // frontend::TaggedParserAtomIndex doesn't require poison value. // Fill with null value instead. inline void PoisonNames( AbstractBindingName* data, uint32_t length) { std::fill_n(data, length, AbstractBindingName()); } template static inline void PoisonNames(ScopeDataT* data, uint32_t length) { if (length) { PoisonNames(GetScopeDataTrailingNamesPointer(data), length); } } // // Allow using is and as on Rooted and Handle. // template class WrappedPtrOperations { public: template JS::Handle as() const { const Wrapper& self = *static_cast(this); MOZ_ASSERT_IF(self, self->template is()); return Handle::fromMarkedLocation( reinterpret_cast(self.address())); } }; // // The base class of all Scopes. // class Scope : public gc::TenuredCellWithNonGCPointer { friend class GCMarker; friend class frontend::ScopeStencil; friend class js::AbstractBindingIter; 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 environmentShape_; // The enclosing scope or nullptr. HeapPtr enclosingScope_; Scope(ScopeKind kind, Scope* enclosing, SharedShape* environmentShape) : TenuredCellWithNonGCPointer(nullptr), kind_(kind), environmentShape_(environmentShape), enclosingScope_(enclosing) {} static Scope* create(JSContext* cx, ScopeKind kind, Handle enclosing, Handle envShape); template void initData( MutableHandle> data); template void applyScopeDataTyped(F&& f); static void updateEnvShapeIfRequired(mozilla::Maybe* envShape, bool needsEnvironment); public: template static ConcreteScope* create( JSContext* cx, ScopeKind kind, Handle enclosing, Handle envShape, MutableHandle> data); static const JS::TraceKind TraceKind = JS::TraceKind::Scope; template bool is() const { return kind_ == T::classScopeKind_; } template T& as() { MOZ_ASSERT(this->is()); return *static_cast(this); } template const T& as() const { MOZ_ASSERT(this->is()); return *static_cast(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 bool hasOnChain() const { for (const Scope* it = this; it; it = it->enclosing()) { if (it->is()) { 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, GenericPrinter& out, const char* indent); #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ }; template inline size_t SizeOfScopeData(uint32_t length) { using BindingT = AbstractBindingName; return GetOffsetOfScopeDataTrailingNames() + 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 using AbstractScopeData = typename ScopeT::template AbstractData; // Binding names are stored from `this+1`. // Make sure the class aligns the binding name size. template struct alignas(alignof(AbstractBindingName)) ParserScopeData : public AbstractBaseScopeData { 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 struct alignas(ScopeDataAlignBytes) RuntimeScopeData : public AbstractBaseScopeData { 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; 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; using ParserData = ParserScopeData; template using AbstractData = typename std::conditional_t::value, RuntimeData, ParserData>; private: static void prepareForScopeCreation(ScopeKind kind, uint32_t firstFrameSlot, LexicalScope::ParserData* data, mozilla::Maybe* envShape); RuntimeData& data() { return *static_cast(rawData()); } const RuntimeData& data() const { return *static_cast(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() 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; 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; using ParserData = ParserScopeData; template using AbstractData = typename std::conditional_t::value, RuntimeData, ParserData>; private: static void prepareForScopeCreation(ScopeKind kind, uint32_t firstFrameSlot, ClassBodyScope::ParserData* data, mozilla::Maybe* envShape); RuntimeData& data() { return *static_cast(rawData()); } const RuntimeData& data() const { return *static_cast(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; friend class AbstractPositionalFormalParameterIter; 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 { 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 canonicalFunction = {}; explicit RuntimeData(size_t length) { PoisonNames(this, length); } RuntimeData() = delete; void trace(JSTracer* trc); }; using ParserData = ParserScopeData; template using AbstractData = typename std::conditional_t::value, RuntimeData, ParserData>; static void prepareForScopeCreation(FunctionScope::ParserData* data, bool hasParameterExprs, bool needsEnvironment, mozilla::Maybe* envShape); private: RuntimeData& data() { return *static_cast(rawData()); } const RuntimeData& data() const { return *static_cast(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; 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; using ParserData = ParserScopeData; template using AbstractData = typename std::conditional_t::value, RuntimeData, ParserData>; private: static void prepareForScopeCreation(ScopeKind kind, VarScope::ParserData* data, uint32_t firstFrameSlot, bool needsEnvironment, mozilla::Maybe* envShape); RuntimeData& data() { return *static_cast(rawData()); } const RuntimeData& data() const { return *static_cast(rawData()); } public: uint32_t nextFrameSlot() const { return data().slotInfo.nextFrameSlot; } }; template <> inline bool Scope::is() 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