/* -*- 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 // std::fill_n #include // size_t #include // uint8_t, uint16_t, uint32_t, uintptr_t #include // std::is_same_v #include "builtin/ModuleObject.h" // ModuleObject, HandleModuleObject #include "frontend/ParserAtom.h" // frontend::TaggedParserAtomIndex #include "gc/Allocator.h" // AllowGC #include "gc/Barrier.h" // HeapPtr #include "gc/Cell.h" // TenuredCellWithNonGCPointer #include "gc/MaybeRooted.h" // MaybeRooted #include "gc/Rooting.h" // HandleScope, HandleShape, MutableHandleShape #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/BytecodeUtil.h" // LOCALNO_LIMIT, ENVCOORD_SLOT_LIMIT #include "vm/JSFunction.h" // JSFunction #include "vm/ScopeKind.h" // ScopeKind #include "vm/Shape.h" // Shape #include "vm/Xdr.h" // XDRResult, XDRState #include "wasm/WasmJS.h" // WasmInstanceObject class JSAtom; class JSFreeOp; class JSFunction; class JSScript; class JSTracer; struct JSContext; namespace JS { class Zone; } // namespace JS namespace js { class GenericPrinter; namespace frontend { struct CompilationAtomCache; class ScopeStencil; class ParserAtom; } // namespace frontend template class AbstractBaseScopeData; template class BaseAbstractBindingIter; template class AbstractBindingIter; 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)) {} private: // For fromXDR. AbstractBindingName(NameT* name, uint8_t flags) : bits_(uintptr_t(name) | flags) { static_assert(FlagMask < alignof(NameT), "Flags should fit into unused low bits of atom repr"); MOZ_ASSERT((flags & FlagMask) == flags); } public: static AbstractBindingName fromXDR(NameT* name, uint8_t flags) { return AbstractBindingName(name, flags); } uint8_t flagsForXDR() const { return static_cast(bits_ & FlagMask); } 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); }; 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: uint32_t* rawData() { return &bits_; } NamePointerT name() const { return TaggedParserAtomIndex::fromRaw(bits_ & ~FlagMask); } bool closedOver() const { return bits_ & ClosedOverFlag; } AbstractBindingName copyWithNewAtom(JSAtom* newName) const { return AbstractBindingName(newName, closedOver(), isTopLevelFunction()); } 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; const size_t ScopeDataAlignBytes = size_t(1) << gc::CellFlagBitsReservedForGC; /** * Empty base class for scope {Runtime,Parser}Data classes to inherit from. * * Scope GC things store a pointer to these 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; }; using BaseScopeData = AbstractBaseScopeData; inline void PoisonNames(AbstractBindingName* data, size_t nameCount) { AlwaysPoison(data, JS_SCOPE_DATA_TRAILING_NAMES_PATTERN, sizeof(AbstractBindingName) * nameCount, MemCheckKind::MakeUndefined); } // frontend::TaggedParserAtomIndex doesn't require poison value. // Fill with null value instead. inline void PoisonNames( AbstractBindingName* data, size_t nameCount) { std::fill_n(data, nameCount, AbstractBindingName()); } /** * The various {Global,Module,...}Scope::{Runtime,Parser}Data classes consist * of always-present bits, then a trailing array of BindingNames. The various * {Runtime,Parser}Data classes all end in a TrailingNamesArray that contains * sized/aligned space for *one* BindingName. {Runtime,Parser}Data instances * that contain N BindingNames, are then allocated in * sizeof({Runtime,Parser}Data) + (space for (N - 1) BindingNames). * Because this class's |data_| field is properly sized/aligned, the * N-BindingName array can start at |data_|. * * This is concededly a very low-level representation, but we want to only * allocate once for data+bindings both, and this does so approximately as * elegantly as C++ allows. * * The names array is implemented in terms of an generic type that * allows specialization between a (JSAtom*) BindingName and a * ParserAtom */ template class AbstractTrailingNamesArray { using BindingNameT = AbstractBindingName; private: alignas(BindingNameT) unsigned char data_[sizeof(BindingNameT)]; private: // Some versions of GCC treat it as a -Wstrict-aliasing violation (ergo a // -Werror compile error) to reinterpret_cast<> |data_| to |T*|, even // through |void*|. Placing the latter cast in these separate functions // breaks the chain such that affected GCC versions no longer warn/error. void* ptr() { return data_; } public: // Explicitly ensure no one accidentally allocates scope data without // poisoning its trailing names. AbstractTrailingNamesArray() = delete; explicit AbstractTrailingNamesArray(size_t nameCount) { if (nameCount) { PoisonNames(reinterpret_cast(&data_), nameCount); } } BindingNameT* start() { return reinterpret_cast(ptr()); } BindingNameT& get(size_t i) { return start()[i]; } BindingNameT& operator[](size_t i) { return get(i); } }; // // 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; 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, Shape* environmentShape) : TenuredCellWithNonGCPointer(nullptr), kind_(kind), environmentShape_(environmentShape), enclosingScope_(enclosing) {} static Scope* create(JSContext* cx, ScopeKind kind, HandleScope enclosing, HandleShape envShape); template static XDRResult XDRSizedBindingNames( XDRState* xdr, Handle scope, MutableHandle data); Shape* maybeCloneEnvironmentShape(JSContext* cx); template void initData( MutableHandle> data); template void applyScopeDataTyped(F&& f); template static bool updateEnvShapeIfRequired(JSContext* cx, MutableHandleShape shape, bool needsEnvironment); template static bool updateEnvShapeIfRequired(JSContext* cx, mozilla::Maybe* envShape, bool needsEnvironment); public: template static ConcreteScope* create( JSContext* cx, ScopeKind kind, HandleScope enclosing, HandleShape 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_; } Shape* environmentShape() const { return environmentShape_; } Scope* enclosing() const { return enclosingScope_; } static bool hasEnvironment(ScopeKind kind, bool environmentShape) { 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 environmentShape; } } 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; } static Scope* clone(JSContext* cx, HandleScope scope, HandleScope enclosing); void traceChildren(JSTracer* trc); void finalize(JSFreeOp* fop); 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 numBindings) { using BindingT = AbstractBindingName; #if JS_BITS_PER_WORD == 64 static_assert( sizeof(DataT) == offsetof(DataT, trailingNames) + sizeof(BindingT), "Unexpected default number of inlined elements"); // -1 because AbstractTrailingNamesArray (trailingNames field in DataT) // contains one inlined element in data_ field. return sizeof(DataT) + ((numBindings ? numBindings - 1 : 0) * sizeof(BindingT)); #else // RuntimeData has alignas(ScopeDataAlignBytes), that is 8-bytes. // RuntimeData on 32-bit arch may have 4-bytes trailing padding after // trailingNames, and in that case there are effectively 2 inlined elements // inside sizeof(DataT). static_assert( sizeof(DataT) == offsetof(DataT, trailingNames) + sizeof(BindingT) || sizeof(DataT) == offsetof(DataT, trailingNames) + 2 * sizeof(BindingT), "Unexpected default number of inlined elements"); if constexpr (sizeof(DataT) == offsetof(DataT, trailingNames) + sizeof(BindingT)) { // There's no trailing padding, and there's only one inlined element. // This is RuntimeData without padding, or ParserData. return sizeof(DataT) + ((numBindings ? numBindings - 1 : 0) * sizeof(BindingT)); } // There's trailing padding, and there are two inlined elements. return sizeof(DataT) + ((numBindings > 2 ? numBindings - 2 : 0) * sizeof(BindingT)); #endif } // // 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; template using MaybeRootedScopeData = std::conditional_t< std::is_same_v, MaybeRooted, AllowGC::CanGC>, MaybeRooted*, AllowGC::NoGC>>; template struct ParserScopeData : public AbstractBaseScopeData { SlotInfo slotInfo; AbstractTrailingNamesArray trailingNames; explicit ParserScopeData(size_t nameCount) : trailingNames(nameCount) {} ParserScopeData() = delete; }; // // 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; uint32_t length = 0; }; struct alignas(ScopeDataAlignBytes) RuntimeData : public AbstractBaseScopeData { SlotInfo slotInfo; AbstractTrailingNamesArray trailingNames; explicit RuntimeData(size_t nameCount) : trailingNames(nameCount) {} RuntimeData() = delete; void trace(JSTracer* trc); }; using ParserData = ParserScopeData; template using AbstractData = typename std::conditional_t::value, RuntimeData, ParserData>; template static XDRResult XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope); private: static LexicalScope* createWithData( JSContext* cx, ScopeKind kind, MutableHandle> data, uint32_t firstFrameSlot, HandleScope enclosing); template static bool prepareForScopeCreation( JSContext* cx, ScopeKind kind, uint32_t firstFrameSlot, typename MaybeRootedScopeData::MutableHandleType data, ShapeT envShape); RuntimeData& data() { return *static_cast(rawData()); } const RuntimeData& data() const { return *static_cast(rawData()); } static uint32_t nextFrameSlot(Scope* scope); public: uint32_t nextFrameSlot() const { return data().slotInfo.nextFrameSlot; } // Returns an empty shape for extensible global and non-syntactic lexical // scopes. static Shape* 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 || kind_ == ScopeKind::ClassBody; } // // 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 PositionalFormalParameterIter; 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; uint32_t length = 0; bool hasParameterExprs() const { return flags & HasParameterExprsFlag; } void setHasParameterExprs() { flags |= HasParameterExprsFlag; } }; struct alignas(ScopeDataAlignBytes) RuntimeData : public AbstractBaseScopeData { // 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 = {}; SlotInfo slotInfo; AbstractTrailingNamesArray trailingNames; explicit RuntimeData(size_t nameCount) : trailingNames(nameCount) {} RuntimeData() = delete; void trace(JSTracer* trc); }; using ParserData = ParserScopeData; template using AbstractData = typename std::conditional_t::value, RuntimeData, ParserData>; template static bool prepareForScopeCreation( JSContext* cx, typename MaybeRootedScopeData::MutableHandleType data, bool hasParameterExprs, bool needsEnvironment, HandleFunction fun, ShapeT envShape); static FunctionScope* clone(JSContext* cx, Handle scope, HandleFunction fun, HandleScope enclosing); template static XDRResult XDR(XDRState* xdr, HandleFunction fun, HandleScope enclosing, MutableHandleScope scope); private: static FunctionScope* createWithData( JSContext* cx, MutableHandle> data, bool hasParameterExprs, bool needsEnvironment, HandleFunction fun, HandleScope enclosing); 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; } JSScript* script() const; bool hasParameterExprs() const { return data().slotInfo.hasParameterExprs(); } uint32_t numPositionalFormalParameters() const { return data().slotInfo.nonPositionalFormalStart; } static bool isSpecialName(JSContext* cx, JSAtom* name); static bool isSpecialName(JSContext* cx, 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) uint32_t length = 0; }; struct alignas(ScopeDataAlignBytes) RuntimeData : public AbstractBaseScopeData { SlotInfo slotInfo; AbstractTrailingNamesArray trailingNames; explicit RuntimeData(size_t nameCount) : trailingNames(nameCount) {} RuntimeData() = delete; void trace(JSTracer* trc); }; using ParserData = ParserScopeData; template using AbstractData = typename std::conditional_t::value, RuntimeData, ParserData>; template static XDRResult XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope); private: static VarScope* createWithData(JSContext* cx, ScopeKind kind, MutableHandle> data, uint32_t firstFrameSlot, bool needsEnvironment, HandleScope enclosing); template static bool prepareForScopeCreation( JSContext* cx, ScopeKind kind, typename MaybeRootedScopeData::MutableHandleType data, uint32_t firstFrameSlot, bool needsEnvironment, ShapeT 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