From 3c634f08a174e3a597b02ddd10a8f6e4e1d25a55 Mon Sep 17 00:00:00 2001 From: Shu-yu Guo Date: Mon, 15 Jun 2015 17:38:01 -0700 Subject: [PATCH] Bug 1165486 - Add StaticNonSyntacticScopeObjects and teach scope iterators about it. (r=luke) --- js/src/frontend/BytecodeEmitter.cpp | 4 +- js/src/vm/Interpreter.cpp | 3 +- js/src/vm/ScopeObject-inl.h | 33 +++++-- js/src/vm/ScopeObject.cpp | 71 ++++++++++---- js/src/vm/ScopeObject.h | 143 +++++++++++++++++++++------- js/src/vm/Stack.cpp | 16 +++- 6 files changed, 204 insertions(+), 66 deletions(-) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index a2d5c5da421..02fffe550b5 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -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; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 0e147ba52ec..0d80e0fb479 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -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().callee(); // Arrow functions don't have the information we're looking for, diff --git a/js/src/vm/ScopeObject-inl.h b/js/src/vm/ScopeObject-inl.h index fe6b780cecd..1111e3dea8f 100644 --- a/js/src/vm/ScopeObject-inl.h +++ b/js/src/vm/ScopeObject-inl.h @@ -84,6 +84,8 @@ StaticScopeIter::operator++(int) obj = obj->template as().enclosingScopeForStaticScopeIter(); } else if (obj->template is()) { obj = obj->template as().enclosingScopeForStaticScopeIter(); + } else if (obj->template is()) { + obj = obj->template as().enclosingScopeForStaticScopeIter(); } else if (onNamedLambda || !obj->template as().isNamedLambda()) { onNamedLambda = false; obj = obj->template as().nonLazyScript()->enclosingStaticScope(); @@ -92,27 +94,32 @@ StaticScopeIter::operator++(int) } MOZ_ASSERT_IF(obj, obj->template is() || obj->template is() || + obj->template is() || obj->template is()); MOZ_ASSERT_IF(onNamedLambda, obj->template is()); } template inline bool -StaticScopeIter::hasDynamicScopeObject() const +StaticScopeIter::hasSyntacticDynamicScopeObject() const { - return obj->template is() - ? obj->template as().needsClone() - : (obj->template is() - ? obj->template as().isStrict() - : (obj->template is() || - obj->template as().isHeavyweight())); + if (obj->template is()) + return obj->template as().isHeavyweight(); + if (obj->template is()) + return obj->template as().needsClone(); + if (obj->template is()) + return true; + if (obj->template is()) + return obj->template as().isStrict(); + MOZ_ASSERT(obj->template is()); + return false; } template inline Shape* StaticScopeIter::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::type() const ? With : (obj->template is() ? Eval + : (obj->template is()) + ? NonSyntactic : Function)); } @@ -158,6 +167,14 @@ StaticScopeIter::eval() const return obj->template as(); } +template +inline StaticNonSyntacticScopeObjects& +StaticScopeIter::nonSyntactic() const +{ + MOZ_ASSERT(type() == NonSyntactic); + return obj->template as(); +} + template inline JSScript* StaticScopeIter::funScript() const diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 84a7408f13e..eafc8526c29 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -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 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().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().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(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().setEnclosingScope(enclosing); + obj->setEnclosingScope(enclosing); return obj; } @@ -983,7 +1002,14 @@ ScopeIter::ScopeIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc void ScopeIter::incrementStaticScopeIter() { - ssi_++; + // 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::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::Function: MOZ_ASSERT(scope_->as().callee().nonLazyScript() == ssi_.funScript()); @@ -1024,6 +1050,9 @@ ScopeIter::settle() case StaticScopeIter::Eval: MOZ_ASSERT(scope_->as().isForEval()); break; + case StaticScopeIter::NonSyntactic: + MOZ_ASSERT(!IsSyntacticScope(scope_)); + break; case StaticScopeIter::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().enclosingScope(); if (scope_->is()) scope_ = &scope_->as().enclosingScope(); @@ -1060,6 +1089,8 @@ ScopeIter::type() const return With; case StaticScopeIter::Eval: return Eval; + case StaticScopeIter::NonSyntactic: + return NonSyntactic; case StaticScopeIter::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(); } @@ -1089,6 +1120,8 @@ ScopeIter::maybeStaticScope() const return &staticWith(); case StaticScopeIter::Eval: return &staticEval(); + case StaticScopeIter::NonSyntactic: + return &staticNonSyntactic(); case StaticScopeIter::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()); 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()); #ifdef DEBUG JSObject* o = &enclosing; while ((o = o->enclosingScope())) - MOZ_ASSERT(!IsSyntacticScope(o)); + MOZ_ASSERT(!o->is()); #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); diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index e04ac8a0faf..8fd222af445 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -22,6 +22,7 @@ namespace frontend { struct Definition; } class StaticWithObject; class StaticEvalObject; +class StaticNonSyntacticScopeObjects; /*****************************************************************************/ @@ -62,6 +63,7 @@ class StaticScopeIter obj->is() || obj->is() || obj->is() || + obj->is() || obj->is()); } @@ -81,6 +83,7 @@ class StaticScopeIter obj->is() || obj->is() || obj->is() || + obj->is() || obj->is()); } @@ -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,26 +181,28 @@ 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 - * \ - * NestedScopeObject Scope created for a statement - * \ \ \ - * \ \ 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 + * | + * 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 + * | + * 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, @@ -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::NonSyntactic) { + MOZ_ASSERT_IF(scope_->is(), + !scope_->as().isSyntactic()); + return scope_->is(); + } + 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& diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index d805b7d0905..3f4650eafca 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -153,7 +153,12 @@ AssertDynamicScopeMatchesStaticScope(JSContext* cx, JSScript* script, JSObject* #ifdef DEBUG RootedObject enclosingScope(cx, script->enclosingStaticScope()); for (StaticScopeIter i(enclosingScope); !i.done(); i++) { - if (i.hasDynamicScopeObject()) { + if (i.type() == StaticScopeIter::NonSyntactic) { + while (scope->is()) { + MOZ_ASSERT(!scope->as().isSyntactic()); + scope = &scope->as().enclosingScope(); + } + } else if (i.hasSyntacticDynamicScopeObject()) { switch (i.type()) { case StaticScopeIter::Function: MOZ_ASSERT(scope->as().callee().nonLazyScript() == i.funScript()); @@ -173,13 +178,16 @@ AssertDynamicScopeMatchesStaticScope(JSContext* cx, JSScript* script, JSObject* case StaticScopeIter::Eval: scope = &scope->as().enclosingScope(); break; + case StaticScopeIter::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()); #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()); } else { /* * Debugger.Object.prototype.evalInGlobal creates indirect eval