/* 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/. */ function CallModuleResolveHook(module, specifier, expectedMinimumStatus) { let requestedModule = HostResolveImportedModule(module, specifier); if (requestedModule.state < expectedMinimumStatus) ThrowInternalError(JSMSG_BAD_MODULE_STATUS); return requestedModule; } // 15.2.1.16.2 GetExportedNames(exportStarSet) function ModuleGetExportedNames(exportStarSet = []) { if (!IsObject(this) || !IsModule(this)) { return callFunction(CallModuleMethodIfWrapped, this, exportStarSet, "ModuleGetExportedNames"); } // Step 1 let module = this; // Step 2 if (callFunction(ArrayIncludes, exportStarSet, module)) return []; // Step 3 _DefineDataProperty(exportStarSet, exportStarSet.length, module); // Step 4 let exportedNames = []; let namesCount = 0; // Step 5 let localExportEntries = module.localExportEntries; for (let i = 0; i < localExportEntries.length; i++) { let e = localExportEntries[i]; _DefineDataProperty(exportedNames, namesCount++, e.exportName); } // Step 6 let indirectExportEntries = module.indirectExportEntries; for (let i = 0; i < indirectExportEntries.length; i++) { let e = indirectExportEntries[i]; _DefineDataProperty(exportedNames, namesCount++, e.exportName); } // Step 7 let starExportEntries = module.starExportEntries; for (let i = 0; i < starExportEntries.length; i++) { let e = starExportEntries[i]; let requestedModule = CallModuleResolveHook(module, e.moduleRequest, MODULE_STATE_INSTANTIATED); let starNames = callFunction(requestedModule.getExportedNames, requestedModule, exportStarSet); for (let j = 0; j < starNames.length; j++) { let n = starNames[j]; if (n !== "default" && !callFunction(ArrayIncludes, exportedNames, n)) _DefineDataProperty(exportedNames, namesCount++, n); } } return exportedNames; } function ModuleSetStatus(module, newStatus) { assert(newStatus >= MODULE_STATUS_ERRORED && newStatus <= MODULE_STATUS_EVALUATED, "Bad new module status in ModuleSetStatus"); if (newStatus !== MODULE_STATUS_ERRORED) assert(newStatus > module.status, "New module status inconsistent with current status"); UnsafeSetReservedSlot(module, MODULE_OBJECT_STATUS_SLOT, newStatus); } // 15.2.1.16.3 ResolveExport(exportName, resolveSet) // // Returns an object describing the location of the resolved export or // indicating a failure. // // On success this returns: { resolved: true, module, bindingName } // // There are three failure cases: // // - The resolution failure can be blamed on a particular module. // Returns: { resolved: false, module, ambiguous: false } // // - No culprit can be determined and the resolution failure was due to star // export ambiguity. // Returns: { resolved: false, module: null, ambiguous: true } // // - No culprit can be determined and the resolution failure was not due to // star export ambiguity. // Returns: { resolved: false, module: null, ambiguous: false } // function ModuleResolveExport(exportName, resolveSet = []) { if (!IsObject(this) || !IsModule(this)) { return callFunction(CallModuleMethodIfWrapped, this, exportName, resolveSet, "ModuleResolveExport"); } // Step 1 let module = this; // Step 2 assert(module.status !== MODULE_STATUS_ERRORED, "Bad module status in ResolveExport"); // Step 3 for (let i = 0; i < resolveSet.length; i++) { let r = resolveSet[i]; if (r.module === module && r.exportName === exportName) { // This is a circular import request. return {resolved: false, module: null, ambiguous: false}; } } // Step 4 _DefineDataProperty(resolveSet, resolveSet.length, {module: module, exportName: exportName}); // Step 5 let localExportEntries = module.localExportEntries; for (let i = 0; i < localExportEntries.length; i++) { let e = localExportEntries[i]; if (exportName === e.exportName) return {resolved: true, module, bindingName: e.localName}; } // Step 6 let indirectExportEntries = module.indirectExportEntries; for (let i = 0; i < indirectExportEntries.length; i++) { let e = indirectExportEntries[i]; if (exportName === e.exportName) { let importedModule = CallModuleResolveHook(module, e.moduleRequest, MODULE_STATUS_UNINSTANTIATED); let resolution = callFunction(importedModule.resolveExport, importedModule, e.importName, resolveSet); if (!resolution.resolved && !resolution.module) resolution.module = module; return resolution; } } // Step 7 if (exportName === "default") { // A default export cannot be provided by an export *. return {resolved: false, module: null, ambiguous: false}; } // Step 8 let starResolution = null; // Step 9 let starExportEntries = module.starExportEntries; for (let i = 0; i < starExportEntries.length; i++) { let e = starExportEntries[i]; let importedModule = CallModuleResolveHook(module, e.moduleRequest, MODULE_STATUS_UNINSTANTIATED); let resolution = callFunction(importedModule.resolveExport, importedModule, exportName, resolveSet); if (!resolution.resolved && (resolution.module || resolution.ambiguous)) return resolution; if (resolution.resolved) { if (starResolution === null) { starResolution = resolution; } else { if (resolution.module !== starResolution.module || resolution.bindingName !== starResolution.bindingName) { return {resolved: false, module: null, ambiguous: true}; } } } } // Step 10 if (starResolution !== null) return starResolution; return {resolved: false, module: null, ambiguous: false}; } // 15.2.1.18 GetModuleNamespace(module) function GetModuleNamespace(module) { // Step 1 assert(IsModule(module), "GetModuleNamespace called with non-module"); // Step 2 assert(module.status !== MODULE_STATUS_UNINSTANTIATED && module.status !== MODULE_STATUS_ERRORED, "Bad module status in GetModuleNamespace"); // Step 3 let namespace = module.namespace; if (typeof namespace === "undefined") { let exportedNames = callFunction(module.getExportedNames, module); let unambiguousNames = []; for (let i = 0; i < exportedNames.length; i++) { let name = exportedNames[i]; let resolution = callFunction(module.resolveExport, module, name); if (resolution.resolved) _DefineDataProperty(unambiguousNames, unambiguousNames.length, name); } namespace = ModuleNamespaceCreate(module, unambiguousNames); } // Step 4 return namespace; } // 9.4.6.13 ModuleNamespaceCreate(module, exports) function ModuleNamespaceCreate(module, exports) { callFunction(std_Array_sort, exports); let ns = NewModuleNamespace(module, exports); // Pre-compute all bindings now rather than calling ResolveExport() on every // access. for (let i = 0; i < exports.length; i++) { let name = exports[i]; let binding = callFunction(module.resolveExport, module, name); assert(binding.resolved, "Failed to resolve binding"); AddModuleNamespaceBinding(ns, name, binding.module, binding.bindingName); } return ns; } function GetModuleEnvironment(module) { assert(IsModule(module), "Non-module passed to GetModuleEnvironment"); // Check for a previous failed attempt to instantiate this module. This can // only happen due to a bug in the module loader. if (module.status === MODULE_STATUS_ERRORED) ThrowInternalError(JSMSG_MODULE_INSTANTIATE_FAILED, module.status); let env = UnsafeGetReservedSlot(module, MODULE_OBJECT_ENVIRONMENT_SLOT); assert(env === undefined || IsModuleEnvironment(env), "Module environment slot contains unexpected value"); return env; } function RecordModuleError(module, error) { // Set the module's status to 'errored' to indicate a failed module // instantiation and record the exception. The environment slot is also // reset to 'undefined'. assert(IsObject(module) && IsModule(module), "Non-module passed to RecordModuleError"); ModuleSetStatus(module, MODULE_STATUS_ERRORED); UnsafeSetReservedSlot(module, MODULE_OBJECT_ERROR_SLOT, error); UnsafeSetReservedSlot(module, MODULE_OBJECT_ENVIRONMENT_SLOT, undefined); } function CountArrayValues(array, value) { let count = 0; for (let i = 0; i < array.length; i++) { if (array[i] === value) count++; } return count; } function ArrayContains(array, value) { for (let i = 0; i < array.length; i++) { if (array[i] === value) return true; } return false; } // 15.2.1.16.4 ModuleInstantiate() function ModuleInstantiate() { if (!IsObject(this) || !IsModule(this)) return callFunction(CallModuleMethodIfWrapped, this, "ModuleInstantiate"); // Step 1 let module = this; // Step 2 if (module.status === MODULE_STATUS_INSTANTIATING || module.status === MODULE_STATUS_EVALUATING) { ThrowInternalError(JSMSG_BAD_MODULE_STATUS); } // Step 3 let stack = []; // Steps 4-5 try { InnerModuleDeclarationInstantiation(module, stack, 0); } catch (error) { for (let i = 0; i < stack.length; i++) { let m = stack[i]; assert(m.status === MODULE_STATUS_INSTANTIATING || m.status === MODULE_STATUS_ERRORED, "Bad module status after failed instantiation"); RecordModuleError(m, error); } if (stack.length === 0 && typeof(UnsafeGetReservedSlot(module, MODULE_OBJECT_ERROR_SLOT)) === "undefined") { // This can happen due to OOM when appending to the stack. assert(error === "out of memory", "Stack must contain module unless we hit OOM"); RecordModuleError(module, error); } assert(module.status === MODULE_STATUS_ERRORED, "Bad module status after failed instantiation"); assert(SameValue(UnsafeGetReservedSlot(module, MODULE_OBJECT_ERROR_SLOT), error), "Module has different error set after failed instantiation"); throw error; } // Step 6 assert(module.status == MODULE_STATUS_INSTANTIATED || module.status == MODULE_STATUS_EVALUATED, "Bad module status after successful instantiation"); // Step 7 assert(stack.length === 0, "Stack should be empty after successful instantiation"); // Step 8 return undefined; } _SetCanonicalName(ModuleInstantiate, "ModuleInstantiate"); // 15.2.1.16.4.1 InnerModuleDeclarationInstantiation(module, stack, index) function InnerModuleDeclarationInstantiation(module, stack, index) { // Step 1 // TODO: Support module records other than source text module records. // Step 2 if (module.status === MODULE_STATUS_INSTANTIATING || module.status === MODULE_STATUS_INSTANTIATED || module.status === MODULE_STATUS_EVALUATED) { return index; } // Step 3 if (module.status === MODULE_STATUS_ERRORED) throw module.error; // Step 4 assert(module.status === MODULE_STATUS_UNINSTANTIATED, "Bad module status in ModuleDeclarationInstantiation"); // Steps 5 ModuleSetStatus(module, MODULE_STATUS_INSTANTIATING); // Step 6-8 UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_INDEX_SLOT, index); UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT, index); index++; // Step 9 _DefineDataProperty(stack, stack.length, module); // Step 10 let requestedModules = module.requestedModules; for (let i = 0; i < requestedModules.length; i++) { let required = requestedModules[i]; let requiredModule = CallModuleResolveHook(module, required, MODULE_STATUS_ERRORED); index = InnerModuleDeclarationInstantiation(requiredModule, stack, index); assert(requiredModule.status === MODULE_STATUS_INSTANTIATING || requiredModule.status === MODULE_STATUS_INSTANTIATED || requiredModule.status === MODULE_STATUS_EVALUATED, "Bad required module status after InnerModuleDeclarationInstantiation"); assert((requiredModule.status === MODULE_STATUS_INSTANTIATING) === ArrayContains(stack, requiredModule), "Required module should be in the stack iff it is currently being instantiated"); assert(typeof requiredModule.dfsIndex === "number", "Bad dfsIndex"); assert(typeof requiredModule.dfsAncestorIndex === "number", "Bad dfsAncestorIndex"); if (requiredModule.status === MODULE_STATUS_INSTANTIATING) { UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT, std_Math_min(module.dfsAncestorIndex, requiredModule.dfsAncestorIndex)); } } // Step 11 ModuleDeclarationEnvironmentSetup(module); // Steps 12-13 assert(CountArrayValues(stack, module) === 1, "Current module should appear exactly once in the stack"); assert(module.dfsAncestorIndex <= module.dfsIndex, "Bad DFS ancestor index"); // Step 14 if (module.dfsAncestorIndex === module.dfsIndex) { let requiredModule; do { requiredModule = callFunction(std_Array_pop, stack); ModuleSetStatus(requiredModule, MODULE_STATUS_INSTANTIATED); } while (requiredModule !== module); } // Step 15 return index; } // 15.2.1.16.4.2 ModuleDeclarationEnvironmentSetup(module) function ModuleDeclarationEnvironmentSetup(module) { // Step 1 let indirectExportEntries = module.indirectExportEntries; for (let i = 0; i < indirectExportEntries.length; i++) { let e = indirectExportEntries[i]; let resolution = callFunction(module.resolveExport, module, e.exportName); assert(resolution.resolved || resolution.module, "Unexpected failure to resolve export in ModuleDeclarationEnvironmentSetup"); if (!resolution.resolved) { return ResolutionError(resolution, "indirectExport", e.exportName, e.lineNumber, e.columnNumber) } } // Steps 5-6 CreateModuleEnvironment(module); let env = GetModuleEnvironment(module); // Step 8 let importEntries = module.importEntries; for (let i = 0; i < importEntries.length; i++) { let imp = importEntries[i]; let importedModule = CallModuleResolveHook(module, imp.moduleRequest, MODULE_STATUS_INSTANTIATING); if (imp.importName === "*") { let namespace = GetModuleNamespace(importedModule); CreateNamespaceBinding(env, imp.localName, namespace); } else { let resolution = callFunction(importedModule.resolveExport, importedModule, imp.importName); if (!resolution.resolved && !resolution.module) resolution.module = module; if (!resolution.resolved) { return ResolutionError(resolution, "import", imp.importName, imp.lineNumber, imp.columnNumber); } CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName); } } InstantiateModuleFunctionDeclarations(module); } // 15.2.1.16.4.3 ResolutionError(module) function ResolutionError(resolution, kind, name, line, column) { let module = resolution.module; assert(module !== null, "Null module passed to ResolutionError"); assert(module.status === MODULE_STATUS_UNINSTANTIATED || module.status === MODULE_STATUS_INSTANTIATING, "Unexpected module status in ResolutionError"); assert(kind === "import" || kind === "indirectExport", "Unexpected kind in ResolutionError"); assert(line > 0, "Line number should be present for all imports and indirect exports"); let errorNumber; if (kind === "import") { errorNumber = resolution.ambiguous ? JSMSG_AMBIGUOUS_IMPORT : JSMSG_MISSING_IMPORT; } else { errorNumber = resolution.ambiguous ? JSMSG_AMBIGUOUS_INDIRECT_EXPORT : JSMSG_MISSING_INDIRECT_EXPORT; } let message = GetErrorMessage(errorNumber) + ": " + name; let error = CreateModuleSyntaxError(module, line, column, message); RecordModuleError(module, error); throw error; } // 15.2.1.16.5 ModuleEvaluate() function ModuleEvaluate() { if (!IsObject(this) || !IsModule(this)) return callFunction(CallModuleMethodIfWrapped, this, "ModuleEvaluate"); // Step 1 let module = this; // Step 2 if (module.status !== MODULE_STATUS_ERRORED && module.status !== MODULE_STATUS_INSTANTIATED && module.status !== MODULE_STATUS_EVALUATED) { ThrowInternalError(JSMSG_BAD_MODULE_STATUS); } // Step 3 let stack = []; // Steps 4-5 try { InnerModuleEvaluation(module, stack, 0); } catch (error) { for (let i = 0; i < stack.length; i++) { let m = stack[i]; assert(m.status === MODULE_STATUS_EVALUATING, "Bad module status after failed evaluation"); RecordModuleError(m, error); } if (stack.length === 0 && typeof(UnsafeGetReservedSlot(module, MODULE_OBJECT_ERROR_SLOT)) === "undefined") { // This can happen due to OOM when appending to the stack. assert(error === "out of memory", "Stack must contain module unless we hit OOM"); RecordModuleError(module, error); } assert(module.status === MODULE_STATUS_ERRORED, "Bad module status after failed evaluation"); assert(SameValue(UnsafeGetReservedSlot(module, MODULE_OBJECT_ERROR_SLOT), error), "Module has different error set after failed evaluation"); throw error; } assert(module.status == MODULE_STATUS_EVALUATED, "Bad module status after successful evaluation"); assert(stack.length === 0, "Stack should be empty after successful evaluation"); return undefined; } _SetCanonicalName(ModuleEvaluate, "ModuleEvaluate"); // 15.2.1.16.5.1 InnerModuleEvaluation(module, stack, index) function InnerModuleEvaluation(module, stack, index) { // Step 1 // TODO: Support module records other than source text module records. // Step 2 if (module.status === MODULE_STATUS_EVALUATING || module.status === MODULE_STATUS_EVALUATED) { return index; } // Step 3 if (module.status === MODULE_STATUS_ERRORED) throw module.error; // Step 4 assert(module.status === MODULE_STATUS_INSTANTIATED, "Bad module status in ModuleEvaluation"); // Step 5 ModuleSetStatus(module, MODULE_STATUS_EVALUATING); // Steps 6-8 UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_INDEX_SLOT, index); UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT, index); index++; // Step 9 _DefineDataProperty(stack, stack.length, module); // Step 10 let requestedModules = module.requestedModules; for (let i = 0; i < requestedModules.length; i++) { let required = requestedModules[i]; let requiredModule = CallModuleResolveHook(module, required, MODULE_STATUS_INSTANTIATED); index = InnerModuleEvaluation(requiredModule, stack, index); assert(requiredModule.status == MODULE_STATUS_EVALUATING || requiredModule.status == MODULE_STATUS_EVALUATED, "Bad module status after InnerModuleEvaluation"); assert((requiredModule.status === MODULE_STATUS_EVALUATING) === ArrayContains(stack, requiredModule), "Required module should be in the stack iff it is currently being evaluated"); assert(typeof requiredModule.dfsIndex === "number", "Bad dfsIndex"); assert(typeof requiredModule.dfsAncestorIndex === "number", "Bad dfsAncestorIndex"); if (requiredModule.status === MODULE_STATUS_EVALUATING) { UnsafeSetReservedSlot(module, MODULE_OBJECT_DFS_ANCESTOR_INDEX_SLOT, std_Math_min(module.dfsAncestorIndex, requiredModule.dfsAncestorIndex)); } } // Step 11 ExecuteModule(module); // Step 12 assert(CountArrayValues(stack, module) === 1, "Current module should appear exactly once in the stack"); // Step 13 assert(module.dfsAncestorIndex <= module.dfsIndex, "Bad DFS ancestor index"); // Step 14 if (module.dfsAncestorIndex === module.dfsIndex) { let requiredModule; do { requiredModule = callFunction(std_Array_pop, stack); ModuleSetStatus(requiredModule, MODULE_STATUS_EVALUATED); } while (requiredModule !== module); } // Step 15 return index; }