Bug 1144743 part 5. Set the hasPollutedGlobalScope flag correctly when cloning functions. r=luke
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 20 Mar 2015 00:34:07 -0400
changeset 263532 4fbc8769b1e5a456765d2ccca82e1a5239bbd288
parent 263531 f1962bdd6fb8ffa5461b12aab7e411f172aa009f
child 263533 0e1e34538124508059d9e96b2c23fb989e7dedaf
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1144743
milestone39.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 1144743 part 5. Set the hasPollutedGlobalScope flag correctly when cloning functions. r=luke
js/src/frontend/BytecodeCompiler.cpp
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsobj.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/Stack.cpp
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -142,16 +142,17 @@ MaybeCheckEvalFreeVariables(ExclusiveCon
     return true;
 }
 
 static inline bool
 CanLazilyParse(ExclusiveContext *cx, const ReadOnlyCompileOptions &options)
 {
     return options.canLazilyParse &&
         options.compileAndGo &&
+        !options.hasPollutedGlobalScope &&
         !cx->compartment()->options().discardSource() &&
         !options.sourceIsLazy;
 }
 
 static void
 MarkFunctionsWithinEvalScript(JSScript *script)
 {
     // Mark top level functions in an eval script as being within an eval and,
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -2076,33 +2076,55 @@ js::NewFunctionWithProto(ExclusiveContex
     if (allocKind == JSFunction::ExtendedFinalizeKind)
         fun->initializeExtended();
     fun->initAtom(atom);
 
     return fun;
 }
 
 bool
-js::CloneFunctionObjectUseSameScript(JSCompartment *compartment, HandleFunction fun)
+js::CloneFunctionObjectUseSameScript(JSCompartment *compartment, HandleFunction fun,
+                                     HandleObject newParent)
 {
-    return compartment == fun->compartment() &&
-           !fun->isSingleton() &&
-           !ObjectGroup::useSingletonForClone(fun);
+    if (compartment != fun->compartment() ||
+        fun->isSingleton() ||
+        ObjectGroup::useSingletonForClone(fun))
+    {
+        return false;
+    }
+
+    if (newParent->is<GlobalObject>())
+        return true;
+
+    // Don't need to clone the script if newParent is not a valid lexical scope
+    // chain terminator, since in that case we have some actual scope objects on
+    // our scope chain and whatnot; whoever put them there should be responsible
+    // for setting our script's flags appropriately.  We hit this case for
+    // JSOP_LAMBDA, for example.
+    if (!IsValidTerminatingScope(newParent))
+        return true;
+
+    // We need to clone the script if we're interpreted and not already marked
+    // as having a polluted scope.  If we're lazy, go ahead and clone the
+    // script; see the big comment at the end of CloneScript for the explanation
+    // of what's going on there.
+    return !fun->isInterpreted() ||
+           (fun->hasScript() && fun->nonLazyScript()->hasPollutedGlobalScope());
 }
 
 JSFunction *
 js::CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent,
                         gc::AllocKind allocKind,
                         NewObjectKind newKindArg /* = GenericObject */,
                         HandleObject proto)
 {
     MOZ_ASSERT(parent);
     MOZ_ASSERT(!fun->isBoundFunction());
 
-    bool useSameScript = CloneFunctionObjectUseSameScript(cx->compartment(), fun);
+    bool useSameScript = CloneFunctionObjectUseSameScript(cx->compartment(), fun, parent);
 
     if (!useSameScript && fun->isInterpretedLazy()) {
         JSAutoCompartment ac(cx, fun);
         if (!fun->getOrCreateScript(cx))
             return nullptr;
     }
 
     NewObjectKind newKind = useSameScript ? newKindArg : SingletonObject;
@@ -2171,23 +2193,29 @@ js::CloneFunctionObject(JSContext *cx, H
         if (fun->getProto() == clone->getProto())
             clone->setGroup(fun->group());
         return clone;
     }
 
     RootedFunction cloneRoot(cx, clone);
 
     /*
-     * Across compartments we have to clone the script for interpreted
-     * functions. Cross-compartment cloning only happens via JSAPI
-     * (JS::CloneFunctionObject) which dynamically ensures that 'script' has
-     * no enclosing lexical scope (only the global scope).
+     * Across compartments or if we have to introduce a polluted scope we have
+     * to clone the script for interpreted functions. Cross-compartment cloning
+     * only happens via JSAPI (JS::CloneFunctionObject) which dynamically
+     * ensures that 'script' has no enclosing lexical scope (only the global
+     * scope or other non-lexical scope).
      */
-    if (cloneRoot->isInterpreted() && !CloneFunctionScript(cx, fun, cloneRoot, newKindArg))
+    PollutedGlobalScopeOption globalScopeOption = parent->is<GlobalObject>() ?
+        HasCleanGlobalScope : HasPollutedGlobalScope;
+    if (cloneRoot->isInterpreted() &&
+        !CloneFunctionScript(cx, fun, cloneRoot, globalScopeOption, newKindArg))
+    {
         return nullptr;
+    }
 
     return cloneRoot;
 }
 
 /*
  * Return an atom for use as the name of a builtin method with the given
  * property id.
  *
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -579,17 +579,18 @@ class FunctionExtended : public JSFuncti
   private:
     friend class JSFunction;
 
     /* Reserved slots available for storage by particular native functions. */
     HeapValue extendedSlots[NUM_EXTENDED_SLOTS];
 };
 
 extern bool
-CloneFunctionObjectUseSameScript(JSCompartment *compartment, HandleFunction fun);
+CloneFunctionObjectUseSameScript(JSCompartment *compartment, HandleFunction fun,
+                                 HandleObject newParent);
 
 extern JSFunction *
 CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent,
                     gc::AllocKind kind = JSFunction::FinalizeKind,
                     NewObjectKind newKindArg = GenericObject,
                     HandleObject proto = NullPtr());
 
 extern bool
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2146,17 +2146,17 @@ js::CloneObjectLiteral(JSContext *cx, Ha
     RootedId id(cx);
     RootedValue value(cx);
     for (size_t i = 0; i < length; i++) {
         // The only markable values in copy on write arrays are atoms, which
         // can be freely copied between compartments.
         value = srcArray->getDenseElement(i);
         MOZ_ASSERT_IF(value.isMarkable(),
                       value.toGCThing()->isTenured() &&
-                      cx->runtime()->isAtomsZone(value.toGCThing()->asTenured().zone()));
+                      cx->runtime()->isAtomsZone(value.toGCThing()->asTenured().zoneFromAnyThread()));
 
         id = INT_TO_JSID(i);
         if (!DefineProperty(cx, res, id, value, nullptr, nullptr, JSPROP_ENUMERATE))
             return nullptr;
     }
 
     if (!ObjectElements::MakeElementsCopyOnWrite(cx, res))
         return nullptr;
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -2954,16 +2954,17 @@ static inline T *
 Rebase(JSScript *dst, JSScript *src, T *srcp)
 {
     size_t off = reinterpret_cast<uint8_t *>(srcp) - src->data;
     return reinterpret_cast<T *>(dst->data + off);
 }
 
 JSScript *
 js::CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript src,
+                PollutedGlobalScopeOption polluted /* = HasCleanGlobalScope */,
                 NewObjectKind newKind /* = GenericObject */)
 {
     /* NB: Keep this in sync with XDRScript. */
 
     /* Some embeddings are not careful to use ExposeObjectToActiveJS as needed. */
     MOZ_ASSERT(!src->sourceObject()->asTenured().isMarked(gc::GRAY));
 
     uint32_t nconsts   = src->hasConsts()   ? src->consts()->length   : 0;
@@ -3077,17 +3078,18 @@ js::CloneScript(JSContext *cx, HandleObj
             return nullptr;
     }
 
     /* Now that all fallible allocation is complete, create the GC thing. */
 
     CompileOptions options(cx);
     options.setMutedErrors(src->mutedErrors())
            .setCompileAndGo(src->compileAndGo())
-           .setHasPollutedScope(src->hasPollutedGlobalScope())
+           .setHasPollutedScope(src->hasPollutedGlobalScope() ||
+                                polluted == HasPollutedGlobalScope)
            .setSelfHostingMode(src->selfHosted())
            .setNoScriptRval(src->noScriptRval())
            .setVersion(src->getVersion());
 
     RootedScript dst(cx, JSScript::Create(cx, enclosingScope, src->savedCallerFun(),
                                           options, src->staticLevel(),
                                           sourceObject, src->sourceStart(), src->sourceEnd()));
     if (!dst) {
@@ -3148,22 +3150,39 @@ js::CloneScript(JSContext *cx, HandleObj
         for (unsigned i = 0; i < nregexps; ++i)
             vector[i].init(&regexps[i]->as<NativeObject>());
     }
     if (ntrynotes != 0)
         dst->trynotes()->vector = Rebase<JSTryNote>(dst, src, src->trynotes()->vector);
     if (nblockscopes != 0)
         dst->blockScopes()->vector = Rebase<BlockScopeNote>(dst, src, src->blockScopes()->vector);
 
+    /*
+     * Function delazification assumes that their script does not have a
+     * polluted global scope.  We ensure that as follows:
+     *
+     * 1) Initial parsing only creates lazy functions if
+     *    !hasPollutedGlobalScope.
+     * 2) Cloning a lazy function into a non-global scope will always require
+     *    that its script be cloned.  See comments in
+     *    CloneFunctionObjectUseSameScript.
+     * 3) Cloning a script never sets a lazyScript on the clone, so the function
+     *    cannot be relazified.
+     *
+     * If you decide that lazy functions should be supported with a polluted
+     * global scope, make sure delazification can deal.
+     */
+    MOZ_ASSERT_IF(dst->hasPollutedGlobalScope(), !dst->maybeLazyScript());
+    MOZ_ASSERT_IF(dst->hasPollutedGlobalScope(), !dst->isRelazifiable());
     return dst;
 }
 
 bool
 js::CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone,
-                        NewObjectKind newKind /* = GenericObject */)
+                        PollutedGlobalScopeOption polluted, NewObjectKind newKind)
 {
     MOZ_ASSERT(clone->isInterpreted());
 
     RootedScript script(cx, clone->nonLazyScript());
     MOZ_ASSERT(script);
     MOZ_ASSERT(script->compartment() == original->compartment());
 
     // The only scripts with enclosing static scopes that may be cloned across
@@ -3175,17 +3194,17 @@ js::CloneFunctionScript(JSContext *cx, H
                    !scope->as<StaticEvalObject>().isStrict());
         scope = StaticEvalObject::create(cx, NullPtr());
         if (!scope)
             return false;
     }
 
     clone->mutableScript().init(nullptr);
 
-    JSScript *cscript = CloneScript(cx, scope, clone, script, newKind);
+    JSScript *cscript = CloneScript(cx, scope, clone, script, polluted, newKind);
     if (!cscript)
         return false;
 
     clone->setScript(cscript);
     cscript->setFunction(clone);
 
     script = clone->nonLazyScript();
     Debugger::onNewScript(cx, script);
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -743,18 +743,24 @@ GeneratorKindFromBits(unsigned val) {
  * subsequent set-up of owning function or script object and then call
  * CallNewScriptHook.
  */
 template<XDRMode mode>
 bool
 XDRScript(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enclosingScript,
           HandleFunction fun, MutableHandleScript scriptp);
 
+enum PollutedGlobalScopeOption {
+    HasPollutedGlobalScope,
+    HasCleanGlobalScope
+};
+
 JSScript *
 CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript script,
+            PollutedGlobalScopeOption polluted = HasCleanGlobalScope,
             NewObjectKind newKind = GenericObject);
 
 template<XDRMode mode>
 bool
 XDRLazyScript(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enclosingScript,
               HandleFunction fun, MutableHandle<LazyScript *> lazy);
 
 /*
@@ -770,17 +776,18 @@ class JSScript : public js::gc::TenuredC
 {
     template <js::XDRMode mode>
     friend
     bool
     js::XDRScript(js::XDRState<mode> *xdr, js::HandleObject enclosingScope, js::HandleScript enclosingScript,
                   js::HandleFunction fun, js::MutableHandleScript scriptp);
 
     friend JSScript *
-    js::CloneScript(JSContext *cx, js::HandleObject enclosingScope, js::HandleFunction fun, js::HandleScript src,
+    js::CloneScript(JSContext *cx, js::HandleObject enclosingScope, js::HandleFunction fun,
+                    js::HandleScript src, js::PollutedGlobalScopeOption polluted,
                     js::NewObjectKind newKind);
 
   public:
     //
     // We order fields according to their size in order to avoid wasting space
     // for alignment.
     //
 
@@ -2163,17 +2170,17 @@ enum LineOption {
 extern void
 DescribeScriptedCallerForCompilation(JSContext *cx, MutableHandleScript maybeScript,
                                      const char **file, unsigned *linenop,
                                      uint32_t *pcOffset, bool *mutedErrors,
                                      LineOption opt = NOT_CALLED_FROM_JSOP_EVAL);
 
 bool
 CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone,
-                    NewObjectKind newKind = GenericObject);
+                    PollutedGlobalScopeOption polluted, NewObjectKind newKind);
 
 } /* namespace js */
 
 // JS::ubi::Nodes can point to js::LazyScripts; they're js::gc::Cell instances
 // with no associated compartment.
 namespace JS {
 namespace ubi {
 template<> struct Concrete<js::LazyScript> : TracerConcrete<js::LazyScript> { };
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1106,17 +1106,18 @@ FrameIter::matchCallee(JSContext *cx, Ha
         currentCallee->nargs() != fun->nargs())
     {
         return false;
     }
 
     // Use the same condition as |js::CloneFunctionObject|, to know if we should
     // expect both functions to have the same JSScript. If so, and if they are
     // different, then they cannot be equal.
-    bool useSameScript = CloneFunctionObjectUseSameScript(fun->compartment(), currentCallee);
+    RootedObject global(cx, &fun->global());
+    bool useSameScript = CloneFunctionObjectUseSameScript(fun->compartment(), currentCallee, global);
     if (useSameScript &&
         (currentCallee->hasScript() != fun->hasScript() ||
          currentCallee->nonLazyScript() != fun->nonLazyScript()))
     {
         return false;
     }
 
     // If none of the previous filters worked, then take the risk of