Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  Modules.cpp   Sprache: C

 
/* -*- 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> rval);
static bool SyntheticModuleEvaluate(JSContext* cx, Handle<ModuleObject*> module,
                                    MutableHandle<Value> rval);

////////////////////////////////////////////////////////////////////////////////
// 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.
  // https://html.spec.whatwg.org/multipage/webappapis.html#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.
  // https://html.spec.whatwg.org/multipage/webappapis.html#validate-requested-module-specifiers
  //
  // 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

// https://tc39.es/proposal-json-modules/#sec-smr-getexportednames
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;
}

// 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());

  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;
}

// 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<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;
}

// https://tc39.es/proposal-json-modules/#sec-smr-resolveexport
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);
}

// 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().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<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;
}

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<ModuleObject*> module,
                                 Handle<Value> resolution, Handle<JSAtom*> 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<JSString*> 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<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. 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<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]]).
      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<ResolvedBindingObject>();
      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<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
static bool 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 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<ModuleRequestObject*> moduleRequest(cx);
  Rooted<ModuleObject*> 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 (moduleRequest->hasFirstUnsupportedAttributeKey()) {
      UniqueChars printableKey = AtomToPrintableString(
          cx, moduleRequest->getFirstUnsupportedAttributeKey());
      JS_ReportErrorNumberASCII(
          cx, GetErrorMessage, nullptr,
          JSMSG_IMPORT_ATTRIBUTES_STATIC_IMPORT_UNSUPPORTED_ATTRIBUTE,
          printableKey ? printableKey.get() : "");

      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<ModuleObject*> moduleArg,
                                    MutableHandle<Value> rval) {
  // Steps 1-12 happen elsewhere in the engine.

  // Step 13. Let pc be ! NewPromiseCapability(%Promise%).
  Rooted<PromiseObject*> resultPromise(cx, CreatePromiseObjectForAsync(cx));
  if (!resultPromise) {
    return false;
  }

  // Since the only synthetic modules we support are JSON modules, result is
  // always |undefined|.

  // Step 14. IfAbruptRejectPromise(result, pc) (Skipped)

  // 15. Perform ! pc.[[Resolve]](result).
  if (!AsyncFunctionReturned(cx, resultPromise, JS::UndefinedHandleValue)) {
    return false;
  }

  // 16. Return pc.[[Promise]].
  rval.set(ObjectValue(*resultPromise));
  return true;
}

// https://tc39.es/ecma262/#sec-moduleevaluation
// ES2023 16.2.1.5.2 Evaluate
static bool 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 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<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:
    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<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) {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=88 H=96 G=91

¤ Dauer der Verarbeitung: 0.20 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge