Bug 1165486 - Add StaticNonSyntacticScopeObjects and teach scope iterators about it. (r=luke)
authorShu-yu Guo <shu@rfrn.org>
Sun, 21 Jun 2015 11:49:57 -0700
changeset 249938 f2f099600f578f5d1c8e8a44b843ab29b02c1647
parent 249937 8a416fedec44d5238cbdc9f1c1970d4e28a98163
child 249939 cf29e5d83bcd6c5de8a8f9177faae482ec7c18a7
push id28940
push usercbook@mozilla.com
push dateMon, 22 Jun 2015 12:03:34 +0000
treeherdermozilla-central@be81b8d6fae9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1165486
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1165486 - Add StaticNonSyntacticScopeObjects and teach scope iterators about it. (r=luke)
js/src/frontend/BytecodeEmitter.cpp
js/src/vm/Interpreter.cpp
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack.cpp
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -1543,24 +1543,24 @@ BytecodeEmitter::tryConvertFreeName(Pars
             return false;
         RootedObject outerScope(cx, script->enclosingStaticScope());
         for (StaticScopeIter<CanGC> ssi(cx, outerScope); !ssi.done(); ssi++) {
             if (ssi.type() != StaticScopeIter<CanGC>::Function) {
                 if (ssi.type() == StaticScopeIter<CanGC>::Block) {
                     // 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;
                     switch (pn->getOp()) {
                       case JSOP_GETNAME: op = JSOP_GETALIASEDVAR; break;
                       case JSOP_SETNAME: op = JSOP_SETALIASEDVAR; break;
                       default: return false;
                     }
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1177,16 +1177,17 @@ PopScope(JSContext* cx, ScopeIter& si)
         if (si.staticBlock().needsClone())
             si.initialFrame().popBlock(cx);
         break;
       case ScopeIter::With:
         si.initialFrame().popWith(cx);
         break;
       case ScopeIter::Call:
       case ScopeIter::Eval:
+      case ScopeIter::NonSyntactic:
         break;
     }
 }
 
 // Unwind scope chain and iterator to match the static scope corresponding to
 // the given bytecode position.
 void
 js::UnwindScope(JSContext* cx, ScopeIter& si, jsbytecode* pc)
@@ -3936,17 +3937,17 @@ CASE(JSOP_INITHOMEOBJECT)
     func->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT, ObjectValue(*obj));
 }
 END_CASE(JSOP_INITHOMEOBJECT)
 
 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,
             // their enclosing scopes do. Nevertheless, they might have call
             // objects. Skip them to find what we came for.
             if (callee.isArrow())
                 continue;
 
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -79,45 +79,52 @@ StaticScopeIter<allowGC>::done() const
 template <AllowGC allowGC>
 inline void
 StaticScopeIter<allowGC>::operator++(int)
 {
     if (obj->template is<NestedScopeObject>()) {
         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();
     } else {
         onNamedLambda = true;
     }
     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();
     return funScript()->callObjShape();
 }
 
 template <AllowGC allowGC>
 inline typename StaticScopeIter<allowGC>::Type
@@ -126,16 +133,18 @@ StaticScopeIter<allowGC>::type() const
     if (onNamedLambda)
         return NamedLambda;
     return obj->template is<StaticBlockObject>()
            ? Block
            : (obj->template is<StaticWithObject>()
               ? With
               : (obj->template is<StaticEvalObject>()
                  ? Eval
+                 : (obj->template is<StaticNonSyntacticScopeObjects>())
+                 ? NonSyntactic
                  : Function));
 }
 
 template <AllowGC allowGC>
 inline StaticBlockObject&
 StaticScopeIter<allowGC>::block() const
 {
     MOZ_ASSERT(type() == Block);
@@ -154,16 +163,24 @@ template <AllowGC allowGC>
 inline StaticEvalObject&
 StaticScopeIter<allowGC>::eval() const
 {
     MOZ_ASSERT(type() == Eval);
     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
 {
     MOZ_ASSERT(type() == Function);
     return obj->template as<JSFunction>().nonLazyScript();
 }
 
 template <AllowGC allowGC>
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -38,17 +38,17 @@ typedef MutableHandle<ArgumentsObject*> 
 Shape*
 js::ScopeCoordinateToStaticScopeShape(JSScript* script, jsbytecode* pc)
 {
     MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
     StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc));
     uint32_t hops = ScopeCoordinate(pc).hops();
     while (true) {
         MOZ_ASSERT(!ssi.done());
-        if (ssi.hasDynamicScopeObject()) {
+        if (ssi.hasSyntacticDynamicScopeObject()) {
             if (!hops)
                 break;
             hops--;
         }
         ssi++;
     }
     return ssi.scopeShape();
 }
@@ -102,17 +102,17 @@ js::ScopeCoordinateName(ScopeCoordinateN
 
 JSScript*
 js::ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc)
 {
     MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
     StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc));
     uint32_t hops = ScopeCoordinate(pc).hops();
     while (true) {
-        if (ssi.hasDynamicScopeObject()) {
+        if (ssi.hasSyntacticDynamicScopeObject()) {
             if (!hops)
                 break;
             hops--;
         }
         ssi++;
     }
     if (ssi.type() != StaticScopeIter<NoGC>::Function)
         return nullptr;
@@ -207,17 +207,17 @@ CallObject::createTemplateObject(JSConte
 CallObject*
 CallObject::create(JSContext* cx, HandleScript script, HandleObject enclosing, HandleFunction callee)
 {
     gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap;
     CallObject* callobj = CallObject::createTemplateObject(cx, script, heap);
     if (!callobj)
         return nullptr;
 
-    callobj->as<ScopeObject>().setEnclosingScope(enclosing);
+    callobj->setEnclosingScope(enclosing);
     callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee));
 
     if (script->treatAsRunOnce()) {
         Rooted<CallObject*> ncallobj(cx, callobj);
         if (!JSObject::setSingleton(cx, ncallobj))
             return nullptr;
         return ncallobj;
     }
@@ -415,17 +415,17 @@ DynamicWithObject::create(JSContext* cx,
                                                            BaseShape::DELEGATE);
     if (!obj)
         return nullptr;
 
     JSObject* thisp = GetThisObject(cx, object);
     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));
 
     return obj;
 }
 
 static bool
@@ -546,16 +546,35 @@ StaticEvalObject::create(JSContext* cx, 
 }
 
 const Class StaticEvalObject::class_ = {
     "StaticEval",
     JSCLASS_HAS_RESERVED_SLOTS(StaticEvalObject::RESERVED_SLOTS) |
     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*
 ClonedBlockObject::create(JSContext* cx, Handle<StaticBlockObject*> block, HandleObject enclosing)
 {
     MOZ_ASSERT(block->getClass() == &BlockObject::class_);
 
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &BlockObject::class_,
@@ -840,17 +859,17 @@ js::CloneNestedScopeObject(JSContext* cx
 /* static */ UninitializedLexicalObject*
 UninitializedLexicalObject::create(JSContext* cx, HandleObject enclosing)
 {
     UninitializedLexicalObject* obj =
         NewObjectWithNullTaggedProto<UninitializedLexicalObject>(cx, GenericObject,
                                                                  BaseShape::DELEGATE);
     if (!obj)
         return nullptr;
-    obj->as<ScopeObject>().setEnclosingScope(enclosing);
+    obj->setEnclosingScope(enclosing);
     return obj;
 }
 
 static void
 ReportUninitializedLexicalId(JSContext* cx, HandleId id)
 {
     if (JSID_IS_ATOM(id)) {
         RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName());
@@ -978,17 +997,24 @@ ScopeIter::ScopeIter(JSContext* cx, Abst
     assertSameCompartment(cx, frame);
     settle();
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
 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<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
     // ScopeIter.
     if (!ssi_.done() && ssi_.type() == StaticScopeIter<CanGC>::NamedLambda)
         ssi_++;
 }
 
@@ -1005,41 +1031,44 @@ ScopeIter::settle()
     }
 
     // Check if we have left the extent of the initial frame after we've
     // settled on a static scope.
     if (frame_ && (ssi_.done() || maybeStaticScope() == frame_.script()->enclosingStaticScope()))
         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());
             break;
           case StaticScopeIter<CanGC>::Block:
             MOZ_ASSERT(scope_->as<ClonedBlockObject>().staticBlock() == staticBlock());
             break;
           case StaticScopeIter<CanGC>::With:
             MOZ_ASSERT(scope_->as<DynamicWithObject>().staticScope() == &staticWith());
             break;
           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");
         }
     }
 #endif
 }
 
 ScopeIter&
 ScopeIter::operator++()
 {
-    if (hasScopeObject()) {
+    if (hasAnyScopeObject()) {
         scope_ = &scope_->as<ScopeObject>().enclosingScope();
         if (scope_->is<DeclEnvObject>())
             scope_ = &scope_->as<DeclEnvObject>().enclosingScope();
     }
 
     incrementStaticScopeIter();
     settle();
 
@@ -1055,27 +1084,29 @@ ScopeIter::type() const
       case StaticScopeIter<CanGC>::Function:
         return Call;
       case StaticScopeIter<CanGC>::Block:
         return Block;
       case StaticScopeIter<CanGC>::With:
         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:
         MOZ_CRASH("bad SSI type");
     }
 }
 
 ScopeObject&
 ScopeIter::scope() const
 {
-    MOZ_ASSERT(hasScopeObject());
+    MOZ_ASSERT(hasAnyScopeObject());
     return scope_->as<ScopeObject>();
 }
 
 JSObject*
 ScopeIter::maybeStaticScope() const
 {
     if (ssi_.done())
         return nullptr;
@@ -1084,16 +1115,18 @@ ScopeIter::maybeStaticScope() const
       case StaticScopeIter<CanGC>::Function:
         return &fun();
       case StaticScopeIter<CanGC>::Block:
         return &staticBlock();
       case StaticScopeIter<CanGC>::With:
         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:
         MOZ_CRASH("bad SSI type");
     }
 }
 
 /* static */ HashNumber
@@ -1683,17 +1716,17 @@ class DebugScopeProxy : public BaseProxy
 
 const char DebugScopeProxy::family = 0;
 const DebugScopeProxy DebugScopeProxy::singleton;
 
 /* static */ DebugScopeObject*
 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,
                                    nullptr /* proto */);
     if (!obj)
         return nullptr;
 
     DebugScopeObject* debugScope = &obj->as<DebugScopeObject>();
@@ -1945,33 +1978,33 @@ DebugScopes::addDebugScope(JSContext* cx
         return false;
 
     return scopes->proxiedScopes.add(cx, &scope, &debugScope);
 }
 
 DebugScopeObject*
 DebugScopes::hasDebugScope(JSContext* cx, const ScopeIter& si)
 {
-    MOZ_ASSERT(!si.hasScopeObject());
+    MOZ_ASSERT(!si.hasSyntacticScopeObject());
 
     DebugScopes* scopes = cx->compartment()->debugScopes;
     if (!scopes)
         return nullptr;
 
     if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
         MOZ_ASSERT(CanUseDebugScopeMaps(cx));
         return p->value();
     }
     return nullptr;
 }
 
 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());
     // Generators should always reify their scopes.
     MOZ_ASSERT_IF(si.type() == ScopeIter::Call, !si.fun().isGenerator());
 
     if (!CanUseDebugScopeMaps(cx))
         return true;
@@ -2179,17 +2212,17 @@ DebugScopes::updateLiveScopes(JSContext*
 
         if (frame.isFunctionFrame() && frame.callee()->isGenerator())
             continue;
 
         if (!frame.isDebuggee())
             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)
                     return false;
                 if (!scopes->liveScopes.put(&si.scope(), LiveScopeVal(si)))
                     return false;
                 liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &si.scope());
             }
@@ -2297,17 +2330,17 @@ GetDebugScopeForScope(JSContext* cx, con
         return nullptr;
 
     return debugScope;
 }
 
 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;
 
     ScopeIter copy(cx, si);
     RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy));
     if (!enclosingDebug)
         return nullptr;
@@ -2364,51 +2397,53 @@ GetDebugScopeForMissing(JSContext* cx, c
             return nullptr;
 
         debugScope = DebugScopeObject::create(cx, *block, enclosingDebug);
         break;
       }
       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;
 
     if (!DebugScopes::addDebugScope(cx, si, *debugScope))
         return nullptr;
 
     return debugScope;
 }
 
 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;
 }
 
 static JSObject*
 GetDebugScope(JSContext* cx, const ScopeIter& si)
 {
     JS_CHECK_RECURSION(cx, return nullptr);
 
     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);
     return GetDebugScope(cx, ++copy);
 }
 
 JSObject*
 js::GetDebugScopeForFunction(JSContext* cx, HandleFunction fun)
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -17,16 +17,17 @@
 #include "vm/WeakMapObject.h"
 
 namespace js {
 
 namespace frontend { struct Definition; }
 
 class StaticWithObject;
 class StaticEvalObject;
+class StaticNonSyntacticScopeObjects;
 
 /*****************************************************************************/
 
 /*
  * All function scripts have an "enclosing static scope" that refers to the
  * innermost enclosing let or function in the program text. This allows full
  * reconstruction of the lexical scope for debugging or compiling efficient
  * access to variables in enclosing scopes. The static scope is represented at
@@ -57,16 +58,17 @@ class StaticScopeIter
     {
         static_assert(allowGC == CanGC,
                       "the context-accepting constructor should only be used "
                       "in CanGC code");
         MOZ_ASSERT_IF(obj,
                       obj->is<StaticBlockObject>() ||
                       obj->is<StaticWithObject>() ||
                       obj->is<StaticEvalObject>() ||
+                      obj->is<StaticNonSyntacticScopeObjects>() ||
                       obj->is<JSFunction>());
     }
 
     StaticScopeIter(ExclusiveContext* cx, const StaticScopeIter<CanGC>& ssi)
       : obj(cx, ssi.obj), onNamedLambda(ssi.onNamedLambda)
     {
         JS_STATIC_ASSERT(allowGC == CanGC);
     }
@@ -76,40 +78,44 @@ class StaticScopeIter
     {
         static_assert(allowGC == NoGC,
                       "the constructor not taking a context should only be "
                       "used in NoGC code");
         MOZ_ASSERT_IF(obj,
                       obj->is<StaticBlockObject>() ||
                       obj->is<StaticWithObject>() ||
                       obj->is<StaticEvalObject>() ||
+                      obj->is<StaticNonSyntacticScopeObjects>() ||
                       obj->is<JSFunction>());
     }
 
     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;
     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;
 };
 
 /*****************************************************************************/
 
 /*
  * A "scope coordinate" describes how to get from head of the scope chain to a
@@ -170,47 +176,52 @@ ScopeCoordinateFunctionScript(JSScript* 
 /*
  * 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
- *   \   \   \   \
- *    \   \   \  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,
  * 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.
  *
+ * NB2: StaticNonSyntacticScopeObjects notify either of 0+ non-syntactic
+ * DynamicWithObjects on the dynamic scope chain or a NonSyntacticScopeObject.
+ *
  * See also "Debug scope objects" below.
  */
 
 class ScopeObject : public NativeObject
 {
   protected:
     static const uint32_t SCOPE_CHAIN_SLOT = 0;
 
@@ -347,18 +358,19 @@ class DeclEnvObject : public ScopeObject
 
     static DeclEnvObject* create(JSContext* cx, HandleObject enclosing, HandleFunction callee);
 
     static inline size_t lambdaSlot() {
         return LAMBDA_SLOT;
     }
 };
 
-// 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;
 
   public:
     static const unsigned RESERVED_SLOTS = 2;
     static const Class class_;
 
@@ -378,16 +390,44 @@ class StaticEvalObject : public ScopeObj
 
     // Indirect evals terminate in the global at run time, and has no static
     // enclosing scope.
     bool isDirect() const {
         return getReservedSlot(SCOPE_CHAIN_SLOT).isObject();
     }
 };
 
+// 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:
     /*
      * A refinement of enclosingScope that returns nullptr if the enclosing
      * scope is not a NestedScopeObject.
      */
     inline NestedScopeObject* enclosingNestedScope() const;
@@ -742,27 +782,30 @@ class ScopeIter
 
     inline bool done() const;
     ScopeIter& operator++();
 
     // If done():
     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_; }
     AbstractFramePtr initialFrame() const { MOZ_ASSERT(withinInitialFrame()); return frame_; }
     AbstractFramePtr maybeInitialFrame() const { return frame_; }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
@@ -1049,26 +1092,60 @@ NestedScopeObject::enclosingNestedScope(
 
 inline bool
 ScopeIter::done() const
 {
     return ssi_.done();
 }
 
 inline bool
-ScopeIter::hasScopeObject() const
+ScopeIter::hasSyntacticScopeObject() const
+{
+    return ssi_.hasSyntacticDynamicScopeObject();
+}
+
+inline bool
+ScopeIter::hasNonSyntacticScopeObject() const
 {
-    return ssi_.hasDynamicScopeObject();
+    // 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::canHaveScopeObject() const
+ScopeIter::hasAnyScopeObject() const
+{
+    return hasSyntacticScopeObject() || hasNonSyntacticScopeObject();
+}
+
+inline bool
+ScopeIter::canHaveSyntacticScopeObject() const
 {
-    // Non-strict eval scopes cannot have dynamic scope objects.
-    return !ssi_.done() && (type() != Eval || staticEval().isStrict());
+    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&
 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
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -129,17 +129,22 @@ InterpreterFrame::createRestParameter(JS
 }
 
 static inline void
 AssertDynamicScopeMatchesStaticScope(JSContext* cx, JSScript* script, JSObject* scope)
 {
 #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());
                 scope = &scope->as<CallObject>().enclosingScope();
                 break;
               case StaticScopeIter<NoGC>::Block:
                 MOZ_ASSERT(&i.block() == scope->as<ClonedBlockObject>().staticScope());
                 scope = &scope->as<ClonedBlockObject>().enclosingScope();
@@ -149,23 +154,26 @@ AssertDynamicScopeMatchesStaticScope(JSC
                 scope = &scope->as<DynamicWithObject>().enclosingScope();
                 break;
               case StaticScopeIter<NoGC>::NamedLambda:
                 scope = &scope->as<DeclEnvObject>().enclosingScope();
                 break;
               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
 }
 
 bool
 InterpreterFrame::initFunctionScopeObjects(JSContext* cx)
 {
     CallObject* callobj = CallObject::createForFunction(cx, this);
     if (!callobj)
@@ -222,17 +230,17 @@ InterpreterFrame::epilogue(JSContext* cx
 
     if (isEvalFrame()) {
         if (isStrictEvalFrame()) {
             MOZ_ASSERT_IF(hasCallObj(), scopeChain()->as<CallObject>().isForEval());
             if (MOZ_UNLIKELY(cx->compartment()->isDebuggee()))
                 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
              * frames scoped to the given global;
              * Debugger.Object.prototype.evalInGlobalWithBindings creates
              * indirect eval frames scoped to an object carrying the introduced
              * bindings.
              */