blob: 4acf849cda3b7c90286821aa2bd3fba1a04674de [file] [log] [blame]
/* -*- 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 "gc/Barrier.h"
namespace js {
/*****************************************************************************/
/*
* All function scripts have an "enclosing static scope" that refers to the
* innermost enclosing let or function in the program text. This allows full
* reconstruction of the lexical scope for debugging or compiling efficient
* access to variables in enclosing scopes. The static scope is represented at
* runtime by a tree of compiler-created objects representing each scope:
* - a StaticBlockObject is created for 'let' and 'catch' scopes
* - a JSFunction+JSScript+Bindings trio is created for function scopes
* (These objects are primarily used to clone objects scopes for the
* dynamic scope chain.)
*
* There is an additional scope for named lambdas. E.g., in:
*
* (function f() { var x; function g() { } })
*
* g's innermost enclosing scope will first be the function scope containing
* 'x', enclosed by a scope containing only the name 'f'. (This separate scope
* is necessary due to the fact that declarations in the function scope shadow
* (dynamically, in the case of 'eval') the lambda name.)
*
* There are two limitations to the current lexical nesting information:
*
* - 'with' is completely absent; this isn't a problem for the current use
* cases since 'with' causes every static scope to be on the dynamic scope
* chain (so the debugger can find everything) and inhibits all upvar
* optimization.
*
* - The "enclosing static scope" chain stops at 'eval'. For example in:
* let (x) { eval("function f() {}") }
* f does not have an enclosing static scope. This is fine for current uses
* for the same reason as 'with'.
*
* (See also AssertDynamicScopeMatchesStaticScope.)
*/
class StaticScopeIter
{
RootedObject obj;
bool onNamedLambda;
public:
explicit StaticScopeIter(JSContext *cx, JSObject *obj);
bool done() const;
void operator++(int);
/* Return whether this static scope will be on the dynamic scope chain. */
bool hasDynamicScopeObject() const;
Shape *scopeShape() const;
enum Type { BLOCK, FUNCTION, NAMED_LAMBDA };
Type type() const;
StaticBlockObject &block() const;
JSScript *funScript() 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
* Additionally (as described in jsopcode.tbl) there is a 'block' index, but
* this is only needed for decompilation/inference so it is not included in the
* main ScopeCoordinate struct: use ScopeCoordinate{BlockChain,Name} instead.
*/
struct ScopeCoordinate
{
uint16_t hops;
uint16_t slot;
inline ScopeCoordinate(jsbytecode *pc);
inline ScopeCoordinate() {}
};
/*
* Return a shape representing the static scope containing the variable
* accessed by the ALIASEDVAR op at 'pc'.
*/
extern Shape *
ScopeCoordinateToStaticScopeShape(JSContext *cx, JSScript *script, jsbytecode *pc);
/* Return the name being accessed by the given ALIASEDVAR op. */
extern PropertyName *
ScopeCoordinateName(JSContext *cx, JSScript *script, jsbytecode *pc);
/* Return the function script accessed by the given ALIASEDVAR op, or NULL. */
extern JSScript *
ScopeCoordinateFunctionScript(JSContext *cx, 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
* \ \ \
* \ \ DeclEnvObject Holds name of recursive/heavyweight named lambda
* \ \
* \ CallObject Scope of entire function or strict eval
* \
* NestedScopeObject Scope created for a statement
* \ \
* \ WithObject with
* \
* 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 JSObject
{
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 getReservedSlot(SCOPE_CHAIN_SLOT).toObject();
}
inline void setEnclosingScope(HandleObject obj);
/*
* Get or set an aliased variable contained in this scope. Unaliased
* variables should instead access the StackFrame. 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 inline size_t offsetOfEnclosingScope();
static inline size_t enclosingScopeSlot() {
return SCOPE_CHAIN_SLOT;
}
};
class CallObject : public ScopeObject
{
static const uint32_t CALLEE_SLOT = 1;
static CallObject *
create(JSContext *cx, HandleScript script, HandleObject enclosing, HandleFunction callee);
public:
static Class class_;
/* These functions are internal and are exposed only for JITs. */
static CallObject *
create(JSContext *cx, HandleScript script, HandleShape shape, HandleTypeObject type, HeapSlot *slots);
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);
/* True if this is for a strict mode eval frame. */
inline bool isForEval() const;
/*
* Returns the function for which this CallObject was created. (This may
* only be called if !isForEval.)
*/
inline JSFunction &callee() const;
/* Get/set the aliased variable referred to by 'bi'. */
inline const Value &aliasedVar(AliasedFormalIter fi);
inline void setAliasedVar(JSContext *cx, AliasedFormalIter fi, PropertyName *name, const Value &v);
/* For jit access. */
static inline size_t offsetOfCallee();
static inline size_t calleeSlot() {
return CALLEE_SLOT;
}
};
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 gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2;
static Class class_;
static DeclEnvObject *
createTemplateObject(JSContext *cx, HandleFunction fun, gc::InitialHeap heap);
static DeclEnvObject *create(JSContext *cx, HandleObject enclosing, HandleFunction callee);
static inline size_t lambdaSlot() {
return LAMBDA_SLOT;
}
};
class NestedScopeObject : public ScopeObject
{
protected:
static const unsigned DEPTH_SLOT = 1;
public:
/* Return the abstract stack depth right before entering this nested scope. */
uint32_t stackDepth() const;
};
class WithObject : public NestedScopeObject
{
static const unsigned THIS_SLOT = 2;
/* Use WithObject::object() instead. */
JSObject *getProto() const;
public:
static const unsigned RESERVED_SLOTS = 3;
static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND;
static Class class_;
static WithObject *
create(JSContext *cx, HandleObject proto, HandleObject enclosing, uint32_t depth);
/* Return object for the 'this' class hook. */
JSObject &withThis() const;
/* Return the 'o' in 'with (o)'. */
JSObject &object() const;
};
class BlockObject : public NestedScopeObject
{
public:
static const unsigned RESERVED_SLOTS = 2;
static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND;
static Class class_;
/* Return the number of variables associated with this block. */
inline uint32_t slotCount() const;
/*
* Return the local corresponding to the ith binding where i is in the
* range [0, slotCount()) and the return local index is in the range
* [script->nfixed, script->nfixed + script->nslots).
*/
unsigned slotToLocalIndex(const Bindings &bindings, unsigned slot);
unsigned localIndexToSlot(const Bindings &bindings, uint32_t i);
protected:
/* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */
inline const Value &slotValue(unsigned i);
inline void setSlotValue(unsigned i, const Value &v);
};
class StaticBlockObject : public BlockObject
{
public:
static StaticBlockObject *create(JSContext *cx);
/* See StaticScopeIter comment. */
inline JSObject *enclosingStaticScope() const;
/*
* A refinement of enclosingStaticScope that returns NULL if the enclosing
* static scope is a JSFunction.
*/
inline StaticBlockObject *enclosingBlock() const;
/*
* Return whether this StaticBlockObject contains a variable stored at
* the given stack depth (i.e., fp->base()[depth]).
*/
bool containsVarAtDepth(uint32_t depth);
/*
* 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);
/*
* A static block object is cloned (when entering the block) iff some
* variable of the block isAliased.
*/
bool needsClone();
/* Frontend-only functions ***********************************************/
/* Initialization functions for above fields. */
void setAliased(unsigned i, bool aliased);
void setStackDepth(uint32_t depth);
void initEnclosingStaticScope(JSObject *obj);
/*
* 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);
frontend::Definition *maybeDefinitionParseNode(unsigned i);
/*
* The parser uses 'enclosingBlock' as the prev-link in the pc->blockChain
* stack. Note: in the case of hoisting, this prev-link will not ultimately
* be the same as enclosingBlock, initEnclosingStaticScope must be called
* separately in the emitter. 'reset' is just for asserting stackiness.
*/
void initPrevBlockChainFromParser(StaticBlockObject *prev);
void resetPrevBlockChainFromParser();
static Shape *addVar(JSContext *cx, Handle<StaticBlockObject*> block, HandleId id,
int index, bool *redeclared);
};
class ClonedBlockObject : public BlockObject
{
public:
static ClonedBlockObject *create(JSContext *cx, Handle<StaticBlockObject *> block,
AbstractFramePtr frame);
/* The static block from which this block was cloned. */
StaticBlockObject &staticBlock() const;
/* Assuming 'put' has been called, return the value of the ith let var. */
const Value &var(unsigned i, MaybeCheckAliasing = CHECK_ALIASING);
void setVar(unsigned i, const Value &v, MaybeCheckAliasing = CHECK_ALIASING);
/* Copy in all the unaliased formals and locals. */
void copyUnaliasedValues(AbstractFramePtr frame);
};
template<XDRMode mode>
bool
XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript script,
StaticBlockObject **objp);
extern JSObject *
CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> src);
/*****************************************************************************/
class ScopeIterKey;
/*
* A scope iterator describes the active scopes enclosing the current point of
* execution for a single frame, proceeding from inner to outer. Here, "frame"
* means a single activation of: a function, eval, or global code. By design,
* ScopeIter exposes *all* scopes, even those that have been optimized away
* (i.e., no ScopeObject was created when entering the scope and thus there is
* no ScopeObject on fp->scopeChain representing the scope).
*
* Note: ScopeIter iterates over all scopes *within* a frame which means that
* all scopes are ScopeObjects. In particular, the GlobalObject enclosing
* global code (and any random objects passed as scopes to Execute) will not
* be included.
*/
class ScopeIter
{
friend class ScopeIterKey;
public:
enum Type { Call, Block, With, StrictEvalScope };
private:
JSContext *cx;
AbstractFramePtr frame_;
RootedObject cur_;
Rooted<StaticBlockObject *> block_;
Type type_;
bool hasScopeObject_;
void settle();
/* ScopeIter does not have value semantics. */
ScopeIter(const ScopeIter &si) MOZ_DELETE;
public:
/* The default constructor leaves ScopeIter totally invalid */
explicit ScopeIter(JSContext *cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
/* Constructing from a copy of an existing ScopeIter. */
explicit ScopeIter(const ScopeIter &si, JSContext *cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
/* Constructing from StackFrame places ScopeIter on the innermost scope. */
explicit ScopeIter(AbstractFramePtr frame, JSContext *cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
/*
* Without a StackFrame, the resulting ScopeIter is done() with
* enclosingScope() as given.
*/
explicit ScopeIter(JSObject &enclosingScope, JSContext *cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
/*
* For the special case of generators, copy the given ScopeIter, with 'fp'
* as the StackFrame instead of si.fp(). Not for general use.
*/
ScopeIter(const ScopeIter &si, AbstractFramePtr frame, JSContext *cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
/* Like ScopeIter(StackFrame *) except start at 'scope'. */
ScopeIter(AbstractFramePtr frame, ScopeObject &scope, JSContext *cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
bool done() const { return !frame_; }
/* If done(): */
JSObject &enclosingScope() const { JS_ASSERT(done()); return *cur_; }
/* If !done(): */
ScopeIter &operator++();
AbstractFramePtr frame() const { JS_ASSERT(!done()); return frame_; }
Type type() const { JS_ASSERT(!done()); return type_; }
bool hasScopeObject() const { JS_ASSERT(!done()); return hasScopeObject_; }
ScopeObject &scope() const;
StaticBlockObject &staticBlock() const { JS_ASSERT(type() == Block); return *block_; }
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
class ScopeIterKey
{
AbstractFramePtr frame_;
JSObject *cur_;
StaticBlockObject *block_;
ScopeIter::Type type_;
public:
ScopeIterKey() : frame_(NullFramePtr()), cur_(NULL), block_(NULL), type_() {}
ScopeIterKey(const ScopeIter &si)
: frame_(si.frame_), cur_(si.cur_), block_(si.block_), type_(si.type_)
{}
AbstractFramePtr frame() const { return frame_; }
ScopeIter::Type type() const { return type_; }
/* For use as hash policy */
typedef ScopeIterKey Lookup;
static HashNumber hash(ScopeIterKey si);
static bool match(ScopeIterKey si1, ScopeIterKey si2);
};
/*****************************************************************************/
/*
* 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(Function|Frame)
* 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);
/* Provides debugger access to a scope. */
class DebugScopeObject : public JSObject
{
/*
* 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. */
JSObject *maybeSnapshot() const;
void initSnapshot(JSObject &snapshot);
/* Currently, the 'declarative' scopes are Call and Block. */
bool isForDeclarative() const;
};
/* Maintains per-compartment debug scope bookkeeping information. */
class DebugScopes
{
/* The map from (non-debug) scopes to debug scopes. */
typedef WeakMap<EncapsulatedPtrObject, RelocatablePtrObject> ObjectWeakMap;
ObjectWeakMap proxiedScopes;
/*
* The map from live frames which have optimized-away scopes to the
* corresponding debug scopes.
*/
typedef HashMap<ScopeIterKey,
ReadBarriered<DebugScopeObject>,
ScopeIterKey,
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 HashMap<ScopeObject *,
AbstractFramePtr,
DefaultHasher<ScopeObject *>,
RuntimeAllocPolicy> LiveScopeMap;
LiveScopeMap liveScopes;
public:
DebugScopes(JSContext *c);
~DebugScopes();
private:
bool init();
static DebugScopes *ensureCompartmentData(JSContext *cx);
public:
void mark(JSTracer *trc);
void sweep(JSRuntime *rt);
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 AbstractFramePtr hasLiveFrame(ScopeObject &scope);
/*
* In debug-mode, these must be called whenever exiting a call/block or
* when activating/yielding a generator.
*/
static void onPopCall(AbstractFramePtr frame, JSContext *cx);
static void onPopBlock(JSContext *cx, AbstractFramePtr frame);
static void onPopWith(AbstractFramePtr frame);
static void onPopStrictEvalScope(AbstractFramePtr frame);
static void onGeneratorFrameChange(AbstractFramePtr from, AbstractFramePtr to, JSContext *cx);
static void onCompartmentLeaveDebugMode(JSCompartment *c);
};
} /* namespace js */
template<>
inline bool
JSObject::is<js::NestedScopeObject>() const
{
return is<js::BlockObject>() || is<js::WithObject>();
}
template<>
inline bool
JSObject::is<js::ScopeObject>() const
{
return is<js::CallObject>() || is<js::DeclEnvObject>() || is<js::NestedScopeObject>();
}
template<>
inline bool
JSObject::is<js::DebugScopeObject>() const
{
extern bool js_IsDebugScopeSlow(JSObject *obj);
return getClass() == &js::ObjectProxyClass && js_IsDebugScopeSlow(const_cast<JSObject*>(this));
}
#endif /* vm_ScopeObject_h */