Bug 1489477 - Stop modules from entraining the top-level JSScript r=sfink

This commit is contained in:
Jon Coppeard 2018-10-11 18:33:57 +01:00
parent f7ab18d265
commit 056511891c
6 changed files with 89 additions and 24 deletions

View File

@ -989,18 +989,22 @@ ModuleObject::fixEnvironmentsAfterCompartmentMerge()
AssertModuleScopesMatch(this);
}
bool
ModuleObject::hasScript() const
JSScript*
ModuleObject::maybeScript() const
{
// When modules are parsed via the Reflect.parse() API, the module object
// doesn't have a script.
return !getReservedSlot(ScriptSlot).isUndefined();
Value value = getReservedSlot(ScriptSlot);
if (value.isUndefined())
return nullptr;
return value.toGCThing()->as<JSScript>();
}
JSScript*
ModuleObject::script() const
{
return getReservedSlot(ScriptSlot).toGCThing()->as<JSScript>();
JSScript* ptr = maybeScript();
MOZ_RELEASE_ASSERT(ptr);
return ptr;
}
static inline void
@ -1157,6 +1161,11 @@ ModuleObject::execute(JSContext* cx, HandleModuleObject self, MutableHandleValue
#endif
RootedScript script(cx, self->script());
// The top-level script if a module is only ever executed once. Clear the
// reference to prevent us keeping this alive unnecessarily.
self->setReservedSlot(ScriptSlot, UndefinedValue());
RootedModuleEnvironmentObject scope(cx, self->environment());
if (!scope) {
JS_ReportErrorASCII(cx, "Module declarations have not yet been instantiated");

View File

@ -302,6 +302,7 @@ class ModuleObject : public NativeObject
#endif
void fixEnvironmentsAfterCompartmentMerge();
JSScript* maybeScript() const;
JSScript* script() const;
Scope* enclosingScope() const;
ModuleEnvironmentObject& initialEnvironment() const;
@ -348,7 +349,6 @@ class ModuleObject : public NativeObject
static void trace(JSTracer* trc, JSObject* obj);
static void finalize(js::FreeOp* fop, JSObject* obj);
bool hasScript() const;
bool hasImportBindings() const;
FunctionDeclarationVector* functionDeclarations();
};

View File

@ -3120,6 +3120,9 @@ extern JS_PUBLIC_API(void)
GetRequestedModuleSourcePos(JSContext* cx, JS::HandleValue requestedModuleObject,
uint32_t* lineNumber, uint32_t* columnNumber);
/*
* Get the top-level script for a module which has not yet been executed.
*/
extern JS_PUBLIC_API(JSScript*)
GetModuleScript(JS::HandleObject moduleRecord);

View File

@ -3,7 +3,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global getModuleLoadPath setModuleLoadHook setModuleResolveHook parseModule os */
/* global getModuleLoadPath setModuleLoadHook setModuleResolveHook setModuleMetadataHook */
/* global parseModule os */
// A basic synchronous module loader for testing the shell.
{
@ -173,6 +174,18 @@ const ReflectLoader = new class {
let path = this.resolve(name, null);
return this.loadAndExecute(path);
}
populateImportMeta(module, metaObject) {
// For the shell, use the script's filename as the base URL.
let path;
if (ReflectApply(MapPrototypeHas, this.modulePaths, [module])) {
path = ReflectApply(MapPrototypeGet, this.modulePaths, [module]);
} else {
path = "(unknown)";
}
metaObject.url = path;
}
};
setModuleLoadHook((path) => ReflectLoader.importRoot(path));
@ -182,4 +195,9 @@ setModuleResolveHook((module, requestName) => {
return ReflectLoader.loadAndParse(path);
});
setModuleMetadataHook((module, metaObject) => {
ReflectLoader.populateImportMeta(module, metaObject);
});
}

View File

@ -192,6 +192,7 @@ enum GlobalAppSlot
{
GlobalAppSlotModuleLoadHook, // Shell-specific; load a module graph
GlobalAppSlotModuleResolveHook, // HostResolveImportedModule
GlobalAppSlotModuleMetadataHook, // HostPopulateImportMeta
GlobalAppSlotCount
};
static_assert(GlobalAppSlotCount <= JSCLASS_GLOBAL_APPLICATION_SLOTS,
@ -3275,7 +3276,7 @@ DisassembleToSprinter(JSContext* cx, unsigned argc, Value* vp, Sprinter* sprinte
RootedScript script(cx);
RootedValue value(cx, p.argv[i]);
if (value.isObject() && value.toObject().is<ModuleObject>()) {
script = value.toObject().as<ModuleObject>().script();
script = value.toObject().as<ModuleObject>().maybeScript();
} else {
script = TestingFunctionArgumentToScript(cx, value, fun.address());
}
@ -4750,25 +4751,47 @@ CallModuleResolveHook(JSContext* cx, HandleObject module, HandleString specifier
}
static bool
ShellModuleMetadataHook(JSContext* cx, HandleObject module, HandleObject metaObject)
SetModuleMetadataHook(JSContext* cx, unsigned argc, Value* vp)
{
// For the shell, just use the script's filename as the base URL.
RootedScript script(cx, module->as<ModuleObject>().script());
const char* filename = script->scriptSource()->filename();
MOZ_ASSERT(filename);
RootedString url(cx, NewStringCopyZ<CanGC>(cx, filename));
if (!url) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
"setModuleMetadataHook", "0", "s");
return false;
}
if (!JS_DefineProperty(cx, metaObject, "url", url, JSPROP_ENUMERATE)) {
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
const char* typeName = InformalValueTypeName(args[0]);
JS_ReportErrorASCII(cx, "expected hook function, got %s", typeName);
return false;
}
Handle<GlobalObject*> global = cx->global();
global->setReservedSlot(GlobalAppSlotModuleMetadataHook, args[0]);
args.rval().setUndefined();
return true;
}
static bool
CallModuleMetadataHook(JSContext* cx, HandleObject module, HandleObject metaObject)
{
Handle<GlobalObject*> global = cx->global();
RootedValue hookValue(cx, global->getReservedSlot(GlobalAppSlotModuleMetadataHook));
if (hookValue.isUndefined()) {
JS_ReportErrorASCII(cx, "Module metadata hook not set");
return false;
}
MOZ_ASSERT(hookValue.toObject().is<JSFunction>());
JS::AutoValueArray<2> args(cx);
args[0].setObject(*module);
args[1].setObject(*metaObject);
RootedValue dummy(cx);
return JS_CallFunctionValue(cx, nullptr, hookValue, args, &dummy);
}
static bool
GetModuleLoadPath(JSContext* cx, unsigned argc, Value* vp)
{
@ -7258,7 +7281,11 @@ DumpScopeChain(JSContext* cx, unsigned argc, Value* vp)
}
script = JSFunction::getOrCreateScript(cx, fun);
} else {
script = obj->as<ModuleObject>().script();
script = obj->as<ModuleObject>().maybeScript();
if (!script) {
JS_ReportErrorASCII(cx, "module does not have an associated script");
return false;
}
}
script->bodyScope()->dump();
@ -7978,6 +8005,12 @@ static const JSFunctionSpecWithHelp shell_functions[] = {
" This hook is used to look up a previously loaded module object. It should\n"
" be implemented by the module loader."),
JS_FN_HELP("setModuleMetadataHook", SetModuleMetadataHook, 1, 0,
"setModuleMetadataHook(function(module) {})",
" Set the HostPopulateImportMeta hook to |function|.\n"
" This hook is used to create the metadata object returned by import.meta for\n"
" a module. It should be implemented by the module loader."),
JS_FN_HELP("getModuleLoadPath", GetModuleLoadPath, 0, 0,
"getModuleLoadPath()",
" Return any --module-load-path argument passed to the shell. Used by the\n"
@ -9687,7 +9720,7 @@ ProcessArgs(JSContext* cx, OptionParser* op)
return false;
}
if (!modulePaths.empty() && !InitModuleLoader(cx)) {
if (!InitModuleLoader(cx)) {
return false;
}
@ -10689,7 +10722,7 @@ main(int argc, char** argv, char** envp)
js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
JS::SetModuleResolveHook(cx->runtime(), CallModuleResolveHook);
JS::SetModuleMetadataHook(cx->runtime(), ShellModuleMetadataHook);
JS::SetModuleMetadataHook(cx->runtime(), CallModuleMetadataHook);
result = Shell(cx, &op, envp);

View File

@ -154,11 +154,13 @@ AssertScopeMatchesEnvironment(Scope* scope, JSObject* originalEnv)
MOZ_CRASH("NonSyntactic should not have a syntactic environment");
break;
case ScopeKind::Module:
MOZ_ASSERT(env->as<ModuleEnvironmentObject>().module().script() ==
si.scope()->as<ModuleScope>().script());
case ScopeKind::Module: {
ModuleObject* module = &env->as<ModuleEnvironmentObject>().module();
MOZ_ASSERT_IF(module->maybeScript(),
module->script() == si.scope()->as<ModuleScope>().script());
env = &env->as<ModuleEnvironmentObject>().enclosingEnvironment();
break;
}
case ScopeKind::WasmInstance:
env = &env->as<WasmInstanceEnvironmentObject>().enclosingEnvironment();