Bug 1165486 - Add StaticNonSyntacticScopeObjects and teach scope iterators about it. (r=luke)

This commit is contained in:
Shu-yu Guo 2015-06-15 17:38:01 -07:00
parent c27c964ec1
commit 3c634f08a1
6 changed files with 204 additions and 66 deletions

View File

@ -1548,14 +1548,14 @@ BytecodeEmitter::tryConvertFreeName(ParseNode* pn)
// Use generic ops if a catch block is encountered.
return false;
}
if (ssi.hasDynamicScopeObject())
if (ssi.hasSyntacticDynamicScopeObject())
hops++;
continue;
}
RootedScript script(cx, ssi.funScript());
if (script->functionNonDelazifying()->atom() == pn->pn_atom)
return false;
if (ssi.hasDynamicScopeObject()) {
if (ssi.hasSyntacticDynamicScopeObject()) {
uint32_t slot;
if (lookupAliasedName(script, pn->pn_atom->asPropertyName(), &slot, pn)) {
JSOp op;

View File

@ -1177,6 +1177,7 @@ PopScope(JSContext* cx, ScopeIter& si)
break;
case ScopeIter::Call:
case ScopeIter::Eval:
case ScopeIter::NonSyntactic:
break;
}
}
@ -3934,7 +3935,7 @@ CASE(JSOP_SUPERBASE)
{
ScopeIter si(cx, REGS.fp()->scopeChain(), REGS.fp()->script()->innermostStaticScope(REGS.pc));
for (; !si.done(); ++si) {
if (si.hasScopeObject() && si.type() == ScopeIter::Call) {
if (si.hasSyntacticScopeObject() && si.type() == ScopeIter::Call) {
JSFunction& callee = si.scope().as<CallObject>().callee();
// Arrow functions don't have the information we're looking for,

View File

@ -84,6 +84,8 @@ StaticScopeIter<allowGC>::operator++(int)
obj = obj->template as<NestedScopeObject>().enclosingScopeForStaticScopeIter();
} else if (obj->template is<StaticEvalObject>()) {
obj = obj->template as<StaticEvalObject>().enclosingScopeForStaticScopeIter();
} else if (obj->template is<StaticNonSyntacticScopeObjects>()) {
obj = obj->template as<StaticNonSyntacticScopeObjects>().enclosingScopeForStaticScopeIter();
} else if (onNamedLambda || !obj->template as<JSFunction>().isNamedLambda()) {
onNamedLambda = false;
obj = obj->template as<JSFunction>().nonLazyScript()->enclosingStaticScope();
@ -92,27 +94,32 @@ StaticScopeIter<allowGC>::operator++(int)
}
MOZ_ASSERT_IF(obj, obj->template is<NestedScopeObject>() ||
obj->template is<StaticEvalObject>() ||
obj->template is<StaticNonSyntacticScopeObjects>() ||
obj->template is<JSFunction>());
MOZ_ASSERT_IF(onNamedLambda, obj->template is<JSFunction>());
}
template <AllowGC allowGC>
inline bool
StaticScopeIter<allowGC>::hasDynamicScopeObject() const
StaticScopeIter<allowGC>::hasSyntacticDynamicScopeObject() const
{
return obj->template is<StaticBlockObject>()
? obj->template as<StaticBlockObject>().needsClone()
: (obj->template is<StaticEvalObject>()
? obj->template as<StaticEvalObject>().isStrict()
: (obj->template is<StaticWithObject>() ||
obj->template as<JSFunction>().isHeavyweight()));
if (obj->template is<JSFunction>())
return obj->template as<JSFunction>().isHeavyweight();
if (obj->template is<StaticBlockObject>())
return obj->template as<StaticBlockObject>().needsClone();
if (obj->template is<StaticWithObject>())
return true;
if (obj->template is<StaticEvalObject>())
return obj->template as<StaticEvalObject>().isStrict();
MOZ_ASSERT(obj->template is<StaticNonSyntacticScopeObjects>());
return false;
}
template <AllowGC allowGC>
inline Shape*
StaticScopeIter<allowGC>::scopeShape() const
{
MOZ_ASSERT(hasDynamicScopeObject());
MOZ_ASSERT(hasSyntacticDynamicScopeObject());
MOZ_ASSERT(type() != NamedLambda && type() != Eval);
if (type() == Block)
return block().lastProperty();
@ -131,6 +138,8 @@ StaticScopeIter<allowGC>::type() const
? With
: (obj->template is<StaticEvalObject>()
? Eval
: (obj->template is<StaticNonSyntacticScopeObjects>())
? NonSyntactic
: Function));
}
@ -158,6 +167,14 @@ StaticScopeIter<allowGC>::eval() const
return obj->template as<StaticEvalObject>();
}
template <AllowGC allowGC>
inline StaticNonSyntacticScopeObjects&
StaticScopeIter<allowGC>::nonSyntactic() const
{
MOZ_ASSERT(type() == NonSyntactic);
return obj->template as<StaticNonSyntacticScopeObjects>();
}
template <AllowGC allowGC>
inline JSScript*
StaticScopeIter<allowGC>::funScript() const

View File

@ -43,7 +43,7 @@ js::ScopeCoordinateToStaticScopeShape(JSScript* script, jsbytecode* pc)
uint32_t hops = ScopeCoordinate(pc).hops();
while (true) {
MOZ_ASSERT(!ssi.done());
if (ssi.hasDynamicScopeObject()) {
if (ssi.hasSyntacticDynamicScopeObject()) {
if (!hops)
break;
hops--;
@ -107,7 +107,7 @@ js::ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc)
StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc));
uint32_t hops = ScopeCoordinate(pc).hops();
while (true) {
if (ssi.hasDynamicScopeObject()) {
if (ssi.hasSyntacticDynamicScopeObject()) {
if (!hops)
break;
hops--;
@ -212,7 +212,7 @@ CallObject::create(JSContext* cx, HandleScript script, HandleObject enclosing, H
if (!callobj)
return nullptr;
callobj->as<ScopeObject>().setEnclosingScope(enclosing);
callobj->setEnclosingScope(enclosing);
callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee));
if (script->treatAsRunOnce()) {
@ -420,7 +420,7 @@ DynamicWithObject::create(JSContext* cx, HandleObject object, HandleObject enclo
if (!thisp)
return nullptr;
obj->as<ScopeObject>().setEnclosingScope(enclosing);
obj->setEnclosingScope(enclosing);
obj->setFixedSlot(OBJECT_SLOT, ObjectValue(*object));
obj->setFixedSlot(THIS_SLOT, ObjectValue(*thisp));
obj->setFixedSlot(KIND_SLOT, Int32Value(kind));
@ -551,6 +551,25 @@ const Class StaticEvalObject::class_ = {
JSCLASS_IS_ANONYMOUS
};
/* static */ StaticNonSyntacticScopeObjects*
StaticNonSyntacticScopeObjects::create(JSContext*cx, HandleObject enclosing)
{
StaticNonSyntacticScopeObjects* obj =
NewObjectWithNullTaggedProto<StaticNonSyntacticScopeObjects>(cx, TenuredObject,
BaseShape::DELEGATE);
if (!obj)
return nullptr;
obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(enclosing));
return obj;
}
const Class StaticNonSyntacticScopeObjects::class_ = {
"StaticNonSyntacticScopeObjects",
JSCLASS_HAS_RESERVED_SLOTS(StaticNonSyntacticScopeObjects::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS
};
/*****************************************************************************/
/* static */ ClonedBlockObject*
@ -845,7 +864,7 @@ UninitializedLexicalObject::create(JSContext* cx, HandleObject enclosing)
BaseShape::DELEGATE);
if (!obj)
return nullptr;
obj->as<ScopeObject>().setEnclosingScope(enclosing);
obj->setEnclosingScope(enclosing);
return obj;
}
@ -983,7 +1002,14 @@ ScopeIter::ScopeIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc
void
ScopeIter::incrementStaticScopeIter()
{
// If settled on a non-syntactic static scope, only increment ssi_ once
// we've iterated through all the non-syntactic dynamic ScopeObjects.
if (ssi_.type() == StaticScopeIter<CanGC>::NonSyntactic) {
if (!hasNonSyntacticScopeObject())
ssi_++;
} else {
ssi_++;
}
// For named lambdas, DeclEnvObject scopes are always attached to their
// CallObjects. Skip it here, as they are special cased in users of
@ -1010,7 +1036,7 @@ ScopeIter::settle()
frame_ = NullFramePtr();
#ifdef DEBUG
if (!ssi_.done() && hasScopeObject()) {
if (!ssi_.done() && hasAnyScopeObject()) {
switch (ssi_.type()) {
case StaticScopeIter<CanGC>::Function:
MOZ_ASSERT(scope_->as<CallObject>().callee().nonLazyScript() == ssi_.funScript());
@ -1024,6 +1050,9 @@ ScopeIter::settle()
case StaticScopeIter<CanGC>::Eval:
MOZ_ASSERT(scope_->as<CallObject>().isForEval());
break;
case StaticScopeIter<CanGC>::NonSyntactic:
MOZ_ASSERT(!IsSyntacticScope(scope_));
break;
case StaticScopeIter<CanGC>::NamedLambda:
MOZ_CRASH("named lambda static scopes should have been skipped");
}
@ -1034,7 +1063,7 @@ ScopeIter::settle()
ScopeIter&
ScopeIter::operator++()
{
if (hasScopeObject()) {
if (hasAnyScopeObject()) {
scope_ = &scope_->as<ScopeObject>().enclosingScope();
if (scope_->is<DeclEnvObject>())
scope_ = &scope_->as<DeclEnvObject>().enclosingScope();
@ -1060,6 +1089,8 @@ ScopeIter::type() const
return With;
case StaticScopeIter<CanGC>::Eval:
return Eval;
case StaticScopeIter<CanGC>::NonSyntactic:
return NonSyntactic;
case StaticScopeIter<CanGC>::NamedLambda:
MOZ_CRASH("named lambda static scopes should have been skipped");
default:
@ -1070,7 +1101,7 @@ ScopeIter::type() const
ScopeObject&
ScopeIter::scope() const
{
MOZ_ASSERT(hasScopeObject());
MOZ_ASSERT(hasAnyScopeObject());
return scope_->as<ScopeObject>();
}
@ -1089,6 +1120,8 @@ ScopeIter::maybeStaticScope() const
return &staticWith();
case StaticScopeIter<CanGC>::Eval:
return &staticEval();
case StaticScopeIter<CanGC>::NonSyntactic:
return &staticNonSyntactic();
case StaticScopeIter<CanGC>::NamedLambda:
MOZ_CRASH("named lambda static scopes should have been skipped");
default:
@ -1688,7 +1721,7 @@ const DebugScopeProxy DebugScopeProxy::singleton;
DebugScopeObject::create(JSContext* cx, ScopeObject& scope, HandleObject enclosing)
{
MOZ_ASSERT(scope.compartment() == cx->compartment());
MOZ_ASSERT(!IsSyntacticScope(enclosing));
MOZ_ASSERT(!enclosing->is<ScopeObject>());
RootedValue priv(cx, ObjectValue(scope));
JSObject* obj = NewProxyObject(cx, &DebugScopeProxy::singleton, priv,
@ -1950,7 +1983,7 @@ DebugScopes::addDebugScope(JSContext* cx, ScopeObject& scope, DebugScopeObject&
DebugScopeObject*
DebugScopes::hasDebugScope(JSContext* cx, const ScopeIter& si)
{
MOZ_ASSERT(!si.hasScopeObject());
MOZ_ASSERT(!si.hasSyntacticScopeObject());
DebugScopes* scopes = cx->compartment()->debugScopes;
if (!scopes)
@ -1966,7 +1999,7 @@ DebugScopes::hasDebugScope(JSContext* cx, const ScopeIter& si)
bool
DebugScopes::addDebugScope(JSContext* cx, const ScopeIter& si, DebugScopeObject& debugScope)
{
MOZ_ASSERT(!si.hasScopeObject());
MOZ_ASSERT(!si.hasSyntacticScopeObject());
MOZ_ASSERT(cx->compartment() == debugScope.compartment());
MOZ_ASSERT_IF(si.withinInitialFrame() && si.initialFrame().isFunctionFrame(),
!si.initialFrame().callee()->isGenerator());
@ -2184,7 +2217,7 @@ DebugScopes::updateLiveScopes(JSContext* cx)
continue;
for (ScopeIter si(cx, frame, i.pc()); si.withinInitialFrame(); ++si) {
if (si.hasScopeObject()) {
if (si.hasSyntacticScopeObject()) {
MOZ_ASSERT(si.scope().compartment() == cx->compartment());
DebugScopes* scopes = ensureCompartmentData(cx);
if (!scopes)
@ -2302,7 +2335,7 @@ GetDebugScopeForScope(JSContext* cx, const ScopeIter& si)
static DebugScopeObject*
GetDebugScopeForMissing(JSContext* cx, const ScopeIter& si)
{
MOZ_ASSERT(!si.hasScopeObject() && si.canHaveScopeObject());
MOZ_ASSERT(!si.hasSyntacticScopeObject() && si.canHaveSyntacticScopeObject());
if (DebugScopeObject* debugScope = DebugScopes::hasDebugScope(cx, si))
return debugScope;
@ -2369,6 +2402,8 @@ GetDebugScopeForMissing(JSContext* cx, const ScopeIter& si)
case ScopeIter::With:
case ScopeIter::Eval:
MOZ_CRASH("should already have a scope");
case ScopeIter::NonSyntactic:
MOZ_CRASH("non-syntactic scopes cannot be synthesized");
}
if (!debugScope)
return nullptr;
@ -2383,11 +2418,11 @@ static JSObject*
GetDebugScopeForNonScopeObject(const ScopeIter& si)
{
JSObject& enclosing = si.enclosingScope();
MOZ_ASSERT(!IsSyntacticScope(&enclosing));
MOZ_ASSERT(!enclosing.is<ScopeObject>());
#ifdef DEBUG
JSObject* o = &enclosing;
while ((o = o->enclosingScope()))
MOZ_ASSERT(!IsSyntacticScope(o));
MOZ_ASSERT(!o->is<ScopeObject>());
#endif
return &enclosing;
}
@ -2400,10 +2435,10 @@ GetDebugScope(JSContext* cx, const ScopeIter& si)
if (si.done())
return GetDebugScopeForNonScopeObject(si);
if (si.hasScopeObject())
if (si.hasAnyScopeObject())
return GetDebugScopeForScope(cx, si);
if (si.canHaveScopeObject())
if (si.canHaveSyntacticScopeObject())
return GetDebugScopeForMissing(cx, si);
ScopeIter copy(cx, si);

View File

@ -22,6 +22,7 @@ namespace frontend { struct Definition; }
class StaticWithObject;
class StaticEvalObject;
class StaticNonSyntacticScopeObjects;
/*****************************************************************************/
@ -62,6 +63,7 @@ class StaticScopeIter
obj->is<StaticBlockObject>() ||
obj->is<StaticWithObject>() ||
obj->is<StaticEvalObject>() ||
obj->is<StaticNonSyntacticScopeObjects>() ||
obj->is<JSFunction>());
}
@ -81,6 +83,7 @@ class StaticScopeIter
obj->is<StaticBlockObject>() ||
obj->is<StaticWithObject>() ||
obj->is<StaticEvalObject>() ||
obj->is<StaticNonSyntacticScopeObjects>() ||
obj->is<JSFunction>());
}
@ -95,16 +98,19 @@ class StaticScopeIter
bool done() const;
void operator++(int);
/* Return whether this static scope will be on the dynamic scope chain. */
bool hasDynamicScopeObject() const;
// 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 { Function, Block, With, NamedLambda, Eval };
enum Type { 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;
};
@ -175,25 +181,27 @@ ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc);
* scope objects is:
*
* JSObject Generic object
* \
* ScopeObject Engine-internal scope
* \ \ \ \
* \ \ \ StaticEvalObject Placeholder so eval scopes may be iterated through
* \ \ \
* \ \ DeclEnvObject Holds name of recursive/heavyweight named lambda
* \ \
* \ CallObject Scope of entire function or strict eval
* \
* |
* ScopeObject---+---+ Engine-internal scope
* | | | | |
* | | | | StaticNonSyntacticScopeObjects See NB2
* | | | |
* | | | StaticEvalObject Placeholder so eval scopes may be iterated through
* | | |
* | | DeclEnvObject Holds name of recursive/heavyweight named lambda
* | |
* | CallObject Scope of entire function or strict eval
* |
* NestedScopeObject Scope created for a statement
* \ \ \
* \ \ StaticWithObject Template for "with" object in static scope chain
* \ \
* \ DynamicWithObject Run-time "with" object on scope chain
* \
* | | |
* | | 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
* \
* | |
* | ClonedBlockObject let, switch, catch, for
* |
* StaticBlockObject See NB
*
* This hierarchy represents more than just the interface hierarchy: reserved
@ -206,6 +214,9 @@ ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc);
* are cloned at runtime. These objects should never escape into the wild and
* support a restricted set of ScopeObject operations.
*
* NB2: StaticNonSyntacticScopeObjects notify either of 0+ non-syntactic
* DynamicWithObjects on the dynamic scope chain or a NonSyntacticScopeObject.
*
* See also "Debug scope objects" below.
*/
@ -352,8 +363,9 @@ class DeclEnvObject : public ScopeObject
}
};
// Static eval scope template objects on the static scope. Created at the
// time of compiling the eval script, and set as its static enclosing scope.
// 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;
@ -383,6 +395,34 @@ class StaticEvalObject : public ScopeObject
}
};
// Static scope objects that stand in for one or more "polluting global"
// scopes on the dynamic scope chain.
//
// There are two flavors of polluting global scopes on the dynamic scope
// chain:
//
// 1. 0+ non-syntactic DynamicWithObjects. This static scope helps ScopeIter
// iterate these DynamicWithObjects.
//
// 2. 1 PlainObject that is a both a QualifiedVarObj and an UnqualifiedVarObj,
// created exclusively in js::ExecuteInGlobalAndReturnScope.
//
// Since this PlainObject is not a ScopeObject, ScopeIter cannot iterate
// through it. Instead, this PlainObject always comes after the syntactic
// portion of the dynamic scope chain in front of a GlobalObject.
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();
}
};
class NestedScopeObject : public ScopeObject
{
public:
@ -747,17 +787,20 @@ class ScopeIter
inline JSObject& enclosingScope() const;
// If !done():
enum Type { Call, Block, With, Eval };
enum Type { Call, Block, With, Eval, NonSyntactic };
Type type() const;
inline bool hasScopeObject() const;
inline bool canHaveScopeObject() 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(); }
bool withinInitialFrame() const { return !!frame_; }
@ -1054,16 +1097,50 @@ ScopeIter::done() const
}
inline bool
ScopeIter::hasScopeObject() const
ScopeIter::hasSyntacticScopeObject() const
{
return ssi_.hasDynamicScopeObject();
return ssi_.hasSyntacticDynamicScopeObject();
}
inline bool
ScopeIter::canHaveScopeObject() const
ScopeIter::hasNonSyntacticScopeObject() const
{
// Non-strict eval scopes cannot have dynamic scope objects.
return !ssi_.done() && (type() != Eval || staticEval().isStrict());
// The case we're worrying about here is a NonSyntactic static scope which
// has 0+ corresponding non-syntactic DynamicWithObject scopes or a
// NonSyntacticVariablesObject.
if (ssi_.type() == StaticScopeIter<CanGC>::NonSyntactic) {
MOZ_ASSERT_IF(scope_->is<DynamicWithObject>(),
!scope_->as<DynamicWithObject>().isSyntactic());
return scope_->is<DynamicWithObject>();
}
return false;
}
inline bool
ScopeIter::hasAnyScopeObject() const
{
return hasSyntacticScopeObject() || hasNonSyntacticScopeObject();
}
inline bool
ScopeIter::canHaveSyntacticScopeObject() const
{
if (ssi_.done())
return false;
switch (type()) {
case Call:
return true;
case Block:
return true;
case With:
return true;
case Eval:
// Only strict eval scopes can have dynamic scope objects.
return staticEval().isStrict();
case NonSyntactic:
return false;
}
}
inline JSObject&

View File

@ -153,7 +153,12 @@ AssertDynamicScopeMatchesStaticScope(JSContext* cx, JSScript* script, JSObject*
#ifdef DEBUG
RootedObject enclosingScope(cx, script->enclosingStaticScope());
for (StaticScopeIter<NoGC> i(enclosingScope); !i.done(); i++) {
if (i.hasDynamicScopeObject()) {
if (i.type() == StaticScopeIter<NoGC>::NonSyntactic) {
while (scope->is<DynamicWithObject>()) {
MOZ_ASSERT(!scope->as<DynamicWithObject>().isSyntactic());
scope = &scope->as<DynamicWithObject>().enclosingScope();
}
} else if (i.hasSyntacticDynamicScopeObject()) {
switch (i.type()) {
case StaticScopeIter<NoGC>::Function:
MOZ_ASSERT(scope->as<CallObject>().callee().nonLazyScript() == i.funScript());
@ -173,13 +178,16 @@ AssertDynamicScopeMatchesStaticScope(JSContext* cx, JSScript* script, JSObject*
case StaticScopeIter<NoGC>::Eval:
scope = &scope->as<CallObject>().enclosingScope();
break;
case StaticScopeIter<NoGC>::NonSyntactic:
MOZ_CRASH("NonSyntactic should not have a syntactic scope");
break;
}
}
}
// The scope chain is always ended by one or more non-syntactic
// ScopeObjects (viz. GlobalObject or a non-syntactic WithObject).
MOZ_ASSERT(!IsSyntacticScope(scope));
// ScopeObjects (viz. GlobalObject or an unqualified varobj).
MOZ_ASSERT(!scope->is<ScopeObject>());
#endif
}
@ -246,7 +254,7 @@ InterpreterFrame::epilogue(JSContext* cx)
DebugScopes::onPopStrictEvalScope(this);
} else if (isDirectEvalFrame()) {
if (isDebuggerEvalFrame())
MOZ_ASSERT(!IsSyntacticScope(scopeChain()));
MOZ_ASSERT(!scopeChain()->is<ScopeObject>());
} else {
/*
* Debugger.Object.prototype.evalInGlobal creates indirect eval