diff options
Diffstat (limited to 'js/src/builtin/Module.js')
-rw-r--r-- | js/src/builtin/Module.js | 533 |
1 files changed, 431 insertions, 102 deletions
diff --git a/js/src/builtin/Module.js b/js/src/builtin/Module.js index 5c3d5e147..064293670 100644 --- a/js/src/builtin/Module.js +++ b/js/src/builtin/Module.js @@ -2,11 +2,11 @@ * 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, expectedMinimumState) +function CallModuleResolveHook(module, specifier, expectedMinimumStatus) { let requestedModule = HostResolveImportedModule(module, specifier); - if (requestedModule.state < expectedMinimumState) - ThrowInternalError(JSMSG_BAD_MODULE_STATE); + if (requestedModule.state < expectedMinimumStatus) + ThrowInternalError(JSMSG_BAD_MODULE_STATUS); return requestedModule; } @@ -65,7 +65,36 @@ function ModuleGetExportedNames(exportStarSet = []) 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)) { @@ -77,88 +106,104 @@ function ModuleResolveExport(exportName, resolveSet = []) 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) - return null; + if (r.module === module && r.exportName === exportName) { + // This is a circular import request. + return {resolved: false, module: null, ambiguous: false}; + } } - // Step 3 + // Step 4 _DefineDataProperty(resolveSet, resolveSet.length, {module: module, exportName: exportName}); - // Step 4 + // Step 5 let localExportEntries = module.localExportEntries; for (let i = 0; i < localExportEntries.length; i++) { let e = localExportEntries[i]; if (exportName === e.exportName) - return {module: module, bindingName: e.localName}; + return {resolved: true, module, bindingName: e.localName}; } - // Step 5 + // 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_STATE_PARSED); - return callFunction(importedModule.resolveExport, importedModule, e.importName, - resolveSet); + MODULE_STATUS_UNINSTANTIATED); + let resolution = callFunction(importedModule.resolveExport, importedModule, e.importName, + resolveSet); + if (!resolution.resolved && !resolution.module) + resolution.module = module; + return resolution; } } - // Step 6 + // Step 7 if (exportName === "default") { // A default export cannot be provided by an export *. - return null; + return {resolved: false, module: null, ambiguous: false}; } - // Step 7 + // Step 8 let starResolution = null; - // Step 8 + // 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_STATE_PARSED); - let resolution = callFunction(importedModule.resolveExport, importedModule, - exportName, resolveSet); - if (resolution === "ambiguous") + MODULE_STATUS_UNINSTANTIATED); + let resolution = callFunction(importedModule.resolveExport, importedModule, exportName, + resolveSet); + if (!resolution.resolved && (resolution.module || resolution.ambiguous)) return resolution; - if (resolution !== null) { + if (resolution.resolved) { if (starResolution === null) { starResolution = resolution; } else { if (resolution.module !== starResolution.module || - resolution.exportName !== starResolution.exportName) + resolution.bindingName !== starResolution.bindingName) { - return "ambiguous"; + return {resolved: false, module: null, ambiguous: true}; } } } } - // Step 9 - return starResolution; + // 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 - let namespace = module.namespace; + 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 === null) - ThrowSyntaxError(JSMSG_MISSING_NAMESPACE_EXPORT); - if (resolution !== "ambiguous") + if (resolution.resolved) _DefineDataProperty(unambiguousNames, unambiguousNames.length, name); } namespace = ModuleNamespaceCreate(module, unambiguousNames); @@ -180,7 +225,7 @@ function ModuleNamespaceCreate(module, exports) for (let i = 0; i < exports.length; i++) { let name = exports[i]; let binding = callFunction(module.resolveExport, module, name); - assert(binding !== null && binding !== "ambiguous", "Failed to resolve binding"); + assert(binding.resolved, "Failed to resolve binding"); AddModuleNamespaceBinding(ns, name, binding.module, binding.bindingName); } @@ -193,8 +238,8 @@ function GetModuleEnvironment(module) // Check for a previous failed attempt to instantiate this module. This can // only happen due to a bug in the module loader. - if (module.state == MODULE_STATE_FAILED) - ThrowInternalError(JSMSG_MODULE_INSTANTIATE_FAILED); + 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), @@ -203,112 +248,396 @@ function GetModuleEnvironment(module) return env; } -function RecordInstantationFailure(module) +function RecordModuleError(module, error) { - // Set the module's state to 'failed' to indicate a failed module - // instantiation and reset the environment slot to 'undefined'. - assert(IsModule(module), "Non-module passed to RecordInstantationFailure"); - SetModuleState(module, MODULE_STATE_FAILED); + // 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); } -// 15.2.1.16.4 ModuleDeclarationInstantiation() -function ModuleDeclarationInstantiation() +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, "ModuleDeclarationInstantiation"); + return callFunction(CallModuleMethodIfWrapped, this, "ModuleInstantiate"); // Step 1 let module = this; - // Step 5 - if (GetModuleEnvironment(module) !== undefined) - return; - - // Step 7 - CreateModuleEnvironment(module); - let env = GetModuleEnvironment(module); + // Step 2 + if (module.status === MODULE_STATUS_INSTANTIATING || + module.status === MODULE_STATUS_EVALUATING) + { + ThrowInternalError(JSMSG_BAD_MODULE_STATUS); + } - SetModuleState(this, MODULE_STATE_INSTANTIATED); + // Step 3 + let stack = []; + // Steps 4-5 try { - // Step 8 - let requestedModules = module.requestedModules; - for (let i = 0; i < requestedModules.length; i++) { - let required = requestedModules[i]; - let requiredModule = CallModuleResolveHook(module, required, MODULE_STATE_PARSED); - callFunction(requiredModule.declarationInstantiation, requiredModule); + 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); } - // Step 9 - let indirectExportEntries = module.indirectExportEntries; - for (let i = 0; i < indirectExportEntries.length; i++) { - let e = indirectExportEntries[i]; - let resolution = callFunction(module.resolveExport, module, e.exportName); - if (resolution === null) - ThrowSyntaxError(JSMSG_MISSING_INDIRECT_EXPORT, e.exportName); - if (resolution === "ambiguous") - ThrowSyntaxError(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, e.exportName); + 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); } - // Step 12 - let importEntries = module.importEntries; - for (let i = 0; i < importEntries.length; i++) { - let imp = importEntries[i]; - let importedModule = CallModuleResolveHook(module, imp.moduleRequest, - MODULE_STATE_INSTANTIATED); - if (imp.importName === "*") { - let namespace = GetModuleNamespace(importedModule); - CreateNamespaceBinding(env, imp.localName, namespace); - } else { - let resolution = callFunction(importedModule.resolveExport, importedModule, - imp.importName); - if (resolution === null) - ThrowSyntaxError(JSMSG_MISSING_IMPORT, imp.importName); - if (resolution === "ambiguous") - ThrowSyntaxError(JSMSG_AMBIGUOUS_IMPORT, imp.importName); - if (resolution.module.state < MODULE_STATE_INSTANTIATED) - ThrowInternalError(JSMSG_BAD_MODULE_STATE); - CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName); - } + 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) + } + + // 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); + + CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName); } + } + + InstantiateModuleFunctionDeclarations(module); +} - // Step 17.a.iii - InstantiateModuleFunctionDeclarations(module); - } catch (e) { - RecordInstantationFailure(module); - throw e; +// 15.2.1.16.4.3 ResolutionError(module) +function ResolutionError(resolution, kind, name) +{ + 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"); + + let message; + if (kind === "import") { + message = resolution.ambiguous ? JSMSG_AMBIGUOUS_IMPORT + : JSMSG_MISSING_IMPORT; + } else { + message = resolution.ambiguous ? JSMSG_AMBIGUOUS_INDIRECT_EXPORT + : JSMSG_MISSING_INDIRECT_EXPORT; + } + + try { + ThrowSyntaxError(message, name); + } catch (error) { + RecordModuleError(module, error); + throw error; } } -_SetCanonicalName(ModuleDeclarationInstantiation, "ModuleDeclarationInstantiation"); -// 15.2.1.16.5 ModuleEvaluation() -function ModuleEvaluation() +// 15.2.1.16.5 ModuleEvaluate() +function ModuleEvaluate() { if (!IsObject(this) || !IsModule(this)) - return callFunction(CallModuleMethodIfWrapped, this, "ModuleEvaluation"); + return callFunction(CallModuleMethodIfWrapped, this, "ModuleEvaluatie"); // Step 1 let module = this; - if (module.state < MODULE_STATE_INSTANTIATED) - ThrowInternalError(JSMSG_BAD_MODULE_STATE); + // 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 - if (module.state == MODULE_STATE_EVALUATED) - return undefined; + assert(module.status === MODULE_STATUS_INSTANTIATED, + "Bad module status in ModuleEvaluation"); // Step 5 - SetModuleState(this, MODULE_STATE_EVALUATED); + ModuleSetStatus(module, MODULE_STATUS_EVALUATING); - // Step 6 + // 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_STATE_INSTANTIATED); - callFunction(requiredModule.evaluation, requiredModule); + 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); } - return EvaluateModule(module); + // Step 15 + return index; } -_SetCanonicalName(ModuleEvaluation, "ModuleEvaluation"); |