/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * 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/. */ /* JavaScript modules (as in, the syntactic construct) implementation. */ #include "vm/Modules.h" #include "mozilla/Assertions.h" // MOZ_ASSERT #include "mozilla/ScopeExit.h" #include "mozilla/Utf8.h" // mozilla::Utf8Unit #include // uint32_t #include "jstypes.h" // JS_PUBLIC_API #include "builtin/JSON.h" // js::ParseJSONWithReviver #include "builtin/ModuleObject.h" // js::FinishDynamicModuleImport, js::{,Requested}ModuleObject #include "builtin/Promise.h" // js::CreatePromiseObjectForAsync, js::AsyncFunctionReturned #include "ds/Sort.h" #include "frontend/BytecodeCompiler.h" // js::frontend::CompileModule #include "frontend/FrontendContext.h" // js::AutoReportFrontendContext #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin #include "js/Context.h" // js::AssertHeapIsIdle #include "js/ErrorReport.h" // JSErrorBase #include "js/RootingAPI.h" // JS::MutableHandle #include "js/Value.h" // JS::Value #include "vm/EnvironmentObject.h" // js::ModuleEnvironmentObject #include "vm/JSAtomUtils.h" // AtomizeString #include "vm/JSContext.h" // CHECK_THREAD, JSContext #include "vm/JSObject.h" // JSObject #include "vm/List.h" // ListObject #include "vm/Runtime.h" // JSRuntime #include "vm/JSAtomUtils-inl.h" // AtomToId #include "vm/JSContext-inl.h" // JSContext::{c,releaseC}heck #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; using mozilla::Utf8Unit; static bool ModuleLink(JSContext* cx, Handle module); static bool ModuleEvaluate(JSContext* cx, Handle module, MutableHandle result); static bool SyntheticModuleEvaluate(JSContext* cx, Handle module, MutableHandle result); //////////////////////////////////////////////////////////////////////////////// // Public API JS_PUBLIC_API JS::ModuleResolveHook JS::GetModuleResolveHook(JSRuntime* rt) { AssertHeapIsIdle(); return rt->moduleResolveHook; } JS_PUBLIC_API void JS::SetModuleResolveHook(JSRuntime* rt, ModuleResolveHook func) { AssertHeapIsIdle(); rt->moduleResolveHook = func; } JS_PUBLIC_API JS::ModuleMetadataHook JS::GetModuleMetadataHook(JSRuntime* rt) { AssertHeapIsIdle(); return rt->moduleMetadataHook; } JS_PUBLIC_API void JS::SetModuleMetadataHook(JSRuntime* rt, ModuleMetadataHook func) { AssertHeapIsIdle(); rt->moduleMetadataHook = func; } JS_PUBLIC_API JS::ModuleDynamicImportHook JS::GetModuleDynamicImportHook( JSRuntime* rt) { AssertHeapIsIdle(); return rt->moduleDynamicImportHook; } JS_PUBLIC_API void JS::SetModuleDynamicImportHook( JSRuntime* rt, ModuleDynamicImportHook func) { AssertHeapIsIdle(); rt->moduleDynamicImportHook = func; } JS_PUBLIC_API bool JS::FinishDynamicModuleImport( JSContext* cx, Handle evaluationPromise, Handle referencingPrivate, Handle moduleRequest, Handle promise) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->check(referencingPrivate, promise); return js::FinishDynamicModuleImport( cx, evaluationPromise, referencingPrivate, moduleRequest, promise); } template static JSObject* CompileModuleHelper(JSContext* cx, const JS::ReadOnlyCompileOptions& options, JS::SourceText& srcBuf) { MOZ_ASSERT(!cx->zone()->isAtomsZone()); AssertHeapIsIdle(); CHECK_THREAD(cx); JS::Rooted mod(cx); { AutoReportFrontendContext fc(cx); mod = frontend::CompileModule(cx, &fc, options, srcBuf); } return mod; } JS_PUBLIC_API JSObject* JS::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options, SourceText& srcBuf) { return CompileModuleHelper(cx, options, srcBuf); } JS_PUBLIC_API JSObject* JS::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options, SourceText& srcBuf) { return CompileModuleHelper(cx, options, srcBuf); } JS_PUBLIC_API JSObject* JS::CompileJsonModule( JSContext* cx, const ReadOnlyCompileOptions& options, SourceText& srcBuf) { MOZ_ASSERT(!cx->zone()->isAtomsZone()); AssertHeapIsIdle(); CHECK_THREAD(cx); JS::RootedValue jsonValue(cx); auto charRange = mozilla::Range(srcBuf.get(), srcBuf.length()); if (!js::ParseJSONWithReviver(cx, charRange, NullHandleValue, &jsonValue)) { return nullptr; } Rooted exportNames(cx); if (!exportNames.reserve(1)) { return nullptr; } exportNames.infallibleAppend(cx->names().default_); Rooted moduleObject( cx, ModuleObject::createSynthetic(cx, &exportNames)); if (!moduleObject) { return nullptr; } Rooted> exportValues(cx, GCVector(cx)); if (!exportValues.reserve(1)) { return nullptr; } exportValues.infallibleAppend(jsonValue); if (!ModuleObject::createSyntheticEnvironment(cx, moduleObject, exportValues)) { return nullptr; } return moduleObject; } JS_PUBLIC_API void JS::SetModulePrivate(JSObject* module, const Value& value) { JSRuntime* rt = module->zone()->runtimeFromMainThread(); module->as().scriptSourceObject()->setPrivate(rt, value); } JS_PUBLIC_API void JS::ClearModulePrivate(JSObject* module) { // |module| may be gray, be careful not to create edges to it. JSRuntime* rt = module->zone()->runtimeFromMainThread(); module->as().scriptSourceObject()->clearPrivate(rt); } JS_PUBLIC_API JS::Value JS::GetModulePrivate(JSObject* module) { return module->as().scriptSourceObject()->getPrivate(); } JS_PUBLIC_API bool JS::ModuleLink(JSContext* cx, Handle moduleArg) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->releaseCheck(moduleArg); return ::ModuleLink(cx, moduleArg.as()); } JS_PUBLIC_API bool JS::ModuleEvaluate(JSContext* cx, Handle moduleRecord, MutableHandle rval) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->releaseCheck(moduleRecord); cx->isEvaluatingModule++; auto guard = mozilla::MakeScopeExit([cx] { MOZ_ASSERT(cx->isEvaluatingModule != 0); cx->isEvaluatingModule--; }); if (moduleRecord.as()->hasSyntheticModuleFields()) { return SyntheticModuleEvaluate(cx, moduleRecord.as(), rval); } return ::ModuleEvaluate(cx, moduleRecord.as(), rval); } JS_PUBLIC_API bool JS::ThrowOnModuleEvaluationFailure( JSContext* cx, Handle evaluationPromise, ModuleErrorBehaviour errorBehaviour) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->releaseCheck(evaluationPromise); return OnModuleEvaluationFailure(cx, evaluationPromise, errorBehaviour); } JS_PUBLIC_API uint32_t JS::GetRequestedModulesCount(JSContext* cx, Handle moduleRecord) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->check(moduleRecord); return moduleRecord->as().requestedModules().Length(); } JS_PUBLIC_API JSString* JS::GetRequestedModuleSpecifier( JSContext* cx, Handle moduleRecord, uint32_t index) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->check(moduleRecord); auto& module = moduleRecord->as(); return module.requestedModules()[index].moduleRequest()->specifier(); } JS_PUBLIC_API void JS::GetRequestedModuleSourcePos( JSContext* cx, Handle moduleRecord, uint32_t index, uint32_t* lineNumber, JS::ColumnNumberOneOrigin* columnNumber) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->check(moduleRecord); MOZ_ASSERT(lineNumber); MOZ_ASSERT(columnNumber); auto& module = moduleRecord->as(); *lineNumber = module.requestedModules()[index].lineNumber(); *columnNumber = module.requestedModules()[index].columnNumber(); } JS_PUBLIC_API JSScript* JS::GetModuleScript(JS::HandleObject moduleRecord) { AssertHeapIsIdle(); return moduleRecord->as().script(); } JS_PUBLIC_API JSObject* JS::GetModuleObject(HandleScript moduleScript) { AssertHeapIsIdle(); MOZ_ASSERT(moduleScript->isModule()); return moduleScript->module(); } JS_PUBLIC_API JSObject* JS::GetModuleNamespace(JSContext* cx, HandleObject moduleRecord) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->check(moduleRecord); MOZ_ASSERT(moduleRecord->is()); return GetOrCreateModuleNamespace(cx, moduleRecord.as()); } JS_PUBLIC_API JSObject* JS::GetModuleForNamespace( JSContext* cx, HandleObject moduleNamespace) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->check(moduleNamespace); MOZ_ASSERT(moduleNamespace->is()); return &moduleNamespace->as().module(); } JS_PUBLIC_API JSObject* JS::GetModuleEnvironment(JSContext* cx, Handle moduleObj) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->check(moduleObj); MOZ_ASSERT(moduleObj->is()); return moduleObj->as().environment(); } JS_PUBLIC_API JSObject* JS::CreateModuleRequest( JSContext* cx, Handle specifierArg) { AssertHeapIsIdle(); CHECK_THREAD(cx); Rooted specifierAtom(cx, AtomizeString(cx, specifierArg)); if (!specifierAtom) { return nullptr; } Rooted> attributes(cx); return ModuleRequestObject::create(cx, specifierAtom, &attributes); } JS_PUBLIC_API JSString* JS::GetModuleRequestSpecifier( JSContext* cx, Handle moduleRequestArg) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->check(moduleRequestArg); return moduleRequestArg->as().specifier(); } JS_PUBLIC_API void JS::ClearModuleEnvironment(JSObject* moduleObj) { MOZ_ASSERT(moduleObj); AssertHeapIsIdle(); js::ModuleEnvironmentObject* env = moduleObj->as().environment(); if (!env) { return; } const JSClass* clasp = env->getClass(); uint32_t numReserved = JSCLASS_RESERVED_SLOTS(clasp); uint32_t numSlots = env->slotSpan(); for (uint32_t i = numReserved; i < numSlots; i++) { env->setSlot(i, UndefinedValue()); } } JS_PUBLIC_API bool JS::ModuleIsLinked(JSObject* moduleObj) { AssertHeapIsIdle(); return moduleObj->as().status() != ModuleStatus::Unlinked; } //////////////////////////////////////////////////////////////////////////////// // Internal implementation class ResolveSetEntry { ModuleObject* module_; JSAtom* exportName_; public: ResolveSetEntry(ModuleObject* module, JSAtom* exportName) : module_(module), exportName_(exportName) {} ModuleObject* module() const { return module_; } JSAtom* exportName() const { return exportName_; } void trace(JSTracer* trc) { TraceRoot(trc, &module_, "ResolveSetEntry::module_"); TraceRoot(trc, &exportName_, "ResolveSetEntry::exportName_"); } }; using ResolveSet = GCVector; using ModuleSet = GCHashSet, SystemAllocPolicy>; static ModuleObject* HostResolveImportedModule( JSContext* cx, Handle module, Handle moduleRequest, ModuleStatus expectedMinimumStatus); static bool CyclicModuleResolveExport(JSContext* cx, Handle module, Handle exportName, MutableHandle resolveSet, MutableHandle result, ModuleErrorInfo* errorInfoOut = nullptr); static bool SyntheticModuleResolveExport(JSContext* cx, Handle module, Handle exportName, MutableHandle result, ModuleErrorInfo* errorInfoOut); static ModuleNamespaceObject* ModuleNamespaceCreate( JSContext* cx, Handle module, MutableHandle> exports); static bool InnerModuleLinking(JSContext* cx, Handle module, MutableHandle stack, size_t index, size_t* indexOut); static bool InnerModuleEvaluation(JSContext* cx, Handle module, MutableHandle stack, size_t index, size_t* indexOut); static bool ExecuteAsyncModule(JSContext* cx, Handle module); static bool GatherAvailableModuleAncestors( JSContext* cx, Handle module, MutableHandle execList); static const char* ModuleStatusName(ModuleStatus status) { switch (status) { case ModuleStatus::Unlinked: return "Unlinked"; case ModuleStatus::Linking: return "Linking"; case ModuleStatus::Linked: return "Linked"; case ModuleStatus::Evaluating: return "Evaluating"; case ModuleStatus::EvaluatingAsync: return "EvaluatingAsync"; case ModuleStatus::Evaluated: return "Evaluated"; default: MOZ_CRASH("Unexpected ModuleStatus"); } } static bool ContainsElement(const ExportNameVector& list, JSAtom* atom) { for (JSAtom* a : list) { if (a == atom) { return true; } } return false; } static bool ContainsElement(Handle stack, ModuleObject* module) { for (ModuleObject* m : stack) { if (m == module) { return true; } } return false; } #ifdef DEBUG static size_t CountElements(Handle stack, ModuleObject* module) { size_t count = 0; for (ModuleObject* m : stack) { if (m == module) { count++; } } return count; } #endif // https://tc39.es/proposal-json-modules/#sec-smr-getexportednames static bool SyntheticModuleGetExportedNames( JSContext* cx, Handle module, MutableHandle exportedNames) { MOZ_ASSERT(exportedNames.empty()); if (!exportedNames.appendAll(module->syntheticExportNames())) { ReportOutOfMemory(cx); return false; } return true; } // https://tc39.es/ecma262/#sec-getexportednames // ES2023 16.2.1.6.2 GetExportedNames static bool ModuleGetExportedNames( JSContext* cx, Handle module, MutableHandle exportStarSet, MutableHandle exportedNames) { // Step 4. Let exportedNames be a new empty List. MOZ_ASSERT(exportedNames.empty()); if (module->hasSyntheticModuleFields()) { return SyntheticModuleGetExportedNames(cx, module, exportedNames); } // Step 2. If exportStarSet contains module, then: if (exportStarSet.has(module)) { // Step 2.a. We've reached the starting point of an export * circularity. // Step 2.b. Return a new empty List. return true; } // Step 3. Append module to exportStarSet. if (!exportStarSet.put(module)) { ReportOutOfMemory(cx); return false; } // Step 5. For each ExportEntry Record e of module.[[LocalExportEntries]], do: for (const ExportEntry& e : module->localExportEntries()) { // Step 5.a. Assert: module provides the direct binding for this export. // Step 5.b. Append e.[[ExportName]] to exportedNames. if (!exportedNames.append(e.exportName())) { ReportOutOfMemory(cx); return false; } } // Step 6. For each ExportEntry Record e of module.[[IndirectExportEntries]], // do: for (const ExportEntry& e : module->indirectExportEntries()) { // Step 6.a. Assert: module imports a specific binding for this export. // Step 6.b. Append e.[[ExportName]] to exportedNames. if (!exportedNames.append(e.exportName())) { ReportOutOfMemory(cx); return false; } } // Step 7. For each ExportEntry Record e of module.[[StarExportEntries]], do: Rooted moduleRequest(cx); Rooted requestedModule(cx); Rooted name(cx); for (const ExportEntry& e : module->starExportEntries()) { // Step 7.a. Let requestedModule be ? HostResolveImportedModule(module, // e.[[ModuleRequest]]). moduleRequest = e.moduleRequest(); requestedModule = HostResolveImportedModule(cx, module, moduleRequest, ModuleStatus::Unlinked); if (!requestedModule) { return false; } // Step 7.b. Let starNames be ? // requestedModule.GetExportedNames(exportStarSet). Rooted starNames(cx); if (!ModuleGetExportedNames(cx, requestedModule, exportStarSet, &starNames)) { return false; } // Step 7.c. For each element n of starNames, do: for (JSAtom* name : starNames) { // Step 7.c.i. If SameValue(n, "default") is false, then: if (name != cx->names().default_) { // Step 7.c.i.1. If n is not an element of exportedNames, then: if (!ContainsElement(exportedNames, name)) { // Step 7.c.i.1.a. Append n to exportedNames. if (!exportedNames.append(name)) { ReportOutOfMemory(cx); return false; } } } } } // Step 8. Return exportedNames. return true; } static void ThrowUnexpectedModuleStatus(JSContext* cx, ModuleStatus status) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_BAD_MODULE_STATUS, ModuleStatusName(status)); } static ModuleObject* HostResolveImportedModule( JSContext* cx, Handle module, Handle moduleRequest, ModuleStatus expectedMinimumStatus) { MOZ_ASSERT(module); MOZ_ASSERT(moduleRequest); Rooted referencingPrivate(cx, JS::GetModulePrivate(module)); Rooted requestedModule(cx); requestedModule = CallModuleResolveHook(cx, referencingPrivate, moduleRequest); if (!requestedModule) { return nullptr; } if (requestedModule->status() < expectedMinimumStatus) { ThrowUnexpectedModuleStatus(cx, requestedModule->status()); return nullptr; } return requestedModule; } // https://tc39.es/ecma262/#sec-resolveexport // ES2023 16.2.1.6.3 ResolveExport // // Returns an value describing the location of the resolved export or indicating // a failure. // // On success this returns a resolved binding record: { module, bindingName } // // There are two failure cases: // // - If no definition was found or the request is found to be circular, *null* // is returned. // // - If the request is found to be ambiguous, the string `"ambiguous"` is // returned. // static bool ModuleResolveExport(JSContext* cx, Handle module, Handle exportName, MutableHandle result, ModuleErrorInfo* errorInfoOut = nullptr) { if (module->hasSyntheticModuleFields()) { return SyntheticModuleResolveExport(cx, module, exportName, result, errorInfoOut); } // Step 1. If resolveSet is not present, set resolveSet to a new empty List. Rooted resolveSet(cx); return CyclicModuleResolveExport(cx, module, exportName, &resolveSet, result, errorInfoOut); } static bool CreateResolvedBindingObject(JSContext* cx, Handle module, Handle bindingName, MutableHandle result) { Rooted obj( cx, ResolvedBindingObject::create(cx, module, bindingName)); if (!obj) { return false; } result.setObject(*obj); return true; } static bool CyclicModuleResolveExport(JSContext* cx, Handle module, Handle exportName, MutableHandle resolveSet, MutableHandle result, ModuleErrorInfo* errorInfoOut) { // Step 2. For each Record { [[Module]], [[ExportName]] } r of resolveSet, do: for (const auto& entry : resolveSet) { // Step 2.a. If module and r.[[Module]] are the same Module Record and // SameValue(exportName, r.[[ExportName]]) is true, then: if (entry.module() == module && entry.exportName() == exportName) { // Step 2.a.i. Assert: This is a circular import request. // Step 2.a.ii. Return null. result.setNull(); if (errorInfoOut) { errorInfoOut->setCircularImport(cx, module); } return true; } } // Step 3. Append the Record { [[Module]]: module, [[ExportName]]: exportName // } to resolveSet. if (!resolveSet.emplaceBack(module, exportName)) { ReportOutOfMemory(cx); return false; } // Step 4. For each ExportEntry Record e of module.[[LocalExportEntries]], do: for (const ExportEntry& e : module->localExportEntries()) { // Step 4.a. If SameValue(exportName, e.[[ExportName]]) is true, then: if (exportName == e.exportName()) { // Step 4.a.i. Assert: module provides the direct binding for this export. // Step 4.a.ii. Return ResolvedBinding Record { [[Module]]: module, // [[BindingName]]: e.[[LocalName]] }. Rooted localName(cx, e.localName()); return CreateResolvedBindingObject(cx, module, localName, result); } } // Step 5. For each ExportEntry Record e of module.[[IndirectExportEntries]], // do: Rooted moduleRequest(cx); Rooted importedModule(cx); Rooted name(cx); for (const ExportEntry& e : module->indirectExportEntries()) { // Step 5.a. If SameValue(exportName, e.[[ExportName]]) is true, then: if (exportName == e.exportName()) { // Step 5.a.i. Let importedModule be ? HostResolveImportedModule(module, // e.[[ModuleRequest]]). moduleRequest = e.moduleRequest(); importedModule = HostResolveImportedModule(cx, module, moduleRequest, ModuleStatus::Unlinked); if (!importedModule) { return false; } // Step 5.a.ii. If e.[[ImportName]] is all, then: if (!e.importName()) { // Step 5.a.ii.1. Assert: module does not provide the direct binding for // this export. // Step 5.a.ii.2. Return ResolvedBinding Record { [[Module]]: // importedModule, [[BindingName]]: namespace }. name = cx->names().star_namespace_star_; return CreateResolvedBindingObject(cx, importedModule, name, result); } else { // Step 5.a.iii.1. Assert: module imports a specific binding for this // export. // Step 5.a.iii.2. Return ? // importedModule.ResolveExport(e.[[ImportName]], // resolveSet). name = e.importName(); return CyclicModuleResolveExport(cx, importedModule, name, resolveSet, result, errorInfoOut); } } } // Step 6. If SameValue(exportName, "default") is true, then: if (exportName == cx->names().default_) { // Step 6.a. Assert: A default export was not explicitly defined by this // module. // Step 6.b. Return null. // Step 6.c. NOTE: A default export cannot be provided by an export * from // "mod" declaration. result.setNull(); if (errorInfoOut) { errorInfoOut->setImportedModule(cx, module); } return true; } // Step 7. Let starResolution be null. Rooted starResolution(cx); // Step 8. For each ExportEntry Record e of module.[[StarExportEntries]], do: Rooted resolution(cx); Rooted binding(cx); for (const ExportEntry& e : module->starExportEntries()) { // Step 8.a. Let importedModule be ? HostResolveImportedModule(module, // e.[[ModuleRequest]]). moduleRequest = e.moduleRequest(); importedModule = HostResolveImportedModule(cx, module, moduleRequest, ModuleStatus::Unlinked); if (!importedModule) { return false; } // Step 8.b. Let resolution be ? importedModule.ResolveExport(exportName, // resolveSet). if (!CyclicModuleResolveExport(cx, importedModule, exportName, resolveSet, &resolution, errorInfoOut)) { return false; } // Step 8.c. If resolution is ambiguous, return ambiguous. if (resolution == StringValue(cx->names().ambiguous)) { result.set(resolution); return true; } // Step 8.d. If resolution is not null, then: if (!resolution.isNull()) { // Step 8.d.i. Assert: resolution is a ResolvedBinding Record. binding = &resolution.toObject().as(); // Step 8.d.ii. If starResolution is null, set starResolution to // resolution. if (!starResolution) { starResolution = binding; } else { // Step 8.d.iii. Else: // Step 8.d.iii.1. Assert: There is more than one * import that includes // the requested name. // Step 8.d.iii.2. If resolution.[[Module]] and // starResolution.[[Module]] are not the same Module // Record, return ambiguous. // Step 8.d.iii.3. If resolution.[[BindingName]] is namespace and // starResolution.[[BindingName]] is not namespace, or // if resolution.[[BindingName]] is not namespace and // starResolution.[[BindingName]] is namespace, return // ambiguous. // Step 8.d.iii.4. If resolution.[[BindingName]] is a String, // starResolution.[[BindingName]] is a String, and // SameValue(resolution.[[BindingName]], // starResolution.[[BindingName]]) is false, return // ambiguous. if (binding->module() != starResolution->module() || binding->bindingName() != starResolution->bindingName()) { result.set(StringValue(cx->names().ambiguous)); if (errorInfoOut) { Rooted module1(cx, starResolution->module()); Rooted module2(cx, binding->module()); errorInfoOut->setForAmbiguousImport(cx, module, module1, module2); } return true; } } } } // Step 9. Return starResolution. result.setObjectOrNull(starResolution); if (!starResolution && errorInfoOut) { errorInfoOut->setImportedModule(cx, module); } return true; } // https://tc39.es/proposal-json-modules/#sec-smr-resolveexport static bool SyntheticModuleResolveExport(JSContext* cx, Handle module, Handle exportName, MutableHandle result, ModuleErrorInfo* errorInfoOut) { // Step 2. If module.[[ExportNames]] does not contain exportName, return null. if (!ContainsElement(module->syntheticExportNames(), exportName)) { result.setNull(); if (errorInfoOut) { errorInfoOut->setImportedModule(cx, module); } return true; } // Step 3. Return ResolvedBinding Record { [[Module]]: module, // [[BindingName]]: exportName }. return CreateResolvedBindingObject(cx, module, exportName, result); } // https://tc39.es/ecma262/#sec-getmodulenamespace // ES2023 16.2.1.10 GetModuleNamespace ModuleNamespaceObject* js::GetOrCreateModuleNamespace( JSContext* cx, Handle module) { // Step 1. Assert: If module is a Cyclic Module Record, then module.[[Status]] // is not unlinked. MOZ_ASSERT(module->status() != ModuleStatus::Unlinked); // Step 2. Let namespace be module.[[Namespace]]. Rooted ns(cx, module->namespace_()); // Step 3. If namespace is empty, then: if (!ns) { // Step 3.a. Let exportedNames be ? module.GetExportedNames(). Rooted exportStarSet(cx); Rooted exportedNames(cx); if (!ModuleGetExportedNames(cx, module, &exportStarSet, &exportedNames)) { return nullptr; } // Step 3.b. Let unambiguousNames be a new empty List. Rooted> unambiguousNames( cx, cx->make_unique()); if (!unambiguousNames) { return nullptr; } // Step 3.c. For each element name of exportedNames, do: Rooted name(cx); Rooted resolution(cx); for (JSAtom* atom : exportedNames) { name = atom; // Step 3.c.i. Let resolution be ? module.ResolveExport(name). if (!ModuleResolveExport(cx, module, name, &resolution)) { return nullptr; } // Step 3.c.ii. If resolution is a ResolvedBinding Record, append name to // unambiguousNames. if (resolution.isObject() && !unambiguousNames->append(name)) { ReportOutOfMemory(cx); return nullptr; } } // Step 3.d. Set namespace to ModuleNamespaceCreate(module, // unambiguousNames). ns = ModuleNamespaceCreate(cx, module, &unambiguousNames); } // Step 4. Return namespace. return ns; } static bool IsResolvedBinding(JSContext* cx, Handle resolution) { MOZ_ASSERT(resolution.isObjectOrNull() || resolution.toString() == cx->names().ambiguous); return resolution.isObject(); } static void InitNamespaceBinding(JSContext* cx, Handle env, Handle name, Handle ns) { // The property already exists in the evironment but is not writable, so set // the slot directly. RootedId id(cx, AtomToId(name)); mozilla::Maybe prop = env->lookup(cx, id); MOZ_ASSERT(prop.isSome()); env->setSlot(prop->slot(), ObjectValue(*ns)); } struct AtomComparator { bool operator()(JSAtom* a, JSAtom* b, bool* lessOrEqualp) { int32_t result = CompareStrings(a, b); *lessOrEqualp = (result <= 0); return true; } }; // https://tc39.es/ecma262/#sec-modulenamespacecreate // ES2023 10.4.6.12 ModuleNamespaceCreate static ModuleNamespaceObject* ModuleNamespaceCreate( JSContext* cx, Handle module, MutableHandle> exports) { // Step 1. Assert: module.[[Namespace]] is empty. MOZ_ASSERT(!module->namespace_()); // Step 6. Let sortedExports be a List whose elements are the elements of // exports ordered as if an Array of the same values had been sorted // using %Array.prototype.sort% using undefined as comparefn. ExportNameVector scratch; if (!scratch.resize(exports->length())) { ReportOutOfMemory(cx); return nullptr; } MOZ_ALWAYS_TRUE(MergeSort(exports->begin(), exports->length(), scratch.begin(), AtomComparator())); // Steps 2 - 5. Rooted ns( cx, ModuleObject::createNamespace(cx, module, exports)); if (!ns) { return nullptr; } // Pre-compute all binding mappings now instead of on each access. // See: // https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver // ES2023 10.4.6.8 Module Namespace Exotic Object [[Get]] Rooted name(cx); Rooted resolution(cx); Rooted binding(cx); Rooted importedModule(cx); Rooted importedNamespace(cx); Rooted bindingName(cx); for (JSAtom* atom : ns->exports()) { name = atom; if (!ModuleResolveExport(cx, module, name, &resolution)) { return nullptr; } MOZ_ASSERT(IsResolvedBinding(cx, resolution)); binding = &resolution.toObject().as(); importedModule = binding->module(); bindingName = binding->bindingName(); if (bindingName == cx->names().star_namespace_star_) { importedNamespace = GetOrCreateModuleNamespace(cx, importedModule); if (!importedNamespace) { return nullptr; } // The spec uses an immutable binding here but we have already generated // bytecode for an indirect binding. Instead, use an indirect binding to // "*namespace*" slot of the target environment. Rooted env( cx, &importedModule->initialEnvironment()); InitNamespaceBinding(cx, env, bindingName, importedNamespace); } if (!ns->addBinding(cx, name, importedModule, bindingName)) { return nullptr; } } // Step 10. Return M. return ns; } void ModuleErrorInfo::setImportedModule(JSContext* cx, ModuleObject* importedModule) { imported = importedModule->filename(); } void ModuleErrorInfo::setCircularImport(JSContext* cx, ModuleObject* importedModule) { setImportedModule(cx, importedModule); isCircular = true; } void ModuleErrorInfo::setForAmbiguousImport(JSContext* cx, ModuleObject* importedModule, ModuleObject* module1, ModuleObject* module2) { setImportedModule(cx, importedModule); entry1 = module1->filename(); entry2 = module2->filename(); } static void CreateErrorNumberMessageUTF8(JSContext* cx, unsigned errorNumber, JSErrorReport* reportOut, ...) { va_list ap; va_start(ap, reportOut); AutoReportFrontendContext fc(cx); if (!ExpandErrorArgumentsVA(&fc, GetErrorMessage, nullptr, errorNumber, ArgumentsAreUTF8, reportOut, ap)) { ReportOutOfMemory(cx); return; } va_end(ap); } static void ThrowResolutionError(JSContext* cx, Handle module, Handle resolution, Handle name, ModuleErrorInfo* errorInfo) { MOZ_ASSERT(errorInfo); auto chars = StringToNewUTF8CharsZ(cx, *name); if (!chars) { ReportOutOfMemory(cx); return; } bool isAmbiguous = resolution == StringValue(cx->names().ambiguous); unsigned errorNumber; if (errorInfo->isCircular) { errorNumber = JSMSG_MODULE_CIRCULAR_IMPORT; } else if (isAmbiguous) { errorNumber = JSMSG_MODULE_AMBIGUOUS; } else { errorNumber = JSMSG_MODULE_NO_EXPORT; } JSErrorReport report; report.isWarning_ = false; report.errorNumber = errorNumber; if (errorNumber == JSMSG_MODULE_AMBIGUOUS) { CreateErrorNumberMessageUTF8(cx, errorNumber, &report, errorInfo->imported, chars.get(), errorInfo->entry1, errorInfo->entry2); } else { CreateErrorNumberMessageUTF8(cx, errorNumber, &report, errorInfo->imported, chars.get()); } Rooted message(cx, report.newMessageString(cx)); if (!message) { ReportOutOfMemory(cx); return; } const char* file = module->filename(); RootedString filename( cx, JS_NewStringCopyUTF8Z(cx, JS::ConstUTF8CharsZ(file, strlen(file)))); if (!filename) { ReportOutOfMemory(cx); return; } RootedValue error(cx); if (!JS::CreateError(cx, JSEXN_SYNTAXERR, nullptr, filename, errorInfo->lineNumber, errorInfo->columnNumber, nullptr, message, JS::NothingHandleValue, &error)) { ReportOutOfMemory(cx); return; } cx->setPendingException(error, nullptr); } // https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment // ES2023 16.2.1.6.4 InitializeEnvironment static bool ModuleInitializeEnvironment(JSContext* cx, Handle module) { MOZ_ASSERT(module->status() == ModuleStatus::Linking); // Step 1. For each ExportEntry Record e of module.[[IndirectExportEntries]], // do: Rooted exportName(cx); Rooted resolution(cx); for (const ExportEntry& e : module->indirectExportEntries()) { // Step 1.a. Assert: e.[[ExportName]] is not null. MOZ_ASSERT(e.exportName()); // Step 1.b. Let resolution be ? module.ResolveExport(e.[[ExportName]]). exportName = e.exportName(); ModuleErrorInfo errorInfo{e.lineNumber(), e.columnNumber()}; if (!ModuleResolveExport(cx, module, exportName, &resolution, &errorInfo)) { return false; } // Step 1.c. If resolution is either null or AMBIGUOUS, throw a SyntaxError // exception. if (!IsResolvedBinding(cx, resolution)) { ThrowResolutionError(cx, module, resolution, exportName, &errorInfo); return false; } } // Step 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). // Step 6. Set module.[[Environment]] to env. // Note that we have already created the environment by this point. Rooted env(cx, &module->initialEnvironment()); // Step 7. For each ImportEntry Record in of module.[[ImportEntries]], do: Rooted moduleRequest(cx); Rooted importedModule(cx); Rooted importName(cx); Rooted localName(cx); Rooted sourceModule(cx); Rooted bindingName(cx); for (const ImportEntry& in : module->importEntries()) { // Step 7.a. Let importedModule be ! HostResolveImportedModule(module, // in.[[ModuleRequest]]). moduleRequest = in.moduleRequest(); importedModule = HostResolveImportedModule(cx, module, moduleRequest, ModuleStatus::Linking); if (!importedModule) { return false; } localName = in.localName(); importName = in.importName(); // Step 7.c. If in.[[ImportName]] is namespace-object, then: if (!importName) { // Step 7.c.i. Let namespace be ? GetModuleNamespace(importedModule). Rooted ns( cx, GetOrCreateModuleNamespace(cx, importedModule)); if (!ns) { return false; } // Step 7.c.ii. Perform ! env.CreateImmutableBinding(in.[[LocalName]], // true). This happens when the environment is created. // Step 7.c.iii. Perform ! env.InitializeBinding(in.[[LocalName]], // namespace). InitNamespaceBinding(cx, env, localName, ns); } else { // Step 7.d. Else: // Step 7.d.i. Let resolution be ? // importedModule.ResolveExport(in.[[ImportName]]). ModuleErrorInfo errorInfo{in.lineNumber(), in.columnNumber()}; if (!ModuleResolveExport(cx, importedModule, importName, &resolution, &errorInfo)) { return false; } // Step 7.d.ii. If resolution is null or ambiguous, throw a SyntaxError // exception. if (!IsResolvedBinding(cx, resolution)) { ThrowResolutionError(cx, module, resolution, importName, &errorInfo); return false; } auto* binding = &resolution.toObject().as(); sourceModule = binding->module(); bindingName = binding->bindingName(); // Step 7.d.iii. If resolution.[[BindingName]] is namespace, then: if (bindingName == cx->names().star_namespace_star_) { // Step 7.d.iii.1. Let namespace be ? // GetModuleNamespace(resolution.[[Module]]). Rooted ns( cx, GetOrCreateModuleNamespace(cx, sourceModule)); if (!ns) { return false; } // Step 7.d.iii.2. Perform ! // env.CreateImmutableBinding(in.[[LocalName]], true). // Step 7.d.iii.3. Perform ! env.InitializeBinding(in.[[LocalName]], // namespace). // // This should be InitNamespaceBinding, but we have already generated // bytecode assuming an indirect binding. Instead, ensure a special // "*namespace*"" binding exists on the target module's environment. We // then generate an indirect binding to this synthetic binding. Rooted sourceEnv( cx, &sourceModule->initialEnvironment()); InitNamespaceBinding(cx, sourceEnv, bindingName, ns); if (!env->createImportBinding(cx, localName, sourceModule, bindingName)) { return false; } } else { // Step 7.d.iv. Else: // Step 7.d.iv.1. 1. Perform env.CreateImportBinding(in.[[LocalName]], // resolution.[[Module]], resolution.[[BindingName]]). if (!env->createImportBinding(cx, localName, sourceModule, bindingName)) { return false; } } } } // Steps 8-26. // // Some of these do not need to happen for practical purposes. For steps // 21-23, the bindings that can be handled in a similar way to regulars // scripts are done separately. Function Declarations are special due to // hoisting and are handled within this function. See ModuleScope and // ModuleEnvironmentObject for further details. // Step 24. For each element d of lexDeclarations, do: // Step 24.a. For each element dn of the BoundNames of d, do: // Step 24.a.iii. If d is a FunctionDeclaration, a GeneratorDeclaration, an // AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, // then: // Step 24.a.iii.1 Let fo be InstantiateFunctionObject of d with arguments env // and privateEnv. // Step 24.a.iii.2. Perform ! env.InitializeBinding(dn, fo). return ModuleObject::instantiateFunctionDeclarations(cx, module); } // https://tc39.es/ecma262/#sec-moduledeclarationlinking // ES2023 16.2.1.5.1 Link static bool ModuleLink(JSContext* cx, Handle module) { // Step 1. Assert: module.[[Status]] is not linking or evaluating. ModuleStatus status = module->status(); if (status == ModuleStatus::Linking || status == ModuleStatus::Evaluating) { ThrowUnexpectedModuleStatus(cx, status); return false; } // Step 2. Let stack be a new empty List. Rooted stack(cx); // Step 3. Let result be Completion(InnerModuleLinking(module, stack, 0)). size_t ignored; bool ok = InnerModuleLinking(cx, module, &stack, 0, &ignored); // Step 4. If result is an abrupt completion, then: if (!ok) { // Step 4.a. For each Cyclic Module Record m of stack, do: for (ModuleObject* m : stack) { // Step 4.a.i. Assert: m.[[Status]] is linking. MOZ_ASSERT(m->status() == ModuleStatus::Linking); // Step 4.a.ii. Set m.[[Status]] to unlinked. m->setStatus(ModuleStatus::Unlinked); m->clearDfsIndexes(); } // Step 4.b. Assert: module.[[Status]] is unlinked. MOZ_ASSERT(module->status() == ModuleStatus::Unlinked); // Step 4.c. return false; } // Step 5. Assert: module.[[Status]] is linked, evaluating-async, or // evaluated. MOZ_ASSERT(module->status() == ModuleStatus::Linked || module->status() == ModuleStatus::EvaluatingAsync || module->status() == ModuleStatus::Evaluated); // Step 6. Assert: stack is empty. MOZ_ASSERT(stack.empty()); // Step 7. Return unused. return true; } // https://tc39.es/proposal-import-attributes/#sec-AllImportAttributesSupported static bool AllImportAttributesSupported( JSContext* cx, mozilla::Span attributes) { // Step 1. Let supported be HostGetSupportedImportAttributes(). // // Note: This should be driven by a host hook // (HostGetSupportedImportAttributes), however the infrastructure of said host // hook is deeply unclear, and so right now embedders will not have the // ability to alter or extend the set of supported attributes. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1840723. // Step 2. For each ImportAttribute Record attribute of attributes, do for (const ImportAttribute& attribute : attributes) { // Step 2.a. If supported does not contain attribute.[[Key]], return false. if (attribute.key() != cx->names().type) { UniqueChars printableKey = AtomToPrintableString(cx, attribute.key()); if (!printableKey) { return false; } JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_IMPORT_ATTRIBUTES_UNSUPPORTED_ATTRIBUTE, printableKey.get()); return false; } } // Step 3. Return true. return true; } // https://tc39.es/ecma262/#sec-InnerModuleLinking // ES2023 16.2.1.5.1.1 InnerModuleLinking static bool InnerModuleLinking(JSContext* cx, Handle module, MutableHandle stack, size_t index, size_t* indexOut) { // Step 1. If module is not a Cyclic Module Record, then if (!module->hasCyclicModuleFields()) { // Step 1.a. Perform ? module.Link(). (Skipped) // Step 2.b. Return index. *indexOut = index; return true; } // Step 2. If module.[[Status]] is linking, linked, evaluating-async, or // evaluated, then: if (module->status() == ModuleStatus::Linking || module->status() == ModuleStatus::Linked || module->status() == ModuleStatus::EvaluatingAsync || module->status() == ModuleStatus::Evaluated) { // Step 2.a. Return index. *indexOut = index; return true; } // Step 3. Assert: module.[[Status]] is unlinked. if (module->status() != ModuleStatus::Unlinked) { ThrowUnexpectedModuleStatus(cx, module->status()); return false; } // Step 8. Append module to stack. // Do this before changing the status so that we can recover on failure. if (!stack.append(module)) { ReportOutOfMemory(cx); return false; } // Step 4. Set module.[[Status]] to linking. module->setStatus(ModuleStatus::Linking); // Step 5. Set module.[[DFSIndex]] to index. module->setDfsIndex(index); // Step 6. Set module.[[DFSAncestorIndex]] to index. module->setDfsAncestorIndex(index); // Step 7. Set index to index + 1. index++; // Step 9. For each String required that is an element of // module.[[RequestedModules]], do: Rooted moduleRequest(cx); Rooted requiredModule(cx); for (const RequestedModule& request : module->requestedModules()) { moduleRequest = request.moduleRequest(); // According to the spec, this should be in InnerModuleLoading, but // currently, our module code is not aligned with the spec text. // https://bugzilla.mozilla.org/show_bug.cgi?id=1894729 if (!AllImportAttributesSupported(cx, moduleRequest->attributes())) { return false; } // Step 9.a. Let requiredModule be ? HostResolveImportedModule(module, // required). requiredModule = HostResolveImportedModule(cx, module, moduleRequest, ModuleStatus::Unlinked); if (!requiredModule) { return false; } // Step 9.b. Set index to ? InnerModuleLinking(requiredModule, stack, // index). if (!InnerModuleLinking(cx, requiredModule, stack, index, &index)) { return false; } // Step 9.c. If requiredModule is a Cyclic Module Record, then: if (requiredModule->hasCyclicModuleFields()) { // Step 9.c.i. Assert: requiredModule.[[Status]] is either linking, // linked, // evaluating-async, or evaluated. MOZ_ASSERT(requiredModule->status() == ModuleStatus::Linking || requiredModule->status() == ModuleStatus::Linked || requiredModule->status() == ModuleStatus::EvaluatingAsync || requiredModule->status() == ModuleStatus::Evaluated); // Step 9.c.ii. Assert: requiredModule.[[Status]] is linking if and only // if // requiredModule is in stack. MOZ_ASSERT((requiredModule->status() == ModuleStatus::Linking) == ContainsElement(stack, requiredModule)); // Step 9.c.iii. If requiredModule.[[Status]] is linking, then: if (requiredModule->status() == ModuleStatus::Linking) { // Step 9.c.iii.1. Set module.[[DFSAncestorIndex]] to // min(module.[[DFSAncestorIndex]], // requiredModule.[[DFSAncestorIndex]]). module->setDfsAncestorIndex(std::min( module->dfsAncestorIndex(), requiredModule->dfsAncestorIndex())); } } } // Step 10. Perform ? module.InitializeEnvironment(). if (!ModuleInitializeEnvironment(cx, module)) { return false; } // Step 11. Assert: module occurs exactly once in stack. MOZ_ASSERT(CountElements(stack, module) == 1); // Step 12. Assert: module.[[DFSAncestorIndex]] <= module.[[DFSIndex]]. MOZ_ASSERT(module->dfsAncestorIndex() <= module->dfsIndex()); // Step 13. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then if (module->dfsAncestorIndex() == module->dfsIndex()) { // Step 13.a. bool done = false; // Step 13.b. Repeat, while done is false: while (!done) { // Step 13.b.i. Let requiredModule be the last element in stack. // Step 13.b.ii. Remove the last element of stack. requiredModule = stack.popCopy(); // Step 13.b.iv. Set requiredModule.[[Status]] to linked. requiredModule->setStatus(ModuleStatus::Linked); // Step 13.b.v. If requiredModule and module are the same Module Record, // set done to true. done = requiredModule == module; } } // Step 14. Return index. *indexOut = index; return true; } static bool SyntheticModuleEvaluate(JSContext* cx, Handle moduleArg, MutableHandle result) { // Steps 1-12 happens elsewhere in the engine. // Step 13. Let pc be ! NewPromiseCapability(%Promise%). Rooted resultPromise(cx, CreatePromiseObjectForAsync(cx)); if (!resultPromise) { return false; } // Step 14. IfAbruptRejectPromise(result, pc) (Skipped) // 15. Perform ! pc.[[Resolve]](result). if (!AsyncFunctionReturned(cx, resultPromise, result)) { return false; } // 16. Return pc.[[Promise]]. result.set(ObjectValue(*resultPromise)); return true; } // https://tc39.es/ecma262/#sec-moduleevaluation // ES2023 16.2.1.5.2 Evaluate static bool ModuleEvaluate(JSContext* cx, Handle moduleArg, MutableHandle result) { Rooted module(cx, moduleArg); // Step 2. Assert: module.[[Status]] is linked, evaluating-async, or // evaluated. ModuleStatus status = module->status(); if (status != ModuleStatus::Linked && status != ModuleStatus::EvaluatingAsync && status != ModuleStatus::Evaluated) { ThrowUnexpectedModuleStatus(cx, status); return false; } // Note: we return early in the error case, as the spec assumes we can get the // cycle root of |module| which may not be available. if (module->hadEvaluationError()) { Rooted capability(cx); if (!module->hasTopLevelCapability()) { capability = ModuleObject::createTopLevelCapability(cx, module); if (!capability) { return false; } Rooted error(cx, module->evaluationError()); if (!ModuleObject::topLevelCapabilityReject(cx, module, error)) { return false; } } capability = module->topLevelCapability(); MOZ_ASSERT(JS::GetPromiseState(capability) == JS::PromiseState::Rejected); MOZ_ASSERT(JS::GetPromiseResult(capability) == module->evaluationError()); result.set(ObjectValue(*capability)); return true; } // Step 3. If module.[[Status]] is evaluating-async or evaluated, set module // to module.[[CycleRoot]]. if (module->status() == ModuleStatus::EvaluatingAsync || module->status() == ModuleStatus::Evaluated) { module = module->getCycleRoot(); } // Step 4. If module.[[TopLevelCapability]] is not empty, then: if (module->hasTopLevelCapability()) { // Step 4.a. Return module.[[TopLevelCapability]].[[Promise]]. result.set(ObjectValue(*module->topLevelCapability())); return true; } // Step 5. Let stack be a new empty List. Rooted stack(cx); // Step 6. Let capability be ! NewPromiseCapability(%Promise%). // Step 7. Set module.[[TopLevelCapability]] to capability. Rooted capability( cx, ModuleObject::createTopLevelCapability(cx, module)); if (!capability) { return false; } // Step 8. Let result be Completion(InnerModuleEvaluation(module, stack, 0)). size_t ignored; bool ok = InnerModuleEvaluation(cx, module, &stack, 0, &ignored); // Step 9. f result is an abrupt completion, then: if (!ok) { // Attempt to take any pending exception, but make sure we still handle // uncatchable exceptions. Rooted error(cx); if (cx->isExceptionPending()) { std::ignore = cx->getPendingException(&error); cx->clearPendingException(); } // Step 9.a. For each Cyclic Module Record m of stack, do for (ModuleObject* m : stack) { // Step 9.a.i. Assert: m.[[Status]] is evaluating. MOZ_ASSERT(m->status() == ModuleStatus::Evaluating); // Step 9.a.ii. Set m.[[Status]] to evaluated. // Step 9.a.iii. Set m.[[EvaluationError]] to result. m->setEvaluationError(error); } // Handle OOM when appending to the stack or over-recursion errors. if (stack.empty() && !module->hadEvaluationError()) { module->setEvaluationError(error); } // Step 9.b. Assert: module.[[Status]] is evaluated. MOZ_ASSERT(module->status() == ModuleStatus::Evaluated); // Step 9.c. Assert: module.[[EvaluationError]] is result. MOZ_ASSERT(module->evaluationError() == error); // Step 9.d. Perform ! Call(capability.[[Reject]], undefined, // result.[[Value]]). if (!ModuleObject::topLevelCapabilityReject(cx, module, error)) { return false; } } else { // Step 10. Else: // Step 10.a. Assert: module.[[Status]] is evaluating-async or evaluated. MOZ_ASSERT(module->status() == ModuleStatus::EvaluatingAsync || module->status() == ModuleStatus::Evaluated); // Step 10.b. Assert: module.[[EvaluationError]] is empty. MOZ_ASSERT(!module->hadEvaluationError()); // Step 10.c. If module.[[AsyncEvaluation]] is false, then: if (module->status() == ModuleStatus::Evaluated) { // Step 10.c.ii. Perform ! Call(capability.[[Resolve]], undefined, // undefined). if (!ModuleObject::topLevelCapabilityResolve(cx, module)) { return false; } } // Step 10.d. Assert: stack is empty. MOZ_ASSERT(stack.empty()); } // Step 11. Return capability.[[Promise]]. result.set(ObjectValue(*capability)); return true; } // https://tc39.es/ecma262/#sec-innermoduleevaluation // 16.2.1.5.2.1 InnerModuleEvaluation static bool InnerModuleEvaluation(JSContext* cx, Handle module, MutableHandle stack, size_t index, size_t* indexOut) { // Step 1: If module is not a Cyclic Module Record, then if (!module->hasCyclicModuleFields()) { // Step 1.a. Let promise be ! module.Evaluate(). (Skipped) // Step 1.b. Assert: promise.[[PromiseState]] is not pending. (Skipped) // Step 1.c. If promise.[[PromiseState]] is rejected, then (Skipped) // Step 1.c.i Return ThrowCompletion(promise.[[PromiseResult]]). (Skipped) // Step 1.d. Return index. *indexOut = index; return true; } // Step 2. If module.[[Status]] is evaluating-async or evaluated, then: if (module->status() == ModuleStatus::EvaluatingAsync || module->status() == ModuleStatus::Evaluated) { // Step 2.a. If module.[[EvaluationError]] is empty, return index. if (!module->hadEvaluationError()) { *indexOut = index; return true; } // Step 2.b. Otherwise, return ? module.[[EvaluationError]]. Rooted error(cx, module->evaluationError()); cx->setPendingException(error, ShouldCaptureStack::Maybe); return false; } // Step 3. If module.[[Status]] is evaluating, return index. if (module->status() == ModuleStatus::Evaluating) { *indexOut = index; return true; } // Step 4. Assert: module.[[Status]] is linked. MOZ_ASSERT(module->status() == ModuleStatus::Linked); // Step 10. Append module to stack. // Do this before changing the status so that we can recover on failure. if (!stack.append(module)) { ReportOutOfMemory(cx); return false; } // Step 5. Set module.[[Status]] to evaluating. module->setStatus(ModuleStatus::Evaluating); // Step 6. Set module.[[DFSIndex]] to index. module->setDfsIndex(index); // Step 7. Set module.[[DFSAncestorIndex]] to index. module->setDfsAncestorIndex(index); // Step 8. Set module.[[PendingAsyncDependencies]] to 0. module->setPendingAsyncDependencies(0); // Step 9. Set index to index + 1. index++; // Step 11. For each String required of module.[[RequestedModules]], do: Rooted required(cx); Rooted requiredModule(cx); for (const RequestedModule& request : module->requestedModules()) { required = request.moduleRequest(); // Step 11.a. Let requiredModule be ! HostResolveImportedModule(module, // required). // Step 11.b. NOTE: Link must be completed successfully prior to invoking // this method, so every requested module is guaranteed to // resolve successfully. requiredModule = HostResolveImportedModule(cx, module, required, ModuleStatus::Linked); if (!requiredModule) { return false; } // Step 11.c. Set index to ? InnerModuleEvaluation(requiredModule, stack, // index). if (!InnerModuleEvaluation(cx, requiredModule, stack, index, &index)) { return false; } // Step 11.d. If requiredModule is a Cyclic Module Record, then: if (requiredModule->hasCyclicModuleFields()) { // Step 11.d.i. Assert: requiredModule.[[Status]] is either evaluating, // evaluating-async, or evaluated. MOZ_ASSERT(requiredModule->status() == ModuleStatus::Evaluating || requiredModule->status() == ModuleStatus::EvaluatingAsync || requiredModule->status() == ModuleStatus::Evaluated); // Step 11.d.ii. Assert: requiredModule.[[Status]] is evaluating if and // only if requiredModule is in stack. MOZ_ASSERT((requiredModule->status() == ModuleStatus::Evaluating) == ContainsElement(stack, requiredModule)); // Step 11.d.iii. If requiredModule.[[Status]] is evaluating, then: if (requiredModule->status() == ModuleStatus::Evaluating) { // Step 11.d.iii.1. Set module.[[DFSAncestorIndex]] to // min(module.[[DFSAncestorIndex]], // requiredModule.[[DFSAncestorIndex]]). module->setDfsAncestorIndex(std::min( module->dfsAncestorIndex(), requiredModule->dfsAncestorIndex())); } else { // Step 11.d.iv. Else: // Step 11.d.iv.1. Set requiredModule to requiredModule.[[CycleRoot]]. requiredModule = requiredModule->getCycleRoot(); // Step 11.d.iv.2. Assert: requiredModule.[[Status]] is evaluating-async // or evaluated. MOZ_ASSERT(requiredModule->status() >= ModuleStatus::EvaluatingAsync || requiredModule->status() == ModuleStatus::Evaluated); // Step 11.d.iv.3. If requiredModule.[[EvaluationError]] is not empty, // return ? requiredModule.[[EvaluationError]]. if (requiredModule->hadEvaluationError()) { Rooted error(cx, requiredModule->evaluationError()); cx->setPendingException(error, ShouldCaptureStack::Maybe); return false; } } // Step 11.d.v. If requiredModule.[[AsyncEvaluation]] is true, then: if (requiredModule->isAsyncEvaluating() && requiredModule->status() != ModuleStatus::Evaluated) { // Step 11.d.v.2. Append module to // requiredModule.[[AsyncParentModules]]. if (!ModuleObject::appendAsyncParentModule(cx, requiredModule, module)) { return false; } // Step 11.d.v.1. Set module.[[PendingAsyncDependencies]] to // module.[[PendingAsyncDependencies]] + 1. module->setPendingAsyncDependencies(module->pendingAsyncDependencies() + 1); } } } // Step 12. If module.[[PendingAsyncDependencies]] > 0 or module.[[HasTLA]] is // true, then: if (module->pendingAsyncDependencies() > 0 || module->hasTopLevelAwait()) { // Step 12.a. Assert: module.[[AsyncEvaluation]] is false and was never // previously set to true. MOZ_ASSERT(!module->isAsyncEvaluating()); // Step 12.b. Set module.[[AsyncEvaluation]] to true. // Step 12.c. NOTE: The order in which module records have their // [[AsyncEvaluation]] fields transition to true is // significant. (See 16.2.1.5.2.4.) module->setAsyncEvaluating(); // Step 12.d. If module.[[PendingAsyncDependencies]] is 0, perform // ExecuteAsyncModule(module). if (module->pendingAsyncDependencies() == 0) { if (!ExecuteAsyncModule(cx, module)) { return false; } } } else { // Step 13. Otherwise, perform ? module.ExecuteModule(). if (!ModuleObject::execute(cx, module)) { return false; } } // Step 14. Assert: module occurs exactly once in stack. MOZ_ASSERT(CountElements(stack, module) == 1); // Step 15. Assert: module.[[DFSAncestorIndex]] <= module.[[DFSIndex]]. MOZ_ASSERT(module->dfsAncestorIndex() <= module->dfsIndex()); // Step 16. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then: if (module->dfsAncestorIndex() == module->dfsIndex()) { // Step 16.a. Let done be false. bool done = false; // Step 16.b. Repeat, while done is false: while (!done) { // Step 16.b.i. Let requiredModule be the last element in stack. // Step 16.b.ii. Remove the last element of stack. requiredModule = stack.popCopy(); // Step 16.b.iv. If requiredModule.[[AsyncEvaluation]] is false, set // requiredModule.[[Status]] to evaluated. if (!requiredModule->isAsyncEvaluating()) { requiredModule->setStatus(ModuleStatus::Evaluated); } else { // Step 16.b.v. Otherwise, set requiredModule.[[Status]] to // evaluating-async. requiredModule->setStatus(ModuleStatus::EvaluatingAsync); } // Step 16.b.vi. If requiredModule and module are the same Module Record, // set done to true. done = requiredModule == module; // Step 16.b.vii. Set requiredModule.[[CycleRoot]] to module. requiredModule->setCycleRoot(module); } } // Step 17. Return index. *indexOut = index; return true; } // https://tc39.es/ecma262/#sec-execute-async-module // ES2023 16.2.1.5.2.2 ExecuteAsyncModule static bool ExecuteAsyncModule(JSContext* cx, Handle module) { // Step 1. Assert: module.[[Status]] is evaluating or evaluating-async. MOZ_ASSERT(module->status() == ModuleStatus::Evaluating || module->status() == ModuleStatus::EvaluatingAsync); // Step 2. Assert: module.[[HasTLA]] is true. MOZ_ASSERT(module->hasTopLevelAwait()); // Steps 3 - 8 are performed by the AsyncAwait opcode. // Step 9. Perform ! module.ExecuteModule(capability). // Step 10. Return unused. return ModuleObject::execute(cx, module); } // https://tc39.es/ecma262/#sec-gather-available-ancestors // ES2023 16.2.1.5.2.3 GatherAvailableAncestors static bool GatherAvailableModuleAncestors( JSContext* cx, Handle module, MutableHandle execList) { MOZ_ASSERT(module->status() == ModuleStatus::EvaluatingAsync); // Step 1. For each Cyclic Module Record m of module.[[AsyncParentModules]], // do: Rooted asyncParentModules(cx, module->asyncParentModules()); Rooted m(cx); for (uint32_t i = 0; i != asyncParentModules->length(); i++) { m = &asyncParentModules->getDenseElement(i).toObject().as(); // Step 1.a. If execList does not contain m and // m.[[CycleRoot]].[[EvaluationError]] is empty, then: // // Note: we also check whether m.[[EvaluationError]] is empty since an error // in synchronous execution can prevent the CycleRoot field from being set. if (!m->hadEvaluationError() && !m->getCycleRoot()->hadEvaluationError() && !ContainsElement(execList, m)) { // Step 1.a.i. Assert: m.[[Status]] is evaluating-async. MOZ_ASSERT(m->status() == ModuleStatus::EvaluatingAsync); // Step 1.a.ii. Assert: m.[[EvaluationError]] is empty. MOZ_ASSERT(!m->hadEvaluationError()); // Step 1.a.iii. Assert: m.[[AsyncEvaluation]] is true. MOZ_ASSERT(m->isAsyncEvaluating()); // Step 1.a.iv. Assert: m.[[PendingAsyncDependencies]] > 0. MOZ_ASSERT(m->pendingAsyncDependencies() > 0); // Step 1.a.v. Set m.[[PendingAsyncDependencies]] to // m.[[PendingAsyncDependencies]] - 1. m->setPendingAsyncDependencies(m->pendingAsyncDependencies() - 1); // Step 1.a.vi. If m.[[PendingAsyncDependencies]] = 0, then: if (m->pendingAsyncDependencies() == 0) { // Step 1.a.vi.1. Append m to execList. if (!execList.append(m)) { return false; } // Step 1.a.vi.2. If m.[[HasTLA]] is false, perform // GatherAvailableAncestors(m, execList). if (!m->hasTopLevelAwait() && !GatherAvailableModuleAncestors(cx, m, execList)) { return false; } } } } // Step 2. Return unused. return true; } struct EvalOrderComparator { bool operator()(ModuleObject* a, ModuleObject* b, bool* lessOrEqualp) { int32_t result = int32_t(a->getAsyncEvaluatingPostOrder()) - int32_t(b->getAsyncEvaluatingPostOrder()); *lessOrEqualp = (result <= 0); return true; } }; static void RejectExecutionWithPendingException(JSContext* cx, Handle module) { // If there is no exception pending then we have been interrupted or have // OOM'd and all bets are off. We reject the execution by throwing // undefined. Not much more we can do. RootedValue exception(cx); if (cx->isExceptionPending()) { std::ignore = cx->getPendingException(&exception); } cx->clearPendingException(); AsyncModuleExecutionRejected(cx, module, exception); } // https://tc39.es/ecma262/#sec-async-module-execution-fulfilled // ES2023 16.2.1.5.2.4 AsyncModuleExecutionFulfilled void js::AsyncModuleExecutionFulfilled(JSContext* cx, Handle module) { // Step 1. If module.[[Status]] is evaluated, then: if (module->status() == ModuleStatus::Evaluated) { // Step 1.a. Assert: module.[[EvaluationError]] is not empty. MOZ_ASSERT(module->hadEvaluationError()); // Step 1.b. Return unused. return; } // Step 2. Assert: module.[[Status]] is evaluating-async. MOZ_ASSERT(module->status() == ModuleStatus::EvaluatingAsync); // Step 3. Assert: module.[[AsyncEvaluation]] is true. MOZ_ASSERT(module->isAsyncEvaluating()); // Step 4. Assert: module.[[EvaluationError]] is empty. MOZ_ASSERT(!module->hadEvaluationError()); // The following steps are performed in a different order from the // spec. Gather available module ancestors before mutating the module object // as this can fail in our implementation. // Step 8. Let execList be a new empty List. Rooted execList(cx); // Step 9. Perform GatherAvailableAncestors(module, execList). if (!GatherAvailableModuleAncestors(cx, module, &execList)) { RejectExecutionWithPendingException(cx, module); return; } // Step 10. Let sortedExecList be a List whose elements are the elements of // execList, in the order in which they had their [[AsyncEvaluation]] // fields set to true in InnerModuleEvaluation. Rooted scratch(cx); if (!scratch.resize(execList.length())) { ReportOutOfMemory(cx); RejectExecutionWithPendingException(cx, module); return; } MOZ_ALWAYS_TRUE(MergeSort(execList.begin(), execList.length(), scratch.begin(), EvalOrderComparator())); // Step 11. Assert: All elements of sortedExecList have their // [[AsyncEvaluation]] field set to true, // [[PendingAsyncDependencies]] field set to 0, and // [[EvaluationError]] field set to empty. #ifdef DEBUG for (ModuleObject* m : execList) { MOZ_ASSERT(m->isAsyncEvaluating()); MOZ_ASSERT(m->pendingAsyncDependencies() == 0); MOZ_ASSERT(!m->hadEvaluationError()); } #endif // Return to original order of steps. ModuleObject::onTopLevelEvaluationFinished(module); // Step 6. Set module.[[Status]] to evaluated. module->setStatus(ModuleStatus::Evaluated); module->clearAsyncEvaluatingPostOrder(); // Step 7. If module.[[TopLevelCapability]] is not empty, then: if (module->hasTopLevelCapability()) { // Step 7.a. Assert: module.[[CycleRoot]] is module. MOZ_ASSERT(module->getCycleRoot() == module); // Step 7.b. Perform ! Call(module.[[TopLevelCapability]].[[Resolve]], // undefined, undefined). if (!ModuleObject::topLevelCapabilityResolve(cx, module)) { // If Resolve fails, there's nothing more we can do here. cx->clearPendingException(); } } // Step 12. For each Cyclic Module Record m of sortedExecList, do: Rooted m(cx); for (ModuleObject* obj : execList) { m = obj; // Step 12.a. If m.[[Status]] is evaluated, then: if (m->status() == ModuleStatus::Evaluated) { // Step 12.a.i. Assert: m.[[EvaluationError]] is not empty. MOZ_ASSERT(m->hadEvaluationError()); } else if (m->hasTopLevelAwait()) { // Step 12.b. Else if m.[[HasTLA]] is true, then: // Step 12.b.i. Perform ExecuteAsyncModule(m). MOZ_ALWAYS_TRUE(ExecuteAsyncModule(cx, m)); } else { // Step 12.c. Else: // Step 12.c.i. Let result be m.ExecuteModule(). bool ok = ModuleObject::execute(cx, m); // Step 12.c.ii. If result is an abrupt completion, then: if (!ok) { // Step 12.c.ii.1. Perform AsyncModuleExecutionRejected(m, // result.[[Value]]). RejectExecutionWithPendingException(cx, m); } else { // Step 12.c.iii. Else: // Step 12.c.iii.1. Set m.[[Status]] to evaluated. m->setStatus(ModuleStatus::Evaluated); m->clearAsyncEvaluatingPostOrder(); // Step 12.c.iii.2. If m.[[TopLevelCapability]] is not empty, then: if (m->hasTopLevelCapability()) { // Step 12.c.iii.2.a. Assert: m.[[CycleRoot]] is m. MOZ_ASSERT(m->getCycleRoot() == m); // Step 12.c.iii.2.b. Perform ! // Call(m.[[TopLevelCapability]].[[Resolve]], // undefined, undefined). if (!ModuleObject::topLevelCapabilityResolve(cx, m)) { // If Resolve fails, there's nothing more we can do here. cx->clearPendingException(); } } } } } // Step 13. Return unused. } // https://tc39.es/ecma262/#sec-async-module-execution-rejected // ES2023 16.2.1.5.2.5 AsyncModuleExecutionRejected void js::AsyncModuleExecutionRejected(JSContext* cx, Handle module, HandleValue error) { // Step 1. If module.[[Status]] is evaluated, then: if (module->status() == ModuleStatus::Evaluated) { // Step 1.a. Assert: module.[[EvaluationError]] is not empty MOZ_ASSERT(module->hadEvaluationError()); // Step 1.b. Return unused. return; } // Step 2. Assert: module.[[Status]] is evaluating-async. MOZ_ASSERT(module->status() == ModuleStatus::EvaluatingAsync); // Step 3. Assert: module.[[AsyncEvaluation]] is true. MOZ_ASSERT(module->isAsyncEvaluating()); // Step 4. 4. Assert: module.[[EvaluationError]] is empty. MOZ_ASSERT(!module->hadEvaluationError()); ModuleObject::onTopLevelEvaluationFinished(module); // Step 5. Set module.[[EvaluationError]] to ThrowCompletion(error). module->setEvaluationError(error); // Step 6. Set module.[[Status]] to evaluated. MOZ_ASSERT(module->status() == ModuleStatus::Evaluated); module->clearAsyncEvaluatingPostOrder(); // Step 7. For each Cyclic Module Record m of module.[[AsyncParentModules]], // do: Rooted parents(cx, module->asyncParentModules()); Rooted parent(cx); for (uint32_t i = 0; i < parents->length(); i++) { parent = &parents->get(i).toObject().as(); // Step 7.a. Perform AsyncModuleExecutionRejected(m, error). AsyncModuleExecutionRejected(cx, parent, error); } // Step 8. If module.[[TopLevelCapability]] is not empty, then: if (module->hasTopLevelCapability()) { // Step 8.a. Assert: module.[[CycleRoot]] is module. MOZ_ASSERT(module->getCycleRoot() == module); // Step 8.b. Perform ! Call(module.[[TopLevelCapability]].[[Reject]], // undefined, error). if (!ModuleObject::topLevelCapabilityReject(cx, module, error)) { // If Reject fails, there's nothing more we can do here. cx->clearPendingException(); } } // Step 9. Return unused. }