| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #ifndef vm_ScopeObject_h |
| #define vm_ScopeObject_h |
| |
| #include "jscntxt.h" |
| #include "jsobj.h" |
| #include "jsweakmap.h" |
| |
| #include "builtin/ModuleObject.h" |
| #include "gc/Barrier.h" |
| #include "js/GCHashTable.h" |
| #include "vm/ArgumentsObject.h" |
| #include "vm/ProxyObject.h" |
| |
| namespace js { |
| |
| namespace frontend { |
| struct Definition; |
| class FunctionBox; |
| class ModuleBox; |
| } |
| |
| class StaticWithObject; |
| class StaticEvalObject; |
| class StaticNonSyntacticScopeObjects; |
| |
| class ModuleObject; |
| typedef Handle<ModuleObject*> HandleModuleObject; |
| |
| /*****************************************************************************/ |
| |
| /* |
| * The static scope chain is the canonical truth for lexical scope contour of |
| * a program. The dynamic scope chain is derived from the static scope chain: |
| * it is the chain of scopes whose static scopes have a runtime |
| * representation, for example, due to aliased bindings. |
| * |
| * Static scopes roughly correspond to a scope in the program text. They are |
| * divided into scopes that have direct correspondence to program text (i.e., |
| * syntactic) and ones used internally for scope walking (i.e., non-syntactic). |
| * |
| * The following are syntactic static scopes: |
| * |
| * StaticBlockObject |
| * Scope for non-function body blocks. e.g., |{ let x; }| |
| * |
| * JSFunction |
| * Scope for function bodies. e.g., |function f() { var x; let y; }| |
| * |
| * ModuleObject |
| * Scope for moddules. |
| * |
| * StaticWithObject |
| * Scope for |with|. e.g., |with ({}) { ... }| |
| * |
| * StaticEvalObject |
| * Scope for |eval|. e.g., |eval(...)| |
| * |
| * The following are non-syntactic static scopes: |
| * |
| * StaticNonSyntacticScopeObjects |
| * Signals presence of "polluting" scope objects. Used by Gecko. |
| * |
| * There is an additional scope for named lambdas without a static scope |
| * object. E.g., in: |
| * |
| * (function f() { var x; function g() { } }) |
| * |
| * All static scope objects are ScopeObjects with the exception of JSFunction |
| * and ModuleObject, which keeps their enclosing scope link on |
| * |JSScript::enclosingStaticScope()|. |
| */ |
| template <AllowGC allowGC> |
| class StaticScopeIter |
| { |
| typename MaybeRooted<JSObject*, allowGC>::RootType obj; |
| bool onNamedLambda; |
| |
| static bool IsStaticScope(JSObject* obj) { |
| return obj->is<StaticBlockObject>() || |
| obj->is<StaticWithObject>() || |
| obj->is<StaticEvalObject>() || |
| obj->is<StaticNonSyntacticScopeObjects>() || |
| obj->is<JSFunction>() || |
| obj->is<ModuleObject>(); |
| } |
| |
| public: |
| StaticScopeIter(ExclusiveContext* cx, JSObject* obj) |
| : obj(cx, obj), onNamedLambda(false) |
| { |
| static_assert(allowGC == CanGC, |
| "the context-accepting constructor should only be used " |
| "in CanGC code"); |
| MOZ_ASSERT_IF(obj, IsStaticScope(obj)); |
| } |
| |
| StaticScopeIter(ExclusiveContext* cx, const StaticScopeIter<CanGC>& ssi) |
| : obj(cx, ssi.obj), onNamedLambda(ssi.onNamedLambda) |
| { |
| JS_STATIC_ASSERT(allowGC == CanGC); |
| } |
| |
| explicit StaticScopeIter(JSObject* obj) |
| : obj((ExclusiveContext*) nullptr, obj), onNamedLambda(false) |
| { |
| static_assert(allowGC == NoGC, |
| "the constructor not taking a context should only be " |
| "used in NoGC code"); |
| MOZ_ASSERT_IF(obj, IsStaticScope(obj)); |
| } |
| |
| explicit StaticScopeIter(const StaticScopeIter<NoGC>& ssi) |
| : obj((ExclusiveContext*) nullptr, ssi.obj), onNamedLambda(ssi.onNamedLambda) |
| { |
| static_assert(allowGC == NoGC, |
| "the constructor not taking a context should only be " |
| "used in NoGC code"); |
| } |
| |
| bool done() const { return !obj; } |
| void operator++(int); |
| |
| JSObject* staticScope() const { MOZ_ASSERT(!done()); return obj; } |
| |
| // Return whether this static scope will have a syntactic scope (i.e. a |
| // ScopeObject that isn't a non-syntactic With or |
| // NonSyntacticVariablesObject) on the dynamic scope chain. |
| bool hasSyntacticDynamicScopeObject() const; |
| Shape* scopeShape() const; |
| |
| enum Type { Module, Function, Block, With, NamedLambda, Eval, NonSyntactic }; |
| Type type() const; |
| |
| StaticBlockObject& block() const; |
| StaticWithObject& staticWith() const; |
| StaticEvalObject& eval() const; |
| StaticNonSyntacticScopeObjects& nonSyntactic() const; |
| JSScript* funScript() const; |
| JSFunction& fun() const; |
| frontend::FunctionBox* maybeFunctionBox() const; |
| JSScript* moduleScript() const; |
| ModuleObject& module() const; |
| }; |
| |
| /*****************************************************************************/ |
| |
| /* |
| * A "scope coordinate" describes how to get from head of the scope chain to a |
| * given lexically-enclosing variable. A scope coordinate has two dimensions: |
| * - hops: the number of scope objects on the scope chain to skip |
| * - slot: the slot on the scope object holding the variable's value |
| */ |
| class ScopeCoordinate |
| { |
| uint32_t hops_; |
| uint32_t slot_; |
| |
| /* |
| * Technically, hops_/slot_ are SCOPECOORD_(HOPS|SLOT)_BITS wide. Since |
| * ScopeCoordinate is a temporary value, don't bother with a bitfield as |
| * this only adds overhead. |
| */ |
| static_assert(SCOPECOORD_HOPS_BITS <= 32, "We have enough bits below"); |
| static_assert(SCOPECOORD_SLOT_BITS <= 32, "We have enough bits below"); |
| |
| public: |
| explicit inline ScopeCoordinate(jsbytecode* pc) |
| : hops_(GET_SCOPECOORD_HOPS(pc)), slot_(GET_SCOPECOORD_SLOT(pc + SCOPECOORD_HOPS_LEN)) |
| { |
| MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD); |
| } |
| |
| inline ScopeCoordinate() {} |
| |
| void setHops(uint32_t hops) { MOZ_ASSERT(hops < SCOPECOORD_HOPS_LIMIT); hops_ = hops; } |
| void setSlot(uint32_t slot) { MOZ_ASSERT(slot < SCOPECOORD_SLOT_LIMIT); slot_ = slot; } |
| |
| uint32_t hops() const { MOZ_ASSERT(hops_ < SCOPECOORD_HOPS_LIMIT); return hops_; } |
| uint32_t slot() const { MOZ_ASSERT(slot_ < SCOPECOORD_SLOT_LIMIT); return slot_; } |
| |
| bool operator==(const ScopeCoordinate& rhs) const { |
| return hops() == rhs.hops() && slot() == rhs.slot(); |
| } |
| }; |
| |
| /* |
| * Return a shape representing the static scope containing the variable |
| * accessed by the ALIASEDVAR op at 'pc'. |
| */ |
| extern Shape* |
| ScopeCoordinateToStaticScopeShape(JSScript* script, jsbytecode* pc); |
| |
| /* Return the name being accessed by the given ALIASEDVAR op. */ |
| extern PropertyName* |
| ScopeCoordinateName(ScopeCoordinateNameCache& cache, JSScript* script, jsbytecode* pc); |
| |
| /* Return the function script accessed by the given ALIASEDVAR op, or nullptr. */ |
| extern JSScript* |
| ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc); |
| |
| /*****************************************************************************/ |
| |
| /* |
| * Scope objects |
| * |
| * Scope objects are technically real JSObjects but only belong on the scope |
| * chain (that is, fp->scopeChain() or fun->environment()). The hierarchy of |
| * scope objects is: |
| * |
| * JSObject Generic object |
| * | |
| * ScopeObject---+---+ Engine-internal scope |
| * | | | | | |
| * | | | | StaticNonSyntacticScopeObjects See "Non-syntactic scope objects" |
| * | | | | |
| * | | | StaticEvalObject Placeholder so eval scopes may be iterated through |
| * | | | |
| * | | DeclEnvObject Holds name of recursive/needsCallObject named lambda |
| * | | |
| * | LexicalScopeBase Shared base for function and modules scopes |
| * | | | |
| * | | CallObject Scope of entire function or strict eval |
| * | | |
| * | ModuleEnvironmentObject Module top-level scope on run-time scope chain |
| * | |
| * NestedScopeObject Statement scopes; don't cross script boundaries |
| * | | | |
| * | | StaticWithObject Template for "with" object in static scope chain |
| * | | |
| * | DynamicWithObject Run-time "with" object on scope chain |
| * | |
| * BlockObject Shared interface of cloned/static block objects |
| * | | |
| * | ClonedBlockObject let, switch, catch, for |
| * | |
| * StaticBlockObject See NB |
| * |
| * This hierarchy represents more than just the interface hierarchy: reserved |
| * slots in base classes are fixed for all derived classes. Thus, for example, |
| * ScopeObject::enclosingScope() can simply access a fixed slot without further |
| * dynamic type information. |
| * |
| * NB: Static block objects are a special case: these objects are created at |
| * compile time to hold the shape/binding information from which block objects |
| * are cloned at runtime. These objects should never escape into the wild and |
| * support a restricted set of ScopeObject operations. |
| * |
| * See also "Debug scope objects" below. |
| */ |
| |
| class ScopeObject : public NativeObject |
| { |
| protected: |
| static const uint32_t SCOPE_CHAIN_SLOT = 0; |
| |
| public: |
| /* |
| * Since every scope chain terminates with a global object and GlobalObject |
| * does not derive ScopeObject (it has a completely different layout), the |
| * enclosing scope of a ScopeObject is necessarily non-null. |
| */ |
| inline JSObject& enclosingScope() const { |
| return getFixedSlot(SCOPE_CHAIN_SLOT).toObject(); |
| } |
| |
| void setEnclosingScope(HandleObject obj); |
| |
| /* |
| * Get or set an aliased variable contained in this scope. Unaliased |
| * variables should instead access the stack frame. Aliased variable access |
| * is primarily made through JOF_SCOPECOORD ops which is why these members |
| * take a ScopeCoordinate instead of just the slot index. |
| */ |
| inline const Value& aliasedVar(ScopeCoordinate sc); |
| |
| inline void setAliasedVar(JSContext* cx, ScopeCoordinate sc, PropertyName* name, const Value& v); |
| |
| /* For jit access. */ |
| static size_t offsetOfEnclosingScope() { |
| return getFixedSlotOffset(SCOPE_CHAIN_SLOT); |
| } |
| |
| static size_t enclosingScopeSlot() { |
| return SCOPE_CHAIN_SLOT; |
| } |
| }; |
| |
| class LexicalScopeBase : public ScopeObject |
| { |
| protected: |
| inline void initRemainingSlotsToUninitializedLexicals(uint32_t begin); |
| inline void initAliasedLexicalsToThrowOnTouch(JSScript* script); |
| |
| public: |
| /* Get/set the aliased variable referred to by 'fi'. */ |
| const Value& aliasedVar(AliasedFormalIter fi) { |
| return getSlot(fi.scopeSlot()); |
| } |
| inline void setAliasedVar(JSContext* cx, AliasedFormalIter fi, PropertyName* name, |
| const Value& v); |
| |
| /* |
| * When an aliased var (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& aliasedVarFromArguments(const Value& argsValue) { |
| return getSlot(ArgumentsObject::SlotFromMagicScopeSlotValue(argsValue)); |
| } |
| inline void setAliasedVarFromArguments(JSContext* cx, const Value& argsValue, jsid id, |
| const Value& v); |
| }; |
| |
| class CallObject : public LexicalScopeBase |
| { |
| protected: |
| static const uint32_t CALLEE_SLOT = 1; |
| |
| static CallObject* |
| create(JSContext* cx, HandleScript script, HandleObject enclosing, HandleFunction callee); |
| |
| public: |
| static const Class class_; |
| |
| /* These functions are internal and are exposed only for JITs. */ |
| |
| /* |
| * Construct a bare-bones call object given a shape and a non-singleton |
| * group. The call object must be further initialized to be usable. |
| */ |
| static CallObject* |
| create(JSContext* cx, HandleShape shape, HandleObjectGroup group, uint32_t lexicalBegin); |
| |
| /* |
| * Construct a bare-bones call object given a shape and make it into |
| * a singleton. The call object must be initialized to be usable. |
| */ |
| static CallObject* |
| createSingleton(JSContext* cx, HandleShape shape, uint32_t lexicalBegin); |
| |
| static CallObject* |
| createTemplateObject(JSContext* cx, HandleScript script, gc::InitialHeap heap); |
| |
| static const uint32_t RESERVED_SLOTS = 2; |
| |
| static CallObject* createForFunction(JSContext* cx, HandleObject enclosing, HandleFunction callee); |
| |
| static CallObject* createForFunction(JSContext* cx, AbstractFramePtr frame); |
| static CallObject* createForStrictEval(JSContext* cx, AbstractFramePtr frame); |
| static CallObject* createHollowForDebug(JSContext* cx, HandleFunction callee); |
| |
| /* True if this is for a strict mode eval frame. */ |
| bool isForEval() const { |
| if (is<ModuleEnvironmentObject>()) |
| return false; |
| MOZ_ASSERT(getFixedSlot(CALLEE_SLOT).isObjectOrNull()); |
| MOZ_ASSERT_IF(getFixedSlot(CALLEE_SLOT).isObject(), |
| getFixedSlot(CALLEE_SLOT).toObject().is<JSFunction>()); |
| return getFixedSlot(CALLEE_SLOT).isNull(); |
| } |
| |
| /* |
| * Returns the function for which this CallObject was created. (This may |
| * only be called if !isForEval.) |
| */ |
| JSFunction& callee() const { |
| MOZ_ASSERT(!is<ModuleEnvironmentObject>()); |
| return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>(); |
| } |
| |
| /* For jit access. */ |
| static size_t offsetOfCallee() { |
| return getFixedSlotOffset(CALLEE_SLOT); |
| } |
| |
| static size_t calleeSlot() { |
| return CALLEE_SLOT; |
| } |
| }; |
| |
| class ModuleEnvironmentObject : public LexicalScopeBase |
| { |
| static const uint32_t MODULE_SLOT = 1; |
| |
| public: |
| static const Class class_; |
| |
| static const uint32_t RESERVED_SLOTS = 2; |
| |
| static ModuleEnvironmentObject* create(ExclusiveContext* cx, HandleModuleObject module); |
| ModuleObject& module(); |
| IndirectBindingMap& importBindings(); |
| |
| bool createImportBinding(JSContext* cx, HandleAtom importName, HandleModuleObject module, |
| HandleAtom exportName); |
| |
| bool hasImportBinding(HandlePropertyName name); |
| |
| bool lookupImport(jsid name, ModuleEnvironmentObject** envOut, Shape** shapeOut); |
| |
| private: |
| static bool lookupProperty(JSContext* cx, HandleObject obj, HandleId id, |
| MutableHandleObject objp, MutableHandleShape 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<JSPropertyDescriptor> desc); |
| static bool deleteProperty(JSContext* cx, HandleObject obj, HandleId id, |
| ObjectOpResult& result); |
| static bool enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, |
| bool enumerableOnly); |
| }; |
| |
| typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject; |
| typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject; |
| typedef MutableHandle<ModuleEnvironmentObject*> MutableHandleModuleEnvironmentObject; |
| |
| class DeclEnvObject : public ScopeObject |
| { |
| // Pre-allocated slot for the named lambda. |
| static const uint32_t LAMBDA_SLOT = 1; |
| |
| public: |
| static const uint32_t RESERVED_SLOTS = 2; |
| static const Class class_; |
| |
| static DeclEnvObject* |
| createTemplateObject(JSContext* cx, HandleFunction fun, NewObjectKind newKind); |
| |
| static DeclEnvObject* create(JSContext* cx, HandleObject enclosing, HandleFunction callee); |
| |
| static inline size_t lambdaSlot() { |
| return LAMBDA_SLOT; |
| } |
| }; |
| |
| // Static eval scope placeholder objects on the static scope chain. Created at |
| // the time of compiling the eval script, and set as its static enclosing |
| // scope. |
| class StaticEvalObject : public ScopeObject |
| { |
| static const uint32_t STRICT_SLOT = 1; |
| |
| public: |
| static const unsigned RESERVED_SLOTS = 2; |
| static const Class class_; |
| |
| static StaticEvalObject* create(JSContext* cx, HandleObject enclosing); |
| |
| JSObject* enclosingScopeForStaticScopeIter() { |
| return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); |
| } |
| |
| void setStrict() { |
| setReservedSlot(STRICT_SLOT, BooleanValue(true)); |
| } |
| |
| bool isStrict() const { |
| return getReservedSlot(STRICT_SLOT).isTrue(); |
| } |
| |
| inline bool isNonGlobal() const; |
| }; |
| |
| /* |
| * Non-syntactic scope objects |
| * |
| * A non-syntactic scope is one that was not created due to source code. On |
| * the static scope chain, a single StaticNonSyntacticScopeObjects maps to 0+ |
| * non-syntactic dynamic scope objects. This is contrasted with syntactic |
| * scopes, where each syntactic static scope corresponds to 0 or 1 dynamic |
| * scope objects. |
| * |
| * There are 3 kinds of dynamic non-syntactic scopes: |
| * |
| * 1. DynamicWithObject |
| * |
| * 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 scope chain. Each |
| * of those objects is wrapped by a DynamicWithObject. |
| * |
| * The innermost scope 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 DynamicWithObjects, this object is itself the holder of 'var' |
| * bindings. |
| * |
| * Does not hold 'let' or 'const' bindings. |
| * |
| * 3. ClonedBlockObject |
| * |
| * Each non-syntactic object used as a qualified variables object needs to |
| * enclose a non-syntactic ClonedBlockObject to hold 'let' and 'const' |
| * bindings. There is a bijection per compartment between the non-syntactic |
| * variables objects and their non-syntactic ClonedBlockObjects. |
| * |
| * Does not hold 'var' bindings. |
| * |
| * The embedding (Gecko) uses non-syntactic scopes for various things, some of |
| * which are detailed below. All scope chain listings below are, from top to |
| * bottom, outermost to innermost. |
| * |
| * A. Component loading |
| * |
| * Components may be loaded in "reuse loader global" mode, where to save on |
| * memory, all JSMs and JS-implemented XPCOM modules are loaded into a single |
| * global. Each individual JSMs are compiled as functions with their own |
| * FakeBackstagePass. They have the following dynamic scope chain: |
| * |
| * BackstagePass global |
| * | |
| * Global lexical scope |
| * | |
| * DynamicWithObject wrapping FakeBackstagePass |
| * | |
| * Non-syntactic lexical scope |
| * |
| * B. Subscript loading |
| * |
| * Subscripts may be loaded into a target object. They have the following |
| * dynamic scope chain: |
| * |
| * Loader global |
| * | |
| * Global lexical scope |
| * | |
| * DynamicWithObject wrapping target |
| * | |
| * ClonedBlockObject |
| * |
| * C. Frame scripts |
| * |
| * XUL frame scripts are always loaded with a NonSyntacticVariablesObject as a |
| * "polluting global". This is done exclusively in |
| * js::ExecuteInGlobalAndReturnScope. |
| * |
| * Loader global |
| * | |
| * Global lexical scope |
| * | |
| * NonSyntacticVariablesObject |
| * | |
| * ClonedBlockObject |
| * |
| * D. XBL |
| * |
| * XBL methods are compiled as functions with XUL elements on the scope chain. |
| * For a chain of elements e0,...,eN: |
| * |
| * ... |
| * | |
| * DynamicWithObject wrapping eN |
| * | |
| * ... |
| * | |
| * DynamicWithObject wrapping e0 |
| * | |
| * ClonedBlockObject |
| * |
| */ |
| class StaticNonSyntacticScopeObjects : public ScopeObject |
| { |
| public: |
| static const unsigned RESERVED_SLOTS = 1; |
| static const Class class_; |
| |
| static StaticNonSyntacticScopeObjects* create(JSContext* cx, HandleObject enclosing); |
| |
| JSObject* enclosingScopeForStaticScopeIter() { |
| return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); |
| } |
| }; |
| |
| // 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 scope. |
| // |
| // This is used in ExecuteInGlobalAndReturnScope and sits in front of the |
| // global scope to capture 'var' and bareword asignments. |
| class NonSyntacticVariablesObject : public ScopeObject |
| { |
| public: |
| static const unsigned RESERVED_SLOTS = 1; |
| static const Class class_; |
| |
| static NonSyntacticVariablesObject* create(JSContext* cx, |
| Handle<ClonedBlockObject*> globalLexical); |
| }; |
| |
| class NestedScopeObject : public ScopeObject |
| { |
| public: |
| /* |
| * A refinement of enclosingScope that returns nullptr if the enclosing |
| * scope is not a NestedScopeObject. |
| */ |
| inline NestedScopeObject* enclosingNestedScope() const; |
| |
| // Return true if this object is a compile-time scope template. |
| inline bool isStatic() { return !getProto(); } |
| |
| // Return the static scope corresponding to this scope chain object. |
| inline NestedScopeObject* staticScope() { |
| MOZ_ASSERT(!isStatic()); |
| return &getProto()->as<NestedScopeObject>(); |
| } |
| |
| // At compile-time it's possible for the scope chain to be null. |
| JSObject* enclosingScopeForStaticScopeIter() { |
| return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); |
| } |
| |
| void initEnclosingScope(JSObject* obj) { |
| MOZ_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined()); |
| setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj)); |
| } |
| |
| /* |
| * Note: in the case of hoisting, this prev-link will not ultimately be |
| * the same as enclosingNestedScope; initEnclosingNestedScope must be |
| * called separately in the emitter. 'reset' is just for asserting |
| * stackiness. |
| */ |
| void initEnclosingScopeFromParser(JSObject* prev) { |
| setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(prev)); |
| } |
| |
| void resetEnclosingScopeFromParser() { |
| setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue()); |
| } |
| }; |
| |
| // With scope template objects on the static scope chain. |
| class StaticWithObject : public NestedScopeObject |
| { |
| public: |
| static const unsigned RESERVED_SLOTS = 1; |
| static const Class class_; |
| |
| static StaticWithObject* create(ExclusiveContext* cx); |
| }; |
| |
| // With scope objects on the run-time scope chain. |
| class DynamicWithObject : public NestedScopeObject |
| { |
| static const unsigned OBJECT_SLOT = 1; |
| static const unsigned THIS_SLOT = 2; |
| static const unsigned KIND_SLOT = 3; |
| |
| public: |
| static const unsigned RESERVED_SLOTS = 4; |
| static const Class class_; |
| |
| enum WithKind { |
| SyntacticWith, |
| NonSyntacticWith |
| }; |
| |
| static DynamicWithObject* |
| create(JSContext* cx, HandleObject object, HandleObject enclosing, HandleObject staticWith, |
| WithKind kind = SyntacticWith); |
| |
| StaticWithObject& staticWith() const { |
| return getProto()->as<StaticWithObject>(); |
| } |
| |
| /* Return the 'o' in 'with (o)'. */ |
| JSObject& object() const { |
| return getReservedSlot(OBJECT_SLOT).toObject(); |
| } |
| |
| /* Return object for GetThisValue. */ |
| JSObject* withThis() const { |
| return &getReservedSlot(THIS_SLOT).toObject(); |
| } |
| |
| /* |
| * 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 scope chain someone explicitly passed via JSAPI |
| * to CompileFunction or script evaluation. |
| */ |
| bool isSyntactic() const { |
| return getReservedSlot(KIND_SLOT).toInt32() == SyntacticWith; |
| } |
| |
| static inline size_t objectSlot() { |
| return OBJECT_SLOT; |
| } |
| |
| static inline size_t thisSlot() { |
| return THIS_SLOT; |
| } |
| }; |
| |
| class BlockObject : public NestedScopeObject |
| { |
| public: |
| static const unsigned RESERVED_SLOTS = 2; |
| static const Class class_; |
| |
| /* Return the number of variables associated with this block. */ |
| uint32_t numVariables() const { |
| // TODO: propertyCount() is O(n), use O(1) lastProperty()->slot() instead |
| return propertyCount(); |
| } |
| |
| // Global lexical scopes are extensible. Non-global lexicals scopes are |
| // not. |
| bool isExtensible() const; |
| |
| protected: |
| /* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */ |
| const Value& slotValue(unsigned i) { |
| return getSlotRef(RESERVED_SLOTS + i); |
| } |
| |
| void setSlotValue(unsigned i, const Value& v) { |
| setSlot(RESERVED_SLOTS + i, v); |
| } |
| }; |
| |
| class StaticBlockObject : public BlockObject |
| { |
| static const unsigned LOCAL_OFFSET_SLOT = 1; |
| |
| public: |
| static StaticBlockObject* create(ExclusiveContext* cx); |
| |
| /* See StaticScopeIter comment. */ |
| JSObject* enclosingStaticScope() const { |
| return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); |
| } |
| |
| /* |
| * Return the index (in the range [0, numVariables()) corresponding to the |
| * given shape of a block object. |
| */ |
| uint32_t shapeToIndex(const Shape& shape) { |
| uint32_t slot = shape.slot(); |
| MOZ_ASSERT(slot - RESERVED_SLOTS < numVariables()); |
| return slot - RESERVED_SLOTS; |
| } |
| |
| /* |
| * A refinement of enclosingStaticScope that returns nullptr if the enclosing |
| * static scope is a JSFunction. |
| */ |
| inline StaticBlockObject* enclosingBlock() const; |
| |
| uint32_t localOffset() { |
| return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32(); |
| } |
| |
| // Return the local corresponding to the 'var'th binding where 'var' is in the |
| // range [0, numVariables()). |
| uint32_t blockIndexToLocalIndex(uint32_t index) { |
| MOZ_ASSERT(index < numVariables()); |
| return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32() + index; |
| } |
| |
| // Return the slot corresponding to block index 'index', where 'index' is |
| // in the range [0, numVariables()). The result is in the range |
| // [RESERVED_SLOTS, RESERVED_SLOTS + numVariables()). |
| uint32_t blockIndexToSlot(uint32_t index) { |
| MOZ_ASSERT(index < numVariables()); |
| return RESERVED_SLOTS + index; |
| } |
| |
| // Return the slot corresponding to local variable 'local', where 'local' is |
| // in the range [localOffset(), localOffset() + numVariables()). The result is |
| // in the range [RESERVED_SLOTS, RESERVED_SLOTS + numVariables()). |
| uint32_t localIndexToSlot(uint32_t local) { |
| MOZ_ASSERT(local >= localOffset()); |
| return blockIndexToSlot(local - localOffset()); |
| } |
| |
| /* |
| * A let binding is aliased if accessed lexically by nested functions or |
| * dynamically through dynamic name lookup (eval, with, function::, etc). |
| */ |
| bool isAliased(unsigned i) { |
| return slotValue(i).isTrue(); |
| } |
| |
| // Look up if the block has an aliased binding named |name|. |
| Shape* lookupAliasedName(PropertyName* name); |
| |
| /* |
| * A static block object is cloned (when entering the block) iff some |
| * variable of the block isAliased. |
| */ |
| bool needsClone() { |
| return numVariables() > 0 && !getSlot(RESERVED_SLOTS).isFalse(); |
| } |
| |
| // Is this the static global lexical scope? |
| bool isGlobal() const { |
| return !enclosingStaticScope(); |
| } |
| |
| bool isSyntactic() const { |
| return !isExtensible() || isGlobal(); |
| } |
| |
| /* Frontend-only functions ***********************************************/ |
| |
| /* Initialization functions for above fields. */ |
| void setAliased(unsigned i, bool aliased) { |
| MOZ_ASSERT_IF(i > 0, slotValue(i-1).isBoolean()); |
| setSlotValue(i, BooleanValue(aliased)); |
| if (aliased && !needsClone()) { |
| setSlotValue(0, MagicValue(JS_BLOCK_NEEDS_CLONE)); |
| MOZ_ASSERT(needsClone()); |
| } |
| } |
| |
| void setLocalOffset(uint32_t offset) { |
| MOZ_ASSERT(getReservedSlot(LOCAL_OFFSET_SLOT).isUndefined()); |
| initReservedSlot(LOCAL_OFFSET_SLOT, PrivateUint32Value(offset)); |
| } |
| |
| /* |
| * Frontend compilation temporarily uses the object's slots to link |
| * a let var to its associated Definition parse node. |
| */ |
| void setDefinitionParseNode(unsigned i, frontend::Definition* def) { |
| MOZ_ASSERT(slotValue(i).isUndefined()); |
| setSlotValue(i, PrivateValue(def)); |
| } |
| |
| frontend::Definition* definitionParseNode(unsigned i) { |
| Value v = slotValue(i); |
| return reinterpret_cast<frontend::Definition*>(v.toPrivate()); |
| } |
| |
| // Called by BytecodeEmitter to mark regular block scopes as |
| // non-extensible. By contrast, the global lexical scope is extensible. |
| bool makeNonExtensible(ExclusiveContext* cx); |
| |
| /* |
| * While ScopeCoordinate can generally reference up to 2^24 slots, block objects have an |
| * additional limitation that all slot indices must be storable as uint16_t short-ids in the |
| * associated Shape. If we could remove the block dependencies on shape->shortid, we could |
| * remove INDEX_LIMIT. |
| */ |
| static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16); |
| |
| static Shape* addVar(ExclusiveContext* cx, Handle<StaticBlockObject*> block, HandleId id, |
| bool constant, unsigned index, bool* redeclared); |
| }; |
| |
| class ClonedBlockObject : public BlockObject |
| { |
| static const unsigned THIS_VALUE_SLOT = 1; |
| |
| static ClonedBlockObject* create(JSContext* cx, Handle<StaticBlockObject*> block, |
| HandleObject enclosing); |
| |
| public: |
| static ClonedBlockObject* create(JSContext* cx, Handle<StaticBlockObject*> block, |
| AbstractFramePtr frame); |
| |
| static ClonedBlockObject* createGlobal(JSContext* cx, Handle<GlobalObject*> global); |
| |
| static ClonedBlockObject* createNonSyntactic(JSContext* cx, HandleObject enclosingStatic, |
| HandleObject enclosingScope); |
| |
| static ClonedBlockObject* createHollowForDebug(JSContext* cx, |
| Handle<StaticBlockObject*> block); |
| |
| /* The static block from which this block was cloned. */ |
| StaticBlockObject& staticBlock() const { |
| return getProto()->as<StaticBlockObject>(); |
| } |
| |
| /* Assuming 'put' has been called, return the value of the ith let var. */ |
| const Value& var(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) { |
| MOZ_ASSERT_IF(checkAliasing, staticBlock().isAliased(i)); |
| return slotValue(i); |
| } |
| |
| void setVar(unsigned i, const Value& v, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) { |
| MOZ_ASSERT_IF(checkAliasing, staticBlock().isAliased(i)); |
| setSlotValue(i, v); |
| } |
| |
| // Is this the global lexical scope? |
| bool isGlobal() const { |
| MOZ_ASSERT_IF(staticBlock().isGlobal(), enclosingScope().is<GlobalObject>()); |
| return enclosingScope().is<GlobalObject>(); |
| } |
| |
| GlobalObject& global() const { |
| MOZ_ASSERT(isGlobal()); |
| return enclosingScope().as<GlobalObject>(); |
| } |
| |
| bool isSyntactic() const { |
| return !isExtensible() || isGlobal(); |
| } |
| |
| /* Copy in all the unaliased formals and locals. */ |
| void copyUnaliasedValues(AbstractFramePtr frame); |
| |
| /* |
| * Create a new ClonedBlockObject with the same enclosing scope and |
| * variable values as this. |
| */ |
| static ClonedBlockObject* clone(JSContext* cx, Handle<ClonedBlockObject*> block); |
| |
| Value thisValue() const; |
| }; |
| |
| // 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 ScopeObject |
| { |
| static const unsigned ERROR_SLOT = 1; |
| |
| public: |
| static const unsigned RESERVED_SLOTS = 2; |
| static const Class class_; |
| |
| static RuntimeLexicalErrorObject* create(JSContext* cx, HandleObject enclosing, |
| unsigned errorNumber); |
| |
| unsigned errorNumber() { |
| return getReservedSlot(ERROR_SLOT).toInt32(); |
| } |
| }; |
| |
| template<XDRMode mode> |
| bool |
| XDRStaticBlockObject(XDRState<mode>* xdr, HandleObject enclosingScope, |
| MutableHandle<StaticBlockObject*> objp); |
| |
| template<XDRMode mode> |
| bool |
| XDRStaticWithObject(XDRState<mode>* xdr, HandleObject enclosingScope, |
| MutableHandle<StaticWithObject*> objp); |
| |
| extern JSObject* |
| CloneNestedScopeObject(JSContext* cx, HandleObject enclosingScope, Handle<NestedScopeObject*> src); |
| |
| /*****************************************************************************/ |
| |
| // A scope iterator describes the active scopes starting from a dynamic scope, |
| // static scope pair. This pair may be derived from the current point of |
| // execution in a frame. If derived in such a fashion, the ScopeIter 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 ScopeIter |
| { |
| StaticScopeIter<CanGC> ssi_; |
| RootedObject scope_; |
| AbstractFramePtr frame_; |
| |
| void incrementStaticScopeIter(); |
| void settle(); |
| |
| // No value semantics. |
| ScopeIter(const ScopeIter& si) = delete; |
| |
| public: |
| // Constructing from a copy of an existing ScopeIter. |
| ScopeIter(JSContext* cx, const ScopeIter& si |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM); |
| |
| // Constructing from a dynamic scope, static scope pair. All scopes are |
| // considered not to be withinInitialFrame, since no frame is given. |
| ScopeIter(JSContext* cx, JSObject* scope, JSObject* staticScope |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM); |
| |
| // Constructing from a frame. Places the ScopeIter on the innermost scope |
| // at pc. |
| ScopeIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM); |
| |
| inline bool done() const; |
| ScopeIter& operator++(); |
| |
| // If done(): |
| inline JSObject& enclosingScope() const; |
| |
| // If !done(): |
| enum Type { Module, Call, Block, With, Eval, NonSyntactic }; |
| Type type() const; |
| |
| inline bool hasNonSyntacticScopeObject() const; |
| inline bool hasSyntacticScopeObject() const; |
| inline bool hasAnyScopeObject() const; |
| inline bool canHaveSyntacticScopeObject() const; |
| ScopeObject& scope() const; |
| |
| JSObject* maybeStaticScope() const; |
| StaticBlockObject& staticBlock() const { return ssi_.block(); } |
| StaticWithObject& staticWith() const { return ssi_.staticWith(); } |
| StaticEvalObject& staticEval() const { return ssi_.eval(); } |
| StaticNonSyntacticScopeObjects& staticNonSyntactic() const { return ssi_.nonSyntactic(); } |
| JSFunction& fun() const { return ssi_.fun(); } |
| ModuleObject& module() const { return ssi_.module(); } |
| |
| bool withinInitialFrame() const { return !!frame_; } |
| AbstractFramePtr initialFrame() const { MOZ_ASSERT(withinInitialFrame()); return frame_; } |
| AbstractFramePtr maybeInitialFrame() const { return frame_; } |
| |
| MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
| }; |
| |
| // The key in MissingScopeMap. For live frames, maps live frames to their |
| // synthesized scopes. For completely optimized-out scopes, maps the static |
| // scope objects to their synthesized scopes. The scopes we synthesize for |
| // static scope objects are read-only, and we never use their parent links, so |
| // they don't need to be distinct. |
| // |
| // That is, completely optimized out scopes can't be distinguished by |
| // frame. Note that even if the frame corresponding to the static scope is |
| // live on the stack, it is unsound to synthesize a scope from that live |
| // frame. In other words, the provenance of the scope chain is from allocated |
| // closures (i.e., allocation sites) and is irrecoverable from simple stack |
| // inspection (i.e., call sites). |
| class MissingScopeKey |
| { |
| friend class LiveScopeVal; |
| |
| AbstractFramePtr frame_; |
| JSObject* staticScope_; |
| |
| public: |
| explicit MissingScopeKey(const ScopeIter& si) |
| : frame_(si.maybeInitialFrame()), |
| staticScope_(si.maybeStaticScope()) |
| { } |
| |
| AbstractFramePtr frame() const { return frame_; } |
| JSObject* staticScope() const { return staticScope_; } |
| |
| void updateStaticScope(JSObject* obj) { staticScope_ = obj; } |
| void updateFrame(AbstractFramePtr frame) { frame_ = frame; } |
| |
| // For use as hash policy. |
| typedef MissingScopeKey Lookup; |
| static HashNumber hash(MissingScopeKey sk); |
| static bool match(MissingScopeKey sk1, MissingScopeKey sk2); |
| bool operator!=(const MissingScopeKey& other) const { |
| return frame_ != other.frame_ || staticScope_ != other.staticScope_; |
| } |
| static void rekey(MissingScopeKey& k, const MissingScopeKey& newKey) { |
| k = newKey; |
| } |
| }; |
| |
| // The value in LiveScopeMap, mapped from by live scope objects. |
| class LiveScopeVal |
| { |
| friend class DebugScopes; |
| friend class MissingScopeKey; |
| |
| AbstractFramePtr frame_; |
| RelocatablePtrObject staticScope_; |
| |
| static void staticAsserts(); |
| |
| public: |
| explicit LiveScopeVal(const ScopeIter& si) |
| : frame_(si.initialFrame()), |
| staticScope_(si.maybeStaticScope()) |
| { } |
| |
| AbstractFramePtr frame() const { return frame_; } |
| JSObject* staticScope() const { return staticScope_; } |
| |
| void updateFrame(AbstractFramePtr frame) { frame_ = frame; } |
| |
| bool needsSweep(); |
| }; |
| |
| /*****************************************************************************/ |
| |
| /* |
| * Debug scope objects |
| * |
| * The debugger effectively turns every opcode into a potential direct eval. |
| * Naively, this would require creating a ScopeObject for every call/block |
| * scope and using JSOP_GETALIASEDVAR for every access. To optimize this, the |
| * engine assumes there is no debugger and optimizes scope access and creation |
| * accordingly. When the debugger wants to perform an unexpected eval-in-frame |
| * (or other, similar dynamic-scope-requiring operations), fp->scopeChain is |
| * now incomplete: it may not contain all, or any, of the ScopeObjects to |
| * represent the current scope. |
| * |
| * To resolve this, the debugger first calls GetDebugScopeFor* to synthesize a |
| * "debug scope chain". A debug scope chain is just a chain of objects that |
| * fill in missing scopes and protect the engine from unexpected access. (The |
| * latter means that some debugger operations, like redefining a lexical |
| * binding, can fail when a true eval would succeed.) To do both of these |
| * things, GetDebugScopeFor* creates a new proxy DebugScopeObject to sit in |
| * front of every existing ScopeObject. |
| * |
| * GetDebugScopeFor* ensures the invariant that the same DebugScopeObject is |
| * always produced for the same underlying scope (optimized or not!). This is |
| * maintained by some bookkeeping information stored in DebugScopes. |
| */ |
| |
| extern JSObject* |
| GetDebugScopeForFunction(JSContext* cx, HandleFunction fun); |
| |
| extern JSObject* |
| GetDebugScopeForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc); |
| |
| extern JSObject* |
| GetDebugScopeForGlobalLexicalScope(JSContext* cx); |
| |
| /* Provides debugger access to a scope. */ |
| class DebugScopeObject : public ProxyObject |
| { |
| /* |
| * The enclosing scope on the dynamic scope chain. This slot is analogous |
| * to the SCOPE_CHAIN_SLOT of a ScopeObject. |
| */ |
| static const unsigned ENCLOSING_EXTRA = 0; |
| |
| /* |
| * NullValue or a dense array holding the unaliased variables of a function |
| * frame that has been popped. |
| */ |
| static const unsigned SNAPSHOT_EXTRA = 1; |
| |
| public: |
| static DebugScopeObject* create(JSContext* cx, ScopeObject& scope, HandleObject enclosing); |
| |
| ScopeObject& scope() const; |
| JSObject& enclosingScope() const; |
| |
| /* May only be called for proxies to function call objects. */ |
| ArrayObject* maybeSnapshot() const; |
| void initSnapshot(ArrayObject& snapshot); |
| |
| /* Currently, the 'declarative' scopes are Call and Block. */ |
| bool isForDeclarative() const; |
| |
| // Get a property by 'id', but returns sentinel values instead of throwing |
| // on exceptional cases. |
| bool getMaybeSentinelValue(JSContext* cx, HandleId id, MutableHandleValue vp); |
| |
| // Returns true iff this is a function scope with its own this-binding |
| // (all functions except arrow functions and generator expression lambdas). |
| bool isFunctionScopeWithThis(); |
| |
| // Does this debug scope not have a dynamic counterpart or was never live |
| // (and thus does not have a synthesized ScopeObject or a snapshot)? |
| bool isOptimizedOut() const; |
| }; |
| |
| /* Maintains per-compartment debug scope bookkeeping information. */ |
| class DebugScopes |
| { |
| /* The map from (non-debug) scopes to debug scopes. */ |
| ObjectWeakMap proxiedScopes; |
| |
| /* |
| * The map from live frames which have optimized-away scopes to the |
| * corresponding debug scopes. |
| */ |
| typedef HashMap<MissingScopeKey, |
| ReadBarrieredDebugScopeObject, |
| MissingScopeKey, |
| RuntimeAllocPolicy> MissingScopeMap; |
| MissingScopeMap missingScopes; |
| |
| /* |
| * The map from scope objects of live frames to the live frame. This map |
| * updated lazily whenever the debugger needs the information. In between |
| * two lazy updates, liveScopes becomes incomplete (but not invalid, onPop* |
| * removes scopes as they are popped). Thus, two consecutive debugger lazy |
| * updates of liveScopes need only fill in the new scopes. |
| */ |
| typedef GCHashMap<ReadBarriered<ScopeObject*>, |
| LiveScopeVal, |
| MovableCellHasher<ReadBarriered<ScopeObject*>>, |
| RuntimeAllocPolicy> LiveScopeMap; |
| LiveScopeMap liveScopes; |
| static MOZ_ALWAYS_INLINE void liveScopesPostWriteBarrier(JSRuntime* rt, LiveScopeMap* map, |
| ScopeObject* key); |
| |
| public: |
| explicit DebugScopes(JSContext* c); |
| ~DebugScopes(); |
| |
| private: |
| bool init(); |
| |
| static DebugScopes* ensureCompartmentData(JSContext* cx); |
| |
| public: |
| void mark(JSTracer* trc); |
| void sweep(JSRuntime* rt); |
| #if defined(JSGC_HASH_TABLE_CHECKS) |
| void checkHashTablesAfterMovingGC(JSRuntime* rt); |
| #endif |
| |
| static DebugScopeObject* hasDebugScope(JSContext* cx, ScopeObject& scope); |
| static bool addDebugScope(JSContext* cx, ScopeObject& scope, DebugScopeObject& debugScope); |
| |
| static DebugScopeObject* hasDebugScope(JSContext* cx, const ScopeIter& si); |
| static bool addDebugScope(JSContext* cx, const ScopeIter& si, DebugScopeObject& debugScope); |
| |
| static bool updateLiveScopes(JSContext* cx); |
| static LiveScopeVal* hasLiveScope(ScopeObject& scope); |
| static void unsetPrevUpToDateUntil(JSContext* cx, AbstractFramePtr frame); |
| |
| // When a frame bails out from Ion to Baseline, there might be missing |
| // scopes keyed on, and live scopes containing, the old |
| // RematerializedFrame. Forward those values to the new BaselineFrame. |
| static void forwardLiveFrame(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to); |
| |
| // In debug-mode, these must be called whenever exiting a scope that might |
| // have stack-allocated locals. |
| static void onPopCall(AbstractFramePtr frame, JSContext* cx); |
| static void onPopBlock(JSContext* cx, const ScopeIter& si); |
| static void onPopBlock(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc); |
| static void onPopWith(AbstractFramePtr frame); |
| static void onPopStrictEvalScope(AbstractFramePtr frame); |
| static void onCompartmentUnsetIsDebuggee(JSCompartment* c); |
| }; |
| |
| } /* namespace js */ |
| |
| template<> |
| inline bool |
| JSObject::is<js::NestedScopeObject>() const |
| { |
| return is<js::BlockObject>() || |
| is<js::StaticWithObject>() || |
| is<js::DynamicWithObject>(); |
| } |
| |
| template<> |
| inline bool |
| JSObject::is<js::LexicalScopeBase>() const |
| { |
| return is<js::CallObject>() || |
| is<js::ModuleEnvironmentObject>(); |
| } |
| |
| template<> |
| inline bool |
| JSObject::is<js::ScopeObject>() const |
| { |
| return is<js::LexicalScopeBase>() || |
| is<js::DeclEnvObject>() || |
| is<js::NestedScopeObject>() || |
| is<js::RuntimeLexicalErrorObject>() || |
| is<js::NonSyntacticVariablesObject>(); |
| } |
| |
| template<> |
| bool |
| JSObject::is<js::DebugScopeObject>() const; |
| |
| template<> |
| inline bool |
| JSObject::is<js::ClonedBlockObject>() const |
| { |
| return is<js::BlockObject>() && !!getProto(); |
| } |
| |
| template<> |
| inline bool |
| JSObject::is<js::StaticBlockObject>() const |
| { |
| return is<js::BlockObject>() && !getProto(); |
| } |
| |
| namespace js { |
| |
| inline bool |
| IsSyntacticScope(JSObject* scope) |
| { |
| if (!scope->is<ScopeObject>()) |
| return false; |
| |
| if (scope->is<DynamicWithObject>()) |
| return scope->as<DynamicWithObject>().isSyntactic(); |
| |
| if (scope->is<ClonedBlockObject>()) |
| return scope->as<ClonedBlockObject>().isSyntactic(); |
| |
| if (scope->is<NonSyntacticVariablesObject>()) |
| return false; |
| |
| return true; |
| } |
| |
| inline bool |
| IsExtensibleLexicalScope(JSObject* scope) |
| { |
| return scope->is<ClonedBlockObject>() && scope->as<ClonedBlockObject>().isExtensible(); |
| } |
| |
| inline bool |
| IsGlobalLexicalScope(JSObject* scope) |
| { |
| return scope->is<ClonedBlockObject>() && scope->as<ClonedBlockObject>().isGlobal(); |
| } |
| |
| inline bool |
| IsStaticGlobalLexicalScope(JSObject* scope) |
| { |
| return scope->is<StaticBlockObject>() && scope->as<StaticBlockObject>().isGlobal(); |
| } |
| |
| inline const Value& |
| ScopeObject::aliasedVar(ScopeCoordinate sc) |
| { |
| MOZ_ASSERT(is<LexicalScopeBase>() || is<ClonedBlockObject>()); |
| return getSlot(sc.slot()); |
| } |
| |
| inline NestedScopeObject* |
| NestedScopeObject::enclosingNestedScope() const |
| { |
| JSObject* obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); |
| return obj && obj->is<NestedScopeObject>() ? &obj->as<NestedScopeObject>() : nullptr; |
| } |
| |
| inline bool |
| StaticEvalObject::isNonGlobal() const |
| { |
| if (isStrict()) |
| return true; |
| return !IsStaticGlobalLexicalScope(&getReservedSlot(SCOPE_CHAIN_SLOT).toObject()); |
| } |
| |
| inline bool |
| ScopeIter::done() const |
| { |
| return ssi_.done(); |
| } |
| |
| inline bool |
| ScopeIter::hasSyntacticScopeObject() const |
| { |
| return ssi_.hasSyntacticDynamicScopeObject(); |
| } |
| |
| inline bool |
| ScopeIter::hasNonSyntacticScopeObject() const |
| { |
| // The case we're worrying about here is a NonSyntactic static scope which |
| // has 0+ corresponding non-syntactic DynamicWithObject scopes, a |
| // NonSyntacticVariablesObject, or a non-syntactic ClonedBlockObject. |
| if (ssi_.type() == StaticScopeIter<CanGC>::NonSyntactic) { |
| MOZ_ASSERT_IF(scope_->is<DynamicWithObject>(), |
| !scope_->as<DynamicWithObject>().isSyntactic()); |
| return scope_->is<ScopeObject>() && !IsSyntacticScope(scope_); |
| } |
| return false; |
| } |
| |
| inline bool |
| ScopeIter::hasAnyScopeObject() const |
| { |
| return hasSyntacticScopeObject() || hasNonSyntacticScopeObject(); |
| } |
| |
| inline bool |
| ScopeIter::canHaveSyntacticScopeObject() const |
| { |
| if (ssi_.done()) |
| return false; |
| |
| switch (type()) { |
| case Module: |
| case Call: |
| case Block: |
| case With: |
| return true; |
| |
| case Eval: |
| // Only strict eval scopes can have dynamic scope objects. |
| return staticEval().isStrict(); |
| |
| case NonSyntactic: |
| return false; |
| } |
| |
| // Silence warnings. |
| return false; |
| } |
| |
| inline JSObject& |
| ScopeIter::enclosingScope() const |
| { |
| // As an engine invariant (maintained internally and asserted by Execute), |
| // ScopeObjects and non-ScopeObjects cannot be interleaved on the scope |
| // chain; every scope chain must start with zero or more ScopeObjects and |
| // terminate with one or more non-ScopeObjects (viz., GlobalObject). |
| MOZ_ASSERT(done()); |
| MOZ_ASSERT(!IsSyntacticScope(scope_)); |
| return *scope_; |
| } |
| |
| extern bool |
| CreateScopeObjectsForScopeChain(JSContext* cx, AutoObjectVector& scopeChain, |
| HandleObject dynamicTerminatingScope, |
| MutableHandleObject dynamicScopeObj); |
| |
| bool HasNonSyntacticStaticScopeChain(JSObject* staticScope); |
| uint32_t StaticScopeChainLength(JSObject* staticScope); |
| |
| ModuleEnvironmentObject* GetModuleEnvironmentForScript(JSScript* script); |
| |
| bool GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, |
| MutableHandleValue res); |
| |
| bool CheckVarNameConflict(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope, |
| HandlePropertyName name); |
| |
| bool CheckLexicalNameConflict(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope, |
| HandleObject varObj, HandlePropertyName name); |
| |
| bool CheckGlobalDeclarationConflicts(JSContext* cx, HandleScript script, |
| Handle<ClonedBlockObject*> lexicalScope, |
| HandleObject varObj); |
| |
| bool CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script, |
| HandleObject scopeChain, HandleObject varObj); |
| |
| #ifdef DEBUG |
| void DumpStaticScopeChain(JSScript* script); |
| void DumpStaticScopeChain(JSObject* staticScope); |
| bool |
| AnalyzeEntrainedVariables(JSContext* cx, HandleScript script); |
| #endif |
| |
| } // namespace js |
| |
| #endif /* vm_ScopeObject_h */ |