blob: 31c15725e5527044cb84f3ca9af23ca2040d8ad4 [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 "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 */