michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef vm_ScopeObject_h michael@0: #define vm_ScopeObject_h michael@0: michael@0: #include "jscntxt.h" michael@0: #include "jsobj.h" michael@0: #include "jsweakmap.h" michael@0: michael@0: #include "gc/Barrier.h" michael@0: #include "vm/ProxyObject.h" michael@0: michael@0: namespace js { michael@0: michael@0: namespace frontend { struct Definition; } michael@0: michael@0: class StaticWithObject; michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: /* michael@0: * All function scripts have an "enclosing static scope" that refers to the michael@0: * innermost enclosing let or function in the program text. This allows full michael@0: * reconstruction of the lexical scope for debugging or compiling efficient michael@0: * access to variables in enclosing scopes. The static scope is represented at michael@0: * runtime by a tree of compiler-created objects representing each scope: michael@0: * - a StaticBlockObject is created for 'let' and 'catch' scopes michael@0: * - a JSFunction+JSScript+Bindings trio is created for function scopes michael@0: * (These objects are primarily used to clone objects scopes for the michael@0: * dynamic scope chain.) michael@0: * michael@0: * There is an additional scope for named lambdas. E.g., in: michael@0: * michael@0: * (function f() { var x; function g() { } }) michael@0: * michael@0: * g's innermost enclosing scope will first be the function scope containing michael@0: * 'x', enclosed by a scope containing only the name 'f'. (This separate scope michael@0: * is necessary due to the fact that declarations in the function scope shadow michael@0: * (dynamically, in the case of 'eval') the lambda name.) michael@0: * michael@0: * There are two limitations to the current lexical nesting information: michael@0: * michael@0: * - 'with' is completely absent; this isn't a problem for the current use michael@0: * cases since 'with' causes every static scope to be on the dynamic scope michael@0: * chain (so the debugger can find everything) and inhibits all upvar michael@0: * optimization. michael@0: * michael@0: * - The "enclosing static scope" chain stops at 'eval'. For example in: michael@0: * let (x) { eval("function f() {}") } michael@0: * f does not have an enclosing static scope. This is fine for current uses michael@0: * for the same reason as 'with'. michael@0: * michael@0: * (See also AssertDynamicScopeMatchesStaticScope.) michael@0: */ michael@0: template michael@0: class StaticScopeIter michael@0: { michael@0: typename MaybeRooted::RootType obj; michael@0: bool onNamedLambda; michael@0: michael@0: public: michael@0: StaticScopeIter(ExclusiveContext *cx, JSObject *obj) michael@0: : obj(cx, obj), onNamedLambda(false) michael@0: { michael@0: JS_STATIC_ASSERT(allowGC == CanGC); michael@0: JS_ASSERT_IF(obj, obj->is() || obj->is() || michael@0: obj->is()); michael@0: } michael@0: michael@0: StaticScopeIter(JSObject *obj) michael@0: : obj((ExclusiveContext *) nullptr, obj), onNamedLambda(false) michael@0: { michael@0: JS_STATIC_ASSERT(allowGC == NoGC); michael@0: JS_ASSERT_IF(obj, obj->is() || obj->is() || michael@0: obj->is()); michael@0: } michael@0: michael@0: bool done() const; michael@0: void operator++(int); michael@0: michael@0: /* Return whether this static scope will be on the dynamic scope chain. */ michael@0: bool hasDynamicScopeObject() const; michael@0: Shape *scopeShape() const; michael@0: michael@0: enum Type { WITH, BLOCK, FUNCTION, NAMED_LAMBDA }; michael@0: Type type() const; michael@0: michael@0: StaticBlockObject &block() const; michael@0: StaticWithObject &staticWith() const; michael@0: JSScript *funScript() const; michael@0: }; michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: /* michael@0: * A "scope coordinate" describes how to get from head of the scope chain to a michael@0: * given lexically-enclosing variable. A scope coordinate has two dimensions: michael@0: * - hops: the number of scope objects on the scope chain to skip michael@0: * - slot: the slot on the scope object holding the variable's value michael@0: */ michael@0: class ScopeCoordinate michael@0: { michael@0: uint32_t hops_; michael@0: uint32_t slot_; michael@0: michael@0: /* michael@0: * Technically, hops_/slot_ are SCOPECOORD_(HOPS|SLOT)_BITS wide. Since michael@0: * ScopeCoordinate is a temporary value, don't bother with a bitfield as michael@0: * this only adds overhead. michael@0: */ michael@0: static_assert(SCOPECOORD_HOPS_BITS <= 32, "We have enough bits below"); michael@0: static_assert(SCOPECOORD_SLOT_BITS <= 32, "We have enough bits below"); michael@0: michael@0: public: michael@0: inline ScopeCoordinate(jsbytecode *pc) michael@0: : hops_(GET_SCOPECOORD_HOPS(pc)), slot_(GET_SCOPECOORD_SLOT(pc + SCOPECOORD_HOPS_LEN)) michael@0: { michael@0: JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD); michael@0: } michael@0: michael@0: inline ScopeCoordinate() {} michael@0: michael@0: void setHops(uint32_t hops) { JS_ASSERT(hops < SCOPECOORD_HOPS_LIMIT); hops_ = hops; } michael@0: void setSlot(uint32_t slot) { JS_ASSERT(slot < SCOPECOORD_SLOT_LIMIT); slot_ = slot; } michael@0: michael@0: uint32_t hops() const { JS_ASSERT(hops_ < SCOPECOORD_HOPS_LIMIT); return hops_; } michael@0: uint32_t slot() const { JS_ASSERT(slot_ < SCOPECOORD_SLOT_LIMIT); return slot_; } michael@0: }; michael@0: michael@0: /* michael@0: * Return a shape representing the static scope containing the variable michael@0: * accessed by the ALIASEDVAR op at 'pc'. michael@0: */ michael@0: extern Shape * michael@0: ScopeCoordinateToStaticScopeShape(JSScript *script, jsbytecode *pc); michael@0: michael@0: /* Return the name being accessed by the given ALIASEDVAR op. */ michael@0: extern PropertyName * michael@0: ScopeCoordinateName(ScopeCoordinateNameCache &cache, JSScript *script, jsbytecode *pc); michael@0: michael@0: /* Return the function script accessed by the given ALIASEDVAR op, or nullptr. */ michael@0: extern JSScript * michael@0: ScopeCoordinateFunctionScript(JSScript *script, jsbytecode *pc); michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: /* michael@0: * Scope objects michael@0: * michael@0: * Scope objects are technically real JSObjects but only belong on the scope michael@0: * chain (that is, fp->scopeChain() or fun->environment()). The hierarchy of michael@0: * scope objects is: michael@0: * michael@0: * JSObject Generic object michael@0: * \ michael@0: * ScopeObject Engine-internal scope michael@0: * \ \ \ michael@0: * \ \ DeclEnvObject Holds name of recursive/heavyweight named lambda michael@0: * \ \ michael@0: * \ CallObject Scope of entire function or strict eval michael@0: * \ michael@0: * NestedScopeObject Scope created for a statement michael@0: * \ \ \ michael@0: * \ \ StaticWithObject Template for "with" object in static scope chain michael@0: * \ \ michael@0: * \ DynamicWithObject Run-time "with" object on scope chain michael@0: * \ michael@0: * BlockObject Shared interface of cloned/static block objects michael@0: * \ \ michael@0: * \ ClonedBlockObject let, switch, catch, for michael@0: * \ michael@0: * StaticBlockObject See NB michael@0: * michael@0: * This hierarchy represents more than just the interface hierarchy: reserved michael@0: * slots in base classes are fixed for all derived classes. Thus, for example, michael@0: * ScopeObject::enclosingScope() can simply access a fixed slot without further michael@0: * dynamic type information. michael@0: * michael@0: * NB: Static block objects are a special case: these objects are created at michael@0: * compile time to hold the shape/binding information from which block objects michael@0: * are cloned at runtime. These objects should never escape into the wild and michael@0: * support a restricted set of ScopeObject operations. michael@0: * michael@0: * See also "Debug scope objects" below. michael@0: */ michael@0: michael@0: class ScopeObject : public JSObject michael@0: { michael@0: protected: michael@0: static const uint32_t SCOPE_CHAIN_SLOT = 0; michael@0: michael@0: public: michael@0: /* michael@0: * Since every scope chain terminates with a global object and GlobalObject michael@0: * does not derive ScopeObject (it has a completely different layout), the michael@0: * enclosing scope of a ScopeObject is necessarily non-null. michael@0: */ michael@0: inline JSObject &enclosingScope() const { michael@0: return getFixedSlot(SCOPE_CHAIN_SLOT).toObject(); michael@0: } michael@0: michael@0: void setEnclosingScope(HandleObject obj); michael@0: michael@0: /* michael@0: * Get or set an aliased variable contained in this scope. Unaliased michael@0: * variables should instead access the stack frame. Aliased variable access michael@0: * is primarily made through JOF_SCOPECOORD ops which is why these members michael@0: * take a ScopeCoordinate instead of just the slot index. michael@0: */ michael@0: inline const Value &aliasedVar(ScopeCoordinate sc); michael@0: michael@0: inline void setAliasedVar(JSContext *cx, ScopeCoordinate sc, PropertyName *name, const Value &v); michael@0: michael@0: /* For jit access. */ michael@0: static size_t offsetOfEnclosingScope() { michael@0: return getFixedSlotOffset(SCOPE_CHAIN_SLOT); michael@0: } michael@0: michael@0: static size_t enclosingScopeSlot() { michael@0: return SCOPE_CHAIN_SLOT; michael@0: } michael@0: }; michael@0: michael@0: class CallObject : public ScopeObject michael@0: { michael@0: static const uint32_t CALLEE_SLOT = 1; michael@0: michael@0: static CallObject * michael@0: create(JSContext *cx, HandleScript script, HandleObject enclosing, HandleFunction callee); michael@0: michael@0: public: michael@0: static const Class class_; michael@0: michael@0: /* These functions are internal and are exposed only for JITs. */ michael@0: michael@0: /* michael@0: * Construct a bare-bones call object given a shape, a non-singleton type, michael@0: * and slots pointer. The call object must be further initialized to be michael@0: * usable. michael@0: */ michael@0: static CallObject * michael@0: create(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots); michael@0: michael@0: /* michael@0: * Construct a bare-bones call object given a shape and slots pointer, and michael@0: * make it have singleton type. The call object must be initialized to be michael@0: * usable. michael@0: */ michael@0: static CallObject * michael@0: createSingleton(JSContext *cx, HandleShape shape, HeapSlot *slots); michael@0: michael@0: static CallObject * michael@0: createTemplateObject(JSContext *cx, HandleScript script, gc::InitialHeap heap); michael@0: michael@0: static const uint32_t RESERVED_SLOTS = 2; michael@0: michael@0: static CallObject *createForFunction(JSContext *cx, HandleObject enclosing, HandleFunction callee); michael@0: michael@0: static CallObject *createForFunction(JSContext *cx, AbstractFramePtr frame); michael@0: static CallObject *createForStrictEval(JSContext *cx, AbstractFramePtr frame); michael@0: michael@0: /* True if this is for a strict mode eval frame. */ michael@0: bool isForEval() const { michael@0: JS_ASSERT(getFixedSlot(CALLEE_SLOT).isObjectOrNull()); michael@0: JS_ASSERT_IF(getFixedSlot(CALLEE_SLOT).isObject(), michael@0: getFixedSlot(CALLEE_SLOT).toObject().is()); michael@0: return getFixedSlot(CALLEE_SLOT).isNull(); michael@0: } michael@0: michael@0: /* michael@0: * Returns the function for which this CallObject was created. (This may michael@0: * only be called if !isForEval.) michael@0: */ michael@0: JSFunction &callee() const { michael@0: return getFixedSlot(CALLEE_SLOT).toObject().as(); michael@0: } michael@0: michael@0: /* Get/set the aliased variable referred to by 'bi'. */ michael@0: const Value &aliasedVar(AliasedFormalIter fi) { michael@0: return getSlot(fi.scopeSlot()); michael@0: } michael@0: inline void setAliasedVar(JSContext *cx, AliasedFormalIter fi, PropertyName *name, michael@0: const Value &v); michael@0: michael@0: /* michael@0: * When an aliased var (var accessed by nested closures) is also aliased by michael@0: * the arguments object, it must of course exist in one canonical location michael@0: * and that location is always the CallObject. For this to work, the michael@0: * ArgumentsObject stores special MagicValue in its array for forwarded-to- michael@0: * CallObject variables. This MagicValue's payload is the slot of the michael@0: * CallObject to access. michael@0: */ michael@0: const Value &aliasedVarFromArguments(const Value &argsValue) { michael@0: return getSlot(argsValue.magicUint32()); michael@0: } michael@0: inline void setAliasedVarFromArguments(JSContext *cx, const Value &argsValue, jsid id, michael@0: const Value &v); michael@0: michael@0: /* For jit access. */ michael@0: static size_t offsetOfCallee() { michael@0: return getFixedSlotOffset(CALLEE_SLOT); michael@0: } michael@0: michael@0: static size_t calleeSlot() { michael@0: return CALLEE_SLOT; michael@0: } michael@0: }; michael@0: michael@0: class DeclEnvObject : public ScopeObject michael@0: { michael@0: // Pre-allocated slot for the named lambda. michael@0: static const uint32_t LAMBDA_SLOT = 1; michael@0: michael@0: public: michael@0: static const uint32_t RESERVED_SLOTS = 2; michael@0: static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND; michael@0: michael@0: static const Class class_; michael@0: michael@0: static DeclEnvObject * michael@0: createTemplateObject(JSContext *cx, HandleFunction fun, gc::InitialHeap heap); michael@0: michael@0: static DeclEnvObject *create(JSContext *cx, HandleObject enclosing, HandleFunction callee); michael@0: michael@0: static inline size_t lambdaSlot() { michael@0: return LAMBDA_SLOT; michael@0: } michael@0: }; michael@0: michael@0: class NestedScopeObject : public ScopeObject michael@0: { michael@0: public: michael@0: /* michael@0: * A refinement of enclosingScope that returns nullptr if the enclosing michael@0: * scope is not a NestedScopeObject. michael@0: */ michael@0: inline NestedScopeObject *enclosingNestedScope() const; michael@0: michael@0: // Return true if this object is a compile-time scope template. michael@0: inline bool isStatic() { return !getProto(); } michael@0: michael@0: // Return the static scope corresponding to this scope chain object. michael@0: inline NestedScopeObject* staticScope() { michael@0: JS_ASSERT(!isStatic()); michael@0: return &getProto()->as(); michael@0: } michael@0: michael@0: // At compile-time it's possible for the scope chain to be null. michael@0: JSObject *enclosingScopeForStaticScopeIter() { michael@0: return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); michael@0: } michael@0: michael@0: void initEnclosingNestedScope(JSObject *obj) { michael@0: JS_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined()); michael@0: setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj)); michael@0: } michael@0: michael@0: /* michael@0: * The parser uses 'enclosingNestedScope' as the prev-link in the michael@0: * pc->staticScope stack. Note: in the case of hoisting, this prev-link will michael@0: * not ultimately be the same as enclosingNestedScope; michael@0: * initEnclosingNestedScope must be called separately in the michael@0: * emitter. 'reset' is just for asserting stackiness. michael@0: */ michael@0: void initEnclosingNestedScopeFromParser(NestedScopeObject *prev) { michael@0: setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(prev)); michael@0: } michael@0: michael@0: void resetEnclosingNestedScopeFromParser() { michael@0: setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue()); michael@0: } michael@0: }; michael@0: michael@0: // With scope template objects on the static scope chain. michael@0: class StaticWithObject : public NestedScopeObject michael@0: { michael@0: public: michael@0: static const unsigned RESERVED_SLOTS = 1; michael@0: static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND; michael@0: michael@0: static const Class class_; michael@0: michael@0: static StaticWithObject *create(ExclusiveContext *cx); michael@0: }; michael@0: michael@0: // With scope objects on the run-time scope chain. michael@0: class DynamicWithObject : public NestedScopeObject michael@0: { michael@0: static const unsigned OBJECT_SLOT = 1; michael@0: static const unsigned THIS_SLOT = 2; michael@0: michael@0: public: michael@0: static const unsigned RESERVED_SLOTS = 3; michael@0: static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND; michael@0: michael@0: static const Class class_; michael@0: michael@0: static DynamicWithObject * michael@0: create(JSContext *cx, HandleObject object, HandleObject enclosing, HandleObject staticWith); michael@0: michael@0: StaticWithObject& staticWith() const { michael@0: return getProto()->as(); michael@0: } michael@0: michael@0: /* Return the 'o' in 'with (o)'. */ michael@0: JSObject &object() const { michael@0: return getReservedSlot(OBJECT_SLOT).toObject(); michael@0: } michael@0: michael@0: /* Return object for the 'this' class hook. */ michael@0: JSObject &withThis() const { michael@0: return getReservedSlot(THIS_SLOT).toObject(); michael@0: } michael@0: }; michael@0: michael@0: class BlockObject : public NestedScopeObject michael@0: { michael@0: protected: michael@0: static const unsigned DEPTH_SLOT = 1; michael@0: michael@0: public: michael@0: static const unsigned RESERVED_SLOTS = 2; michael@0: static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND; michael@0: michael@0: static const Class class_; michael@0: michael@0: /* Return the abstract stack depth right before entering this nested scope. */ michael@0: uint32_t stackDepth() const { michael@0: return getReservedSlot(DEPTH_SLOT).toPrivateUint32(); michael@0: } michael@0: michael@0: /* Return the number of variables associated with this block. */ michael@0: uint32_t numVariables() const { michael@0: // TODO: propertyCount() is O(n), use O(1) lastProperty()->slot() instead michael@0: return propertyCount(); michael@0: } michael@0: michael@0: protected: michael@0: /* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */ michael@0: const Value &slotValue(unsigned i) { michael@0: return getSlotRef(RESERVED_SLOTS + i); michael@0: } michael@0: michael@0: void setSlotValue(unsigned i, const Value &v) { michael@0: setSlot(RESERVED_SLOTS + i, v); michael@0: } michael@0: }; michael@0: michael@0: class StaticBlockObject : public BlockObject michael@0: { michael@0: static const unsigned LOCAL_OFFSET_SLOT = 1; michael@0: michael@0: public: michael@0: static StaticBlockObject *create(ExclusiveContext *cx); michael@0: michael@0: /* See StaticScopeIter comment. */ michael@0: JSObject *enclosingStaticScope() const { michael@0: return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); michael@0: } michael@0: michael@0: /* michael@0: * Return the index (in the range [0, numVariables()) corresponding to the michael@0: * given shape of a block object. michael@0: */ michael@0: uint32_t shapeToIndex(const Shape &shape) { michael@0: uint32_t slot = shape.slot(); michael@0: JS_ASSERT(slot - RESERVED_SLOTS < numVariables()); michael@0: return slot - RESERVED_SLOTS; michael@0: } michael@0: michael@0: /* michael@0: * A refinement of enclosingStaticScope that returns nullptr if the enclosing michael@0: * static scope is a JSFunction. michael@0: */ michael@0: inline StaticBlockObject *enclosingBlock() const; michael@0: michael@0: uint32_t localOffset() { michael@0: return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32(); michael@0: } michael@0: michael@0: // Return the local corresponding to the 'var'th binding where 'var' is in the michael@0: // range [0, numVariables()). michael@0: uint32_t blockIndexToLocalIndex(uint32_t index) { michael@0: JS_ASSERT(index < numVariables()); michael@0: return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32() + index; michael@0: } michael@0: michael@0: // Return the slot corresponding to local variable 'local', where 'local' is michael@0: // in the range [localOffset(), localOffset() + numVariables()). The result is michael@0: // in the range [RESERVED_SLOTS, RESERVED_SLOTS + numVariables()). michael@0: uint32_t localIndexToSlot(uint32_t local) { michael@0: JS_ASSERT(local >= localOffset()); michael@0: local -= localOffset(); michael@0: JS_ASSERT(local < numVariables()); michael@0: return RESERVED_SLOTS + local; michael@0: } michael@0: michael@0: /* michael@0: * A let binding is aliased if accessed lexically by nested functions or michael@0: * dynamically through dynamic name lookup (eval, with, function::, etc). michael@0: */ michael@0: bool isAliased(unsigned i) { michael@0: return slotValue(i).isTrue(); michael@0: } michael@0: michael@0: /* michael@0: * A static block object is cloned (when entering the block) iff some michael@0: * variable of the block isAliased. michael@0: */ michael@0: bool needsClone() { michael@0: return !getFixedSlot(RESERVED_SLOTS).isFalse(); michael@0: } michael@0: michael@0: /* Frontend-only functions ***********************************************/ michael@0: michael@0: /* Initialization functions for above fields. */ michael@0: void setAliased(unsigned i, bool aliased) { michael@0: JS_ASSERT_IF(i > 0, slotValue(i-1).isBoolean()); michael@0: setSlotValue(i, BooleanValue(aliased)); michael@0: if (aliased && !needsClone()) { michael@0: setSlotValue(0, MagicValue(JS_BLOCK_NEEDS_CLONE)); michael@0: JS_ASSERT(needsClone()); michael@0: } michael@0: } michael@0: michael@0: void setLocalOffset(uint32_t offset) { michael@0: JS_ASSERT(getReservedSlot(LOCAL_OFFSET_SLOT).isUndefined()); michael@0: initReservedSlot(LOCAL_OFFSET_SLOT, PrivateUint32Value(offset)); michael@0: } michael@0: michael@0: /* michael@0: * Frontend compilation temporarily uses the object's slots to link michael@0: * a let var to its associated Definition parse node. michael@0: */ michael@0: void setDefinitionParseNode(unsigned i, frontend::Definition *def) { michael@0: JS_ASSERT(slotValue(i).isUndefined()); michael@0: setSlotValue(i, PrivateValue(def)); michael@0: } michael@0: michael@0: frontend::Definition *definitionParseNode(unsigned i) { michael@0: Value v = slotValue(i); michael@0: return reinterpret_cast(v.toPrivate()); michael@0: } michael@0: michael@0: /* michael@0: * While ScopeCoordinate can generally reference up to 2^24 slots, block objects have an michael@0: * additional limitation that all slot indices must be storable as uint16_t short-ids in the michael@0: * associated Shape. If we could remove the block dependencies on shape->shortid, we could michael@0: * remove INDEX_LIMIT. michael@0: */ michael@0: static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16); michael@0: michael@0: static Shape *addVar(ExclusiveContext *cx, Handle block, HandleId id, michael@0: unsigned index, bool *redeclared); michael@0: }; michael@0: michael@0: class ClonedBlockObject : public BlockObject michael@0: { michael@0: public: michael@0: static ClonedBlockObject *create(JSContext *cx, Handle block, michael@0: AbstractFramePtr frame); michael@0: michael@0: /* The static block from which this block was cloned. */ michael@0: StaticBlockObject &staticBlock() const { michael@0: return getProto()->as(); michael@0: } michael@0: michael@0: /* Assuming 'put' has been called, return the value of the ith let var. */ michael@0: const Value &var(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) { michael@0: JS_ASSERT_IF(checkAliasing, staticBlock().isAliased(i)); michael@0: return slotValue(i); michael@0: } michael@0: michael@0: void setVar(unsigned i, const Value &v, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) { michael@0: JS_ASSERT_IF(checkAliasing, staticBlock().isAliased(i)); michael@0: setSlotValue(i, v); michael@0: } michael@0: michael@0: /* Copy in all the unaliased formals and locals. */ michael@0: void copyUnaliasedValues(AbstractFramePtr frame); michael@0: }; michael@0: michael@0: template michael@0: bool michael@0: XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, michael@0: StaticBlockObject **objp); michael@0: michael@0: template michael@0: bool michael@0: XDRStaticWithObject(XDRState *xdr, HandleObject enclosingScope, michael@0: StaticWithObject **objp); michael@0: michael@0: extern JSObject * michael@0: CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle src); michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: class ScopeIterKey; michael@0: class ScopeIterVal; michael@0: michael@0: /* michael@0: * A scope iterator describes the active scopes enclosing the current point of michael@0: * execution for a single frame, proceeding from inner to outer. Here, "frame" michael@0: * means a single activation of: a function, eval, or global code. By design, michael@0: * ScopeIter exposes *all* scopes, even those that have been optimized away michael@0: * (i.e., no ScopeObject was created when entering the scope and thus there is michael@0: * no ScopeObject on fp->scopeChain representing the scope). michael@0: * michael@0: * Note: ScopeIter iterates over all scopes *within* a frame which means that michael@0: * all scopes are ScopeObjects. In particular, the GlobalObject enclosing michael@0: * global code (and any random objects passed as scopes to Execute) will not michael@0: * be included. michael@0: */ michael@0: class ScopeIter michael@0: { michael@0: friend class ScopeIterKey; michael@0: friend class ScopeIterVal; michael@0: michael@0: public: michael@0: enum Type { Call, Block, With, StrictEvalScope }; michael@0: michael@0: private: michael@0: JSContext *cx; michael@0: AbstractFramePtr frame_; michael@0: RootedObject cur_; michael@0: Rooted staticScope_; michael@0: Type type_; michael@0: bool hasScopeObject_; michael@0: michael@0: void settle(); michael@0: michael@0: /* ScopeIter does not have value semantics. */ michael@0: ScopeIter(const ScopeIter &si) MOZ_DELETE; michael@0: michael@0: ScopeIter(JSContext *cx) MOZ_DELETE; michael@0: michael@0: public: michael@0: michael@0: /* Constructing from a copy of an existing ScopeIter. */ michael@0: ScopeIter(const ScopeIter &si, JSContext *cx michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM); michael@0: michael@0: /* Constructing from AbstractFramePtr places ScopeIter on the innermost scope. */ michael@0: ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM); michael@0: michael@0: /* michael@0: * Without a stack frame, the resulting ScopeIter is done() with michael@0: * enclosingScope() as given. michael@0: */ michael@0: ScopeIter(JSObject &enclosingScope, JSContext *cx michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM); michael@0: michael@0: ScopeIter(const ScopeIterVal &hashVal, JSContext *cx michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM); michael@0: michael@0: bool done() const { return !frame_; } michael@0: michael@0: /* If done(): */ michael@0: michael@0: JSObject &enclosingScope() const { JS_ASSERT(done()); return *cur_; } michael@0: michael@0: /* If !done(): */ michael@0: michael@0: ScopeIter &operator++(); michael@0: michael@0: AbstractFramePtr frame() const { JS_ASSERT(!done()); return frame_; } michael@0: Type type() const { JS_ASSERT(!done()); return type_; } michael@0: bool hasScopeObject() const { JS_ASSERT(!done()); return hasScopeObject_; } michael@0: ScopeObject &scope() const; michael@0: NestedScopeObject* staticScope() const { return staticScope_; } michael@0: michael@0: StaticBlockObject &staticBlock() const { michael@0: JS_ASSERT(type() == Block); michael@0: return staticScope_->as(); michael@0: } michael@0: michael@0: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER michael@0: }; michael@0: michael@0: class ScopeIterKey michael@0: { michael@0: friend class ScopeIterVal; michael@0: michael@0: AbstractFramePtr frame_; michael@0: JSObject *cur_; michael@0: NestedScopeObject *staticScope_; michael@0: ScopeIter::Type type_; michael@0: bool hasScopeObject_; michael@0: michael@0: public: michael@0: ScopeIterKey(const ScopeIter &si) michael@0: : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_), michael@0: hasScopeObject_(si.hasScopeObject_) {} michael@0: michael@0: AbstractFramePtr frame() const { return frame_; } michael@0: JSObject *cur() const { return cur_; } michael@0: NestedScopeObject *staticScope() const { return staticScope_; } michael@0: ScopeIter::Type type() const { return type_; } michael@0: bool hasScopeObject() const { return hasScopeObject_; } michael@0: JSObject *enclosingScope() const { return cur_; } michael@0: JSObject *&enclosingScope() { return cur_; } michael@0: michael@0: /* For use as hash policy */ michael@0: typedef ScopeIterKey Lookup; michael@0: static HashNumber hash(ScopeIterKey si); michael@0: static bool match(ScopeIterKey si1, ScopeIterKey si2); michael@0: bool operator!=(const ScopeIterKey &other) const { michael@0: return frame_ != other.frame_ || michael@0: cur_ != other.cur_ || michael@0: staticScope_ != other.staticScope_ || michael@0: type_ != other.type_; michael@0: } michael@0: static void rekey(ScopeIterKey &k, const ScopeIterKey& newKey) { michael@0: k = newKey; michael@0: } michael@0: }; michael@0: michael@0: class ScopeIterVal michael@0: { michael@0: friend class ScopeIter; michael@0: friend class DebugScopes; michael@0: michael@0: AbstractFramePtr frame_; michael@0: RelocatablePtr cur_; michael@0: RelocatablePtr staticScope_; michael@0: ScopeIter::Type type_; michael@0: bool hasScopeObject_; michael@0: michael@0: static void staticAsserts(); michael@0: michael@0: public: michael@0: ScopeIterVal(const ScopeIter &si) michael@0: : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_), michael@0: hasScopeObject_(si.hasScopeObject_) {} michael@0: michael@0: AbstractFramePtr frame() const { return frame_; } michael@0: }; michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: /* michael@0: * Debug scope objects michael@0: * michael@0: * The debugger effectively turns every opcode into a potential direct eval. michael@0: * Naively, this would require creating a ScopeObject for every call/block michael@0: * scope and using JSOP_GETALIASEDVAR for every access. To optimize this, the michael@0: * engine assumes there is no debugger and optimizes scope access and creation michael@0: * accordingly. When the debugger wants to perform an unexpected eval-in-frame michael@0: * (or other, similar dynamic-scope-requiring operations), fp->scopeChain is michael@0: * now incomplete: it may not contain all, or any, of the ScopeObjects to michael@0: * represent the current scope. michael@0: * michael@0: * To resolve this, the debugger first calls GetDebugScopeFor(Function|Frame) michael@0: * to synthesize a "debug scope chain". A debug scope chain is just a chain of michael@0: * objects that fill in missing scopes and protect the engine from unexpected michael@0: * access. (The latter means that some debugger operations, like redefining a michael@0: * lexical binding, can fail when a true eval would succeed.) To do both of michael@0: * these things, GetDebugScopeFor* creates a new proxy DebugScopeObject to sit michael@0: * in front of every existing ScopeObject. michael@0: * michael@0: * GetDebugScopeFor* ensures the invariant that the same DebugScopeObject is michael@0: * always produced for the same underlying scope (optimized or not!). This is michael@0: * maintained by some bookkeeping information stored in DebugScopes. michael@0: */ michael@0: michael@0: extern JSObject * michael@0: GetDebugScopeForFunction(JSContext *cx, HandleFunction fun); michael@0: michael@0: extern JSObject * michael@0: GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc); michael@0: michael@0: /* Provides debugger access to a scope. */ michael@0: class DebugScopeObject : public ProxyObject michael@0: { michael@0: /* michael@0: * The enclosing scope on the dynamic scope chain. This slot is analogous michael@0: * to the SCOPE_CHAIN_SLOT of a ScopeObject. michael@0: */ michael@0: static const unsigned ENCLOSING_EXTRA = 0; michael@0: michael@0: /* michael@0: * NullValue or a dense array holding the unaliased variables of a function michael@0: * frame that has been popped. michael@0: */ michael@0: static const unsigned SNAPSHOT_EXTRA = 1; michael@0: michael@0: public: michael@0: static DebugScopeObject *create(JSContext *cx, ScopeObject &scope, HandleObject enclosing); michael@0: michael@0: ScopeObject &scope() const; michael@0: JSObject &enclosingScope() const; michael@0: michael@0: /* May only be called for proxies to function call objects. */ michael@0: JSObject *maybeSnapshot() const; michael@0: void initSnapshot(JSObject &snapshot); michael@0: michael@0: /* Currently, the 'declarative' scopes are Call and Block. */ michael@0: bool isForDeclarative() const; michael@0: michael@0: // Get a property by 'id', but returns sentinel values instead of throwing michael@0: // on exceptional cases. michael@0: bool getMaybeSentinelValue(JSContext *cx, HandleId id, MutableHandleValue vp); michael@0: }; michael@0: michael@0: /* Maintains per-compartment debug scope bookkeeping information. */ michael@0: class DebugScopes michael@0: { michael@0: /* The map from (non-debug) scopes to debug scopes. */ michael@0: typedef WeakMap ObjectWeakMap; michael@0: ObjectWeakMap proxiedScopes; michael@0: static MOZ_ALWAYS_INLINE void proxiedScopesPostWriteBarrier(JSRuntime *rt, ObjectWeakMap *map, michael@0: const EncapsulatedPtrObject &key); michael@0: michael@0: /* michael@0: * The map from live frames which have optimized-away scopes to the michael@0: * corresponding debug scopes. michael@0: */ michael@0: typedef HashMap, michael@0: ScopeIterKey, michael@0: RuntimeAllocPolicy> MissingScopeMap; michael@0: MissingScopeMap missingScopes; michael@0: class MissingScopesRef; michael@0: static MOZ_ALWAYS_INLINE void missingScopesPostWriteBarrier(JSRuntime *rt, MissingScopeMap *map, michael@0: const ScopeIterKey &key); michael@0: michael@0: /* michael@0: * The map from scope objects of live frames to the live frame. This map michael@0: * updated lazily whenever the debugger needs the information. In between michael@0: * two lazy updates, liveScopes becomes incomplete (but not invalid, onPop* michael@0: * removes scopes as they are popped). Thus, two consecutive debugger lazy michael@0: * updates of liveScopes need only fill in the new scopes. michael@0: */ michael@0: typedef HashMap, michael@0: RuntimeAllocPolicy> LiveScopeMap; michael@0: LiveScopeMap liveScopes; michael@0: static MOZ_ALWAYS_INLINE void liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map, michael@0: ScopeObject *key); michael@0: michael@0: public: michael@0: DebugScopes(JSContext *c); michael@0: ~DebugScopes(); michael@0: michael@0: private: michael@0: bool init(); michael@0: michael@0: static DebugScopes *ensureCompartmentData(JSContext *cx); michael@0: michael@0: public: michael@0: void mark(JSTracer *trc); michael@0: void sweep(JSRuntime *rt); michael@0: #if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL) michael@0: void checkHashTablesAfterMovingGC(JSRuntime *rt); michael@0: #endif michael@0: michael@0: static DebugScopeObject *hasDebugScope(JSContext *cx, ScopeObject &scope); michael@0: static bool addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope); michael@0: michael@0: static DebugScopeObject *hasDebugScope(JSContext *cx, const ScopeIter &si); michael@0: static bool addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope); michael@0: michael@0: static bool updateLiveScopes(JSContext *cx); michael@0: static ScopeIterVal *hasLiveScope(ScopeObject &scope); michael@0: michael@0: // In debug-mode, these must be called whenever exiting a scope that might michael@0: // have stack-allocated locals. michael@0: static void onPopCall(AbstractFramePtr frame, JSContext *cx); michael@0: static void onPopBlock(JSContext *cx, const ScopeIter &si); michael@0: static void onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc); michael@0: static void onPopWith(AbstractFramePtr frame); michael@0: static void onPopStrictEvalScope(AbstractFramePtr frame); michael@0: static void onCompartmentLeaveDebugMode(JSCompartment *c); michael@0: }; michael@0: michael@0: } /* namespace js */ michael@0: michael@0: template<> michael@0: inline bool michael@0: JSObject::is() const michael@0: { michael@0: return is() || is() || is(); michael@0: } michael@0: michael@0: template<> michael@0: inline bool michael@0: JSObject::is() const michael@0: { michael@0: return is() || is() || is(); michael@0: } michael@0: michael@0: template<> michael@0: inline bool michael@0: JSObject::is() const michael@0: { michael@0: extern bool js_IsDebugScopeSlow(js::ProxyObject *proxy); michael@0: michael@0: // Note: don't use is() here -- it also matches subclasses! michael@0: return hasClass(&js::ProxyObject::uncallableClass_) && michael@0: js_IsDebugScopeSlow(&const_cast(this)->as()); michael@0: } michael@0: michael@0: template<> michael@0: inline bool michael@0: JSObject::is() const michael@0: { michael@0: return is() && !!getProto(); michael@0: } michael@0: michael@0: template<> michael@0: inline bool michael@0: JSObject::is() const michael@0: { michael@0: return is() && !getProto(); michael@0: } michael@0: michael@0: inline JSObject * michael@0: JSObject::enclosingScope() michael@0: { michael@0: return is() michael@0: ? &as().enclosingScope() michael@0: : is() michael@0: ? &as().enclosingScope() michael@0: : getParent(); michael@0: } michael@0: michael@0: namespace js { michael@0: michael@0: inline const Value & michael@0: ScopeObject::aliasedVar(ScopeCoordinate sc) michael@0: { michael@0: JS_ASSERT(is() || is()); michael@0: return getSlot(sc.slot()); michael@0: } michael@0: michael@0: inline NestedScopeObject * michael@0: NestedScopeObject::enclosingNestedScope() const michael@0: { michael@0: JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); michael@0: return obj && obj->is() ? &obj->as() : nullptr; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: bool michael@0: AnalyzeEntrainedVariables(JSContext *cx, HandleScript script); michael@0: #endif michael@0: michael@0: } // namespace js michael@0: michael@0: #endif /* vm_ScopeObject_h */