Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 <stdint.h> // 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/JSONParser.h" // JSONParser
#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<ModuleObject*> module);
static bool ModuleEvaluate(JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<Value> result);
static bool SyntheticModuleEvaluate(JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<Value> 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<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 JSObject* JS::CompileJsonModule(
JSContext* cx, const ReadOnlyCompileOptions& options,
SourceText<mozilla::Utf8Unit>& srcBuf) {
size_t length = srcBuf.length();
auto chars =
UniqueTwoByteChars(UTF8CharsToNewTwoByteCharsZ(
cx, JS::UTF8Chars(srcBuf.get(), srcBuf.length()),
&length, js::MallocArena)
.get());
if (!chars) {
return nullptr;
}
JS::SourceText<char16_t> source;
if (!source.init(cx, std::move(chars), length)) {
return nullptr;
}
return CompileJsonModule(cx, options, source);
}
JS_PUBLIC_API JSObject* JS::CompileJsonModule(
JSContext* cx, const ReadOnlyCompileOptions& options,
SourceText<char16_t>& srcBuf) {
MOZ_ASSERT(!cx->zone()->isAtomsZone());
AssertHeapIsIdle();
CHECK_THREAD(cx);
auto charRange =
mozilla::Range<const char16_t>(srcBuf.get(), srcBuf.length());
Rooted<JSONParser<char16_t>> parser(
cx, cx, charRange, JSONParser<char16_t>::ParseType::JSONParse);
parser.reportLineNumbersFromParsedData(true);
parser.setFilename(options.filename());
JS::RootedValue jsonValue(cx);
if (!parser.parse(&jsonValue)) {
return nullptr;
}
Rooted<ExportNameVector> exportNames(cx);
if (!exportNames.append(cx->names().default_)) {
ReportOutOfMemory(cx);
return nullptr;
}
Rooted<ModuleObject*> moduleObject(
cx, ModuleObject::createSynthetic(cx, &exportNames));
if (!moduleObject) {
return nullptr;
}
RootedVector<Value> exportValues(cx);
if (!exportValues.append(jsonValue)) {
ReportOutOfMemory(cx);
return nullptr;
}
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<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::IsCyclicModule(JSObject* module) {
return module->as<ModuleObject>().hasCyclicModuleFields();
}
JS_PUBLIC_API bool JS::ModuleLink(JSContext* cx, Handle<JSObject*> moduleArg) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->releaseCheck(moduleArg);
return ::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);
cx->isEvaluatingModule++;
auto guard = mozilla::MakeScopeExit([cx] {
MOZ_ASSERT(cx->isEvaluatingModule != 0);
cx->isEvaluatingModule--;
});
if (moduleRecord.as<ModuleObject>()->hasSyntheticModuleFields()) {
return SyntheticModuleEvaluate(cx, moduleRecord.as<ModuleObject>(), rval);
}
return ::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* moduleRequest = moduleRecord->as<ModuleObject>()
.requestedModules()[index]
.moduleRequest();
// This implements step 7.1.1 in HostLoadImportedModule.
//
// If moduleRequest.[[Attributes]] contains a Record entry such that
// entry.[[Key]] is not "type",
if (moduleRequest->hasFirstUnsupportedAttributeKey()) {
UniqueChars printableKey = AtomToPrintableString(
cx, moduleRequest->getFirstUnsupportedAttributeKey());
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr,
JSMSG_IMPORT_ATTRIBUTES_STATIC_IMPORT_UNSUPPORTED_ATTRIBUTE,
printableKey ? printableKey.get() : "");
return nullptr;
}
// This implements step 7.1.5 in HostLoadImportedModule.
//
// If the result of running the module type allowed steps given moduleType and
// settings is false.
if (moduleRequest->moduleType() == JS::ModuleType::Unknown) {
JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
JSMSG_BAD_MODULE_TYPE);
return nullptr;
}
return moduleRequest->specifier();
}
JS_PUBLIC_API void JS::GetRequestedModuleSourcePos(
JSContext* cx, Handle<JSObject*> 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<ModuleObject>();
*lineNumber = module.requestedModules()[index].lineNumber();
*columnNumber = module.requestedModules()[index].columnNumber();
}
JS_PUBLIC_API JS::ModuleType JS::GetRequestedModuleType(
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()->moduleType();
}
JS_PUBLIC_API JSScript* JS::GetModuleScript(JS::HandleObject moduleRecord) {
AssertHeapIsIdle();
auto& module = moduleRecord->as<ModuleObject>();
// A synthetic module does not have a script associated with it.
if (module.hasSyntheticModuleFields()) {
return nullptr;
}
return module.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,
JS::ModuleType moduleType) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
Rooted<JSAtom*> specifierAtom(cx, AtomizeString(cx, specifierArg));
if (!specifierAtom) {
return nullptr;
}
return ModuleRequestObject::create(cx, specifierAtom, moduleType);
}
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 JS::ModuleType JS::GetModuleRequestType(
JSContext* cx, Handle<JSObject*> moduleRequestArg) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(moduleRequestArg);
return moduleRequestArg->as<ModuleRequestObject>().moduleType();
}
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());
}
}
JS_PUBLIC_API bool JS::ModuleIsLinked(JSObject* moduleObj) {
AssertHeapIsIdle();
return moduleObj->as<ModuleObject>().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<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 CyclicModuleResolveExport(JSContext* cx,
Handle<ModuleObject*> module,
Handle<JSAtom*> exportName,
MutableHandle<ResolveSet> resolveSet,
MutableHandle<Value> result,
ModuleErrorInfo* errorInfoOut = nullptr);
static bool SyntheticModuleResolveExport(JSContext* cx,
Handle<ModuleObject*> module,
Handle<JSAtom*> exportName,
MutableHandle<Value> result,
ModuleErrorInfo* errorInfoOut);
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(const 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
static bool SyntheticModuleGetExportedNames(
JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<ExportNameVector> exportedNames) {
MOZ_ASSERT(exportedNames.empty());
if (!exportedNames.appendAll(module->syntheticExportNames())) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
// 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());
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<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;
}
// 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<ModuleObject*> module,
Handle<JSAtom*> exportName,
MutableHandle<Value> 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> resolveSet(cx);
return CyclicModuleResolveExport(cx, module, exportName, &resolveSet, result,
errorInfoOut);
}
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 CyclicModuleResolveExport(JSContext* cx,
Handle<ModuleObject*> module,
Handle<JSAtom*> exportName,
MutableHandle<ResolveSet> resolveSet,
MutableHandle<Value> 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<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().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<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 (!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<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));
if (errorInfoOut) {
Rooted<ModuleObject*> module1(cx, starResolution->module());
Rooted<ModuleObject*> 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;
}
static bool SyntheticModuleResolveExport(JSContext* cx,
Handle<ModuleObject*> module,
Handle<JSAtom*> exportName,
MutableHandle<Value> 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);
}
// 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;
}
};
// 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:
// 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();