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
--- 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