summaryrefslogtreecommitdiffstats
path: root/js/src/vm/EnvironmentObject.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/EnvironmentObject.h')
-rw-r--r--js/src/vm/EnvironmentObject.h1565
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 */