summaryrefslogtreecommitdiffstats
path: root/js/src/vm/Modules.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/vm/Modules.cpp
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm/Modules.cpp')
-rw-r--r--js/src/vm/Modules.cpp1830
1 files changed, 1830 insertions, 0 deletions
diff --git a/js/src/vm/Modules.cpp b/js/src/vm/Modules.cpp
new file mode 100644
index 0000000000..fe3a2fb30f
--- /dev/null
+++ b/js/src/vm/Modules.cpp
@@ -0,0 +1,1830 @@
+/* -*- 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/Utf8.h" // mozilla::Utf8Unit
+
+#include <stdint.h> // uint32_t
+
+#include "jstypes.h" // JS_PUBLIC_API
+
+#include "builtin/ModuleObject.h" // js::FinishDynamicModuleImport, js::{,Requested}ModuleObject
+#include "ds/Sort.h"
+#include "frontend/BytecodeCompiler.h" // js::frontend::CompileModule
+#include "frontend/FrontendContext.h" // js::AutoReportFrontendContext
+#include "js/Context.h" // js::AssertHeapIsIdle
+#include "js/RootingAPI.h" // JS::MutableHandle
+#include "js/Value.h" // JS::Value
+#include "vm/EnvironmentObject.h" // js::ModuleEnvironmentObject
+#include "vm/JSContext.h" // CHECK_THREAD, JSContext
+#include "vm/JSObject.h" // JSObject
+#include "vm/List.h" // ListObject
+#include "vm/Runtime.h" // JSRuntime
+
+#include "vm/JSAtom-inl.h"
+#include "vm/JSContext-inl.h" // JSContext::{c,releaseC}heck
+
+using namespace js;
+
+using mozilla::Utf8Unit;
+
+////////////////////////////////////////////////////////////////////////////////
+// Public API
+
+JS_PUBLIC_API void JS::SetSupportedImportAssertions(
+ JSRuntime* rt, const ImportAssertionVector& assertions) {
+ AssertHeapIsIdle();
+ MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
+ MOZ_ASSERT(rt->supportedImportAssertions.ref().empty());
+
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!rt->supportedImportAssertions.ref().appendAll(assertions)) {
+ oomUnsafe.crash("SetSupportedImportAssertions");
+ }
+}
+
+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<JSObject*> evaluationPromise,
+ Handle<Value> referencingPrivate, Handle<JSObject*> moduleRequest,
+ Handle<JSObject*> promise) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(referencingPrivate, promise);
+
+ return js::FinishDynamicModuleImport(
+ cx, evaluationPromise, referencingPrivate, moduleRequest, promise);
+}
+
+template <typename Unit>
+static JSObject* CompileModuleHelper(JSContext* cx,
+ const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<Unit>& srcBuf) {
+ MOZ_ASSERT(!cx->zone()->isAtomsZone());
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+
+ JS::Rooted<JSObject*> 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<char16_t>& srcBuf) {
+ return CompileModuleHelper(cx, options, srcBuf);
+}
+
+JS_PUBLIC_API JSObject* JS::CompileModule(JSContext* cx,
+ const ReadOnlyCompileOptions& options,
+ SourceText<Utf8Unit>& srcBuf) {
+ return CompileModuleHelper(cx, options, srcBuf);
+}
+
+JS_PUBLIC_API void JS::SetModulePrivate(JSObject* module, const Value& value) {
+ JSRuntime* rt = module->zone()->runtimeFromMainThread();
+ module->as<ModuleObject>().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<ModuleObject>().scriptSourceObject()->clearPrivate(rt);
+}
+
+JS_PUBLIC_API JS::Value JS::GetModulePrivate(JSObject* module) {
+ return module->as<ModuleObject>().scriptSourceObject()->getPrivate();
+}
+
+JS_PUBLIC_API bool JS::ModuleLink(JSContext* cx, Handle<JSObject*> moduleArg) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->releaseCheck(moduleArg);
+
+ return js::ModuleLink(cx, moduleArg.as<ModuleObject>());
+}
+
+JS_PUBLIC_API bool JS::ModuleEvaluate(JSContext* cx,
+ Handle<JSObject*> moduleRecord,
+ MutableHandle<JS::Value> rval) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->releaseCheck(moduleRecord);
+
+ return js::ModuleEvaluate(cx, moduleRecord.as<ModuleObject>(), rval);
+}
+
+JS_PUBLIC_API bool JS::ThrowOnModuleEvaluationFailure(
+ JSContext* cx, Handle<JSObject*> 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<JSObject*> moduleRecord) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(moduleRecord);
+
+ return moduleRecord->as<ModuleObject>().requestedModules().Length();
+}
+
+JS_PUBLIC_API JSString* JS::GetRequestedModuleSpecifier(
+ JSContext* cx, Handle<JSObject*> moduleRecord, uint32_t index) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(moduleRecord);
+
+ auto& module = moduleRecord->as<ModuleObject>();
+ return module.requestedModules()[index].moduleRequest()->specifier();
+}
+
+JS_PUBLIC_API void JS::GetRequestedModuleSourcePos(
+ JSContext* cx, Handle<JSObject*> moduleRecord, uint32_t index,
+ uint32_t* lineNumber, uint32_t* columnNumber) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(moduleRecord);
+ MOZ_ASSERT(lineNumber);
+ MOZ_ASSERT(columnNumber);
+
+ auto& module = moduleRecord->as<ModuleObject>();
+ *lineNumber = module.requestedModules()[index].lineNumber();
+ *columnNumber = module.requestedModules()[index].columnNumber();
+}
+
+JS_PUBLIC_API JSScript* JS::GetModuleScript(JS::HandleObject moduleRecord) {
+ AssertHeapIsIdle();
+
+ return moduleRecord->as<ModuleObject>().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<ModuleObject>());
+
+ return GetOrCreateModuleNamespace(cx, moduleRecord.as<ModuleObject>());
+}
+
+JS_PUBLIC_API JSObject* JS::GetModuleForNamespace(
+ JSContext* cx, HandleObject moduleNamespace) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(moduleNamespace);
+ MOZ_ASSERT(moduleNamespace->is<ModuleNamespaceObject>());
+
+ return &moduleNamespace->as<ModuleNamespaceObject>().module();
+}
+
+JS_PUBLIC_API JSObject* JS::GetModuleEnvironment(JSContext* cx,
+ Handle<JSObject*> moduleObj) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(moduleObj);
+ MOZ_ASSERT(moduleObj->is<ModuleObject>());
+
+ return moduleObj->as<ModuleObject>().environment();
+}
+
+JS_PUBLIC_API JSObject* JS::CreateModuleRequest(
+ JSContext* cx, Handle<JSString*> specifierArg) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+
+ Rooted<JSAtom*> specifierAtom(cx, AtomizeString(cx, specifierArg));
+ if (!specifierAtom) {
+ return nullptr;
+ }
+
+ return ModuleRequestObject::create(cx, specifierAtom, nullptr);
+}
+
+JS_PUBLIC_API JSString* JS::GetModuleRequestSpecifier(
+ JSContext* cx, Handle<JSObject*> moduleRequestArg) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(moduleRequestArg);
+
+ return moduleRequestArg->as<ModuleRequestObject>().specifier();
+}
+
+JS_PUBLIC_API void JS::ClearModuleEnvironment(JSObject* moduleObj) {
+ MOZ_ASSERT(moduleObj);
+ AssertHeapIsIdle();
+
+ js::ModuleEnvironmentObject* env =
+ moduleObj->as<js::ModuleObject>().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());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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<ResolveSetEntry, 0, SystemAllocPolicy>;
+
+using ModuleSet =
+ GCHashSet<ModuleObject*, DefaultHasher<ModuleObject*>, SystemAllocPolicy>;
+
+static ModuleObject* HostResolveImportedModule(
+ JSContext* cx, Handle<ModuleObject*> module,
+ Handle<ModuleRequestObject*> moduleRequest,
+ ModuleStatus expectedMinimumStatus);
+static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module,
+ Handle<JSAtom*> exportName,
+ MutableHandle<ResolveSet> resolveSet,
+ MutableHandle<Value> result);
+static ModuleNamespaceObject* ModuleNamespaceCreate(
+ JSContext* cx, Handle<ModuleObject*> module,
+ MutableHandle<UniquePtr<ExportNameVector>> exports);
+static bool InnerModuleLinking(JSContext* cx, Handle<ModuleObject*> module,
+ MutableHandle<ModuleVector> stack, size_t index,
+ size_t* indexOut);
+static bool InnerModuleEvaluation(JSContext* cx, Handle<ModuleObject*> module,
+ MutableHandle<ModuleVector> stack,
+ size_t index, size_t* indexOut);
+static bool ExecuteAsyncModule(JSContext* cx, Handle<ModuleObject*> module);
+static bool GatherAvailableModuleAncestors(
+ JSContext* cx, Handle<ModuleObject*> module,
+ MutableHandle<ModuleVector> 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(Handle<ExportNameVector> list, JSAtom* atom) {
+ for (JSAtom* a : list) {
+ if (a == atom) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool ContainsElement(Handle<ModuleVector> stack, ModuleObject* module) {
+ for (ModuleObject* m : stack) {
+ if (m == module) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef DEBUG
+static size_t CountElements(Handle<ModuleVector> stack, ModuleObject* module) {
+ size_t count = 0;
+ for (ModuleObject* m : stack) {
+ if (m == module) {
+ count++;
+ }
+ }
+
+ return count;
+}
+#endif
+
+// https://tc39.es/ecma262/#sec-getexportednames
+// ES2023 16.2.1.6.2 GetExportedNames
+static bool ModuleGetExportedNames(
+ JSContext* cx, Handle<ModuleObject*> module,
+ MutableHandle<ModuleSet> exportStarSet,
+ MutableHandle<ExportNameVector> exportedNames) {
+ // Step 4. Let exportedNames be a new empty List.
+ MOZ_ASSERT(exportedNames.empty());
+
+ // 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<ModuleRequestObject*> moduleRequest(cx);
+ Rooted<ModuleObject*> requestedModule(cx);
+ Rooted<JSAtom*> 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<ExportNameVector> 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<ModuleObject*> module,
+ Handle<ModuleRequestObject*> moduleRequest,
+ ModuleStatus expectedMinimumStatus) {
+ MOZ_ASSERT(module);
+ MOZ_ASSERT(moduleRequest);
+
+ Rooted<Value> referencingPrivate(cx, JS::GetModulePrivate(module));
+ Rooted<ModuleObject*> 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.
+//
+bool js::ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module,
+ Handle<JSAtom*> exportName,
+ MutableHandle<Value> result) {
+ // Step 1. If resolveSet is not present, set resolveSet to a new empty List.
+ Rooted<ResolveSet> resolveSet(cx);
+
+ return ::ModuleResolveExport(cx, module, exportName, &resolveSet, result);
+}
+
+static bool CreateResolvedBindingObject(JSContext* cx,
+ Handle<ModuleObject*> module,
+ Handle<JSAtom*> bindingName,
+ MutableHandle<Value> result) {
+ Rooted<ResolvedBindingObject*> obj(
+ cx, ResolvedBindingObject::create(cx, module, bindingName));
+ if (!obj) {
+ return false;
+ }
+
+ result.setObject(*obj);
+ return true;
+}
+
+static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module,
+ Handle<JSAtom*> exportName,
+ MutableHandle<ResolveSet> resolveSet,
+ MutableHandle<Value> result) {
+ // 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();
+ 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<JSAtom*> localName(cx, e.localName());
+ return CreateResolvedBindingObject(cx, module, localName, result);
+ }
+ }
+
+ // Step 5. For each ExportEntry Record e of module.[[IndirectExportEntries]],
+ // do:
+ Rooted<ModuleRequestObject*> moduleRequest(cx);
+ Rooted<ModuleObject*> importedModule(cx);
+ Rooted<JSAtom*> 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().starNamespaceStar;
+ 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 ModuleResolveExport(cx, importedModule, name, resolveSet,
+ result);
+ }
+ }
+ }
+
+ // 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();
+ return true;
+ }
+
+ // Step 7. Let starResolution be null.
+ Rooted<ResolvedBindingObject*> starResolution(cx);
+
+ // Step 8. For each ExportEntry Record e of module.[[StarExportEntries]], do:
+ Rooted<Value> resolution(cx);
+ Rooted<ResolvedBindingObject*> 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 (!ModuleResolveExport(cx, importedModule, exportName, resolveSet,
+ &resolution)) {
+ 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<ResolvedBindingObject>();
+
+ // 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));
+ return true;
+ }
+ }
+ }
+ }
+
+ // Step 9. Return starResolution.
+ result.setObjectOrNull(starResolution);
+ return true;
+}
+
+// https://tc39.es/ecma262/#sec-getmodulenamespace
+// ES2023 16.2.1.10 GetModuleNamespace
+ModuleNamespaceObject* js::GetOrCreateModuleNamespace(
+ JSContext* cx, Handle<ModuleObject*> 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<ModuleNamespaceObject*> ns(cx, module->namespace_());
+
+ // Step 3. If namespace is empty, then:
+ if (!ns) {
+ // Step 3.a. Let exportedNames be ? module.GetExportedNames().
+ Rooted<ModuleSet> exportStarSet(cx);
+ Rooted<ExportNameVector> exportedNames(cx);
+ if (!ModuleGetExportedNames(cx, module, &exportStarSet, &exportedNames)) {
+ return nullptr;
+ }
+
+ // Step 3.b. Let unambiguousNames be a new empty List.
+ Rooted<UniquePtr<ExportNameVector>> unambiguousNames(
+ cx, cx->make_unique<ExportNameVector>());
+ if (!unambiguousNames) {
+ return nullptr;
+ }
+
+ // Step 3.c. For each element name of exportedNames, do:
+ Rooted<JSAtom*> name(cx);
+ Rooted<Value> 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<Value> resolution) {
+ MOZ_ASSERT(resolution.isObjectOrNull() ||
+ resolution.toString() == cx->names().ambiguous);
+ return resolution.isObject();
+}
+
+static void InitNamespaceBinding(JSContext* cx,
+ Handle<ModuleEnvironmentObject*> env,
+ Handle<JSAtom*> name,
+ Handle<ModuleNamespaceObject*> ns) {
+ // The property already exists in the evironment but is not writable, so set
+ // the slot directly.
+ RootedId id(cx, AtomToId(name));
+ mozilla::Maybe<PropertyInfo> 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<ModuleObject*> module,
+ MutableHandle<UniquePtr<ExportNameVector>> 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<ModuleNamespaceObject*> 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<JSAtom*> name(cx);
+ Rooted<Value> resolution(cx);
+ Rooted<ResolvedBindingObject*> binding(cx);
+ Rooted<ModuleObject*> importedModule(cx);
+ Rooted<ModuleNamespaceObject*> importedNamespace(cx);
+ Rooted<JSAtom*> 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<ResolvedBindingObject>();
+ importedModule = binding->module();
+ bindingName = binding->bindingName();
+
+ if (bindingName == cx->names().starNamespaceStar) {
+ 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<ModuleEnvironmentObject*> env(
+ cx, &importedModule->initialEnvironment());
+ InitNamespaceBinding(cx, env, bindingName, importedNamespace);
+ }
+
+ if (!ns->addBinding(cx, name, importedModule, bindingName)) {
+ return nullptr;
+ }
+ }
+
+ // Step 10. Return M.
+ return ns;
+}
+
+static void ThrowResolutionError(JSContext* cx, Handle<ModuleObject*> module,
+ Handle<Value> resolution, bool isDirectImport,
+ Handle<JSAtom*> name, uint32_t line,
+ uint32_t column) {
+ MOZ_ASSERT(line != 0);
+
+ bool isAmbiguous = resolution == StringValue(cx->names().ambiguous);
+
+ static constexpr unsigned ErrorNumbers[2][2] = {
+ {JSMSG_AMBIGUOUS_IMPORT, JSMSG_MISSING_IMPORT},
+ {JSMSG_AMBIGUOUS_INDIRECT_EXPORT, JSMSG_MISSING_INDIRECT_EXPORT}};
+ unsigned errorNumber = ErrorNumbers[isDirectImport][isAmbiguous];
+
+ const JSErrorFormatString* errorString =
+ GetErrorMessage(nullptr, errorNumber);
+ MOZ_ASSERT(errorString);
+
+ MOZ_ASSERT(errorString->argCount == 0);
+ Rooted<JSString*> message(cx, JS_NewStringCopyZ(cx, errorString->format));
+ if (!message) {
+ return;
+ }
+
+ Rooted<JSString*> separator(cx, JS_NewStringCopyZ(cx, ": "));
+ if (!separator) {
+ return;
+ }
+
+ message = ConcatStrings<CanGC>(cx, message, separator);
+ if (!message) {
+ return;
+ }
+
+ message = ConcatStrings<CanGC>(cx, message, name);
+ if (!message) {
+ return;
+ }
+
+ RootedString filename(cx);
+ if (const char* chars = module->script()->filename()) {
+ filename =
+ JS_NewStringCopyUTF8Z(cx, JS::ConstUTF8CharsZ(chars, strlen(chars)));
+ } else {
+ filename = cx->names().empty;
+ }
+ if (!filename) {
+ return;
+ }
+
+ RootedValue error(cx);
+ if (!JS::CreateError(cx, JSEXN_SYNTAXERR, nullptr, filename, line, column,
+ nullptr, message, JS::NothingHandleValue, &error)) {
+ return;
+ }
+
+ cx->setPendingException(error, nullptr);
+}
+
+// https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment
+// ES2023 16.2.1.6.4 InitializeEnvironment
+bool js::ModuleInitializeEnvironment(JSContext* cx,
+ Handle<ModuleObject*> module) {
+ MOZ_ASSERT(module->status() == ModuleStatus::Linking);
+
+ // Step 1. For each ExportEntry Record e of module.[[IndirectExportEntries]],
+ // do:
+ Rooted<JSAtom*> exportName(cx);
+ Rooted<Value> resolution(cx);
+ for (const ExportEntry& e : module->indirectExportEntries()) {
+ // Step 1.a. Let resolution be ? module.ResolveExport(e.[[ExportName]]).
+ exportName = e.exportName();
+ if (!ModuleResolveExport(cx, module, exportName, &resolution)) {
+ return false;
+ }
+
+ // Step 1.b. If resolution is null or ambiguous, throw a SyntaxError
+ // exception.
+ if (!IsResolvedBinding(cx, resolution)) {
+ ThrowResolutionError(cx, module, resolution, false, exportName,
+ e.lineNumber(), e.columnNumber());
+ 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<ModuleEnvironmentObject*> env(cx, &module->initialEnvironment());
+
+ // Step 7. For each ImportEntry Record in of module.[[ImportEntries]], do:
+ Rooted<ModuleRequestObject*> moduleRequest(cx);
+ Rooted<ModuleObject*> importedModule(cx);
+ Rooted<JSAtom*> importName(cx);
+ Rooted<JSAtom*> localName(cx);
+ Rooted<ModuleObject*> sourceModule(cx);
+ Rooted<JSAtom*> 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<ModuleNamespaceObject*> 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]]).
+ if (!ModuleResolveExport(cx, importedModule, importName, &resolution)) {
+ return false;
+ }
+
+ // Step 7.d.ii. If resolution is null or ambiguous, throw a SyntaxError
+ // exception.
+ if (!IsResolvedBinding(cx, resolution)) {
+ ThrowResolutionError(cx, module, resolution, true, importName,
+ in.lineNumber(), in.columnNumber());
+ return false;
+ }
+
+ auto* binding = &resolution.toObject().as<ResolvedBindingObject>();
+ sourceModule = binding->module();
+ bindingName = binding->bindingName();
+
+ // Step 7.d.iii. If resolution.[[BindingName]] is namespace, then:
+ if (bindingName == cx->names().starNamespaceStar) {
+ // Step 7.d.iii.1. Let namespace be ?
+ // GetModuleNamespace(resolution.[[Module]]).
+ Rooted<ModuleNamespaceObject*> 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<ModuleEnvironmentObject*> 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
+bool js::ModuleLink(JSContext* cx, Handle<ModuleObject*> 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<ModuleVector> 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/ecma262/#sec-InnerModuleLinking
+// ES2023 16.2.1.5.1.1 InnerModuleLinking
+static bool InnerModuleLinking(JSContext* cx, Handle<ModuleObject*> module,
+ MutableHandle<ModuleVector> stack, size_t index,
+ size_t* indexOut) {
+ // 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<ModuleRequestObject*> moduleRequest(cx);
+ Rooted<ModuleObject*> requiredModule(cx);
+ for (const RequestedModule& request : module->requestedModules()) {
+ moduleRequest = request.moduleRequest();
+
+ // 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:
+ // 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;
+}
+
+// https://tc39.es/ecma262/#sec-moduleevaluation
+// ES2023 16.2.1.5.2 Evaluate
+bool js::ModuleEvaluate(JSContext* cx, Handle<ModuleObject*> moduleArg,
+ MutableHandle<Value> result) {
+ Rooted<ModuleObject*> 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<PromiseObject*> capability(cx);
+ if (!module->hasTopLevelCapability()) {
+ capability = ModuleObject::createTopLevelCapability(cx, module);
+ if (!capability) {
+ return false;
+ }
+
+ Rooted<Value> 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<ModuleVector> stack(cx);
+
+ // Step 6. Let capability be ! NewPromiseCapability(%Promise%).
+ // Step 7. Set module.[[TopLevelCapability]] to capability.
+ Rooted<PromiseObject*> 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<Value> 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<ModuleObject*> module,
+ MutableHandle<ModuleVector> stack,
+ size_t index, size_t* indexOut) {
+ // 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<Value> 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<ModuleRequestObject*> required(cx);
+ Rooted<ModuleObject*> 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:
+ // 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<Value> 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<ModuleObject*> 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<ModuleObject*> module,
+ MutableHandle<ModuleVector> execList) {
+ MOZ_ASSERT(module->status() == ModuleStatus::EvaluatingAsync);
+
+ // Step 1. For each Cyclic Module Record m of module.[[AsyncParentModules]],
+ // do:
+ Rooted<ListObject*> asyncParentModules(cx, module->asyncParentModules());
+ Rooted<ModuleObject*> m(cx);
+ for (uint32_t i = 0; i != asyncParentModules->length(); i++) {
+ m = &asyncParentModules->getDenseElement(i).toObject().as<ModuleObject>();
+
+ // 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<ModuleObject*> 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<ModuleObject*> 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<ModuleVector> 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<ModuleVector> 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<ModuleObject*> 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<ModuleObject*> 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<ListObject*> parents(cx, module->asyncParentModules());
+ Rooted<ModuleObject*> parent(cx);
+ for (uint32_t i = 0; i < parents->length(); i++) {
+ parent = &parents->get(i).toObject().as<ModuleObject>();
+
+ // 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.
+}