Bug 673569 - Allow scripts to be run in a non-global scope (r=luke)
authorBill McCloskey <wmccloskey@mozilla.com>
Wed, 16 Jul 2014 23:03:44 -0700
changeset 216473 666a2522440ade8fe0e74a45f0cc650b5680bb1a
parent 216472 4c5c545d78746bcb5769078fbcd835554399f275
child 216474 6cc6ba5dccac10bcbc686b6116aa3c64d01177b4
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs673569
milestone33.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 673569 - Allow scripts to be run in a non-global scope (r=luke)
js/src/builtin/Eval.cpp
js/src/builtin/TestingFunctions.cpp
js/src/jit-test/tests/basic/bug673569.js
js/src/jit/BaselineIC.cpp
js/src/jit/IonCaches.cpp
js/src/jit/VMFunctions.cpp
js/src/jsfriendapi.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsscript.cpp
js/src/vm/GlobalObject.cpp
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/Shape.h
js/src/vm/Stack-inl.h
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -474,8 +474,49 @@ js::DirectEval(JSContext *cx, const Call
     return EvalKernel(cx, args, DIRECT_EVAL, caller, scopeChain, iter.pc());
 }
 
 bool
 js::IsAnyBuiltinEval(JSFunction *fun)
 {
     return fun->maybeNative() == IndirectEval;
 }
+
+JS_FRIEND_API(bool)
+js::ExecuteInGlobalAndReturnScope(JSContext *cx, HandleObject global, HandleScript scriptArg,
+                                  MutableHandleObject scopeArg)
+{
+    CHECK_REQUEST(cx);
+    assertSameCompartment(cx, global);
+    MOZ_ASSERT(global->is<GlobalObject>());
+
+    RootedScript script(cx, scriptArg);
+    if (script->compartment() != cx->compartment()) {
+        script = CloneScript(cx, NullPtr(), NullPtr(), script);
+        if (!script)
+            return false;
+    }
+
+    RootedObject scope(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
+    if (!scope)
+        return false;
+
+    if (!scope->setQualifiedVarObj(cx))
+        return false;
+
+    if (!scope->setUnqualifiedVarObj(cx))
+        return false;
+
+    JSObject *thisobj = JSObject::thisObject(cx, global);
+    if (!thisobj)
+        return false;
+
+    RootedValue thisv(cx, ObjectValue(*thisobj));
+    RootedValue rval(cx);
+    if (!ExecuteKernel(cx, script, *scope, thisv, EXECUTE_GLOBAL,
+                       NullFramePtr() /* evalInFrame */, rval.address()))
+    {
+        return false;
+    }
+
+    scopeArg.set(scope);
+    return true;
+}
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1885,16 +1885,57 @@ FindPath(JSContext *cx, unsigned argc, j
 
         result->setDenseElement(length - i - 1, ObjectValue(*obj));
     }
 
     args.rval().setObject(*result);
     return true;
 }
 
+static bool
+EvalReturningScope(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedString str(cx);
+    if (!JS_ConvertArguments(cx, args, "S", str.address()))
+        return false;
+
+    AutoStableStringChars strChars(cx);
+    if (!strChars.initTwoByte(cx, str))
+        return false;
+
+    mozilla::Range<const jschar> chars = strChars.twoByteRange();
+    size_t srclen = chars.length();
+    const jschar *src = chars.start().get();
+
+    JS::AutoFilename filename;
+    unsigned lineno;
+
+    DescribeScriptedCaller(cx, &filename, &lineno);
+
+    JS::CompileOptions options(cx);
+    options.setFileAndLine(filename.get(), lineno);
+    options.setNoScriptRval(true);
+    options.setCompileAndGo(false);
+
+    JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
+    RootedScript script(cx);
+    if (!JS::Compile(cx, JS::NullPtr(), options, srcBuf, &script))
+        return false;
+
+    RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+    RootedObject scope(cx);
+    if (!js::ExecuteInGlobalAndReturnScope(cx, global, script, &scope))
+        return false;
+
+    args.rval().setObject(*scope);
+    return true;
+}
+
 static const JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
 "gc([obj] | 'compartment')",
 "  Run the garbage collector. When obj is given, GC only its compartment.\n"
 "  If 'compartment' is given, GC any compartments that were scheduled for\n"
 "  GC via schedulegc."),
 
     JS_FN_HELP("minorgc", ::MinorGC, 0, 0,
@@ -2184,16 +2225,20 @@ static const JSFunctionSpecWithHelp Test
 "  the last array element is implicitly |target|.\n"),
 
 #ifdef DEBUG
     JS_FN_HELP("dumpObject", DumpObject, 1, 0,
 "dumpObject()",
 "  Dump an internal representation of an object."),
 #endif
 
+    JS_FN_HELP("evalReturningScope", EvalReturningScope, 1, 0,
+"evalReturningScope(scriptStr)",
+"  Evaluate the script in a new scope and return the scope."),
+
     JS_FS_HELP_END
 };
 
 static const JSPropertySpec TestingProperties[] = {
     JS_PSG("timesAccessed", TimesAccessed, 0),
     JS_PS_END
 };
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug673569.js
@@ -0,0 +1,24 @@
+function qualified_tests(prefix) {
+  let scope = evalReturningScope(prefix + "let x = 1");
+  assertEq(scope.x, 1);
+
+  scope = evalReturningScope(prefix + "var x = 1");
+  assertEq(scope.x, 1);
+
+  scope = evalReturningScope(prefix + "const x = 1");
+  assertEq(scope.x, 1);
+}
+
+qualified_tests("");
+qualified_tests("'use strict'; ");
+
+let scope = evalReturningScope("x = 1");
+assertEq(scope.x, 1);
+
+let fail = true;
+try {
+  evalReturningScope("'use strict'; x = 1");
+} catch (e) {
+  fail = false;
+}
+assertEq(fail, false);
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -5969,17 +5969,17 @@ DoBindNameFallback(JSContext *cx, Baseli
     mozilla::DebugOnly<JSOp> op = JSOp(*pc);
     FallbackICSpew(cx, stub, "BindName(%s)", js_CodeName[JSOp(*pc)]);
 
     JS_ASSERT(op == JSOP_BINDNAME);
 
     RootedPropertyName name(cx, frame->script()->getName(pc));
 
     RootedObject scope(cx);
-    if (!LookupNameWithGlobalDefault(cx, name, scopeChain, &scope))
+    if (!LookupNameUnqualified(cx, name, scopeChain, &scope))
         return false;
 
     res.setObject(*scope);
     return true;
 }
 
 typedef bool (*DoBindNameFallbackFn)(JSContext *, BaselineFrame *, ICBindName_Fallback *,
                                      HandleObject, MutableHandleValue);
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -4161,17 +4161,17 @@ BindNameIC::update(JSContext *cx, size_t
     IonScript *ion = outerScript->ionScript();
     BindNameIC &cache = ion->getCache(cacheIndex).toBindName();
     HandlePropertyName name = cache.name();
 
     RootedObject holder(cx);
     if (scopeChain->is<GlobalObject>()) {
         holder = scopeChain;
     } else {
-        if (!LookupNameWithGlobalDefault(cx, name, scopeChain, &holder))
+        if (!LookupNameUnqualified(cx, name, scopeChain, &holder))
             return nullptr;
     }
 
     // Stop generating new stubs once we hit the stub count limit, see
     // GetPropertyCache.
     if (cache.canAttachStub()) {
         if (scopeChain->is<GlobalObject>()) {
             if (!cache.attachGlobal(cx, outerScript, ion, scopeChain))
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -181,28 +181,28 @@ CheckOverRecursedWithExtra(JSContext *cx
     return true;
 }
 
 bool
 DefVarOrConst(JSContext *cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeChain)
 {
     // Given the ScopeChain, extract the VarObj.
     RootedObject obj(cx, scopeChain);
-    while (!obj->isVarObj())
+    while (!obj->isQualifiedVarObj())
         obj = obj->enclosingScope();
 
     return DefVarOrConstOperation(cx, obj, dn, attrs);
 }
 
 bool
 SetConst(JSContext *cx, HandlePropertyName name, HandleObject scopeChain, HandleValue rval)
 {
     // Given the ScopeChain, extract the VarObj.
     RootedObject obj(cx, scopeChain);
-    while (!obj->isVarObj())
+    while (!obj->isQualifiedVarObj())
         obj = obj->enclosingScope();
 
     return SetConstOperation(cx, obj, name, rval);
 }
 
 bool
 MutatePrototype(JSContext *cx, HandleObject obj, HandleValue value)
 {
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2326,16 +2326,21 @@ DefaultValue(JSContext *cx, JS::HandleOb
 extern JS_FRIEND_API(bool)
 CheckDefineProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue value,
                     unsigned attrs,
                     JSPropertyOp getter = nullptr, JSStrictPropertyOp setter = nullptr);
 
 JS_FRIEND_API(void)
 ReportErrorWithId(JSContext *cx, const char *msg, JS::HandleId id);
 
+// This function is for one specific use case, please don't use this for anything else!
+extern JS_FRIEND_API(bool)
+ExecuteInGlobalAndReturnScope(JSContext *cx, JS::HandleObject obj, JS::HandleScript script,
+                              JS::MutableHandleObject scope);
+
 } /* namespace js */
 
 extern JS_FRIEND_API(bool)
 js_DefineOwnProperty(JSContext *cx, JSObject *objArg, jsid idArg,
                      JS::Handle<JSPropertyDescriptor> descriptor, bool *bp);
 
 extern JS_FRIEND_API(bool)
 js_ReportIsNotFunction(JSContext *cx, JS::HandleValue v);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -4374,16 +4374,37 @@ js::LookupNameWithGlobalDefault(JSContex
         if (prop)
             break;
     }
 
     objp.set(scope);
     return true;
 }
 
+bool
+js::LookupNameUnqualified(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
+                          MutableHandleObject objp)
+{
+    RootedId id(cx, NameToId(name));
+
+    RootedObject pobj(cx);
+    RootedShape prop(cx);
+
+    RootedObject scope(cx, scopeChain);
+    for (; !scope->isUnqualifiedVarObj(); scope = scope->enclosingScope()) {
+        if (!JSObject::lookupGeneric(cx, scope, id, &pobj, &prop))
+            return false;
+        if (prop)
+            break;
+    }
+
+    objp.set(scope);
+    return true;
+}
+
 template <AllowGC allowGC>
 bool
 js::HasOwnProperty(JSContext *cx, LookupGenericOp lookup,
                    typename MaybeRooted<JSObject*, allowGC>::HandleType obj,
                    typename MaybeRooted<jsid, allowGC>::HandleType id,
                    typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp,
                    typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp)
 {
@@ -5028,17 +5049,17 @@ baseops::SetPropertyHelper(typename Exec
             }
 
             shape = nullptr;
         }
     } else {
         /* We should never add properties to lexical blocks. */
         JS_ASSERT(!obj->is<BlockObject>());
 
-        if (obj->is<GlobalObject>() && !qualified) {
+        if (obj->isUnqualifiedVarObj() && !qualified) {
             if (mode == ParallelExecution)
                 return false;
 
             if (!MaybeReportUndeclaredVarAssignment(cxArg->asJSContext(), JSID_TO_STRING(id)))
                 return false;
         }
     }
 
@@ -5833,17 +5854,18 @@ JSObject::dump()
     const Class *clasp = obj->getClass();
     fprintf(stderr, "class %p %s\n", (const void *)clasp, clasp->name);
 
     fprintf(stderr, "flags:");
     if (obj->isDelegate()) fprintf(stderr, " delegate");
     if (!obj->is<ProxyObject>() && !obj->nonProxyIsExtensible()) fprintf(stderr, " not_extensible");
     if (obj->isIndexed()) fprintf(stderr, " indexed");
     if (obj->isBoundFunction()) fprintf(stderr, " bound_function");
-    if (obj->isVarObj()) fprintf(stderr, " varobj");
+    if (obj->isQualifiedVarObj()) fprintf(stderr, " varobj");
+    if (obj->isUnqualifiedVarObj()) fprintf(stderr, " unqualified_varobj");
     if (obj->watched()) fprintf(stderr, " watched");
     if (obj->isIteratedSingleton()) fprintf(stderr, " iterated_singleton");
     if (obj->isNewTypeUnknown()) fprintf(stderr, " new_type_unknown");
     if (obj->hasUncacheableProto()) fprintf(stderr, " has_uncacheable_proto");
     if (obj->hadElementsAccess()) fprintf(stderr, " had_elements_access");
 
     if (obj->isNative()) {
         if (obj->inDictionaryMode())
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -277,19 +277,24 @@ class JSObject : public js::ObjectImpl
     bool watched() const {
         return lastProperty()->hasObjectFlag(js::BaseShape::WATCHED);
     }
     bool setWatched(js::ExclusiveContext *cx) {
         return setFlag(cx, js::BaseShape::WATCHED, GENERATE_SHAPE);
     }
 
     /* See InterpreterFrame::varObj. */
-    inline bool isVarObj();
-    bool setVarObj(js::ExclusiveContext *cx) {
-        return setFlag(cx, js::BaseShape::VAROBJ);
+    inline bool isQualifiedVarObj();
+    bool setQualifiedVarObj(js::ExclusiveContext *cx) {
+        return setFlag(cx, js::BaseShape::QUALIFIED_VAROBJ);
+    }
+
+    inline bool isUnqualifiedVarObj();
+    bool setUnqualifiedVarObj(js::ExclusiveContext *cx) {
+        return setFlag(cx, js::BaseShape::UNQUALIFIED_VAROBJ);
     }
 
     /*
      * Objects with an uncacheable proto can have their prototype mutated
      * without inducing a shape change on the object. Property cache entries
      * and JIT inline caches should not be filled for lookups across prototype
      * lookups on the object.
      */
@@ -1399,25 +1404,36 @@ LookupName(JSContext *cx, HandleProperty
            MutableHandleObject objp, MutableHandleObject pobjp, MutableHandleShape propp);
 
 extern bool
 LookupNameNoGC(JSContext *cx, PropertyName *name, JSObject *scopeChain,
                JSObject **objp, JSObject **pobjp, Shape **propp);
 
 /*
  * Like LookupName except returns the global object if 'name' is not found in
- * any preceding non-global scope.
+ * any preceding scope.
  *
  * Additionally, pobjp and propp are not needed by callers so they are not
  * returned.
  */
 extern bool
 LookupNameWithGlobalDefault(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
                             MutableHandleObject objp);
 
+/*
+ * Like LookupName except returns the unqualified var object if 'name' is not found in
+ * any preceding scope. Normally the unqualified var object is the global.
+ *
+ * Additionally, pobjp and propp are not needed by callers so they are not
+ * returned.
+ */
+extern bool
+LookupNameUnqualified(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
+                      MutableHandleObject objp);
+
 }
 
 extern JSObject *
 js_FindVariableScope(JSContext *cx, JSFunction **funp);
 
 
 namespace js {
 
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -493,21 +493,29 @@ JSObject::setProto(JSContext *cx, JS::Ha
             return false;
     }
 
     JS::Rooted<js::TaggedProto> taggedProto(cx, js::TaggedProto(proto));
     return SetClassAndProto(cx, obj, obj->getClass(), taggedProto, succeeded);
 }
 
 inline bool
-JSObject::isVarObj()
+JSObject::isQualifiedVarObj()
 {
     if (is<js::DebugScopeObject>())
-        return as<js::DebugScopeObject>().scope().isVarObj();
-    return lastProperty()->hasObjectFlag(js::BaseShape::VAROBJ);
+        return as<js::DebugScopeObject>().scope().isQualifiedVarObj();
+    return lastProperty()->hasObjectFlag(js::BaseShape::QUALIFIED_VAROBJ);
+}
+
+inline bool
+JSObject::isUnqualifiedVarObj()
+{
+    if (is<js::DebugScopeObject>())
+        return as<js::DebugScopeObject>().scope().isUnqualifiedVarObj();
+    return lastProperty()->hasObjectFlag(js::BaseShape::UNQUALIFIED_VAROBJ);
 }
 
 /* static */ inline JSObject *
 JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
                  js::HandleShape shape, js::HandleTypeObject type)
 {
     JS_ASSERT(shape && type);
     JS_ASSERT(type->clasp() == shape->getObjectClass());
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -113,17 +113,17 @@ Bindings::initWithTemporaryStorage(Exclu
     }
 
     // Put as many of nslots inline into the object header as possible.
     uint32_t nfixed = gc::GetGCKindSlots(gc::GetGCObjectKind(nslots));
 
     // Start with the empty shape and then append one shape per aliased binding.
     RootedShape shape(cx,
         EmptyShape::getInitialShape(cx, &CallObject::class_, TaggedProto(nullptr), nullptr, nullptr,
-                                    nfixed, BaseShape::VAROBJ | BaseShape::DELEGATE));
+                                    nfixed, BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE));
     if (!shape)
         return false;
 
 #ifdef DEBUG
     HashSet<PropertyName *> added(cx);
     if (!added.init())
         return false;
 #endif
@@ -136,17 +136,17 @@ Bindings::initWithTemporaryStorage(Exclu
 #ifdef DEBUG
         // The caller ensures no duplicate aliased names.
         JS_ASSERT(!added.has(bi->name()));
         if (!added.put(bi->name()))
             return false;
 #endif
 
         StackBaseShape stackBase(cx, &CallObject::class_, nullptr, nullptr,
-                                 BaseShape::VAROBJ | BaseShape::DELEGATE);
+                                 BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE);
 
         UnownedBaseShape *base = BaseShape::getUnowned(cx, stackBase);
         if (!base)
             return false;
 
         unsigned attrs = JSPROP_PERMANENT |
                          JSPROP_ENUMERATE |
                          (bi->kind() == Binding::CONSTANT ? JSPROP_READONLY : 0);
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -230,17 +230,19 @@ GlobalObject::create(JSContext *cx, cons
     JSObject *obj = NewObjectWithGivenProto(cx, clasp, nullptr, nullptr, SingletonObject);
     if (!obj)
         return nullptr;
 
     Rooted<GlobalObject *> global(cx, &obj->as<GlobalObject>());
 
     cx->compartment()->initGlobal(*global);
 
-    if (!global->setVarObj(cx))
+    if (!global->setQualifiedVarObj(cx))
+        return nullptr;
+    if (!global->setUnqualifiedVarObj(cx))
         return nullptr;
     if (!global->setDelegate(cx))
         return nullptr;
 
     return global;
 }
 
 /* static */ bool
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -236,31 +236,31 @@ SetNameOperation(JSContext *cx, JSScript
     RootedPropertyName name(cx, script->getName(pc));
     RootedValue valCopy(cx, val);
 
     /*
      * In strict-mode, we need to trigger an error when trying to assign to an
      * undeclared global variable. To do this, we call SetPropertyHelper
      * directly and pass Unqualified.
      */
-    if (scope->is<GlobalObject>()) {
+    if (scope->isUnqualifiedVarObj()) {
         JS_ASSERT(!scope->getOps()->setProperty);
         RootedId id(cx, NameToId(name));
         return baseops::SetPropertyHelper<SequentialExecution>(cx, scope, scope, id,
                                                                baseops::Unqualified, &valCopy,
                                                                strict);
     }
 
     return JSObject::setProperty(cx, scope, scope, name, &valCopy, strict);
 }
 
 inline bool
 DefVarOrConstOperation(JSContext *cx, HandleObject varobj, HandlePropertyName dn, unsigned attrs)
 {
-    JS_ASSERT(varobj->isVarObj());
+    JS_ASSERT(varobj->isQualifiedVarObj());
 
     RootedShape prop(cx);
     RootedObject obj2(cx);
     if (!JSObject::lookupProperty(cx, varobj, dn, &obj2, &prop))
         return false;
 
     /* Steps 8c, 8d. */
     if (!prop || (obj2 != varobj && varobj->is<GlobalObject>())) {
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -634,17 +634,17 @@ js::Execute(JSContext *cx, HandleScript 
     do {
         assertSameCompartment(cx, s);
         JS_ASSERT_IF(!s->enclosingScope(), s->is<GlobalObject>());
     } while ((s = s->enclosingScope()));
 #endif
 
     /* The VAROBJFIX option makes varObj == globalObj in global code. */
     if (!cx->runtime()->options().varObjFix()) {
-        if (!scopeChain->setVarObj(cx))
+        if (!scopeChain->setQualifiedVarObj(cx))
             return false;
     }
 
     /* Use the scope chain as 'this', modulo outerization. */
     JSObject *thisObj = JSObject::thisObject(cx, scopeChain);
     if (!thisObj)
         return false;
     Value thisv = ObjectValue(*thisObj);
@@ -2019,17 +2019,17 @@ CASE(JSOP_BINDNAME)
     RootedObject &scopeChain = rootObject0;
     scopeChain = REGS.fp()->scopeChain();
 
     RootedPropertyName &name = rootName0;
     name = script->getName(REGS.pc);
 
     /* Assigning to an undeclared name adds a property to the global object. */
     RootedObject &scope = rootObject1;
-    if (!LookupNameWithGlobalDefault(cx, name, scopeChain, &scope))
+    if (!LookupNameUnqualified(cx, name, scopeChain, &scope))
         goto error;
 
     PUSH_OBJECT(*scope);
 }
 END_CASE(JSOP_BINDNAME)
 
 #define BITWISE_OP(OP)                                                        \
     JS_BEGIN_MACRO                                                            \
@@ -3572,17 +3572,17 @@ js::DefFunOperation(JSContext *cx, Handl
     }
 
     /*
      * We define the function as a property of the variable object and not the
      * current scope chain even for the case of function expression statements
      * and functions defined by eval inside let or with blocks.
      */
     RootedObject parent(cx, scopeChain);
-    while (!parent->isVarObj())
+    while (!parent->isQualifiedVarObj())
         parent = parent->enclosingScope();
 
     /* ES5 10.5 (NB: with subsequent errata). */
     RootedPropertyName name(cx, fun->atom()->asPropertyName());
 
     RootedShape shape(cx);
     RootedObject pobj(cx);
     if (!JSObject::lookupProperty(cx, parent, name, &pobj, &shape))
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -303,24 +303,34 @@ class BaseShape : public gc::BarrieredCe
          * If you add a new flag here, please add appropriate code to
          * JSObject::dump to dump it as part of object representation.
          */
 
         DELEGATE            =    0x8,
         NOT_EXTENSIBLE      =   0x10,
         INDEXED             =   0x20,
         BOUND_FUNCTION      =   0x40,
-        VAROBJ              =   0x80,
+        HAD_ELEMENTS_ACCESS =   0x80,
         WATCHED             =  0x100,
         ITERATED_SINGLETON  =  0x200,
         NEW_TYPE_UNKNOWN    =  0x400,
         UNCACHEABLE_PROTO   =  0x800,
-        HAD_ELEMENTS_ACCESS = 0x1000,
 
-        OBJECT_FLAG_MASK    = 0x1ff8
+        // These two flags control which scope a new variables ends up on in the
+        // scope chain. If the variable is "qualified" (i.e., if it was defined
+        // using var, let, or const) then it ends up on the lowest scope in the
+        // chain that has the QUALIFIED_VAROBJ flag set. If it's "unqualified"
+        // (i.e., if it was introduced without any var, let, or const, which
+        // incidentally is an error in strict mode) then it goes on the lowest
+        // scope in the chain with the UNQUALIFIED_VAROBJ flag set (which is
+        // typically the global).
+        QUALIFIED_VAROBJ    = 0x1000,
+        UNQUALIFIED_VAROBJ  = 0x2000,
+
+        OBJECT_FLAG_MASK    = 0x3ff8
     };
 
   private:
     const Class         *clasp_;        /* Class of referring object. */
     HeapPtrObject       parent;         /* Parent of referring object. */
     HeapPtrObject       metadata;       /* Optional holder of metadata about
                                          * the referring object. */
     JSCompartment       *compartment_;  /* Compartment shape belongs to. */
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -53,17 +53,17 @@ InterpreterFrame::global() const
 {
     return scopeChain()->global();
 }
 
 inline JSObject &
 InterpreterFrame::varObj()
 {
     JSObject *obj = scopeChain();
-    while (!obj->isVarObj())
+    while (!obj->isQualifiedVarObj())
         obj = obj->enclosingScope();
     return *obj;
 }
 
 inline JSCompartment *
 InterpreterFrame::compartment() const
 {
     JS_ASSERT(scopeChain()->compartment() == script()->compartment());