Bug 1416066 - Use XPConnect compilation scope for non-cached local scripts with codebase principal when preload cache is enabled. draft
authorimjching <jlim@mozilla.com>
Thu, 28 Jun 2018 21:29:08 -0400
changeset 812490 188718618f7c032d9a9c992a0f2002e3479bab65
parent 812489 9a686ca5316544002ca2f1e9dcfba8a4d76a45d9
child 812491 af51350dd3d39dd91bc46d922bec9e0e89fd8aee
push id114570
push userbmo:jlim@mozilla.com
push dateFri, 29 Jun 2018 14:20:03 +0000
bugs1416066
milestone63.0a1
Bug 1416066 - Use XPConnect compilation scope for non-cached local scripts with codebase principal when preload cache is enabled. When we use the preload cache for 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 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
@@ -73,16 +73,17 @@ public:
 #define LOAD_ERROR_NOURI "Error creating URI (invalid URL scheme?)"
 #define LOAD_ERROR_NOSCHEME "Failed to get URI scheme.  This is bad."
 #define LOAD_ERROR_URI_NOT_LOCAL "Trying to load a non-local URI."
 #define LOAD_ERROR_NOSTREAM  "Error opening input stream (invalid filename?)"
 #define LOAD_ERROR_NOCONTENT "ContentLength not available (not a local URL?)"
 #define LOAD_ERROR_BADCHARSET "Error converting to specified charset"
 #define LOAD_ERROR_NOSPEC "Failed to get URI spec.  This is bad."
 #define LOAD_ERROR_CONTENTTOOBIG "ContentLength is too large"
+#define LOAD_ERROR_NO_COMPILATION_SCOPE "Failed to initialize compilation scope"
 
 mozJSSubScriptLoader::mozJSSubScriptLoader()
 {
 }
 
 mozJSSubScriptLoader::~mozJSSubScriptLoader()
 {
 }
@@ -245,17 +246,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
@@ -499,16 +502,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;
@@ -546,16 +550,34 @@ mozJSSubScriptLoader::ReadScript(nsIURI*
         ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri);
         return false;
     }
 
     nsCString buf;
     rv = NS_ReadInputStreamToString(instream, buf, len);
     NS_ENSURE_SUCCESS(rv, false);
 
+    AutoJSAPI jsapi;
+
+    // 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) {
+        if (!jsapi.Init(xpc::CompilationScope())) {
+            ReportError(cx, LOAD_ERROR_NO_COMPILATION_SCOPE, uri);
+            return false;
+        }
+        cx = jsapi.cx();
+    }
+
     return PrepareScript(uri, cx, targetObj, uriStr, charset,
                          buf.get(), len, wantReturnValue,
                          script);
 }
 
 NS_IMETHODIMP
 mozJSSubScriptLoader::LoadSubScript(const nsAString& url,
                                     HandleValue target,
@@ -715,17 +737,18 @@ 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,
+                        isCodebaseFile && !options.wantReturnValue, &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,