/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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/Maybe.h" #include "mozilla/Variant.h" #include "jsobj.h" #include "jsopcode.h" #include "gc/Heap.h" #include "gc/Policy.h" #include "js/UbiNode.h" #include "js/UniquePtr.h" #include "vm/Xdr.h" namespace js { class ModuleObject; enum class BindingKind : uint8_t { Import, FormalParameter, Var, Let, Const, // So you think named lambda callee names are consts? Nope! They don't // throw when being assigned to in sloppy mode. NamedLambdaCallee }; static inline bool BindingKindIsLexical(BindingKind kind) { return kind == BindingKind::Let || kind == BindingKind::Const; } enum class ScopeKind : uint8_t { // FunctionScope Function, // VarScope FunctionBodyVar, ParameterExpressionVar, // LexicalScope Lexical, SimpleCatch, Catch, NamedLambda, StrictNamedLambda, // WithScope With, // EvalScope Eval, StrictEval, // GlobalScope Global, NonSyntactic, // ModuleScope Module }; static inline bool ScopeKindIsCatch(ScopeKind kind) { return kind == ScopeKind::SimpleCatch || kind == ScopeKind::Catch; } const char* BindingKindString(BindingKind kind); const char* ScopeKindString(ScopeKind kind); class BindingName { // A JSAtom* with its low bit used as a tag for whether it is closed over // (i.e., exists in the environment shape). uintptr_t bits_; static const uintptr_t ClosedOverFlag = 0x1; static const uintptr_t FlagMask = 0x1; public: BindingName() : bits_(0) { } BindingName(JSAtom* name, bool closedOver) : bits_(uintptr_t(name) | (closedOver ? ClosedOverFlag : 0x0)) { } JSAtom* name() const { return reinterpret_cast(bits_ & ~FlagMask); } bool closedOver() const { return bits_ & ClosedOverFlag; } void trace(JSTracer* trc); }; class BindingLocation { public: enum class Kind { Global, Argument, Frame, Environment, Import, NamedLambdaCallee }; private: Kind kind_; uint32_t slot_; BindingLocation(Kind kind, uint32_t slot) : kind_(kind), slot_(slot) { } public: static BindingLocation Global() { return BindingLocation(Kind::Global, UINT32_MAX); } static BindingLocation Argument(uint16_t slot) { return BindingLocation(Kind::Argument, slot); } static BindingLocation Frame(uint32_t slot) { MOZ_ASSERT(slot < LOCALNO_LIMIT); return BindingLocation(Kind::Frame, slot); } static BindingLocation Environment(uint32_t slot) { MOZ_ASSERT(slot < ENVCOORD_SLOT_LIMIT); return BindingLocation(Kind::Environment, slot); } static BindingLocation Import() { return BindingLocation(Kind::Import, UINT32_MAX); } static BindingLocation NamedLambdaCallee() { return BindingLocation(Kind::NamedLambdaCallee, UINT32_MAX); } bool operator==(const BindingLocation& other) const { return kind_ == other.kind_ && slot_ == other.slot_; } bool operator!=(const BindingLocation& other) const { return !operator==(other); } Kind kind() const { return kind_; } uint32_t slot() const { MOZ_ASSERT(kind_ == Kind::Frame || kind_ == Kind::Environment); return slot_; } uint16_t argumentSlot() const { MOZ_ASSERT(kind_ == Kind::Argument); return mozilla::AssertedCast(slot_); } }; // // The base class of all Scopes. // class Scope : public js::gc::TenuredCell { friend class GCMarker; // The kind determines data_. ScopeKind kind_; // The enclosing scope or nullptr. GCPtrScope enclosing_; // If there are any aliased bindings, the shape for the // EnvironmentObject. Otherwise nullptr. GCPtrShape environmentShape_; protected: uintptr_t data_; Scope(ScopeKind kind, Scope* enclosing, Shape* environmentShape) : kind_(kind), enclosing_(enclosing), environmentShape_(environmentShape), data_(0) { } static Scope* create(ExclusiveContext* cx, ScopeKind kind, HandleScope enclosing, HandleShape envShape); template static Scope* create(ExclusiveContext* cx, ScopeKind kind, HandleScope enclosing, HandleShape envShape, mozilla::UniquePtr data); template static bool XDRSizedBindingNames(XDRState* xdr, Handle scope, MutableHandle data); Shape* maybeCloneEnvironmentShape(JSContext* cx); template void initData(mozilla::UniquePtr data) { MOZ_ASSERT(!data_); data_ = reinterpret_cast(data.release()); } public: 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_; } Scope* enclosing() const { return enclosing_; } Shape* environmentShape() const { return environmentShape_; } bool hasEnvironment() const { 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_ != nullptr; } } 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(FreeOp* fop); size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; void dump(); }; // // 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 BindingIter; public: // Data is public because it is created by the frontend. See // Parser::newLexicalScopeData. struct Data { // Bindings are sorted by kind in both frames and environments. // // lets - [0, constStart) // consts - [constStart, length) uint32_t constStart; uint32_t length; // Frame slots [0, nextFrameSlot) are live when this is the innermost // scope. uint32_t nextFrameSlot; // The array of tagged JSAtom* names, allocated beyond the end of the // struct. BindingName names[1]; void trace(JSTracer* trc); }; static size_t sizeOfData(uint32_t length) { return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName); } static LexicalScope* create(ExclusiveContext* cx, ScopeKind kind, Handle data, uint32_t firstFrameSlot, HandleScope enclosing); template static bool XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope); private: static LexicalScope* createWithData(ExclusiveContext* cx, ScopeKind kind, MutableHandle> data, uint32_t firstFrameSlot, HandleScope enclosing); Data& data() { return *reinterpret_cast(data_); } const Data& data() const { return *reinterpret_cast(data_); } static uint32_t nextFrameSlot(Scope* start); public: uint32_t firstFrameSlot() const; uint32_t nextFrameSlot() const { return data().nextFrameSlot; } // Returns an empty shape for extensible global and non-syntactic lexical // scopes. static Shape* getEmptyExtensibleEnvironmentShape(ExclusiveContext* cx); }; template <> inline bool Scope::is() const { return kind_ == ScopeKind::Lexical || kind_ == ScopeKind::SimpleCatch || kind_ == ScopeKind::Catch || kind_ == ScopeKind::NamedLambda || kind_ == ScopeKind::StrictNamedLambda; } // // Scope corresponding to a function. Holds formal parameter names 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 BindingIter; friend class PositionalFormalParameterIter; friend class Scope; static const ScopeKind classScopeKind_ = ScopeKind::Function; public: // Data is public because it is created by the // frontend. See Parser::newFunctionScopeData. struct Data { // 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). GCPtrFunction canonicalFunction; // If parameter expressions are present, parameters act like lexical // bindings. bool hasParameterExprs; // 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. // // positional formals - [0, nonPositionalFormalStart) // other formals - [nonPositionalParamStart, varStart) // vars - [varStart, length) uint16_t nonPositionalFormalStart; uint16_t varStart; uint32_t length; // Frame slots [0, nextFrameSlot) are live when this is the innermost // scope. uint32_t nextFrameSlot; // The array of tagged JSAtom* names, allocated beyond the end of the // struct. BindingName names[1]; void trace(JSTracer* trc); }; static size_t sizeOfData(uint32_t length) { return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName); } static FunctionScope* create(ExclusiveContext* cx, Handle data, bool hasParameterExprs, bool needsEnvironment, HandleFunction fun, HandleScope enclosing); static FunctionScope* clone(JSContext* cx, Handle scope, HandleFunction fun, HandleScope enclosing); template static bool XDR(XDRState* xdr, HandleFunction fun, HandleScope enclosing, MutableHandleScope scope); private: static FunctionScope* createWithData(ExclusiveContext* cx, MutableHandle> data, bool hasParameterExprs, bool needsEnvironment, HandleFunction fun, HandleScope enclosing); Data& data() { return *reinterpret_cast(data_); } const Data& data() const { return *reinterpret_cast(data_); } public: uint32_t nextFrameSlot() const { return data().nextFrameSlot; } JSFunction* canonicalFunction() const { return data().canonicalFunction; } JSScript* script() const; bool hasParameterExprs() const { return data().hasParameterExprs; } uint32_t numPositionalFormalParameters() const { return data().nonPositionalFormalStart; } static Shape* getEmptyEnvironmentShape(ExclusiveContext* cx, bool hasParameterExprs); }; // // Scope holding only vars. There are 2 kinds of VarScopes. // // FunctionBodyVar // Corresponds to the extra var scope present in functions with parameter // expressions. See examples in comment above FunctionScope. // // ParameterExpressionVar // Each parameter expression is evaluated in its own var environment. For // example, f() below will print 'fml', then 'global'. That's right. // // var a = 'global'; // function f(x = (eval(`var a = 'fml'`), a), y = a) { // print(x); // print(y); // }; // // Corresponds to VarEnvironmentObject on environment chain. // class VarScope : public Scope { friend class GCMarker; friend class BindingIter; friend class Scope; public: // Data is public because it is created by the // frontend. See Parser::newVarScopeData. struct Data { // All bindings are vars. uint32_t length; // Frame slots [firstFrameSlot(), nextFrameSlot) are live when this is // the innermost scope. uint32_t nextFrameSlot; // The array of tagged JSAtom* names, allocated beyond the end of the // struct. BindingName names[1]; void trace(JSTracer* trc); }; static size_t sizeOfData(uint32_t length) { return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName); } static VarScope* create(ExclusiveContext* cx, ScopeKind kind, Handle data, uint32_t firstFrameSlot, bool needsEnvironment, HandleScope enclosing); template static bool XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope); private: static VarScope* createWithData(ExclusiveContext* cx, ScopeKind kind, MutableHandle> data, uint32_t firstFrameSlot, bool needsEnvironment, HandleScope enclosing); Data& data() { return *reinterpret_cast(data_); } const Data& data() const { return *reinterpret_cast(data_); } public: uint32_t firstFrameSlot() const; uint32_t nextFrameSlot() const { return data().nextFrameSlot; } static Shape* getEmptyEnvironmentShape(ExclusiveContext* cx); }; template <> inline bool Scope::is() const { return kind_ == ScopeKind::FunctionBodyVar || kind_ == ScopeKind::ParameterExpressionVar; } // // Scope corresponding to both the global object scope and the global lexical // scope. // // Both are extensible and are singletons across