/* -*- 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_EnvironmentObject_h #define vm_EnvironmentObject_h #include #include "frontend/NameAnalysisTypes.h" #include "gc/Barrier.h" #include "gc/WeakMap.h" #include "js/GCHashTable.h" #include "vm/ArgumentsObject.h" #include "vm/GlobalObject.h" #include "vm/JSObject.h" #include "vm/ProxyObject.h" #include "vm/Scope.h" #include "vm/ScopeKind.h" // ScopeKind namespace js { class AbstractGeneratorObject; class IndirectBindingMap; class ModuleObject; /* * Return a shape representing the static scope containing the variable * accessed by the ALIASEDVAR op at 'pc'. */ extern SharedShape* EnvironmentCoordinateToEnvironmentShape(JSScript* script, jsbytecode* pc); // Return the name being accessed by the given ALIASEDVAR op. This function is // relatively slow so it should not be used on hot paths. extern PropertyName* EnvironmentCoordinateNameSlow(JSScript* script, jsbytecode* pc); /*** Environment objects ****************************************************/ // clang-format off /* * [SMDOC] Environment Objects * * About environments * ------------------ * * See also: https://tc39.es/ecma262/#sec-environment-records * * Scoping in ES is specified in terms of "Environment Records". There's a * global Environment Record per realm, and a new Environment Record is created * whenever control enters a function, block, or other scope. * * A "Lexical Environment" is a list of nested Environment Records, innermost * first: everything that's in scope. Throughout SpiderMonkey, "environment" * means a Lexical Environment. * * N.B.: "Scope" means something different: a static scope, the compile-time * analogue of an environment. See Scope.h. * * How SpiderMonkey represents environments * ---------------------------------------- * * Some environments are stored as JSObjects. Several kinds of objects * represent environments: * * JSObject * | * +--NativeObject * | | * | +--EnvironmentObject Engine-internal environment * | | | * | | +--CallObject Environment of entire function * | | | * | | +--VarEnvironmentObject See VarScope in Scope.h. * | | | * | | +--ModuleEnvironmentObject * | | | Module top-level environment * | | | * | | +--WasmInstanceEnvironmentObject * | | | * | | +--WasmFunctionCallObject * | | | * | | +--LexicalEnvironmentObject * | | | | * | | | +--ScopedLexicalEnvironmentObject * | | | | | Non-extensible lexical environment * | | | | | * | | | | +--BlockLexicalEnvironmentObject * | | | | | | Blocks and such: syntactic, * | | | | | | non-extensible * | | | | | | * | | | | | +--NamedLambdaObject * | | | | | Environment for `(function f(){...})` * | | | | | containing only a binding for `f` * | | | | | * | | | | +--ClassBodyLexicalEnvironmentObject * | | | | Environment for class body, containing * | | | | private names, private brands, and * | | | | static initializers list * | | | | * | | | +--ExtensibleLexicalEnvironmentObject * | | | | * | | | +--GlobalLexicalEnvironmentObject * | | | | Top-level let/const/class in scripts * | | | | * | | | +--NonSyntacticLexicalEnvironmentObject * | | | See "Non-syntactic environments" below * | | | * | | +--NonSyntacticVariablesObject * | | | See "Non-syntactic environments" below * | | | * | | +--WithEnvironmentObject Presents object properties as bindings * | | | * | | +--RuntimeLexicalErrorObject * | | Special value represents uninitialized * | | lexical slots * | | * | +--GlobalObject The global environment (dynamically * | presents its properties as bindings) * +--ProxyObject * | * +--DebugEnvironmentProxy Environment for debugger eval-in-frame * * EnvironmentObjects are technically real JSObjects but only belong on the * environment chain (that is, fp->environmentChain() or fun->environment()). * They are never exposed to scripts. * * Note that reserved slots in any base classes shown above are fixed for all * derived classes. So e.g. EnvironmentObject::enclosingEnvironment() can * simply access a fixed slot without further dynamic type information. * * When the current environment is represented by an object, the stack frame * has a pointer to that object (see AbstractFramePtr::environmentChain()). * However, that isn't always the case. Where possible, we store binding values * in JS stack slots. For block and function scopes where all bindings can be * stored in stack slots, nothing is allocated in the heap; there is no * environment object. * * Full information about the environment chain is always recoverable: * EnvironmentIter can do it, and we construct a fake environment for debugger * eval-in-frame (see "Debug environment objects" below). * * Syntactic Environments * ---------------------- * * Environments may be syntactic, i.e., corresponding to source text, or * non-syntactic, i.e., specially created by embedding. The distinction is * necessary to maintain invariants about the environment chain: non-syntactic * environments may not occur in arbitrary positions in the chain. * * CallObject, ModuleEnvironmentObject, BlockLexicalEnvironmentObject, and * GlobalLexicalEnvironmentObject always represent syntactic * environments. (CallObject is considered syntactic even when it's used as the * scope of strict eval code.) WithEnvironmentObject is syntactic when it's * used to represent the scope of a `with` block. * * * Non-syntactic Environments * -------------------------- * * A non-syntactic environment is one that was not created due to JS source * code. On the scope chain, a single NonSyntactic GlobalScope maps to 0+ * non-syntactic environment objects. This is contrasted with syntactic * environments, where each scope corresponds to 0 or 1 environment object. * * There are 3 kinds of dynamic environment objects: * * 1. WithEnvironmentObject * * When the embedding compiles or executes a script, it has the option to * pass in a vector of objects to be used as the initial env chain, ordered * from outermost env to innermost env. Each of those objects is wrapped by * a WithEnvironmentObject. * * The innermost object passed in by the embedding becomes a qualified * variables object that captures 'var' bindings. That is, it wraps the * holder object of 'var' bindings. * * Does not hold 'let' or 'const' bindings. * * 2. NonSyntacticVariablesObject * * When the embedding wants qualified 'var' bindings and unqualified * bareword assignments to go on a different object than the global * object. While any object can be made into a qualified variables object, * only the GlobalObject and NonSyntacticVariablesObject are considered * unqualified variables objects. * * Unlike WithEnvironmentObjects that delegate to the object they wrap, * this object is itself the holder of 'var' bindings. * * Does not hold 'let' or 'const' bindings. * * 3. NonSyntacticLexicalEnvironmentObject * * Each non-syntactic object used as a qualified variables object needs to * enclose a non-syntactic lexical environment to hold 'let' and 'const' * bindings. There is a bijection per realm between the non-syntactic * variables objects and their non-syntactic LexicalEnvironmentObjects. * * Does not hold 'var' bindings. * * The embedding (Gecko) and debugger uses non-syntactic envs for various * things, all of which are detailed below. All env chain listings below are, * from top to bottom, outermost to innermost. * * A. Component loading * * Components may be loaded in a shared global mode where most JSMs share a * single global in order to save on memory and avoid CCWs. To support this, a * NonSyntacticVariablesObject is used for each JSM to provide a basic form of * isolation. NonSyntacticLexicalEnvironmentObject and * NonSyntacticVariablesObject are allocated for each JSM, and * NonSyntacticLexicalEnvironmentObject holds lexical variables and * NonSyntacticVariablesObject holds qualified variables. JSMs cannot have * unqualified names, but if unqualified names are used by subscript, they * goes to NonSyntacticVariablesObject. * They have the following env chain: * * BackstagePass global * | * GlobalLexicalEnvironmentObject[this=global] * | * NonSyntacticVariablesObject (qualified 'var's and unqualified names) * | * NonSyntacticLexicalEnvironmentObject[this=nsvo] (lexical vars) * * B.1 Subscript loading * * Subscripts may be loaded into a target object and it's associated global. * NonSyntacticLexicalEnvironmentObject holds lexical variables and * WithEnvironmentObject holds qualified variables. Unqualified names goes * to the target object's global. * They have the following env chain: * * Target object's global (unqualified names) * | * GlobalLexicalEnvironmentObject[this=global] * | * WithEnvironmentObject wrapping target (qualified 'var's) * | * NonSyntacticLexicalEnvironmentObject[this=target] (lexical vars) * * B.2 Subscript loading (Shared-global JSM) * * The target object of a subscript load may be in a JSM with a shared global, * in which case we will also have the NonSyntacticVariablesObject on the * chain. * NonSyntacticLexicalEnvironmentObject for target object holds lexical * variables and WithEnvironmentObject holds qualified variables. * Unqualified names goes to NonSyntacticVariablesObject. * * Target object's global * | * GlobalLexicalEnvironmentObject[this=global] * | * NonSyntacticVariablesObject (unqualified names) * | * NonSyntacticLexicalEnvironmentObject[this=nsvo] * | * WithEnvironmentObject wrapping target (qualified 'var's) * | * NonSyntacticLexicalEnvironmentObject[this=target] (lexical vars) * * C.1. Frame scripts with unique scope * * XUL frame scripts with unique scope are loaded in the same global as * components, with a NonSyntacticVariablesObject as a "polluting global" for * both qualified 'var' variables and unqualified names, and a with * environment wrapping a message manager object, and * NonSyntacticLexicalEnvironmentObject holding the message manager as `this`, * that holds lexical variables. * These environment objects except for globals are created for each run and * not shared across multiple runs. This is done exclusively in * js::ExecuteInScopeChainAndReturnNewScope. * * BackstagePass global * | * GlobalLexicalEnvironmentObject[this=global] * | * NonSyntacticVariablesObject (qualified 'var's and unqualified names) * | * WithEnvironmentObject wrapping messageManager * | * NonSyntacticLexicalEnvironmentObject[this=messageManager] (lexical vars) * * C.2. Frame scripts without unique scope * * XUL frame scripts without unique scope are loaded in the same global as * components, with a with environment wrapping a message manager object for * qualified 'var' variables, and NonSyntacticLexicalEnvironmentObject holding * the message manager as `this`, that holds lexical variables. * The environment chain is associated with the message manager object * and cached for subsequent runs. * * BackstagePass global (unqualified names) * | * GlobalLexicalEnvironmentObject[this=global] * | * WithEnvironmentObject wrapping messageManager (qualified names) * | * NonSyntacticLexicalEnvironmentObject[this=messageManager] (lexical vars) * * D.1. DOM event handlers without direct eval * * DOM event handlers are compiled as functions with HTML elements on the * environment chain. For a chain of elements e0, e1, ..., eN, where innerrmost * element is the target element, enclosing elements are such as forms, and the * outermost one is the document. * If the DOM event handlers don't have direct eval, the function's scopes are * optimized and frame slots are used for qualified 'var's and lexical vars. * NonSyntacticLexicalEnvironmentObject's `this` value is not used, given * the function's `this` value is used instead: * * global (unqualified names) * | * GlobalLexicalEnvironmentObject[this=global] * | * WithEnvironmentObject wrapping eN * | * ... * | * WithEnvironmentObject wrapping e1 * | * WithEnvironmentObject wrapping e0 * | * NonSyntacticLexicalEnvironmentObject [this=*unused*] * * D.2. DOM event handlers with direct eval * * If DOM event handlers have direct eval, the function's scopes are allocated * as environment object: * * global (unqualified names) * | * GlobalLexicalEnvironmentObject[this=global] * | * ... * | * WithEnvironmentObject wrapping e1 * | * WithEnvironmentObject wrapping e0 * | * NonSyntacticLexicalEnvironmentObject [this=*unused*] * | * CallObject (qualified 'var's) * | * BlockLexicalEnvironmentObject (lexical vars) * * E.1. Debugger.Frame.prototype.evalWithBindings * * Debugger.Frame.prototype.evalWithBindings uses WithEnvironmentObject for * given bindings, and the frame's enclosing scope * * If the frame is function, it has the following env chain. * lexical variables are optimized and uses frame slots: * * global (unqualified names) * | * [DebugProxy] GlobalLexicalEnvironmentObject[this=global] * | * [DebugProxy] CallObject (qualified 'var's) * | * WithEnvironmentObject wrapping bindings * * If the script has direct eval, BlockLexicalEnvironmentObject is created for * it: * * global (unqualified names) * | * [DebugProxy] GlobalLexicalEnvironmentObject[this=global] * | * [DebugProxy] CallObject (qualified 'var's) * | * BlockLexicalEnvironmentObject (lexical) * | * WithEnvironmentObject wrapping bindings * * NOTE: Debugger.Frame.prototype.eval uses the frame's enclosing scope only, * and it doesn't use any dynamic environment, but still uses * non-syntactic scope to perform `eval` operation. * * E.2. Debugger.Object.prototype.executeInGlobalWithBindings * * Debugger.Object.prototype.executeInGlobalWithBindings uses * WithEnvironmentObject for given bindings, and the object's global scope: * * global (qualified 'var's and unqualified names) * | * GlobalLexicalEnvironmentObject[this=global] (lexical) * | * WithEnvironmentObject wrapping bindings * * NOTE: Debugger.Object.prototype.executeInGlobal uses the object's global * scope only, and it doesn't use any dynamic environment or * non-syntactic scope. * */ // clang-format on class EnvironmentObject : public NativeObject { protected: // The enclosing environment. Either another EnvironmentObject, a // GlobalObject, or a non-syntactic environment object. static const uint32_t ENCLOSING_ENV_SLOT = 0; inline void setAliasedBinding(uint32_t slot, const Value& v); public: // Since every env chain terminates with a global object, whether // GlobalObject or a non-syntactic one, and since those objects do not // derive EnvironmentObject (they have completely different layouts), the // enclosing environment of an EnvironmentObject is necessarily non-null. JSObject& enclosingEnvironment() const { return getReservedSlot(ENCLOSING_ENV_SLOT).toObject(); } void initEnclosingEnvironment(JSObject* enclosing) { initReservedSlot(ENCLOSING_ENV_SLOT, ObjectOrNullValue(enclosing)); } static bool nonExtensibleIsFixedSlot(EnvironmentCoordinate ec) { // For non-extensible environment objects isFixedSlot(slot) is equivalent to // slot < MAX_FIXED_SLOTS. return ec.slot() < MAX_FIXED_SLOTS; } static size_t nonExtensibleDynamicSlotIndex(EnvironmentCoordinate ec) { MOZ_ASSERT(!nonExtensibleIsFixedSlot(ec)); return ec.slot() - MAX_FIXED_SLOTS; } // Get or set a name contained in this environment. inline const Value& aliasedBinding(EnvironmentCoordinate ec); const Value& aliasedBinding(const BindingIter& bi) { MOZ_ASSERT(bi.location().kind() == BindingLocation::Kind::Environment); return getSlot(bi.location().slot()); } inline void setAliasedBinding(EnvironmentCoordinate ec, const Value& v); inline void setAliasedBinding(const BindingIter& bi, const Value& v); // For JITs. static size_t offsetOfEnclosingEnvironment() { return getFixedSlotOffset(ENCLOSING_ENV_SLOT); } static uint32_t enclosingEnvironmentSlot() { return ENCLOSING_ENV_SLOT; } const char* typeString() const; #if defined(DEBUG) || defined(JS_JITSPEW) void dump(); #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ }; class CallObject : public EnvironmentObject { protected: static constexpr uint32_t CALLEE_SLOT = 1; static CallObject* create(JSContext* cx, HandleScript script, HandleObject enclosing, gc::Heap heap); public: static const JSClass class_; static constexpr uint32_t RESERVED_SLOTS = 2; static constexpr ObjectFlags OBJECT_FLAGS = {ObjectFlag::QualifiedVarObj}; /* These functions are internal and are exposed only for JITs. */ /* * Construct a bare-bones call object given a shape. * The call object must be further initialized to be usable. */ static CallObject* createWithShape(JSContext* cx, Handle shape); static CallObject* createTemplateObject(JSContext* cx, HandleScript script, HandleObject enclosing); static CallObject* create(JSContext* cx, AbstractFramePtr frame); static CallObject* createHollowForDebug(JSContext* cx, HandleFunction callee); // If `env` or any enclosing environment is a CallObject, return that // CallObject; else null. // // `env` may be a DebugEnvironmentProxy, but not a hollow environment. static CallObject* find(JSObject* env); /* * When an aliased formal (var accessed by nested closures) is also * aliased by the arguments object, it must of course exist in one * canonical location and that location is always the CallObject. For this * to work, the ArgumentsObject stores special MagicValue in its array for * forwarded-to-CallObject variables. This MagicValue's payload is the * slot of the CallObject to access. */ const Value& aliasedFormalFromArguments(const Value& argsValue) { return getSlot(ArgumentsObject::SlotFromMagicScopeSlotValue(argsValue)); } inline void setAliasedFormalFromArguments(const Value& argsValue, const Value& v); JSFunction& callee() const { return getReservedSlot(CALLEE_SLOT).toObject().as(); } /* For jit access. */ static size_t offsetOfCallee() { return getFixedSlotOffset(CALLEE_SLOT); } static size_t calleeSlot() { return CALLEE_SLOT; } }; class VarEnvironmentObject : public EnvironmentObject { static constexpr uint32_t SCOPE_SLOT = 1; static VarEnvironmentObject* createInternal(JSContext* cx, Handle shape, HandleObject enclosing, gc::Heap heap); static VarEnvironmentObject* create(JSContext* cx, Handle scope, HandleObject enclosing, gc::Heap heap); void initScope(Scope* scope) { initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope)); } public: static const JSClass class_; static constexpr uint32_t RESERVED_SLOTS = 2; static constexpr ObjectFlags OBJECT_FLAGS = {ObjectFlag::QualifiedVarObj}; static VarEnvironmentObject* createForFrame(JSContext* cx, Handle scope, AbstractFramePtr frame); static VarEnvironmentObject* createHollowForDebug(JSContext* cx, Handle scope); static VarEnvironmentObject* createTemplateObject(JSContext* cx, Handle scope); static VarEnvironmentObject* createWithoutEnclosing(JSContext* cx, Handle scope); Scope& scope() const { Value v = getReservedSlot(SCOPE_SLOT); MOZ_ASSERT(v.isPrivateGCThing()); Scope& s = *static_cast(v.toGCThing()); MOZ_ASSERT(s.is() || s.is()); return s; } bool isForEval() const { return scope().is(); } bool isForNonStrictEval() const { return scope().kind() == ScopeKind::Eval; } }; class ModuleEnvironmentObject : public EnvironmentObject { static constexpr uint32_t MODULE_SLOT = 1; static const ObjectOps objectOps_; static const JSClassOps classOps_; public: static const JSClass class_; static constexpr uint32_t RESERVED_SLOTS = 2; static constexpr ObjectFlags OBJECT_FLAGS = {ObjectFlag::NotExtensible, ObjectFlag::QualifiedVarObj}; static ModuleEnvironmentObject* create(JSContext* cx, Handle module); ModuleObject& module() const; IndirectBindingMap& importBindings() const; bool createImportBinding(JSContext* cx, Handle importName, Handle module, Handle exportName); bool hasImportBinding(Handle name); bool lookupImport(jsid name, ModuleEnvironmentObject** envOut, mozilla::Maybe* propOut); // If `env` or any enclosing environment is a ModuleEnvironmentObject, // return that ModuleEnvironmentObject; else null. // // `env` may be a DebugEnvironmentProxy, but not a hollow environment. static ModuleEnvironmentObject* find(JSObject* env); private: static bool lookupProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp, PropertyResult* propp); static bool hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp); static bool getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id, MutableHandleValue vp); static bool setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, JS::ObjectOpResult& result); static bool getOwnPropertyDescriptor( JSContext* cx, HandleObject obj, HandleId id, MutableHandle> desc); static bool deleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result); static bool newEnumerate(JSContext* cx, HandleObject obj, MutableHandleIdVector properties, bool enumerableOnly); }; class WasmInstanceEnvironmentObject : public EnvironmentObject { // Currently WasmInstanceScopes do not use their scopes in a // meaningful way. However, it is an invariant of DebugEnvironments that // environments kept in those maps have live scopes, thus this strong // reference. static constexpr uint32_t SCOPE_SLOT = 1; public: static const JSClass class_; static constexpr uint32_t RESERVED_SLOTS = 2; static constexpr ObjectFlags OBJECT_FLAGS = {ObjectFlag::NotExtensible}; static WasmInstanceEnvironmentObject* createHollowForDebug( JSContext* cx, Handle scope); WasmInstanceScope& scope() const { Value v = getReservedSlot(SCOPE_SLOT); MOZ_ASSERT(v.isPrivateGCThing()); return *static_cast(v.toGCThing()); } }; class WasmFunctionCallObject : public EnvironmentObject { // Currently WasmFunctionCallObjects do not use their scopes in a // meaningful way. However, it is an invariant of DebugEnvironments that // environments kept in those maps have live scopes, thus this strong // reference. static constexpr uint32_t SCOPE_SLOT = 1; public: static const JSClass class_; // TODO Check what Debugger behavior should be when it evaluates a // var declaration. static constexpr uint32_t RESERVED_SLOTS = 2; static constexpr ObjectFlags OBJECT_FLAGS = {ObjectFlag::NotExtensible}; static WasmFunctionCallObject* createHollowForDebug( JSContext* cx, HandleObject enclosing, Handle scope); WasmFunctionScope& scope() const { Value v = getReservedSlot(SCOPE_SLOT); MOZ_ASSERT(v.isPrivateGCThing()); return *static_cast(v.toGCThing()); } }; // Abstract base class for environments that can contain let/const bindings, // plus a few other kinds of environments, such as `catch` blocks, that have // similar behavior. class LexicalEnvironmentObject : public EnvironmentObject { protected: // Global and non-syntactic lexical environments need to store a 'this' // object and all other lexical environments have a fixed shape and store a // backpointer to the LexicalScope. // // Since the two sets are disjoint, we only use one slot to save space. static constexpr uint32_t THIS_VALUE_OR_SCOPE_SLOT = 1; public: static const JSClass class_; static constexpr uint32_t RESERVED_SLOTS = 2; protected: static LexicalEnvironmentObject* create(JSContext* cx, Handle shape, HandleObject enclosing, gc::Heap heap); public: // Is this the global lexical scope? bool isGlobal() const { return enclosingEnvironment().is(); } // Global and non-syntactic lexical scopes are extensible. All other // lexical scopes are not. bool isExtensible() const; // Is this a syntactic (i.e. corresponds to a source text) lexical // environment? bool isSyntactic() const { return !isExtensible() || isGlobal(); } }; // A non-extensible lexical environment. // // Used for blocks (ScopeKind::Lexical) and several other scope kinds, // including Catch, NamedLambda, FunctionLexical, and ClassBody. class ScopedLexicalEnvironmentObject : public LexicalEnvironmentObject { public: static constexpr ObjectFlags OBJECT_FLAGS = {ObjectFlag::NotExtensible}; Scope& scope() const { Value v = getReservedSlot(THIS_VALUE_OR_SCOPE_SLOT); MOZ_ASSERT(!isExtensible() && v.isPrivateGCThing()); return *static_cast(v.toGCThing()); } bool isClassBody() const { return scope().kind() == ScopeKind::ClassBody; } void initScope(Scope* scope) { initReservedSlot(THIS_VALUE_OR_SCOPE_SLOT, PrivateGCThingValue(scope)); } }; class BlockLexicalEnvironmentObject : public ScopedLexicalEnvironmentObject { protected: static BlockLexicalEnvironmentObject* create(JSContext* cx, Handle scope, HandleObject enclosing, gc::Heap heap); public: static constexpr ObjectFlags OBJECT_FLAGS = {ObjectFlag::NotExtensible}; static BlockLexicalEnvironmentObject* createForFrame( JSContext* cx, Handle scope, AbstractFramePtr frame); static BlockLexicalEnvironmentObject* createHollowForDebug( JSContext* cx, Handle scope); static BlockLexicalEnvironmentObject* createTemplateObject( JSContext* cx, Handle scope); static BlockLexicalEnvironmentObject* createWithoutEnclosing( JSContext* cx, Handle scope); // Create a new BlockLexicalEnvironmentObject with the same enclosing env and // variable values as this. static BlockLexicalEnvironmentObject* clone( JSContext* cx, Handle env); // Create a new BlockLexicalEnvironmentObject with the same enclosing env as // this, with all variables uninitialized. static BlockLexicalEnvironmentObject* recreate( JSContext* cx, Handle env); // The LexicalScope that created this environment. LexicalScope& scope() const { return ScopedLexicalEnvironmentObject::scope().as(); } }; class NamedLambdaObject : public BlockLexicalEnvironmentObject { static NamedLambdaObject* create(JSContext* cx, HandleFunction callee, HandleObject enclosing, gc::Heap heap); public: static NamedLambdaObject* createTemplateObject(JSContext* cx, HandleFunction callee); static NamedLambdaObject* createWithoutEnclosing(JSContext* cx, HandleFunction callee); static NamedLambdaObject* create(JSContext* cx, AbstractFramePtr frame); // For JITs. static size_t lambdaSlot(); static size_t offsetOfLambdaSlot() { return getFixedSlotOffset(lambdaSlot()); } }; class ClassBodyLexicalEnvironmentObject : public ScopedLexicalEnvironmentObject { static ClassBodyLexicalEnvironmentObject* create( JSContext* cx, Handle scope, HandleObject enclosing, gc::Heap heap); public: static ClassBodyLexicalEnvironmentObject* createForFrame( JSContext* cx, Handle scope, AbstractFramePtr frame); static ClassBodyLexicalEnvironmentObject* createTemplateObject( JSContext* cx, Handle scope); static ClassBodyLexicalEnvironmentObject* createWithoutEnclosing( JSContext* cx, Handle scope); // The ClassBodyScope that created this environment. ClassBodyScope& scope() const { return ScopedLexicalEnvironmentObject::scope().as(); } static uint32_t privateBrandSlot() { return JSSLOT_FREE(&class_); } }; // Global and non-syntactic lexical environments are extensible. class ExtensibleLexicalEnvironmentObject : public LexicalEnvironmentObject { public: JSObject* thisObject() const; // For a given global object or JSMEnvironment `obj`, return the associated // global lexical or non-syntactic lexical environment, where top-level `let` // bindings are added. static ExtensibleLexicalEnvironmentObject* forVarEnvironment(JSObject* obj); protected: void initThisObject(JSObject* obj) { MOZ_ASSERT(isGlobal() || !isSyntactic()); JSObject* thisObj = GetThisObject(obj); initReservedSlot(THIS_VALUE_OR_SCOPE_SLOT, ObjectValue(*thisObj)); } }; // The global lexical environment, where global let/const/class bindings are // added. class GlobalLexicalEnvironmentObject : public ExtensibleLexicalEnvironmentObject { public: static GlobalLexicalEnvironmentObject* create(JSContext* cx, Handle global); GlobalObject& global() const { return enclosingEnvironment().as(); } void setWindowProxyThisObject(JSObject* obj); static constexpr size_t offsetOfThisValueSlot() { return getFixedSlotOffset(THIS_VALUE_OR_SCOPE_SLOT); } }; // Non-standard. See "Non-syntactic Environments" above. class NonSyntacticLexicalEnvironmentObject : public ExtensibleLexicalEnvironmentObject { public: static NonSyntacticLexicalEnvironmentObject* create(JSContext* cx, HandleObject enclosing, HandleObject thisv); }; // A non-syntactic dynamic scope object that captures non-lexical // bindings. That is, a scope object that captures both qualified var // assignments and unqualified bareword assignments. Its parent is always the // global lexical environment. // // This is used in ExecuteInGlobalAndReturnScope and sits in front of the // global scope to store 'var' bindings, and to store fresh properties created // by assignments to undeclared variables that otherwise would have gone on // the global object. class NonSyntacticVariablesObject : public EnvironmentObject { public: static const JSClass class_; static constexpr uint32_t RESERVED_SLOTS = 1; static constexpr ObjectFlags OBJECT_FLAGS = {}; static NonSyntacticVariablesObject* create(JSContext* cx); }; extern bool CreateNonSyntacticEnvironmentChain(JSContext* cx, JS::HandleObjectVector envChain, MutableHandleObject env); // With environment objects on the run-time environment chain. class WithEnvironmentObject : public EnvironmentObject { static constexpr uint32_t OBJECT_SLOT = 1; static constexpr uint32_t THIS_SLOT = 2; static constexpr uint32_t SCOPE_SLOT = 3; public: static const JSClass class_; static constexpr uint32_t RESERVED_SLOTS = 4; static constexpr ObjectFlags OBJECT_FLAGS = {}; static WithEnvironmentObject* create(JSContext* cx, HandleObject object, HandleObject enclosing, Handle scope); static WithEnvironmentObject* createNonSyntactic(JSContext* cx, HandleObject object, HandleObject enclosing); /* Return the 'o' in 'with (o)'. */ JSObject& object() const; /* Return object for GetThisValue. */ JSObject* withThis() const; /* * Return whether this object is a syntactic with object. If not, this is * a With object we inserted between the outermost syntactic scope and the * global object to wrap the environment chain someone explicitly passed * via JSAPI to CompileFunction or script evaluation. */ bool isSyntactic() const; // For syntactic with environment objects, the with scope. WithScope& scope() const; static inline size_t objectSlot() { return OBJECT_SLOT; } static inline size_t thisSlot() { return THIS_SLOT; } }; // Internal scope object used by JSOp::BindName upon encountering an // uninitialized lexical slot or an assignment to a 'const' binding. // // ES6 lexical bindings cannot be accessed in any way (throwing // ReferenceErrors) until initialized. Normally, NAME operations // unconditionally check for uninitialized lexical slots. When getting or // looking up names, this can be done without slowing down normal operations // on the return value. When setting names, however, we do not want to pollute // all set-property paths with uninitialized lexical checks. For setting names // (i.e. JSOp::SetName), we emit an accompanying, preceding JSOp::BindName which // finds the right scope on which to set the name. Moreover, when the name on // the scope is an uninitialized lexical, we cannot throw eagerly, as the spec // demands that the error be thrown after evaluating the RHS of // assignments. Instead, this sentinel scope object is pushed on the stack. // Attempting to access anything on this scope throws the appropriate // ReferenceError. // // ES6 'const' bindings induce a runtime error when assigned to outside // of initialization, regardless of strictness. class RuntimeLexicalErrorObject : public EnvironmentObject { static const unsigned ERROR_SLOT = 1; public: static const unsigned RESERVED_SLOTS = 2; static const JSClass class_; static RuntimeLexicalErrorObject* create(JSContext* cx, HandleObject enclosing, unsigned errorNumber); unsigned errorNumber() { return getReservedSlot(ERROR_SLOT).toInt32(); } }; /****************************************************************************/ // A environment iterator describes the active environments starting from an // environment, scope pair. This pair may be derived from the current point of // execution in a frame. If derived in such a fashion, the EnvironmentIter // tracks whether the current scope is within the extent of this initial // frame. Here, "frame" means a single activation of: a function, eval, or // global code. class MOZ_RAII EnvironmentIter { Rooted si_; RootedObject env_; AbstractFramePtr frame_; void incrementScopeIter(); void settle(); // No value semantics. EnvironmentIter(const EnvironmentIter& ei) = delete; public: // Constructing from a copy of an existing EnvironmentIter. EnvironmentIter(JSContext* cx, const EnvironmentIter& ei); // Constructing from an environment, scope pair. All environments // considered not to be withinInitialFrame, since no frame is given. EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope); // Constructing from a frame. Places the EnvironmentIter on the innermost // environment at pc. EnvironmentIter(JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc); // Constructing from an environment, scope and frame. The frame is given // to initialize to proper enclosing environment/scope. EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope, AbstractFramePtr frame); bool done() const { return si_.done(); } explicit operator bool() const { return !done(); } void operator++(int) { if (hasAnyEnvironmentObject()) { env_ = &env_->as().enclosingEnvironment(); } incrementScopeIter(); settle(); } EnvironmentIter& operator++() { operator++(1); return *this; } // If done(): JSObject& enclosingEnvironment() const; // If !done(): bool hasNonSyntacticEnvironmentObject() const; bool hasSyntacticEnvironment() const { return si_.hasSyntacticEnvironment(); } bool hasAnyEnvironmentObject() const { return hasNonSyntacticEnvironmentObject() || hasSyntacticEnvironment(); } EnvironmentObject& environment() const { MOZ_ASSERT(hasAnyEnvironmentObject()); return env_->as(); } Scope& scope() const { return *si_.scope(); } Scope* maybeScope() const { if (si_) { return si_.scope(); } return nullptr; } JSFunction& callee() const { return env_->as().callee(); } bool withinInitialFrame() const { return !!frame_; } AbstractFramePtr initialFrame() const { MOZ_ASSERT(withinInitialFrame()); return frame_; } AbstractFramePtr maybeInitialFrame() const { return frame_; } }; // The key in MissingEnvironmentMap. For live frames, maps live frames to // their synthesized environments. For completely optimized-out environments, // maps the Scope to their synthesized environments. The env we synthesize for // Scopes are read-only, and we never use their parent links, so they don't // need to be distinct. // // That is, completely optimized out environments can't be distinguished by // frame. Note that even if the frame corresponding to the Scope is live on // the stack, it is unsound to synthesize an environment from that live // frame. In other words, the provenance of the environment chain is from // allocated closures (i.e., allocation sites) and is irrecoverable from // simple stack inspection (i.e., call sites). class MissingEnvironmentKey { friend class LiveEnvironmentVal; AbstractFramePtr frame_; Scope* scope_; public: explicit MissingEnvironmentKey(const EnvironmentIter& ei) : frame_(ei.maybeInitialFrame()), scope_(ei.maybeScope()) {} MissingEnvironmentKey(AbstractFramePtr frame, Scope* scope) : frame_(frame), scope_(scope) {} AbstractFramePtr frame() const { return frame_; } Scope* scope() const { return scope_; } void updateScope(Scope* scope) { scope_ = scope; } void updateFrame(AbstractFramePtr frame) { frame_ = frame; } // For use as hash policy. using Lookup = MissingEnvironmentKey; static HashNumber hash(MissingEnvironmentKey sk); static bool match(MissingEnvironmentKey sk1, MissingEnvironmentKey sk2); bool operator!=(const MissingEnvironmentKey& other) const { return frame_ != other.frame_ || scope_ != other.scope_; } static void rekey(MissingEnvironmentKey& k, const MissingEnvironmentKey& newKey) { k = newKey; } }; // The value in LiveEnvironmentMap, mapped from by live environment objects. class LiveEnvironmentVal { friend class DebugEnvironments; friend class MissingEnvironmentKey; AbstractFramePtr frame_; HeapPtr scope_; static void staticAsserts(); public: explicit LiveEnvironmentVal(const EnvironmentIter& ei) : frame_(ei.initialFrame()), scope_(ei.maybeScope()) {} AbstractFramePtr frame() const { return frame_; } void updateFrame(AbstractFramePtr frame) { frame_ = frame; } bool traceWeak(JSTracer* trc); }; /****************************************************************************/ /* * [SMDOC] Debug environment objects * * The frontend optimizes unaliased variables into stack slots and can optimize * away whole EnvironmentObjects. So when the debugger wants to perform an * unexpected eval-in-frame (or otherwise access the environment), * `fp->environmentChain` is often incomplete. This is a problem: a major use * case for eval-in-frame is to access the local variables in debuggee code. * * Even when all EnvironmentObjects exist, giving complete information for all * bindings, stack and heap, there's another issue: eval-in-frame code can * create closures that capture stack locals. The variable slots go away when * the frame is popped, but the closure, which uses them, may survive. * * To solve both problems, eval-in-frame code is compiled and run against a * "debug environment chain" of DebugEnvironmentProxy objects rather than real * EnvironmentObjects. The `GetDebugEnvironmentFor` functions below create * these proxies, one to sit in front of each existing EnvironmentObject. They * also create bogus "hollow" EnvironmentObjects to stand in for environments * that were optimized away; and proxies for those. The frontend sees these * environments as something like `with` scopes, and emits deoptimized bytecode * instructions for all variable accesses. * * When eval-in-frame code runs, `fp->environmentChain` points to this chain of * proxies. On each variable access, the proxy laboriously figures out what to * do. See e.g. `DebuggerEnvironmentProxyHandler::handleUnaliasedAccess`. * * There's a limit to what the proxies can manage, since they're proxying * environments that are already optimized. Some debugger operations, like * redefining a lexical binding, can fail when a true direct eval would * succeed. Even plain variable accesses can throw, if the variable has been * optimized away. * * To support accessing stack variables after they've gone out of scope, we * copy the variables to the heap as they leave scope. See * `DebugEnvironments::onPopCall` and `onPopLexical`. * * `GetDebugEnvironmentFor*` guarantees that the same DebugEnvironmentProxy is * always produced for the same underlying environment (optimized or not!). * This is maintained by some bookkeeping information stored in * `DebugEnvironments`. */ extern JSObject* GetDebugEnvironmentForFunction(JSContext* cx, HandleFunction fun); extern JSObject* GetDebugEnvironmentForSuspendedGenerator( JSContext* cx, JSScript* script, AbstractGeneratorObject& genObj); extern JSObject* GetDebugEnvironmentForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc); extern JSObject* GetDebugEnvironmentForGlobalLexicalEnvironment(JSContext* cx); extern Scope* GetEnvironmentScope(const JSObject& env); /* Provides debugger access to a environment. */ class DebugEnvironmentProxy : public ProxyObject { /* * The enclosing environment on the dynamic environment chain. This slot is * analogous to the ENCLOSING_ENV_SLOT of a EnvironmentObject. */ static const unsigned ENCLOSING_SLOT = 0; /* * NullValue or a dense array holding the unaliased variables of a function * frame that has been popped. */ static const unsigned SNAPSHOT_SLOT = 1; public: static DebugEnvironmentProxy* create(JSContext* cx, EnvironmentObject& env, HandleObject enclosing); // NOTE: The environment may be a debug hollow with invalid // enclosingEnvironment. Always use the enclosingEnvironment accessor on // the DebugEnvironmentProxy in order to walk the environment chain. EnvironmentObject& environment() const; JSObject& enclosingEnvironment() const; // May only be called for proxies to function call objects or modules // with top-level-await. ArrayObject* maybeSnapshot() const; void initSnapshot(ArrayObject& snapshot); // Currently, the 'declarative' environments are function, module, and // lexical environments. bool isForDeclarative() const; // Get a property by 'id', but returns sentinel values instead of throwing // on exceptional cases. static bool getMaybeSentinelValue(JSContext* cx, Handle env, HandleId id, MutableHandleValue vp); // Returns true iff this is a function environment with its own this-binding // (all functions except arrow functions). bool isFunctionEnvironmentWithThis(); // Does this debug environment not have a real counterpart or was never // live (and thus does not have a synthesized EnvironmentObject or a // snapshot)? bool isOptimizedOut() const; #if defined(DEBUG) || defined(JS_JITSPEW) void dump(); #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ }; /* Maintains per-realm debug environment bookkeeping information. */ class DebugEnvironments { Zone* zone_; /* The map from (non-debug) environments to debug environments. */ ObjectWeakMap proxiedEnvs; /* * The map from live frames which have optimized-away environments to the * corresponding debug environments. */ typedef HashMap, MissingEnvironmentKey, ZoneAllocPolicy> MissingEnvironmentMap; MissingEnvironmentMap missingEnvs; /* * The map from environment objects of live frames to the live frame. This * map updated lazily whenever the debugger needs the information. In * between two lazy updates, liveEnvs becomes incomplete (but not invalid, * onPop* removes environments as they are popped). Thus, two consecutive * debugger lazy updates of liveEnvs need only fill in the new * environments. */ typedef GCHashMap, LiveEnvironmentVal, StableCellHasher>, ZoneAllocPolicy> LiveEnvironmentMap; LiveEnvironmentMap liveEnvs; public: DebugEnvironments(JSContext* cx, Zone* zone); ~DebugEnvironments(); Zone* zone() const { return zone_; } private: static DebugEnvironments* ensureRealmData(JSContext* cx); template static void onPopGeneric(JSContext* cx, const EnvironmentIter& ei); public: void trace(JSTracer* trc); void traceWeak(JSTracer* trc); void finish(); #ifdef JS_GC_ZEAL void checkHashTablesAfterMovingGC(); #endif // If a live frame has a synthesized entry in missingEnvs, make sure it's not // collected. void traceLiveFrame(JSTracer* trc, AbstractFramePtr frame); static DebugEnvironmentProxy* hasDebugEnvironment(JSContext* cx, EnvironmentObject& env); static bool addDebugEnvironment(JSContext* cx, Handle env, Handle debugEnv); static DebugEnvironmentProxy* hasDebugEnvironment(JSContext* cx, const EnvironmentIter& ei); static bool addDebugEnvironment(JSContext* cx, const EnvironmentIter& ei, Handle debugEnv); static bool updateLiveEnvironments(JSContext* cx); static LiveEnvironmentVal* hasLiveEnvironment(EnvironmentObject& env); static void unsetPrevUpToDateUntil(JSContext* cx, AbstractFramePtr frame); // When a frame bails out from Ion to Baseline, there might be missing // envs keyed on, and live envs containing, the old // RematerializedFrame. Forward those values to the new BaselineFrame. static void forwardLiveFrame(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to); // When an environment is popped, we store a snapshot of its bindings that // live on the frame. // // This is done during frame unwinding, which cannot handle errors // gracefully. Errors result in no snapshot being set on the // DebugEnvironmentProxy. static void takeFrameSnapshot(JSContext* cx, Handle debugEnv, AbstractFramePtr frame); // In debug-mode, these must be called whenever exiting a scope that might // have stack-allocated locals. static void onPopCall(JSContext* cx, AbstractFramePtr frame); static void onPopVar(JSContext* cx, const EnvironmentIter& ei); static void onPopLexical(JSContext* cx, const EnvironmentIter& ei); static void onPopLexical(JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc); static void onPopWith(AbstractFramePtr frame); static void onPopModule(JSContext* cx, const EnvironmentIter& ei); static void onRealmUnsetIsDebuggee(Realm* realm); }; } /* namespace js */ template <> inline bool JSObject::is() const { return is() || is() || is() || is() || is() || is() || is() || is() || is(); } template <> inline bool JSObject::is() const { return is() && !as().isExtensible(); } template <> inline bool JSObject::is() const { return is() && !as().isClassBody(); } template <> inline bool JSObject::is() const { return is() && as().isClassBody(); } template <> inline bool JSObject::is() const { return is() && as().isExtensible(); } template <> inline bool JSObject::is() const { return is() && as().isGlobal(); } template <> inline bool JSObject::is() const { return is() && !as().isSyntactic(); } template <> inline bool JSObject::is() const { return is() && as().scope().isNamedLambda(); } template <> bool JSObject::is() const; namespace js { inline bool IsSyntacticEnvironment(JSObject* env) { if (!env->is()) { return false; } if (env->is()) { return env->as().isSyntactic(); } if (env->is()) { return env->as().isSyntactic(); } if (env->is()) { return false; } return true; } inline bool IsExtensibleLexicalEnvironment(JSObject* env) { return env->is(); } inline bool IsGlobalLexicalEnvironment(JSObject* env) { return env->is(); } inline bool IsNSVOLexicalEnvironment(JSObject* env) { return env->is() && env->as() .enclosingEnvironment() .is(); } inline JSObject* MaybeUnwrapWithEnvironment(JSObject* env) { if (env->is()) { return &env->as().object(); } return env; } template inline bool IsFrameInitialEnvironment(AbstractFramePtr frame, SpecificEnvironment& env) { // A frame's initial environment is the innermost environment // corresponding to the scope chain from frame.script()->bodyScope() to // frame.script()->outermostScope(). This environment must be on the chain // for the frame to be considered initialized. That is, it must be on the // chain for the environment chain to fully match the scope chain at the // start of execution in the frame. // // This logic must be in sync with the HAS_INITIAL_ENV logic in // BaselineStackBuilder::buildBaselineFrame. // A function frame's CallObject, if present, is always the initial // environment. if constexpr (std::is_same_v) { return true; } // For an eval frame, the VarEnvironmentObject, if present, is always the // initial environment. if constexpr (std::is_same_v) { if (frame.isEvalFrame()) { return true; } } // For named lambda frames without CallObjects (i.e., no binding in the // body of the function was closed over), the NamedLambdaObject // corresponding to the named lambda scope is the initial environment. if constexpr (std::is_same_v) { if (frame.isFunctionFrame() && frame.callee()->needsNamedLambdaEnvironment() && !frame.callee()->needsCallObject()) { LexicalScope* namedLambdaScope = frame.script()->maybeNamedLambdaScope(); return &env.scope() == namedLambdaScope; } } return false; } extern bool CreateObjectsForEnvironmentChain(JSContext* cx, HandleObjectVector chain, HandleObject terminatingEnv, MutableHandleObject envObj); ModuleObject* GetModuleObjectForScript(JSScript* script); ModuleEnvironmentObject* GetModuleEnvironmentForScript(JSScript* script); [[nodiscard]] bool GetThisValueForDebuggerFrameMaybeOptimizedOut( JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc, MutableHandleValue res); [[nodiscard]] bool GetThisValueForDebuggerSuspendedGeneratorMaybeOptimizedOut( JSContext* cx, AbstractGeneratorObject& genObj, JSScript* script, MutableHandleValue res); [[nodiscard]] bool CheckCanDeclareGlobalBinding(JSContext* cx, Handle global, Handle name, bool isFunction); [[nodiscard]] bool CheckLexicalNameConflict( JSContext* cx, Handle lexicalEnv, HandleObject varObj, Handle name); [[nodiscard]] bool CheckGlobalDeclarationConflicts( JSContext* cx, HandleScript script, Handle lexicalEnv, HandleObject varObj); [[nodiscard]] bool GlobalOrEvalDeclInstantiation(JSContext* cx, HandleObject envChain, HandleScript script, GCThingIndex lastFun); [[nodiscard]] bool InitFunctionEnvironmentObjects(JSContext* cx, AbstractFramePtr frame); [[nodiscard]] bool PushVarEnvironmentObject(JSContext* cx, Handle scope, AbstractFramePtr frame); [[nodiscard]] bool GetFrameEnvironmentAndScope(JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc, MutableHandleObject env, MutableHandle scope); void GetSuspendedGeneratorEnvironmentAndScope(AbstractGeneratorObject& genObj, JSScript* script, MutableHandleObject env, MutableHandle scope); #ifdef DEBUG bool AnalyzeEntrainedVariables(JSContext* cx, HandleScript script); #endif extern JSObject* MaybeOptimizeBindGlobalName(JSContext* cx, Handle global, Handle name); } // namespace js #endif /* vm_EnvironmentObject_h */