Bug 1416066 - Use XPConnect compilation scope for some non-cached local scripts with codebase principal when preload cache is enabled. draft
authorimjching <jlim@mozilla.com>
Tue, 03 Jul 2018 21:26:04 -0400
changeset 816197 58650e44e30df89de10b2a61cfff3f0797b4068f
parent 816196 8e9355a75e5d68a0d9ab0f49de0859b5f57e2692
child 816198 190125a38a1bd10c653ece79d8cb356969c9773a
push id115771
push userbmo:jlim@mozilla.com
push dateTue, 10 Jul 2018 17:44:26 +0000
bugs1416066
milestone63.0a1
Bug 1416066 - Use XPConnect compilation scope for some non-cached local scripts with codebase principal when preload cache is enabled. When we use the preload cache for some non-cached local scripts in loadSubScript, we will keep the global that the script was compiled for alive, resulting in a leak. We will compile these chrome:// and resource:// scripts with codebase principal in the XPConnect compilation scope when using mozJSSubScriptLoader to load scripts synchronously. When the script is evaluated, it will be cloned into the target scope to be executed. By compiling the script in a different scope, we can avoid keeping the global that the script was compiled for originally alive. MozReview-Commit-ID: HYSTvmPCbyR
js/xpconnect/loader/mozJSSubScriptLoader.cpp
js/xpconnect/loader/mozJSSubScriptLoader.h
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -132,17 +132,17 @@ ReportError(JSContext* cx, const char* o
     msg.AppendLiteral(": ");
     msg.Append(spec);
     ReportError(cx, msg);
 }
 
 static bool
 PrepareScript(nsIURI* uri,
               JSContext* cx,
-              HandleObject targetObj,
+              bool wantGlobalScript,
               const char* uriStr,
               const nsAString& charset,
               const char* buf,
               int64_t len,
               bool wantReturnValue,
               MutableHandleScript script)
 {
     JS::CompileOptions options(cx);
@@ -159,25 +159,25 @@ PrepareScript(nsIURI* uri,
         JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength,
                                       JS::SourceBufferHolder::GiveOwnership);
 
         if (NS_FAILED(rv)) {
             ReportError(cx, LOAD_ERROR_BADCHARSET, uri);
             return false;
         }
 
-        if (JS_IsGlobalObject(targetObj)) {
+        if (wantGlobalScript) {
             return JS::Compile(cx, options, srcBuf, script);
         }
         return JS::CompileForNonSyntacticScope(cx, options, srcBuf, script);
     }
     // We only use lazy source when no special encoding is specified because
     // the lazy source loader doesn't know the encoding.
     options.setSourceIsLazy(true);
-    if (JS_IsGlobalObject(targetObj)) {
+    if (wantGlobalScript) {
         return JS::Compile(cx, options, buf, len, script);
     }
     return JS::CompileForNonSyntacticScope(cx, options, buf, len, script);
 }
 
 static bool
 EvalScript(JSContext* cx,
            HandleObject targetObj,
@@ -249,17 +249,19 @@ EvalScript(JSContext* cx,
             // This has the side-effect of keeping the global that the script
             // was compiled for alive, too.
             //
             // For most startups, the global in question will be the
             // CompilationScope, since we pre-compile any scripts that were
             // needed during the last startup in that scope. But for startups
             // when a non-cached script is used (e.g., after add-on
             // installation), this may be a Sandbox global, which may be
-            // nuked but held alive by the JSScript.
+            // nuked but held alive by the JSScript. We can avoid this problem
+            // by using a different scope when compiling the script. See
+            // useCompilationScope in ReadScript().
             //
             // In general, this isn't a problem, since add-on Sandboxes which
             // use the script preloader are not destroyed until add-on shutdown,
             // and when add-ons are uninstalled or upgraded, the preloader cache
             // is immediately flushed after shutdown. But it's possible to
             // disable and reenable an add-on without uninstalling it, leading
             // to cached scripts being held alive, and tied to nuked Sandbox
             // globals. Given the unusual circumstances required to trigger
@@ -414,18 +416,18 @@ AsyncScriptLoader::OnStreamComplete(nsII
     RootedScript script(cx);
     nsAutoCString spec;
     nsresult rv = uri->GetSpec(spec);
     NS_ENSURE_SUCCESS(rv, rv);
 
     RootedObject targetObj(cx, mTargetObj);
     RootedObject loadScope(cx, mLoadScope);
 
-    if (!PrepareScript(uri, cx, targetObj, spec.get(), mCharset,
-                       reinterpret_cast<const char*>(aBuf), aLength,
+    if (!PrepareScript(uri, cx, JS_IsGlobalObject(targetObj), spec.get(),
+                       mCharset, reinterpret_cast<const char*>(aBuf), aLength,
                        mWantReturnValue, &script))
     {
         return NS_OK;
     }
 
     JS::Rooted<JS::Value> retval(cx);
     if (EvalScript(cx, targetObj, loadScope, &retval, uri, mCache,
                    mCache && !mWantReturnValue,
@@ -503,16 +505,17 @@ mozJSSubScriptLoader::ReadScriptAsync(ns
 bool
 mozJSSubScriptLoader::ReadScript(nsIURI* uri,
                                  JSContext* cx,
                                  HandleObject targetObj,
                                  const nsAString& charset,
                                  const char* uriStr,
                                  nsIIOService* serv,
                                  bool wantReturnValue,
+                                 bool useCompilationScope,
                                  MutableHandleScript script)
 {
     script.set(nullptr);
 
     // We create a channel and call SetContentType, to avoid expensive MIME type
     // lookups (bug 632490).
     nsCOMPtr<nsIChannel> chan;
     nsCOMPtr<nsIInputStream> instream;
@@ -550,18 +553,32 @@ mozJSSubScriptLoader::ReadScript(nsIURI*
         ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri);
         return false;
     }
 
     nsCString buf;
     rv = NS_ReadInputStreamToString(instream, buf, len);
     NS_ENSURE_SUCCESS(rv, false);
 
-    return PrepareScript(uri, cx, targetObj, uriStr, charset,
-                         buf.get(), len, wantReturnValue,
+    Maybe<JSAutoRealm> ar;
+
+    // Note that when using the ScriptPreloader cache with loadSubScript, there
+    // will be a side-effect of keeping the global that the script was compiled
+    // for alive. See note above in EvalScript().
+    //
+    // This will compile the script in XPConnect compilation scope. When the
+    // script is evaluated, it will be cloned into the target scope to be
+    // executed, avoiding leaks on the first session when we don't have a
+    // startup cache.
+    if (useCompilationScope) {
+        ar.emplace(cx, xpc::CompilationScope());
+    }
+
+    return PrepareScript(uri, cx, JS_IsGlobalObject(targetObj),
+                         uriStr, charset, buf.get(), len, wantReturnValue,
                          script);
 }
 
 NS_IMETHODIMP
 mozJSSubScriptLoader::LoadSubScript(const nsAString& url,
                                     HandleValue target,
                                     const nsAString& charset,
                                     JSContext* cx,
@@ -687,33 +704,35 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
         tmp.AppendLiteral(" -> ");
         tmp.Append(uriStr);
 
         uriStr = tmp;
     }
 
     // Suppress caching if we're compiling as content or if we're loading a
     // blob: URI.
+    bool useCompilationScope = false;
     auto* principal = BasePrincipal::Cast(GetObjectPrincipal(targetObj));
     bool isSystem = principal->Is<SystemPrincipal>();
     if (!isSystem && principal->Is<ContentPrincipal>()) {
         auto* content = principal->As<ContentPrincipal>();
 
         nsAutoCString scheme;
         content->mCodebase->GetScheme(scheme);
 
         // We want to enable caching for scripts with Activity Stream's
         // codebase URLs.
         if (scheme.EqualsLiteral("about")) {
             nsAutoCString filePath;
             content->mCodebase->GetFilePath(filePath);
 
-            isSystem = filePath.EqualsLiteral("home") ||
-                       filePath.EqualsLiteral("newtab") ||
-                       filePath.EqualsLiteral("welcome");
+            useCompilationScope = filePath.EqualsLiteral("home") ||
+                                  filePath.EqualsLiteral("newtab") ||
+                                  filePath.EqualsLiteral("welcome");
+            isSystem = true;
         }
     }
     bool ignoreCache = options.ignoreCache || !isSystem || scheme.EqualsLiteral("blob");
 
     StartupCache* cache = ignoreCache ? nullptr : StartupCache::GetSingleton();
 
     nsAutoCString cachePath;
     SubscriptCachePath(cx, uri, targetObj, cachePath);
@@ -737,17 +756,17 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
     }
 
     if (script) {
         // |script| came from the cache, so don't bother writing it
         // |back there.
         cache = nullptr;
     } else if (!ReadScript(uri, cx, targetObj, options.charset,
                         static_cast<const char*>(uriStr.get()), serv,
-                        options.wantReturnValue, &script)) {
+                        options.wantReturnValue, useCompilationScope, &script)) {
         return NS_OK;
     }
 
     Unused << EvalScript(cx, targetObj, loadScope, retval, uri, !!cache,
                          !ignoreCache && !options.wantReturnValue,
                          &script);
     return NS_OK;
 }
--- a/js/xpconnect/loader/mozJSSubScriptLoader.h
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.h
@@ -31,17 +31,17 @@ public:
     NS_DECL_MOZIJSSUBSCRIPTLOADER
 
 private:
     virtual ~mozJSSubScriptLoader();
 
     bool ReadScript(nsIURI* uri, JSContext* cx, JS::HandleObject targetObj,
                     const nsAString& charset, const char* uriStr,
                     nsIIOService* serv,
-                    bool wantReturnValue,
+                    bool wantReturnValue, bool useCompilationScope,
                     JS::MutableHandleScript script);
 
     nsresult ReadScriptAsync(nsIURI* uri,
                              JS::HandleObject targetObj,
                              JS::HandleObject loadScope,
                              const nsAString& charset,
                              nsIIOService* serv,
                              bool wantReturnValue,