mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-09-26 23:54:56 +00:00
1511 lines
50 KiB
C++
1511 lines
50 KiB
C++
/* -*- 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));
|
|
}
|
|
|
|
// XXXshu Used only for phasing in block-scope function early
|
|
// XXXshu errors.
|
|
// XXXshu
|
|
// XXXshu Back out when major version >= 50. See [1].
|
|
// XXXshu
|
|
// XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
|
|
void updateDefinitionParseNode(unsigned i,
|
|
frontend::Definition* oldDef,
|
|
frontend::Definition* newDef)
|
|
{
|
|
MOZ_ASSERT(definitionParseNode(i) == oldDef);
|
|
setSlotValue(i, PrivateValue(newDef));
|
|
}
|
|
|
|
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);
|
|
#ifdef JS_GC_ZEAL
|
|
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
|
|
|
|
namespace JS {
|
|
|
|
template <>
|
|
struct DeletePolicy<js::DebugScopeObject>
|
|
{
|
|
explicit DeletePolicy(JSRuntime* rt) : rt_(rt) {}
|
|
void operator()(const js::DebugScopeObject* ptr);
|
|
|
|
private:
|
|
JSRuntime* rt_;
|
|
};
|
|
|
|
} // namespace JS
|
|
|
|
#endif /* vm_ScopeObject_h */
|