Bug 1398601 - Support target objects in js::ExecuteInJSMEnvironment draft
authorTed Campbell <tcampbell@mozilla.com>
Sun, 10 Sep 2017 14:03:42 -0400
changeset 662645 e8618ba6a5d03b85d386773addd99665ba03fc4b
parent 662043 fd87bb184e299fec695f69bd2977276c25719b98
child 662646 ad6015911256766924742b7d43408ccfaf67c29c
child 663331 4a24a89103e46185190298af5d4fc5b704d40050
child 663460 289d7c8d3491530f01370e15ada334b5300c3d8f
child 664081 cebdd9b2872b4b7d65caa4b5e82cc9077ff27a35
push id79151
push userbmo:tcampbell@mozilla.com
push dateMon, 11 Sep 2017 22:34:59 +0000
bugs1398601
milestone57.0a1
Bug 1398601 - Support target objects in js::ExecuteInJSMEnvironment This allows js::ExecuteInJSMEnvironment to take a target object argument as used by the subscript loader. This adds WithEnvironments with a corresponding lexical on top of the ordinary NonSyntacticVariablesObject environment chain. MozReview-Commit-ID: JhHEfV92Zpv
js/src/builtin/Eval.cpp
js/src/jsapi-tests/testExecuteInJSMEnvironment.cpp
js/src/jsfriendapi.h
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -440,39 +440,34 @@ js::DirectEval(JSContext* cx, HandleValu
 
 bool
 js::IsAnyBuiltinEval(JSFunction* fun)
 {
     return fun->maybeNative() == IndirectEval;
 }
 
 static bool
-ExecuteInNonSyntacticGlobalInternal(JSContext* cx, HandleObject global, HandleScript scriptArg,
-                                    HandleObject varEnv, HandleObject lexEnv)
+ExecuteInExtensibleLexicalEnvironment(JSContext* cx, HandleScript scriptArg, HandleObject env)
 {
     CHECK_REQUEST(cx);
-    assertSameCompartment(cx, global, varEnv, lexEnv);
-    MOZ_ASSERT(global->is<GlobalObject>());
-    MOZ_ASSERT(varEnv->is<NonSyntacticVariablesObject>());
-    MOZ_ASSERT(IsExtensibleLexicalEnvironment(lexEnv));
-    MOZ_ASSERT(lexEnv->enclosingEnvironment() == varEnv);
+    assertSameCompartment(cx, env);
+    MOZ_ASSERT(IsExtensibleLexicalEnvironment(env));
     MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
 
     RootedScript script(cx, scriptArg);
-    Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>());
     if (script->compartment() != cx->compartment()) {
         script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script);
         if (!script)
             return false;
 
         Debugger::onNewScript(cx, script);
     }
 
     RootedValue rval(cx);
-    return ExecuteKernel(cx, script, *lexEnv, UndefinedValue(),
+    return ExecuteKernel(cx, script, *env, UndefinedValue(),
                          NullFramePtr() /* evalInFrame */, rval.address());
 }
 
 JS_FRIEND_API(bool)
 js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScript scriptArg,
                                   MutableHandleObject envArg)
 {
     RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
@@ -480,17 +475,17 @@ js::ExecuteInGlobalAndReturnScope(JSCont
         return false;
 
     // Create lexical environment with |this| == global.
     // NOTE: This is required behavior for Gecko FrameScriptLoader
     RootedObject lexEnv(cx, LexicalEnvironmentObject::createNonSyntactic(cx, varEnv, global));
     if (!lexEnv)
         return false;
 
-    if (!ExecuteInNonSyntacticGlobalInternal(cx, global, scriptArg, varEnv, lexEnv))
+    if (!ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, lexEnv))
         return false;
 
     envArg.set(lexEnv);
     return true;
 }
 
 JS_FRIEND_API(JSObject*)
 js::NewJSMEnvironment(JSContext* cx)
@@ -505,23 +500,60 @@ js::NewJSMEnvironment(JSContext* cx)
         return nullptr;
 
     return varEnv;
 }
 
 JS_FRIEND_API(bool)
 js::ExecuteInJSMEnvironment(JSContext* cx, HandleScript scriptArg, HandleObject varEnv)
 {
+    AutoObjectVector emptyChain(cx);
+    return ExecuteInJSMEnvironment(cx, scriptArg, varEnv, emptyChain);
+}
+
+JS_FRIEND_API(bool)
+js::ExecuteInJSMEnvironment(JSContext* cx, HandleScript scriptArg, HandleObject varEnv,
+                            AutoObjectVector& targetObj)
+{
     assertSameCompartment(cx, varEnv);
     MOZ_ASSERT(cx->compartment()->getNonSyntacticLexicalEnvironment(varEnv));
+    MOZ_DIAGNOSTIC_ASSERT(scriptArg->noScriptRval());
 
-    RootedObject global(cx, &varEnv->global());
-    RootedObject lexEnv(cx, JS_ExtensibleLexicalEnvironment(varEnv));
+    RootedObject env(cx, JS_ExtensibleLexicalEnvironment(varEnv));
 
-    return ExecuteInNonSyntacticGlobalInternal(cx, global, scriptArg, varEnv, lexEnv);
+    // If the Gecko subscript loader specifies target objects, we need to add
+    // them to the environment. These are added after the NSVO environment.
+    if (!targetObj.empty()) {
+        // The environment chain will be as follows:
+        //      GlobalObject / BackstagePass
+        //      LexicalEnvironmentObject[this=global]
+        //      NonSyntacticVariablesObject (the JSMEnvironment)
+        //      LexicalEnvironmentObject[this=nsvo]
+        //      WithEnvironmentObject[target=targetObj]
+        //      LexicalEnvironmentObject[this=targetObj] (*)
+        //
+        //  (*) This environment intentionally intercepts JSOP_GLOBALTHIS, but
+        //  not JSOP_FUNCTIONTHIS (which instead will fallback to the NSVO). I
+        //  don't make the rules, I just record them.
+
+        // Wrap the target objects in WithEnvironments.
+        if (!js::CreateObjectsForEnvironmentChain(cx, targetObj, env, &env))
+            return false;
+
+        // See CreateNonSyntacticEnvironmentChain
+        if (!JSObject::setQualifiedVarObj(cx, env))
+            return false;
+
+        // Create an extensible LexicalEnvironmentObject for target object
+        env = cx->compartment()->getOrCreateNonSyntacticLexicalEnvironment(cx, env);
+        if (!env)
+            return false;
+    }
+
+    return ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, env);
 }
 
 JS_FRIEND_API(JSObject*)
 js::GetJSMEnvironmentOfScriptedCaller(JSContext* cx)
 {
     FrameIter iter(cx);
     if (iter.done())
         return nullptr;
@@ -531,8 +563,16 @@ js::GetJSMEnvironmentOfScriptedCaller(JS
     MOZ_RELEASE_ASSERT(!iter.isWasm());
 
     RootedObject env(cx, iter.environmentChain(cx));
     while (env && !env->is<NonSyntacticVariablesObject>())
         env = env->enclosingEnvironment();
 
     return env;
 }
+
+JS_FRIEND_API(bool)
+js::IsJSMEnvironment(JSObject* obj)
+{
+    // NOTE: This also returns true if the NonSyntacticVariablesObject was
+    // created for reasons other than the JSM loader.
+    return obj->is<NonSyntacticVariablesObject>();
+}
--- a/js/src/jsapi-tests/testExecuteInJSMEnvironment.cpp
+++ b/js/src/jsapi-tests/testExecuteInJSMEnvironment.cpp
@@ -19,16 +19,17 @@ BEGIN_TEST(testExecuteInJSMEnvironment_B
         "eval('this.e = 5');\n"
         "(0,eval)('this.f = 6');\n"
         "(function() { this.g = 7; })();\n"
         "function f_h() { this.h = 8; }; f_h();\n"
         ;
 
     JS::CompileOptions options(cx);
     options.setFileAndLine(__FILE__, __LINE__);
+    options.setNoScriptRval(true);
 
     JS::RootedScript script(cx);
     CHECK(JS::CompileForNonSyntacticScope(cx, options, src, sizeof(src)-1, &script));
 
     JS::RootedObject varEnv(cx, js::NewJSMEnvironment(cx));
     JS::RootedObject lexEnv(cx, JS_ExtensibleLexicalEnvironment(varEnv));
     CHECK(varEnv && varEnv->is<js::NonSyntacticVariablesObject>());
     CHECK(lexEnv && js::IsExtensibleLexicalEnvironment(lexEnv));
@@ -77,16 +78,17 @@ BEGIN_TEST(testExecuteInJSMEnvironment_C
     static const char src[] =
         "var output = callback();\n"
         ;
 
     CHECK(JS_DefineFunctions(cx, global, testFunctions));
 
     JS::CompileOptions options(cx);
     options.setFileAndLine(__FILE__, __LINE__);
+    options.setNoScriptRval(true);
 
     JS::RootedScript script(cx);
     CHECK(JS::CompileForNonSyntacticScope(cx, options, src, sizeof(src)-1, &script));
 
     JS::RootedObject nsvo(cx, js::NewJSMEnvironment(cx));
     CHECK(nsvo);
     CHECK(js::ExecuteInJSMEnvironment(cx, script, nsvo));
 
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2883,26 +2883,79 @@ SetPropertyIgnoringNamedGetter(JSContext
                                JS::Handle<JS::PropertyDescriptor> ownDesc,
                                JS::ObjectOpResult& result);
 
 // 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);
 
-// These functions are only for JSM component loader, please don't use this for anything else!
+// These functions are provided for the JSM component loader in Gecko.
+//
+// A 'JSMEnvironment' refers to an environment chain constructed for JSM loading
+// in a shared global. Internally it is a NonSyntacticVariablesObject with a
+// corresponding extensible LexicalEnvironmentObject that is accessible by
+// JS_ExtensibleLexicalEnvironment. The |this| value of that lexical environment
+// is the NSVO itself.
+//
+// Normal global environment (ES6):     JSM "global" environment:
+//
+//                                      * - extensible lexical environment
+//                                      |   (code runs in this environment;
+//                                      |    `let/const` bindings go here)
+//                                      |
+//                                      * - JSMEnvironment (=== `this`)
+//                                      |   (`var` bindings go here)
+//                                      |
+// * - extensible lexical environment   * - extensible lexical environment
+// |   (code runs in this environment;  |   (empty)
+// |    `let/const` bindings go here)   |
+// |                                    |
+// * - actual global (=== `this`)       * - shared JSM global
+//     (var bindings go here; and           (Object, Math, etc. live here)
+//      Object, Math, etc. live here)
+
+// Allocate a new environment in current compartment that is compatible with JSM
+// shared loading.
 extern JS_FRIEND_API(JSObject*)
 NewJSMEnvironment(JSContext* cx);
 
+// Execute the given script (copied into compartment if necessary) in the given
+// JSMEnvironment. The script must have been compiled for hasNonSyntacticScope.
+// The |jsmEnv| must have been previously allocated by NewJSMEnvironment.
+//
+// NOTE: The associated extensible lexical environment is reused.
 extern JS_FRIEND_API(bool)
-ExecuteInJSMEnvironment(JSContext* cx, JS::HandleScript script, JS::HandleObject nsvo);
-
+ExecuteInJSMEnvironment(JSContext* cx, JS::HandleScript script, JS::HandleObject jsmEnv);
+
+// Additionally, target objects may be specified as required by the Gecko
+// subscript loader. These are wrapped in non-syntactic WithEnvironments and
+// temporarily placed on environment chain.
+//
+// See also: JS::CloneAndExecuteScript(...)
+extern JS_FRIEND_API(bool)
+ExecuteInJSMEnvironment(JSContext* cx, JS::HandleScript script, JS::HandleObject jsmEnv,
+                        JS::AutoObjectVector& targetObj);
+
+// Used by native methods to determine the JSMEnvironment of caller if possible
+// by looking at stack frames. Returns nullptr if top frame isn't a scripted
+// caller in a JSM.
+//
+// NOTE: This may find NonSyntacticVariablesObject generated by other embedding
+// such as a Gecko FrameScript. Caller can check the compartment if needed.
 extern JS_FRIEND_API(JSObject*)
 GetJSMEnvironmentOfScriptedCaller(JSContext* cx);
 
+// Determine if obj is a JSMEnvironment
+//
+// NOTE: This may return true for an NonSyntacticVariablesObject generated by
+// other embedding such as a Gecko FrameScript. Caller can check compartment.
+extern JS_FRIEND_API(bool)
+IsJSMEnvironment(JSObject* obj);
+
 
 #if defined(XP_WIN) && defined(_WIN64)
 // Parameters use void* types to avoid #including windows.h. The return value of
 // this function is returned from the exception handler.
 typedef long
 (*JitExceptionHandler)(void* exceptionRecord,  // PEXECTION_RECORD
                        void* context);         // PCONTEXT