Issue #1691 - Part 5b: Stop modules from entraining the top-level JSScript. https://bugzilla.mozilla.org/show_bug.cgi?id=1489477

(cherry picked from commit 6ce0af856186e65fb4987daf4e06dc68fb3f7483)
This commit is contained in:
Brian Smith 2023-04-09 10:25:44 -05:00 • committed by roytam1
commit 5c09a3b3cc
4 changed files with 71 additions and 21 deletions

View file

@ -835,18 +835,22 @@ ModuleObject::fixEnvironmentsAfterCompartmentMerge()
AssertModuleScopesMatch(this); AssertModuleScopesMatch(this);
} }
bool JSScript*
ModuleObject::hasScript() const ModuleObject::maybeScript() const
{ {
// When modules are parsed via the Reflect.parse() API, the module object Value value = getReservedSlot(ScriptSlot);
// doesn't have a script. if (value.isUndefined())
return !getReservedSlot(ScriptSlot).isUndefined(); return nullptr;
return value.toGCThing()->as<JSScript>();
} }
JSScript* JSScript*
ModuleObject::script() const ModuleObject::script() const
{ {
return static_cast<JSScript*>(getReservedSlot(ScriptSlot).toPrivate()); JSScript* ptr = maybeScript();
MOZ_RELEASE_ASSERT(ptr);
return ptr;
} }
static inline void static inline void
@ -906,8 +910,8 @@ ModuleObject::enclosingScope() const
ModuleObject::trace(JSTracer* trc, JSObject* obj) ModuleObject::trace(JSTracer* trc, JSObject* obj)
{ {
ModuleObject& module = obj->as<ModuleObject>(); ModuleObject& module = obj->as<ModuleObject>();
if (module.hasScript()) { JSScript* script = module.maybeScript();
JSScript* script = module.script(); if (script) {
TraceManuallyBarrieredEdge(trc, &script, "Module script"); TraceManuallyBarrieredEdge(trc, &script, "Module script");
module.setReservedSlot(ScriptSlot, PrivateValue(script)); module.setReservedSlot(ScriptSlot, PrivateValue(script));
} }
@ -976,6 +980,11 @@ ModuleObject::execute(JSContext* cx, HandleModuleObject self, MutableHandleValue
#endif #endif
RootedScript script(cx, self->script()); 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()); RootedModuleEnvironmentObject scope(cx, self->environment());
if (!scope) { if (!scope) {
JS_ReportErrorASCII(cx, "Module declarations have not yet been instantiated"); JS_ReportErrorASCII(cx, "Module declarations have not yet been instantiated");

View file

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

View file

@ -38,6 +38,18 @@ Reflect.Loader = new class {
module.declarationInstantiation(); module.declarationInstantiation();
return module.evaluation(); return module.evaluation();
} }
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;
}
}; };
setModuleResolveHook((referencingInfo, requestName) => { setModuleResolveHook((referencingInfo, requestName) => {

View file

@ -2830,7 +2830,7 @@ DisassembleToSprinter(JSContext* cx, unsigned argc, Value* vp, Sprinter* sprinte
RootedScript script(cx); RootedScript script(cx);
RootedValue value(cx, p.argv[i]); RootedValue value(cx, p.argv[i]);
if (value.isObject() && value.toObject().is<ModuleObject>()) if (value.isObject() && value.toObject().is<ModuleObject>())
script = value.toObject().as<ModuleObject>().script(); script = value.toObject().as<ModuleObject>().maybeScript();
else else
script = ValueToScript(cx, value, fun.address()); script = ValueToScript(cx, value, fun.address());
if (!script) if (!script)
@ -4132,23 +4132,48 @@ ShellGetModulePrivate(JSContext* cx, unsigned argc, Value* vp)
return true; return true;
} }
ShellModuleMetadataHook(JSContext* cx, HandleObject module, HandleObject metaObject) static bool
SetModuleMetadataHook(JSContext* cx, unsigned argc, Value* vp)
{ {
// For the shell, just use the script's filename as the base URL. CallArgs args = CallArgsFromVp(argc, vp);
RootedScript script(cx, module->as<ModuleObject>().script()); if (args.length() != 1) {
const char* filename = script->scriptSource()->filename(); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
MOZ_ASSERT(filename); "setModuleMetadataHook", "0", "s");
RootedString url(cx, NewStringCopyZ<CanGC>(cx, filename));
if (!url)
return false; 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; return false;
}
Handle<GlobalObject*> global = cx->global();
global->setReservedSlot(GlobalAppSlotModuleMetadataHook, args[0]);
args.rval().setUndefined();
return true; 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 static bool
SetModuleDynamicImportHook(JSContext* cx, unsigned argc, Value* vp) SetModuleDynamicImportHook(JSContext* cx, unsigned argc, Value* vp)
{ {
@ -5616,7 +5641,11 @@ DumpScopeChain(JSContext* cx, unsigned argc, Value* vp)
} }
script = JSFunction::getOrCreateScript(cx, fun); script = JSFunction::getOrCreateScript(cx, fun);
} else { } 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(); script->bodyScope()->dump();
@ -7451,7 +7480,7 @@ ProcessArgs(JSContext* cx, OptionParser* op)
if (const char* path = op->getStringOption("module-load-path")) if (const char* path = op->getStringOption("module-load-path"))
moduleLoadPath = path; moduleLoadPath = path;
if (!modulePaths.empty() && !InitModuleLoader(cx)) if (!InitModuleLoader(cx))
return false; return false;
while (!filePaths.empty() || !codeChunks.empty() || !modulePaths.empty()) { while (!filePaths.empty() || !codeChunks.empty() || !modulePaths.empty()) {