diff options
Diffstat (limited to 'js/src/vm/EnvironmentObject.h')
-rw-r--r-- | js/src/vm/EnvironmentObject.h | 1565 |
1 files changed, 1565 insertions, 0 deletions
diff --git a/js/src/vm/EnvironmentObject.h b/js/src/vm/EnvironmentObject.h new file mode 100644 index 0000000000..fd60128e1f --- /dev/null +++ b/js/src/vm/EnvironmentObject.h @@ -0,0 +1,1565 @@ +/* -*- 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 <type_traits> + +#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. JSM loading + * + * Most JSMs are loaded into a shared system global in order to save the memory + * consumption 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 (see B.3 and B.4). + * 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 into a target object + * + * 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 into global this + * + * Subscript may be loaded into global this. In this case no extra environment + * object is created. + * + * global (qualified 'var's and unqualified names) + * | + * GlobalLexicalEnvironmentObject[this=global] (lexical vars) + * + * B.3 Subscript loading into a target object in JSM + * + * The target object of a subscript load may be in a JSM, 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. + * + * BackstagePass global + * | + * GlobalLexicalEnvironmentObject[this=global] + * | + * NonSyntacticVariablesObject (unqualified names) + * | + * NonSyntacticLexicalEnvironmentObject[this=nsvo] + * | + * WithEnvironmentObject wrapping target (qualified 'var's) + * | + * NonSyntacticLexicalEnvironmentObject[this=target] (lexical vars) + * + * B.4 Subscript loading into per-JSM this + * + * Subscript may be loaded into global this. In this case no extra environment + * object is created. + * + * BackstagePass global + * | + * GlobalLexicalEnvironmentObject[this=global] + * | + * NonSyntacticVariablesObject (qualified 'var's and unqualified names) + * | + * NonSyntacticLexicalEnvironmentObject[this=nsvo] (lexical vars) + * + * C.1. Frame scripts with unique scope + * + * XUL frame scripts with unique scope are loaded in the same global as + * JSMs, 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::ExecuteInFrameScriptEnvironment. + * + * 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 + * JSMs, 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 'var's) + * | + * 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 qualified 'var's or unqualified names conflict with the bindings object's + * properties, they go to the WithEnvironmentObject. + * + * If the frame is function, it has the following env chain. + * lexical variables are optimized and uses frame slots, regardless of the name + * conflicts with bindings: + * + * global (unqualified names) + * | + * [DebugProxy] GlobalLexicalEnvironmentObject[this=global] + * | + * [DebugProxy] CallObject (qualified 'var's) + * | + * WithEnvironmentObject wrapping bindings (conflicting 'var's and names) + * + * If the script has direct eval, BlockLexicalEnvironmentObject is created for + * it: + * + * global (unqualified names) + * | + * [DebugProxy] GlobalLexicalEnvironmentObject[this=global] + * | + * [DebugProxy] CallObject (qualified 'var's) + * | + * WithEnvironmentObject wrapping bindings (conflicting 'var's and names) + * | + * BlockLexicalEnvironmentObject (lexical vars, and conflicting lexical vars) + * + * 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. + * + * If `options.useInnerBindings` is not true, if bindings conflict with + * qualified 'var's or global lexicals, those bindings are shadowed and not + * stored into the bindings object wrapped by WithEnvironmentObject. + * + * global (qualified 'var's and unqualified names) + * | + * GlobalLexicalEnvironmentObject[this=global] (lexical vars) + * | + * WithEnvironmentObject wrapping object with not-conflicting bindings + * + * If `options.useInnerBindings` is true, all bindings are stored into the + * bindings object wrapped by WithEnvironmentObject, and they shadow globals + * + * global (qualified 'var's and unqualified names) + * | + * GlobalLexicalEnvironmentObject[this=global] (lexical vars) + * | + * WithEnvironmentObject wrapping object with all bindings + * + * NOTE: If `options.useInnerBindings` is true, and if lexical variable names + * conflict with the bindings object's properties, the write on them + * within declarations is done for the GlobalLexicalEnvironmentObject, + * but the write within assignments and the read on lexicals are done + * from the WithEnvironmentObject (bug 1841964 and bug 1847219). + * + * // bindings = { x: 10, y: 20 }; + * + * let x = 11; // written to GlobalLexicalEnvironmentObject + * x; // read from WithEnvironmentObject + * let y; + * y = 21; // written to WithEnvironmentObject + * y; // read from WithEnvironmentObject + * + * NOTE: Debugger.Object.prototype.executeInGlobal uses the object's global + * scope only, and it doesn't use any dynamic environment or + * non-syntactic scope. + * NOTE: If no extra bindings are used by script, + * Debugger.Object.prototype.executeInGlobalWithBindings 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<SharedShape*> 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<JSFunction>(); + } + + /* 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<SharedShape*> shape, + HandleObject enclosing, + gc::Heap heap); + + static VarEnvironmentObject* create(JSContext* cx, Handle<Scope*> 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*> scope, + AbstractFramePtr frame); + static VarEnvironmentObject* createHollowForDebug(JSContext* cx, + Handle<Scope*> scope); + static VarEnvironmentObject* createTemplateObject(JSContext* cx, + Handle<VarScope*> scope); + static VarEnvironmentObject* createWithoutEnclosing(JSContext* cx, + Handle<VarScope*> scope); + + Scope& scope() const { + Value v = getReservedSlot(SCOPE_SLOT); + MOZ_ASSERT(v.isPrivateGCThing()); + Scope& s = *static_cast<Scope*>(v.toGCThing()); + MOZ_ASSERT(s.is<VarScope>() || s.is<EvalScope>()); + return s; + } + + bool isForEval() const { return scope().is<EvalScope>(); } + 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<ModuleObject*> module); + ModuleObject& module() const; + IndirectBindingMap& importBindings() const; + + bool createImportBinding(JSContext* cx, Handle<JSAtom*> importName, + Handle<ModuleObject*> module, + Handle<JSAtom*> exportName); + + bool hasImportBinding(Handle<PropertyName*> name); + + bool lookupImport(jsid name, ModuleEnvironmentObject** envOut, + mozilla::Maybe<PropertyInfo>* 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<mozilla::Maybe<PropertyDescriptor>> 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<WasmInstanceScope*> scope); + WasmInstanceScope& scope() const { + Value v = getReservedSlot(SCOPE_SLOT); + MOZ_ASSERT(v.isPrivateGCThing()); + return *static_cast<WasmInstanceScope*>(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<WasmFunctionScope*> scope); + WasmFunctionScope& scope() const { + Value v = getReservedSlot(SCOPE_SLOT); + MOZ_ASSERT(v.isPrivateGCThing()); + return *static_cast<WasmFunctionScope*>(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<SharedShape*> shape, + HandleObject enclosing, + gc::Heap heap); + + public: + // Is this the global lexical scope? + bool isGlobal() const { return enclosingEnvironment().is<GlobalObject>(); } + + // 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<Scope*>(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<LexicalScope*> scope, + HandleObject enclosing, + gc::Heap heap); + + public: + static constexpr ObjectFlags OBJECT_FLAGS = {ObjectFlag::NotExtensible}; + + static BlockLexicalEnvironmentObject* createForFrame( + JSContext* cx, Handle<LexicalScope*> scope, AbstractFramePtr frame); + + static BlockLexicalEnvironmentObject* createHollowForDebug( + JSContext* cx, Handle<LexicalScope*> scope); + + static BlockLexicalEnvironmentObject* createTemplateObject( + JSContext* cx, Handle<LexicalScope*> scope); + + static BlockLexicalEnvironmentObject* createWithoutEnclosing( + JSContext* cx, Handle<LexicalScope*> scope); + + // Create a new BlockLexicalEnvironmentObject with the same enclosing env and + // variable values as this. + static BlockLexicalEnvironmentObject* clone( + JSContext* cx, Handle<BlockLexicalEnvironmentObject*> env); + + // Create a new BlockLexicalEnvironmentObject with the same enclosing env as + // this, with all variables uninitialized. + static BlockLexicalEnvironmentObject* recreate( + JSContext* cx, Handle<BlockLexicalEnvironmentObject*> env); + + // The LexicalScope that created this environment. + LexicalScope& scope() const { + return ScopedLexicalEnvironmentObject::scope().as<LexicalScope>(); + } +}; + +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<ClassBodyScope*> scope, HandleObject enclosing, + gc::Heap heap); + + public: + static ClassBodyLexicalEnvironmentObject* createForFrame( + JSContext* cx, Handle<ClassBodyScope*> scope, AbstractFramePtr frame); + + static ClassBodyLexicalEnvironmentObject* createTemplateObject( + JSContext* cx, Handle<ClassBodyScope*> scope); + + static ClassBodyLexicalEnvironmentObject* createWithoutEnclosing( + JSContext* cx, Handle<ClassBodyScope*> scope); + + // The ClassBodyScope that created this environment. + ClassBodyScope& scope() const { + return ScopedLexicalEnvironmentObject::scope().as<ClassBodyScope>(); + } + + 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<GlobalObject*> global); + + GlobalObject& global() const { + return enclosingEnvironment().as<GlobalObject>(); + } + + 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. +// +// See the long "Non-syntactic Environments" comment above. +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<WithScope*> 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<ScopeIter> 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<EnvironmentObject>().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<EnvironmentObject>(); + } + + Scope& scope() const { return *si_.scope(); } + + Scope* maybeScope() const { + if (si_) { + return si_.scope(); + } + return nullptr; + } + + JSFunction& callee() const { return env_->as<CallObject>().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*> 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<DebugEnvironmentProxy*> 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, WeakHeapPtr<DebugEnvironmentProxy*>, + 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<WeakHeapPtr<JSObject*>, LiveEnvironmentVal, + StableCellHasher<WeakHeapPtr<JSObject*>>, ZoneAllocPolicy> + LiveEnvironmentMap; + LiveEnvironmentMap liveEnvs; + + public: + DebugEnvironments(JSContext* cx, Zone* zone); + ~DebugEnvironments(); + + Zone* zone() const { return zone_; } + + private: + static DebugEnvironments* ensureRealmData(JSContext* cx); + + template <typename Environment, typename Scope> + 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<EnvironmentObject*> env, + Handle<DebugEnvironmentProxy*> debugEnv); + + static DebugEnvironmentProxy* hasDebugEnvironment(JSContext* cx, + const EnvironmentIter& ei); + static bool addDebugEnvironment(JSContext* cx, const EnvironmentIter& ei, + Handle<DebugEnvironmentProxy*> 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<DebugEnvironmentProxy*> 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<js::EnvironmentObject>() const { + return is<js::CallObject>() || is<js::VarEnvironmentObject>() || + is<js::ModuleEnvironmentObject>() || + is<js::WasmInstanceEnvironmentObject>() || + is<js::WasmFunctionCallObject>() || + is<js::LexicalEnvironmentObject>() || + is<js::WithEnvironmentObject>() || + is<js::NonSyntacticVariablesObject>() || + is<js::RuntimeLexicalErrorObject>(); +} + +template <> +inline bool JSObject::is<js::ScopedLexicalEnvironmentObject>() const { + return is<js::LexicalEnvironmentObject>() && + !as<js::LexicalEnvironmentObject>().isExtensible(); +} + +template <> +inline bool JSObject::is<js::BlockLexicalEnvironmentObject>() const { + return is<js::ScopedLexicalEnvironmentObject>() && + !as<js::ScopedLexicalEnvironmentObject>().isClassBody(); +} + +template <> +inline bool JSObject::is<js::ClassBodyLexicalEnvironmentObject>() const { + return is<js::ScopedLexicalEnvironmentObject>() && + as<js::ScopedLexicalEnvironmentObject>().isClassBody(); +} + +template <> +inline bool JSObject::is<js::ExtensibleLexicalEnvironmentObject>() const { + return is<js::LexicalEnvironmentObject>() && + as<js::LexicalEnvironmentObject>().isExtensible(); +} + +template <> +inline bool JSObject::is<js::GlobalLexicalEnvironmentObject>() const { + return is<js::LexicalEnvironmentObject>() && + as<js::LexicalEnvironmentObject>().isGlobal(); +} + +template <> +inline bool JSObject::is<js::NonSyntacticLexicalEnvironmentObject>() const { + return is<js::LexicalEnvironmentObject>() && + !as<js::LexicalEnvironmentObject>().isSyntactic(); +} + +template <> +inline bool JSObject::is<js::NamedLambdaObject>() const { + return is<js::BlockLexicalEnvironmentObject>() && + as<js::BlockLexicalEnvironmentObject>().scope().isNamedLambda(); +} + +template <> +bool JSObject::is<js::DebugEnvironmentProxy>() const; + +namespace js { + +inline bool IsSyntacticEnvironment(JSObject* env) { + if (!env->is<EnvironmentObject>()) { + return false; + } + + if (env->is<WithEnvironmentObject>()) { + return env->as<WithEnvironmentObject>().isSyntactic(); + } + + if (env->is<LexicalEnvironmentObject>()) { + return env->as<LexicalEnvironmentObject>().isSyntactic(); + } + + if (env->is<NonSyntacticVariablesObject>()) { + return false; + } + + return true; +} + +inline bool IsExtensibleLexicalEnvironment(JSObject* env) { + return env->is<ExtensibleLexicalEnvironmentObject>(); +} + +inline bool IsGlobalLexicalEnvironment(JSObject* env) { + return env->is<GlobalLexicalEnvironmentObject>(); +} + +inline bool IsNSVOLexicalEnvironment(JSObject* env) { + return env->is<LexicalEnvironmentObject>() && + env->as<LexicalEnvironmentObject>() + .enclosingEnvironment() + .is<NonSyntacticVariablesObject>(); +} + +inline JSObject* MaybeUnwrapWithEnvironment(JSObject* env) { + if (env->is<WithEnvironmentObject>()) { + return &env->as<WithEnvironmentObject>().object(); + } + return env; +} + +template <typename SpecificEnvironment> +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<SpecificEnvironment, CallObject>) { + return true; + } + + // For an eval frame, the VarEnvironmentObject, if present, is always the + // initial environment. + if constexpr (std::is_same_v<SpecificEnvironment, VarEnvironmentObject>) { + 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<SpecificEnvironment, NamedLambdaObject>) { + 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<GlobalObject*> global, + Handle<PropertyName*> name, + bool isFunction); + +[[nodiscard]] bool CheckLexicalNameConflict( + JSContext* cx, Handle<ExtensibleLexicalEnvironmentObject*> lexicalEnv, + HandleObject varObj, Handle<PropertyName*> name); + +[[nodiscard]] bool CheckGlobalDeclarationConflicts( + JSContext* cx, HandleScript script, + Handle<ExtensibleLexicalEnvironmentObject*> 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*> scope, + AbstractFramePtr frame); + +[[nodiscard]] bool GetFrameEnvironmentAndScope(JSContext* cx, + AbstractFramePtr frame, + const jsbytecode* pc, + MutableHandleObject env, + MutableHandle<Scope*> scope); + +void GetSuspendedGeneratorEnvironmentAndScope(AbstractGeneratorObject& genObj, + JSScript* script, + MutableHandleObject env, + MutableHandle<Scope*> scope); + +#ifdef DEBUG +bool AnalyzeEntrainedVariables(JSContext* cx, HandleScript script); +#endif + +extern JSObject* MaybeOptimizeBindGlobalName(JSContext* cx, + Handle<GlobalObject*> global, + Handle<PropertyName*> name); +} // namespace js + +#endif /* vm_EnvironmentObject_h */ |