diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index bef88b9548f..f64e6ae9349 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -15,6 +15,7 @@ set(SOURCES Bytecode/Pass/UnifySameBlocks.cpp Bytecode/StringTable.cpp Console.cpp + CyclicModule.cpp Heap/BlockAllocator.cpp Heap/CellAllocator.cpp Heap/Handle.cpp diff --git a/Userland/Libraries/LibJS/CyclicModule.cpp b/Userland/Libraries/LibJS/CyclicModule.cpp new file mode 100644 index 00000000000..529b9429569 --- /dev/null +++ b/Userland/Libraries/LibJS/CyclicModule.cpp @@ -0,0 +1,658 @@ +/* + * Copyright (c) 2022, David Tuin + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace JS { + +CyclicModule::CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector requested_modules) + : Module(realm, filename) + , m_requested_modules(move(requested_modules)) + , m_has_top_level_await(has_top_level_await) +{ +} + +// 16.2.1.5.1 Link ( ), https://tc39.es/ecma262/#sec-moduledeclarationlinking +ThrowCompletionOr CyclicModule::link(VM& vm) +{ + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] link[{}]()", this); + // 1. Assert: module.[[Status]] is not linking or evaluating. + VERIFY(m_status != ModuleStatus::Linking && m_status != ModuleStatus::Evaluating); + // 2. Let stack be a new empty List. + Vector stack; + + // 3. Let result be InnerModuleLinking(module, stack, 0). + auto inner_module_linked_or_error = inner_module_linking(vm, stack, 0); + + // 4. If result is an abrupt completion, then + if (inner_module_linked_or_error.is_error()) { + // a. For each Cyclic Module Record m of stack, do + for (auto* module : stack) { + if (is(module)) { + auto& cyclic_module = static_cast(*module); + // i. Assert: m.[[Status]] is linking. + VERIFY(cyclic_module.m_status == ModuleStatus::Linking); + + // ii. Set m.[[Status]] to unlinked. + cyclic_module.m_status = ModuleStatus::Unlinked; + } + } + // b. Assert: module.[[Status]] is unlinked. + VERIFY(m_status == ModuleStatus::Unlinked); + + // c. Return result. + return inner_module_linked_or_error.release_error(); + } + + // 5. Assert: module.[[Status]] is linked, evaluating-async, or evaluated. + VERIFY(m_status == ModuleStatus::Linked || m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated); + // 6. Assert: stack is empty. + VERIFY(stack.is_empty()); + + // 7. Return undefined. + // Note: We return void since the result of this is never used. + return {}; +} + +// 16.2.1.5.1.1 InnerModuleLinking ( module, stack, index ), https://tc39.es/ecma262/#sec-InnerModuleLinking +ThrowCompletionOr CyclicModule::inner_module_linking(VM& vm, Vector& stack, u32 index) +{ + // 1. If module is not a Cyclic Module Record, then + // a. Perform ? module.Link(). + // b. Return index. + // Note: Step 1, 1.a and 1.b are handled in Module.cpp + + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] inner_module_linking[{}](vm, {}, {})", this, String::join(",", stack), index); + + // 2. If module.[[Status]] is linking, linked, evaluating-async, or evaluated, then + if (m_status == ModuleStatus::Linking || m_status == ModuleStatus::Linked || m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated) { + // a. Return index. + return index; + } + + // 3. Assert: module.[[Status]] is unlinked. + VERIFY(m_status == ModuleStatus::Unlinked); + + // 4. Set module.[[Status]] to linking. + m_status = ModuleStatus::Linking; + + // 5. Set module.[[DFSIndex]] to index. + m_dfs_index = index; + + // 6. Set module.[[DFSAncestorIndex]] to index. + m_dfs_ancestor_index = index; + + // 7. Set index to index + 1. + ++index; + + // 8. Append module to stack. + stack.append(this); + + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] module: {} has requested modules: [{}]", filename(), String::join(",", m_requested_modules)); + + // 9. For each String required of module.[[RequestedModules]], do + for (auto& required_string : m_requested_modules) { + ModuleRequest required { required_string }; + + // a. Let requiredModule be ? HostResolveImportedModule(module, required). + auto required_module = TRY(vm.host_resolve_imported_module(this, required)); + + // b. Set index to ? InnerModuleLinking(requiredModule, stack, index). + index = TRY(required_module->inner_module_linking(vm, stack, index)); + + // c. If requiredModule is a Cyclic Module Record, then + if (is(*required_module)) { + auto& cyclic_module = static_cast(*required_module); + // i. Assert: requiredModule.[[Status]] is either linking, linked, evaluating-async, or evaluated. + VERIFY(cyclic_module.m_status == ModuleStatus::Linking || cyclic_module.m_status == ModuleStatus::Linked || cyclic_module.m_status == ModuleStatus::EvaluatingAsync || cyclic_module.m_status == ModuleStatus::Evaluated); + + // ii. Assert: requiredModule.[[Status]] is linking if and only if requiredModule is in stack. + VERIFY((cyclic_module.m_status == ModuleStatus::Linking) == (stack.contains_slow(&cyclic_module))); + + // iii. If requiredModule.[[Status]] is linking, then + if (cyclic_module.m_status == ModuleStatus::Linking) { + // 1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]). + m_dfs_ancestor_index = min(m_dfs_ancestor_index.value(), cyclic_module.m_dfs_ancestor_index.value()); + } + } + } + + // 10. Perform ? module.InitializeEnvironment(). + (void)TRY(initialize_environment(vm)); + + // 11. Assert: module occurs exactly once in stack. + auto count = 0; + for (auto* module : stack) { + if (module == this) + count++; + } + VERIFY(count == 1); + + // 12. Assert: module.[[DFSAncestorIndex]] ≤ module.[[DFSIndex]]. + VERIFY(m_dfs_ancestor_index.value() <= m_dfs_index.value()); + + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] module {} after inner_linking has dfs {} and ancestor dfs {}", filename(), m_dfs_index.value(), m_dfs_ancestor_index.value()); + + // 13. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then + if (m_dfs_ancestor_index == m_dfs_index) { + // a. Let done be false. + // b. Repeat, while done is false, + while (true) { + // i. Let requiredModule be the last element in stack. + // ii. Remove the last element of stack. + auto* required_module = stack.take_last(); + + // iii. Assert: requiredModule is a Cyclic Module Record. + VERIFY(is(*required_module)); + + // iv. Set requiredModule.[[Status]] to linked. + static_cast(*required_module).m_status = ModuleStatus::Linked; + + // v. If requiredModule and module are the same Module Record, set done to true. + if (required_module == this) + break; + } + } + + // 14. Return index. + return index; +} + +// 16.2.1.5.2 Evaluate ( ), https://tc39.es/ecma262/#sec-moduleevaluation +ThrowCompletionOr CyclicModule::evaluate(VM& vm) +{ + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] evaluate[{}](vm)", this); + // 1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent. + // FIXME: Verify this somehow + + // 2. Assert: module.[[Status]] is linked, evaluating-async, or evaluated. + VERIFY(m_status == ModuleStatus::Linked || m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated); + + // 3. If module.[[Status]] is evaluating-async or evaluated, set module to module.[[CycleRoot]]. + if (m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated) { + // Note: This will continue this function with module.[[CycleRoot]] + VERIFY(m_cycle_root && m_cycle_root->m_status == ModuleStatus::Linked && this != m_cycle_root); + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] evaluate[{}](vm) deferring to cycle root at {}", this, m_cycle_root); + return m_cycle_root->evaluate(vm); + } + + // 4. If module.[[TopLevelCapability]] is not empty, then + if (m_top_level_capability.has_value()) { + // a. Return module.[[TopLevelCapability]].[[Promise]]. + VERIFY(is(*m_top_level_capability->promise)); + return static_cast(m_top_level_capability->promise); + } + + // 5. Let stack be a new empty List. + Vector stack; + + auto& global_object = realm().global_object(); + + // 6. Let capability be ! NewPromiseCapability(%Promise%). + // 7. Set module.[[TopLevelCapability]] to capability. + m_top_level_capability = MUST(new_promise_capability(global_object, global_object.promise_constructor())); + + // 8. Let result be InnerModuleEvaluation(module, stack, 0). + auto result = inner_module_evaluation(vm, stack, 0); + + VERIFY(!vm.exception()); + + // 9. If result is an abrupt completion, then + if (result.is_throw_completion()) { + VERIFY(!m_evaluation_error.is_error()); + + // a. For each Cyclic Module Record m of stack, do + for (auto* mod : stack) { + if (!is(*mod)) + continue; + + auto& cyclic_module = static_cast(*mod); + + // i. Assert: m.[[Status]] is evaluating. + VERIFY(cyclic_module.m_status == ModuleStatus::Evaluating); + + // ii. Set m.[[Status]] to evaluated. + cyclic_module.m_status = ModuleStatus::Evaluated; + + // iii. Set m.[[EvaluationError]] to result. + cyclic_module.m_evaluation_error = result.throw_completion(); + } + + // b. Assert: module.[[Status]] is evaluated. + VERIFY(m_status == ModuleStatus::Evaluated); + + // c. Assert: module.[[EvaluationError]] is result. + VERIFY(m_evaluation_error.is_error() && same_value(*m_evaluation_error.throw_completion().value(), *result.throw_completion().value())); + + // d. Perform ! Call(capability.[[Reject]], undefined, « result.[[Value]] »). + MUST(call(global_object, m_top_level_capability->reject, js_undefined(), *result.throw_completion().value())); + + VERIFY(!vm.exception()); + } + // 10. Else, + else { + // a. Assert: module.[[Status]] is evaluating-async or evaluated. + VERIFY(m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated); + // b. Assert: module.[[EvaluationError]] is empty. + VERIFY(!m_evaluation_error.is_error()); + + // c. If module.[[AsyncEvaluation]] is false, then + if (!m_async_evaluation) { + // i. Assert: module.[[Status]] is evaluated. + VERIFY(m_status == ModuleStatus::Evaluated); + // ii. Perform ! Call(capability.[[Resolve]], undefined, « undefined »). + MUST(call(global_object, m_top_level_capability->resolve, js_undefined(), js_undefined())); + } + + // d. Assert: stack is empty. + VERIFY(stack.is_empty()); + } + + // 11. Return capability.[[Promise]]. + VERIFY(is(*m_top_level_capability->promise)); + return static_cast(m_top_level_capability->promise); +} + +// 16.2.1.5.2.1 InnerModuleEvaluation ( module, stack, index ), https://tc39.es/ecma262/#sec-innermoduleevaluation +ThrowCompletionOr CyclicModule::inner_module_evaluation(VM& vm, Vector& stack, u32 index) +{ + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] inner_module_evaluation[{}](vm, {}, {})", this, String::join(", ", stack), index); + // Note: Step 1 is performed in Module.cpp + + // 2. If module.[[Status]] is evaluating-async or evaluated, then + if (m_status == ModuleStatus::EvaluatingAsync || m_status == ModuleStatus::Evaluated) { + // a. If module.[[EvaluationError]] is empty, return index. + if (!m_evaluation_error.is_error()) + return index; + + // b. Otherwise, return module.[[EvaluationError]]. + return m_evaluation_error.throw_completion(); + } + + // 3. If module.[[Status]] is evaluating, return index. + if (m_status == ModuleStatus::Evaluating) + return index; + + // 4. Assert: module.[[Status]] is linked. + VERIFY(m_status == ModuleStatus::Linked); + + // 5. Set module.[[Status]] to evaluating. + m_status = ModuleStatus::Evaluating; + + // 6. Set module.[[DFSIndex]] to index. + m_dfs_index = index; + + // 7. Set module.[[DFSAncestorIndex]] to index. + m_dfs_ancestor_index = index; + + // 8. Set module.[[PendingAsyncDependencies]] to 0. + m_pending_async_dependencies = 0; + + // 9. Set index to index + 1. + ++index; + + // 10. Append module to stack. + stack.append(this); + + // 11. For each String required of module.[[RequestedModules]], do + for (auto& required_string : m_requested_modules) { + ModuleRequest required { required_string }; + + // a. Let requiredModule be ! HostResolveImportedModule(module, required). + auto* required_module = MUST(vm.host_resolve_imported_module(this, required)).ptr(); + // b. NOTE: Link must be completed successfully prior to invoking this method, so every requested module is guaranteed to resolve successfully. + + // c. Set index to ? InnerModuleEvaluation(requiredModule, stack, index). + index = TRY(required_module->inner_module_evaluation(vm, stack, index)); + + // d. If requiredModule is a Cyclic Module Record, then + if (!is(*required_module)) + continue; + + auto* cyclic_module = static_cast(required_module); + // i. Assert: requiredModule.[[Status]] is either evaluating, evaluating-async, or evaluated. + VERIFY(cyclic_module->m_status == ModuleStatus::Evaluating || cyclic_module->m_status == ModuleStatus::EvaluatingAsync || cyclic_module->m_status == ModuleStatus::Evaluated); + + // ii. Assert: requiredModule.[[Status]] is evaluating if and only if requiredModule is in stack. + VERIFY(cyclic_module->m_status != ModuleStatus::Evaluating || stack.contains_slow(cyclic_module)); + + // iii. If requiredModule.[[Status]] is evaluating, then + if (cyclic_module->m_status == ModuleStatus::Evaluating) { + // 1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]). + m_dfs_ancestor_index = min(m_dfs_ancestor_index.value(), cyclic_module->m_dfs_ancestor_index.value()); + } + // iv. Else, + else { + // 1. Set requiredModule to requiredModule.[[CycleRoot]]. + cyclic_module = cyclic_module->m_cycle_root; + + // 2. Assert: requiredModule.[[Status]] is evaluating-async or evaluated. + VERIFY(cyclic_module->m_status == ModuleStatus::EvaluatingAsync || cyclic_module->m_status == ModuleStatus::Evaluated); + + // 3. If requiredModule.[[EvaluationError]] is not empty, return requiredModule.[[EvaluationError]]. + if (cyclic_module->m_evaluation_error.is_error()) + return cyclic_module->m_evaluation_error.throw_completion(); + } + + // v. If requiredModule.[[AsyncEvaluation]] is true, then + if (cyclic_module->m_async_evaluation) { + // 1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. + ++m_pending_async_dependencies.value(); + + // 2. Append module to requiredModule.[[AsyncParentModules]]. + cyclic_module->m_async_parent_modules.append(this); + } + } + + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] inner_module_evaluation on {} has tla: {} and pending async dep: {} dfs: {} ancestor dfs: {}", filename(), m_has_top_level_await, m_pending_async_dependencies.value(), m_dfs_index.value(), m_dfs_ancestor_index.value()); + // 12. If module.[[PendingAsyncDependencies]] > 0 or module.[[HasTLA]] is true, then + if (m_pending_async_dependencies.value() > 0 || m_has_top_level_await) { + // a. Assert: module.[[AsyncEvaluation]] is false and was never previously set to true. + VERIFY(!m_async_evaluation); // FIXME: I don't think we can check previously? + + // b. Set module.[[AsyncEvaluation]] to true. + m_async_evaluation = true; + // c. NOTE: The order in which module records have their [[AsyncEvaluation]] fields transition to true is significant. (See 16.2.1.5.2.4.) + + // d. If module.[[PendingAsyncDependencies]] is 0, perform ! ExecuteAsyncModule(module). + if (m_pending_async_dependencies.value() == 0) + MUST(execute_async_module(vm)); + } + // 13. Otherwise, perform ? module.ExecuteModule(). + else { + (void)TRY(execute_module(vm)); + } + + // 14. Assert: module occurs exactly once in stack. + auto count = 0; + for (auto* module : stack) { + if (module == this) + count++; + } + VERIFY(count == 1); + + // 15. Assert: module.[[DFSAncestorIndex]] ≤ module.[[DFSIndex]]. + VERIFY(m_dfs_ancestor_index.value() <= m_dfs_index.value()); + + // 16. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then + if (m_dfs_ancestor_index == m_dfs_index) { + // a. Let done be false. + bool done = false; + // b. Repeat, while done is false, + while (!done) { + + // i. Let requiredModule be the last element in stack. + // ii. Remove the last element of stack. + auto* required_module = stack.take_last(); + + // iii. Assert: requiredModule is a Cyclic Module Record. + VERIFY(is(*required_module)); + + auto& cyclic_module = static_cast(*required_module); + + // iv. If requiredModule.[[AsyncEvaluation]] is false, set requiredModule.[[Status]] to evaluated. + if (!cyclic_module.m_async_evaluation) + cyclic_module.m_status = ModuleStatus::Evaluated; + // v. Otherwise, set requiredModule.[[Status]] to evaluating-async. + else + cyclic_module.m_status = ModuleStatus::EvaluatingAsync; + + // vi. If requiredModule and module are the same Module Record, set done to true. + if (required_module == this) + done = true; + + // vii. Set requiredModule.[[CycleRoot]] to module. + cyclic_module.m_cycle_root = this; + } + } + + // 17. Return index. + return index; +} + +Completion CyclicModule::initialize_environment(VM&) +{ + // Note: In ecma262 this is never called on a cyclic module only on SourceTextModules. + // So this check is to make sure we don't accidentally call this. + VERIFY_NOT_REACHED(); + return normal_completion({}); +} + +Completion CyclicModule::execute_module(VM&, Optional) +{ + // Note: In ecma262 this is never called on a cyclic module only on SourceTextModules. + // So this check is to make sure we don't accidentally call this. + VERIFY_NOT_REACHED(); + return js_undefined(); +} + +// 16.2.1.5.2.2 ExecuteAsyncModule ( module ), https://tc39.es/ecma262/#sec-execute-async-module +ThrowCompletionOr CyclicModule::execute_async_module(VM& vm) +{ + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] executing async module {}", filename()); + // 1. Assert: module.[[Status]] is evaluating or evaluating-async. + VERIFY(m_status == ModuleStatus::Evaluating || m_status == ModuleStatus::EvaluatingAsync); + // 2. Assert: module.[[HasTLA]] is true. + VERIFY(m_has_top_level_await); + + auto& global_object = realm().global_object(); + + // 3. Let capability be ! NewPromiseCapability(%Promise%). + auto capability = MUST(new_promise_capability(global_object, global_object.promise_constructor())); + + // 4. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and performs the following steps when called: + auto fulfilled_closure = [&](VM& vm, GlobalObject&) -> ThrowCompletionOr { + // a. Perform ! AsyncModuleExecutionFulfilled(module). + MUST(async_module_execution_fulfilled(vm)); + + // b. Return undefined. + return js_undefined(); + }; + + // 5. Let onFulfilled be ! CreateBuiltinFunction(fulfilledClosure, 0, "", « »). + auto* on_fulfilled = NativeFunction::create(global_object, "", move(fulfilled_closure)); + + // 6. Let rejectedClosure be a new Abstract Closure with parameters (error) that captures module and performs the following steps when called: + auto rejected_closure = [&](VM& vm, GlobalObject&) -> ThrowCompletionOr { + auto error = vm.argument(0); + + // a. Perform ! AsyncModuleExecutionRejected(module, error). + MUST(async_module_execution_rejected(vm, error)); + + // b. Return undefined. + return js_undefined(); + }; + + auto* on_rejected = NativeFunction::create(global_object, "", move(rejected_closure)); + // 7. Let onRejected be ! CreateBuiltinFunction(rejectedClosure, 0, "", « »). + + VERIFY(is(*capability.promise)); + + // 8. Perform ! PerformPromiseThen(capability.[[Promise]], onFulfilled, onRejected). + static_cast(capability.promise)->perform_then(on_fulfilled, on_rejected, {}); + + // 9. Perform ! module.ExecuteModule(capability). + (void)MUST(execute_module(vm, capability)); + + return {}; +} + +// 16.2.1.5.2.3 GatherAvailableAncestors ( module, execList ), https://tc39.es/ecma262/#sec-gather-available-ancestors +ThrowCompletionOr CyclicModule::gather_available_ancestors(Vector& exec_list) +{ + // 1. For each Cyclic Module Record m of module.[[AsyncParentModules]], do + for (auto* module : m_async_parent_modules) { + // a. If execList does not contain m and m.[[CycleRoot]].[[EvaluationError]] is empty, then + if (!exec_list.contains_slow(module) && !module->m_cycle_root->m_evaluation_error.is_error()) { + // i. Assert: m.[[Status]] is evaluating-async. + VERIFY(module->m_status == ModuleStatus::EvaluatingAsync); + + // ii. Assert: m.[[EvaluationError]] is empty. + VERIFY(!module->m_evaluation_error.is_error()); + + // iii. Assert: m.[[AsyncEvaluation]] is true. + VERIFY(module->m_async_evaluation); + + // iv. Assert: m.[[PendingAsyncDependencies]] > 0. + VERIFY(module->m_pending_async_dependencies.value() > 0); + + // v. Set m.[[PendingAsyncDependencies]] to m.[[PendingAsyncDependencies]] - 1. + module->m_pending_async_dependencies.value()--; + + // vi. If m.[[PendingAsyncDependencies]] = 0, then + if (module->m_pending_async_dependencies.value() == 0) { + // 1. Append m to execList. + exec_list.append(module); + + // 2. If m.[[HasTLA]] is false, perform ! GatherAvailableAncestors(m, execList). + if (!module->m_has_top_level_await) + MUST(module->gather_available_ancestors(exec_list)); + } + } + } + + return {}; +} + +// 16.2.1.5.2.4 AsyncModuleExecutionFulfilled ( module ), https://tc39.es/ecma262/#sec-async-module-execution-fulfilled +ThrowCompletionOr CyclicModule::async_module_execution_fulfilled(VM& vm) +{ + // 1. If module.[[Status]] is evaluated, then + if (m_status == ModuleStatus::Evaluated) { + // a. Assert: module.[[EvaluationError]] is not empty. + VERIFY(m_evaluation_error.is_error()); + // b. Return. + return {}; + } + + // 2. Assert: module.[[Status]] is evaluating-async. + VERIFY(m_status == ModuleStatus::EvaluatingAsync); + + // 3. Assert: module.[[AsyncEvaluation]] is true. + VERIFY(m_async_evaluation); + + // 4. Assert: module.[[EvaluationError]] is empty. + VERIFY(!m_evaluation_error.is_error()); + + // 5. Set module.[[AsyncEvaluation]] to false. + m_async_evaluation = false; + + // 6. Set module.[[Status]] to evaluated. + m_status = ModuleStatus::Evaluated; + + // 7. If module.[[TopLevelCapability]] is not empty, then + if (m_top_level_capability.has_value()) { + // a. Assert: module.[[CycleRoot]] is module. + VERIFY(m_cycle_root == this); + + VERIFY(vm.current_realm()); + // b. Perform ! Call(module.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »). + MUST(call(vm.current_realm()->global_object(), m_top_level_capability->resolve, js_undefined(), js_undefined())); + } + + // 8. Let execList be a new empty List. + Vector exec_list; + + // 9. Perform ! GatherAvailableAncestors(module, execList). + MUST(gather_available_ancestors(exec_list)); + + // 10. Let sortedExecList be a List whose elements are the elements of execList, in the order in which they had their [[AsyncEvaluation]] fields set to true in InnerModuleEvaluation. + // FIXME: Sort the list. To do this we need to use more than an Optional to track [[AsyncEvaluation]]. + + // 11. Assert: All elements of sortedExecList have their [[AsyncEvaluation]] field set to true, [[PendingAsyncDependencies]] field set to 0, and [[EvaluationError]] field set to empty. + VERIFY(all_of(exec_list, [&](CyclicModule* module) { return module->m_async_evaluation && module->m_pending_async_dependencies.value() == 0 && !module->m_evaluation_error.is_error(); })); + + // 12. For each Cyclic Module Record m of sortedExecList, do + for (auto* module : exec_list) { + // a. If m.[[Status]] is evaluated, then + if (module->m_status == ModuleStatus::Evaluated) { + // i. Assert: m.[[EvaluationError]] is not empty. + VERIFY(module->m_evaluation_error.is_error()); + } + // b. Else if m.[[HasTLA]] is true, then + else if (module->m_has_top_level_await) { + // i. Perform ! ExecuteAsyncModule(m). + MUST(module->execute_async_module(vm)); + } + // c. Else, + else { + // i. Let result be m.ExecuteModule(). + auto result = module->execute_module(vm); + + // ii. If result is an abrupt completion, then + if (result.is_abrupt()) { + // 1. Perform ! AsyncModuleExecutionRejected(m, result.[[Value]]). + module->async_module_execution_rejected(vm, *result.value()); + } + // iii. Else, + else { + // 1. Set m.[[Status]] to evaluated. + module->m_status = ModuleStatus::Evaluated; + + // 2. If m.[[TopLevelCapability]] is not empty, then + if (module->m_top_level_capability.has_value()) { + // a. Assert: m.[[CycleRoot]] is m. + VERIFY(module->m_cycle_root == module); + + VERIFY(vm.current_realm()); + // b. Perform ! Call(m.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »). + MUST(call(vm.current_realm()->global_object(), module->m_top_level_capability->resolve, js_undefined(), js_undefined())); + } + } + } + } + return {}; +} + +// 16.2.1.5.2.5 AsyncModuleExecutionRejected ( module, error ), https://tc39.es/ecma262/#sec-async-module-execution-rejected +ThrowCompletionOr CyclicModule::async_module_execution_rejected(VM& vm, Value error) +{ + // 1. If module.[[Status]] is evaluated, then + if (m_status == ModuleStatus::Evaluated) { + // a. Assert: module.[[EvaluationError]] is not empty. + VERIFY(m_evaluation_error.is_error()); + // b. Return. + return {}; + } + + // 2. Assert: module.[[Status]] is evaluating-async. + VERIFY(m_status == ModuleStatus::EvaluatingAsync); + + // 3. Assert: module.[[AsyncEvaluation]] is true. + VERIFY(m_async_evaluation); + + // 4. Assert: module.[[EvaluationError]] is empty. + VERIFY(!m_evaluation_error.is_error()); + + // 5. Set module.[[EvaluationError]] to ThrowCompletion(error) + m_evaluation_error = throw_completion(error); + + // 6. Set module.[[Status]] to evaluated. + m_status = ModuleStatus::Evaluated; + + // 7. For each Cyclic Module Record m of module.[[AsyncParentModules]], do + for (auto* module : m_async_parent_modules) { + + // a. Perform ! AsyncModuleExecutionRejected(m, error). + MUST(module->async_module_execution_rejected(vm, error)); + } + + // 8. If module.[[TopLevelCapability]] is not empty, then + if (m_top_level_capability.has_value()) { + // a. Assert: module.[[CycleRoot]] is module. + VERIFY(m_cycle_root == this); + + VERIFY(vm.current_realm()); + // b. Perform ! Call(module.[[TopLevelCapability]].[[Reject]], undefined, « error »). + MUST(call(vm.current_realm()->global_object(), m_top_level_capability->reject, js_undefined(), error)); + } + + return {}; +} + +} diff --git a/Userland/Libraries/LibJS/CyclicModule.h b/Userland/Libraries/LibJS/CyclicModule.h new file mode 100644 index 00000000000..14aab0af5aa --- /dev/null +++ b/Userland/Libraries/LibJS/CyclicModule.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, David Tuin + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace JS { + +enum class ModuleStatus { + Unlinked, + Linking, + Linked, + Evaluating, + EvaluatingAsync, + Evaluated +}; + +// 16.2.1.5 Cyclic Module Records, https://tc39.es/ecma262/#sec-cyclic-module-records +class CyclicModule : public Module { +public: + // Note: Do not call these methods directly unless you are HostResolveImportedModule. + // Badges cannot be used because other hosts must be able to call this (and it is called recursively) + virtual ThrowCompletionOr link(VM& vm) override; + virtual ThrowCompletionOr evaluate(VM& vm) override; + +protected: + CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector requested_modules); + + virtual ThrowCompletionOr inner_module_linking(VM& vm, Vector& stack, u32 index) override; + virtual ThrowCompletionOr inner_module_evaluation(VM& vm, Vector& stack, u32 index) override; + + virtual Completion initialize_environment(VM& vm); + virtual Completion execute_module(VM& vm, Optional capability = {}); + + ThrowCompletionOr execute_async_module(VM& vm); + ThrowCompletionOr gather_available_ancestors(Vector& exec_list); + ThrowCompletionOr async_module_execution_fulfilled(VM& vm); + ThrowCompletionOr async_module_execution_rejected(VM& vm, Value error); + + ModuleStatus m_status { ModuleStatus::Unlinked }; // [[Status]] + ThrowCompletionOr m_evaluation_error; // [[EvaluationError]] + Optional m_dfs_index; // [[DFSIndex]] + Optional m_dfs_ancestor_index; // [[DFSAncestorIndex]] + Vector m_requested_modules; // [[RequestedModules]] + CyclicModule* m_cycle_root; // [[CycleRoot]] + bool m_has_top_level_await { false }; // [[HasTLA]] + bool m_async_evaluation { false }; // [[AsyncEvaluation]] + Optional m_top_level_capability; // [[TopLevelCapability]] + Vector m_async_parent_modules; // [[AsyncParentModules]] + Optional m_pending_async_dependencies; // [[PendingAsyncDependencies]] +}; + +} diff --git a/Userland/Libraries/LibJS/Module.cpp b/Userland/Libraries/LibJS/Module.cpp index 38cf4ed746c..72856a2503e 100644 --- a/Userland/Libraries/LibJS/Module.cpp +++ b/Userland/Libraries/LibJS/Module.cpp @@ -1,17 +1,19 @@ /* * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2022, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include +#include namespace JS { -Module::Module(Realm& realm, StringView filename) - : m_vm(realm.vm()) - , m_realm(make_handle(&realm)) - , m_filename(filename) +Module::Module(Realm& realm, String filename) + : m_realm(make_handle(&realm)) + , m_filename(move(filename)) { } @@ -19,4 +21,100 @@ Module::~Module() { } +// 16.2.1.5.1.1 InnerModuleLinking ( module, stack, index ), https://tc39.es/ecma262/#sec-InnerModuleLinking +ThrowCompletionOr Module::inner_module_linking(VM& vm, Vector&, u32 index) +{ + // Note: Until we have something extending module which is not SourceTextModule we crash. + VERIFY_NOT_REACHED(); + + // 1. If module is not a Cyclic Module Record, then + + // a. Perform ? module.Link(). + TRY(link(vm)); + // b. Return index. + return index; +} + +// 16.2.1.5.2.1 InnerModuleEvaluation ( module, stack, index ), https://tc39.es/ecma262/#sec-innermoduleevaluation +ThrowCompletionOr Module::inner_module_evaluation(VM& vm, Vector&, u32 index) +{ + // Note: Until we have something extending module which is not SourceTextModule we crash. + VERIFY_NOT_REACHED(); + + // 1. If module is not a Cyclic Module Record, then + // a. Let promise be ! module.Evaluate(). + auto* promise = TRY(evaluate(vm)); + + // b. Assert: promise.[[PromiseState]] is not pending. + VERIFY(promise->state() != Promise::State::Pending); + + // c. If promise.[[PromiseState]] is rejected, then + if (promise->state() == Promise::State::Rejected) { + // i. Return ThrowCompletion(promise.[[PromiseResult]]). + return throw_completion(promise->result()); + } + + // d. Return index. + return index; +} + +// 16.2.1.10 GetModuleNamespace ( module ), https://tc39.es/ecma262/#sec-getmodulenamespace +ThrowCompletionOr Module::get_module_namespace(VM& vm) +{ + // 1. Assert: If module is a Cyclic Module Record, then module.[[Status]] is not unlinked. + // FIXME: How do we check this without breaking encapsulation? + + // 2. Let namespace be module.[[Namespace]]. + auto* namespace_ = m_namespace.is_null() ? nullptr : m_namespace.cell(); + + // 3. If namespace is empty, then + if (!namespace_) { + // a. Let exportedNames be ? module.GetExportedNames(). + auto exported_names = TRY(get_exported_names(vm)); + + // b. Let unambiguousNames be a new empty List. + Vector unambiguous_names; + + // c. For each element name of exportedNames, do + for (auto& name : exported_names) { + // i. Let resolution be ? module.ResolveExport(name). + auto resolution = TRY(resolve_export(vm, name)); + + // ii. If resolution is a ResolvedBinding Record, append name to unambiguousNames. + if (resolution.is_valid()) + unambiguous_names.append(name); + } + + // d. Set namespace to ModuleNamespaceCreate(module, unambiguousNames). + namespace_ = module_namespace_create(vm, unambiguous_names); + VERIFY(!m_namespace.is_null()); + // Note: This set the local variable 'namespace' and not the member variable which is done by ModuleNamespaceCreate + } + + // 4. Return namespace. + return namespace_; +} + +// 10.4.6.12 ModuleNamespaceCreate ( module, exports ), https://tc39.es/ecma262/#sec-modulenamespacecreate +Object* Module::module_namespace_create(VM& vm, Vector unambiguous_names) +{ + // 1. Assert: module.[[Namespace]] is empty. + VERIFY(m_namespace.is_null()); + + // 2. Let internalSlotsList be the internal slots listed in Table 34. + // 3. Let M be ! MakeBasicObject(internalSlotsList). + // 4. Set M's essential internal methods to the definitions specified in 10.4.6. + // 5. Set M.[[Module]] to module. + // 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. + // 7. Set M.[[Exports]] to sortedExports. + // 8. Create own properties of M corresponding to the definitions in 28.3. + Object* module_namespace = vm.heap().allocate(realm().global_object(), realm().global_object(), this, move(unambiguous_names)); + + // 9. Set module.[[Namespace]] to M. + m_namespace = make_handle(module_namespace); + + // 10. Return M. + return module_namespace; +} + } diff --git a/Userland/Libraries/LibJS/Module.h b/Userland/Libraries/LibJS/Module.h index ebb12ba3dff..820f6af0e44 100644 --- a/Userland/Libraries/LibJS/Module.h +++ b/Userland/Libraries/LibJS/Module.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2022, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ @@ -11,6 +12,46 @@ namespace JS { +struct ResolvedBinding { + enum Type { + BindingName, + Namespace, + Ambiguous, + Null, + }; + + static ResolvedBinding null() + { + return {}; + } + + static ResolvedBinding ambiguous() + { + ResolvedBinding binding; + binding.type = Ambiguous; + return binding; + } + + Type type { Null }; + Module* module { nullptr }; + FlyString export_name; + + bool is_valid() const + { + return type == BindingName || type == Namespace; + } + + bool is_namespace() const + { + return type == Namespace; + } + + bool is_ambiguous() const + { + return type == Ambiguous; + } +}; + // 16.2.1.4 Abstract Module Records, https://tc39.es/ecma262/#sec-abstract-module-records class Module : public RefCounted { public: @@ -22,15 +63,34 @@ public: StringView filename() const { return m_filename; } Environment* environment() { return m_environment.cell(); } - Object* namespace_() { return m_namespace.cell(); } + + ThrowCompletionOr get_module_namespace(VM& vm); + + virtual ThrowCompletionOr link(VM& vm) = 0; + virtual ThrowCompletionOr evaluate(VM& vm) = 0; + + virtual ThrowCompletionOr> get_exported_names(VM& vm, Vector export_star_set = {}) = 0; + virtual ThrowCompletionOr resolve_export(VM& vm, FlyString const& export_name, Vector resolve_set = {}) = 0; + + virtual ThrowCompletionOr inner_module_linking(VM& vm, Vector& stack, u32 index); + virtual ThrowCompletionOr inner_module_evaluation(VM& vm, Vector& stack, u32 index); protected: - explicit Module(Realm&, StringView filename); + Module(Realm&, String filename); + + void set_environment(Environment* environment) + { + m_environment = make_handle(environment); + } private: - // Handles are not safe unless we keep the VM alive. - NonnullRefPtr m_vm; + Object* module_namespace_create(VM& vm, Vector unambiguous_names); + // These handles are only safe as long as the VM they live in is valid. + // But evaluated modules SHOULD be stored in the VM so unless you intentionally + // destroy the VM but keep the modules this should not happen. Because VM + // stores modules with a RefPtr we cannot just store the VM as that leads to + // cycles. Handle m_realm; // [[Realm]] Handle m_environment; // [[Environment]] Handle m_namespace; // [[Namespace]] diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 6c65a23ee3d..826cb74e3f1 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -50,6 +50,7 @@ M(InvalidIndex, "Index must be a positive integer") \ M(InvalidLeftHandAssignment, "Invalid left-hand side in assignment") \ M(InvalidLength, "Invalid {} length") \ + M(InvalidOrAmbiguousExportEntry, "Invalid or ambiguous export entry '{}'") \ M(InvalidPrecision, "Precision must be an integer no less than 1, and no greater than 100") \ M(InvalidTimeValue, "Invalid time value") \ M(InvalidRadix, "Radix must be an integer no less than 2, and no greater than 36") \ diff --git a/Userland/Libraries/LibJS/Runtime/ModuleNamespaceObject.cpp b/Userland/Libraries/LibJS/Runtime/ModuleNamespaceObject.cpp index 59aa4f55fed..27e04943b3c 100644 --- a/Userland/Libraries/LibJS/Runtime/ModuleNamespaceObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ModuleNamespaceObject.cpp @@ -150,8 +150,32 @@ ThrowCompletionOr ModuleNamespaceObject::internal_get(PropertyKey const& // 4. Let m be O.[[Module]]. // 5. Let binding be ! m.ResolveExport(P). - // FIXME: Add steps 5 through 12 - return vm().throw_completion(global_object(), ErrorType::ModuleNoEnvironment); + auto binding = MUST(m_module->resolve_export(vm(), property_key.to_string())); + + // 6. Assert: binding is a ResolvedBinding Record. + VERIFY(binding.is_valid()); + + // 7. Let targetModule be binding.[[Module]]. + auto* target_module = binding.module; + + // 8. Assert: targetModule is not undefined. + VERIFY(target_module); + + // 9. If binding.[[BindingName]] is namespace, then + if (binding.is_namespace()) { + // a. Return ? GetModuleNamespace(targetModule). + return TRY(target_module->get_module_namespace(vm())); + } + + // 10. Let targetEnv be targetModule.[[Environment]]. + auto* target_environment = target_module->environment(); + + // 11. If targetEnv is empty, throw a ReferenceError exception. + if (!target_environment) + return vm().throw_completion(global_object(), ErrorType::ModuleNoEnvironment); + + // 12. Return ? targetEnv.GetBindingValue(binding.[[BindingName]], true). + return target_environment->get_binding_value(global_object(), binding.export_name, true); } // 10.4.6.9 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-set-p-v-receiver diff --git a/Userland/Libraries/LibJS/SourceTextModule.cpp b/Userland/Libraries/LibJS/SourceTextModule.cpp index abc0698f5c5..62324b14797 100644 --- a/Userland/Libraries/LibJS/SourceTextModule.cpp +++ b/Userland/Libraries/LibJS/SourceTextModule.cpp @@ -1,13 +1,75 @@ /* * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2022, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include +#include +#include #include namespace JS { +// 16.2.1.3 Static Semantics: ModuleRequests, https://tc39.es/ecma262/#sec-static-semantics-modulerequests +static Vector module_requests(Program const& program) +{ + // A List of all the ModuleSpecifier strings used by the module represented by this record to request the importation of a module. + // Note: The List is source text occurrence ordered! + struct RequestedModuleAndSourceIndex { + FlyString requested_module; + u64 source_index; + + bool operator<(RequestedModuleAndSourceIndex const& rhs) const + { + return source_index < rhs.source_index; + } + }; + + Vector requested_modules_with_indices; + + for (auto const& import_statement : program.imports()) { + requested_modules_with_indices.append({ import_statement.module_request().module_specifier.view(), + import_statement.source_range().start.offset }); + } + + for (auto const& export_statement : program.exports()) { + for (auto const& export_entry : export_statement.entries()) { + if (export_entry.kind != ExportStatement::ExportEntry::Kind::ModuleRequest) + continue; + requested_modules_with_indices.append({ export_entry.module_request.module_specifier.view(), + export_statement.source_range().start.offset }); + } + } + + quick_sort(requested_modules_with_indices); + + Vector requested_modules_in_source_order; + requested_modules_in_source_order.ensure_capacity(requested_modules_with_indices.size()); + for (auto& module : requested_modules_with_indices) { + requested_modules_in_source_order.append(module.requested_module); + } + + return requested_modules_in_source_order; +} + +SourceTextModule::SourceTextModule(Realm& realm, StringView filename, bool has_top_level_await, NonnullRefPtr body, Vector requested_modules, + Vector import_entries, Vector local_export_entries, + Vector indirect_export_entries, Vector star_export_entries, + RefPtr default_export) + : CyclicModule(realm, filename, has_top_level_await, move(requested_modules)) + , m_ecmascript_code(move(body)) + , m_execution_context(realm.heap()) + , m_import_entries(move(import_entries)) + , m_local_export_entries(move(local_export_entries)) + , m_indirect_export_entries(move(indirect_export_entries)) + , m_star_export_entries(move(star_export_entries)) + , m_default_export(move(default_export)) +{ +} + // 16.2.1.6.1 ParseModule ( sourceText, realm, hostDefined ), https://tc39.es/ecma262/#sec-parsemodule Result, Vector> SourceTextModule::parse(StringView source_text, Realm& realm, StringView filename) { @@ -19,18 +81,567 @@ Result, Vector> SourceTextModule: if (parser.has_errors()) return parser.errors(); - // FIXME: Implement the rest of ParseModule. - return adopt_ref(*new SourceTextModule(realm, filename, move(body))); + // 3. Let requestedModules be the ModuleRequests of body. + auto requested_modules = module_requests(*body); + + // 4. Let importEntries be ImportEntries of body. + Vector import_entries; + for (auto const& import_statement : body->imports()) + import_entries.extend(import_statement.entries()); + + // 5. Let importedBoundNames be ImportedLocalNames(importEntries). + // Note: Since we have to potentially extract the import entry we just use importEntries + // In the future it might be an optimization to have a set/map of string to speed up the search. + + // 6. Let indirectExportEntries be a new empty List. + Vector indirect_export_entries; + + // 7. Let localExportEntries be a new empty List. + Vector local_export_entries; + + // 8. Let starExportEntries be a new empty List. + Vector star_export_entries; + + // Note: Not in the spec but makes it easier to find the default. + RefPtr default_export; + + // 9. Let exportEntries be ExportEntries of body. + // 10. For each ExportEntry Record ee of exportEntries, do + for (auto const& export_statement : body->exports()) { + + if (export_statement.is_default_export()) { + VERIFY(!default_export); + VERIFY(export_statement.entries().size() == 1); + VERIFY(export_statement.has_statement()); + + auto const& entry = export_statement.entries()[0]; + VERIFY(entry.kind == ExportStatement::ExportEntry::Kind::LocalExport); + VERIFY(import_entries.find_if( + [&](ImportEntry const& import_entry) { + return import_entry.local_name == entry.local_or_import_name; + }) + .is_end()); + default_export = export_statement; + } + + for (auto const& export_entry : export_statement.entries()) { + + // a. If ee.[[ModuleRequest]] is null, then + if (export_entry.kind == ExportStatement::ExportEntry::Kind::LocalExport) { + + auto in_imported_bound_names = import_entries.find_if( + [&](ImportEntry const& import_entry) { + return import_entry.local_name == export_entry.local_or_import_name; + }); + + // i. If ee.[[LocalName]] is not an element of importedBoundNames, then + if (in_imported_bound_names.is_end()) { + // 1. Append ee to localExportEntries. + local_export_entries.empend(export_entry); + } + // ii. Else, + else { + // 1. Let ie be the element of importEntries whose [[LocalName]] is the same as ee.[[LocalName]]. + auto& import_entry = *in_imported_bound_names; + + // 2. If ie.[[ImportName]] is namespace-object, then + if (import_entry.is_namespace()) { + // a. NOTE: This is a re-export of an imported module namespace object. + // b. Append ee to localExportEntries. + local_export_entries.empend(export_entry); + } + // 3. Else, + else { + // a. NOTE: This is a re-export of a single name. + // b. Append the ExportEntry Record { [[ModuleRequest]]: ie.[[ModuleRequest]], [[ImportName]]: ie.[[ImportName]], [[LocalName]]: null, [[ExportName]]: ee.[[ExportName]] } to indirectExportEntries. + indirect_export_entries.empend(import_entry.module_request(), import_entry.import_name, export_entry.export_name); + } + } + } + // b. Else if ee.[[ImportName]] is all-but-default, then + else if (export_entry.is_all_but_default()) { + // i. Assert: ee.[[ExportName]] is null. + VERIFY(export_entry.export_name.is_null()); + // ii. Append ee to starExportEntries. + star_export_entries.empend(export_entry); + } + // c. Else, + else { + // i. Append ee to indirectExportEntries. + indirect_export_entries.empend(export_entry); + } + } + } + + // 11. Let async be body Contains await. + bool async = body->has_top_level_await(); + + // 12. Return Source Text Module Record { + // [[Realm]]: realm, [[Environment]]: empty, [[Namespace]]: empty, [[CycleRoot]]: empty, [[HasTLA]]: async, + // [[AsyncEvaluation]]: false, [[TopLevelCapability]]: empty, [[AsyncParentModules]]: « », + // [[PendingAsyncDependencies]]: empty, [[Status]]: unlinked, [[EvaluationError]]: empty, + // [[HostDefined]]: hostDefined, [[ECMAScriptCode]]: body, [[Context]]: empty, [[ImportMeta]]: empty, + // [[RequestedModules]]: requestedModules, [[ImportEntries]]: importEntries, [[LocalExportEntries]]: localExportEntries, + // [[IndirectExportEntries]]: indirectExportEntries, [[StarExportEntries]]: starExportEntries, [[DFSIndex]]: empty, [[DFSAncestorIndex]]: empty }. + // FIXME: Add HostDefined + return adopt_ref(*new SourceTextModule(realm, filename, async, move(body), move(requested_modules), move(import_entries), move(local_export_entries), move(indirect_export_entries), move(star_export_entries), move(default_export))); } -SourceTextModule::SourceTextModule(Realm& realm, StringView filename, NonnullRefPtr program) - : Module(realm, filename) - , m_ecmascript_code(move(program)) +// 16.2.1.6.2 GetExportedNames ( [ exportStarSet ] ), https://tc39.es/ecma262/#sec-getexportednames +ThrowCompletionOr> SourceTextModule::get_exported_names(VM& vm, Vector export_star_set) { + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] get_export_names of {}", filename()); + // 1. If exportStarSet is not present, set exportStarSet to a new empty List. + // Note: This is done by default argument + + // 2. If exportStarSet contains module, then + if (export_star_set.contains_slow(this)) { + // a. Assert: We've reached the starting point of an export * circularity. + // FIXME: How do we check that? + + // b. Return a new empty List. + return Vector {}; + } + + // 3. Append module to exportStarSet. + export_star_set.append(this); + + // 4. Let exportedNames be a new empty List. + Vector exported_names; + + // 5. For each ExportEntry Record e of module.[[LocalExportEntries]], do + for (auto& entry : m_local_export_entries) { + // a. Assert: module provides the direct binding for this export. + // FIXME: How do we check that? + + // b. Append e.[[ExportName]] to exportedNames. + exported_names.empend(entry.export_name); + } + + // 6. For each ExportEntry Record e of module.[[IndirectExportEntries]], do + for (auto& entry : m_indirect_export_entries) { + // a. Assert: module provides the direct binding for this export. + // FIXME: How do we check that? + + // b. Append e.[[ExportName]] to exportedNames. + exported_names.empend(entry.export_name); + } + + // 7. For each ExportEntry Record e of module.[[StarExportEntries]], do + for (auto& entry : m_star_export_entries) { + // a. Let requestedModule be ? HostResolveImportedModule(module, e.[[ModuleRequest]]). + auto requested_module = TRY(vm.host_resolve_imported_module(this, entry.module_request)); + + // b. Let starNames be ? requestedModule.GetExportedNames(exportStarSet). + auto star_names = TRY(requested_module->get_exported_names(vm, export_star_set)); + + // c. For each element n of starNames, do + for (auto& name : star_names) { + // i. If SameValue(n, "default") is false, then + if (name != "default"sv) { + // 1. If n is not an element of exportedNames, then + if (!exported_names.contains_slow(name)) { + // a. Append n to exportedNames. + exported_names.empend(name); + } + } + } + } + + // 8. Return exportedNames. + return exported_names; } -SourceTextModule::~SourceTextModule() +// 16.2.1.6.4 InitializeEnvironment ( ), https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment +Completion SourceTextModule::initialize_environment(VM& vm) { + // 1. For each ExportEntry Record e of module.[[IndirectExportEntries]], do + for (auto& entry : m_indirect_export_entries) { + // a. Let resolution be ? module.ResolveExport(e.[[ExportName]]). + auto resolution = TRY(resolve_export(vm, entry.export_name)); + // b. If resolution is null or ambiguous, throw a SyntaxError exception. + if (!resolution.is_valid()) + return vm.throw_completion(realm().global_object(), ErrorType::InvalidOrAmbiguousExportEntry, entry.export_name); + + // c. Assert: resolution is a ResolvedBinding Record. + VERIFY(resolution.is_valid()); + } + + // 2. Assert: All named exports from module are resolvable. + // Note: We check all the indirect export entries above in step 1 and all + // the local named exports are resolvable by construction. + + // 3. Let realm be module.[[Realm]]. + // 4. Assert: realm is not undefined. + // Note: This must be true because we use a reference. + + auto& global_object = realm().global_object(); + + // 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). + auto* environment = vm.heap().allocate(global_object, &realm().global_environment()); + + // 6. Set module.[[Environment]] to env. + set_environment(environment); + + // 7. For each ImportEntry Record in of module.[[ImportEntries]], do + for (auto& import_entry : m_import_entries) { + if (!import_entry.module_request().assertions.is_empty()) + return vm.throw_completion(global_object, ErrorType::NotImplemented, "import statements with assertions"); + + // a. Let importedModule be ! HostResolveImportedModule(module, in.[[ModuleRequest]]). + auto imported_module = MUST(vm.host_resolve_imported_module(this, import_entry.module_request())); + // b. NOTE: The above call cannot fail because imported module requests are a subset of module.[[RequestedModules]], and these have been resolved earlier in this algorithm. + + // c. If in.[[ImportName]] is namespace-object, then + if (import_entry.is_namespace()) { + // i. Let namespace be ? GetModuleNamespace(importedModule). + auto* namespace_ = TRY(imported_module->get_module_namespace(vm)); + + // ii. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). + MUST(environment->create_immutable_binding(global_object, import_entry.local_name, true)); + + // iii. Call env.InitializeBinding(in.[[LocalName]], namespace). + environment->initialize_binding(global_object, import_entry.local_name, namespace_); + } + // d. Else, + else { + // i. Let resolution be ? importedModule.ResolveExport(in.[[ImportName]]). + auto resolution = TRY(imported_module->resolve_export(vm, import_entry.import_name)); + + // ii. If resolution is null or ambiguous, throw a SyntaxError exception. + if (!resolution.is_valid()) + return vm.throw_completion(global_object, ErrorType::InvalidOrAmbiguousExportEntry, import_entry.import_name); + + // iii. If resolution.[[BindingName]] is namespace, then + if (resolution.is_namespace()) { + // 1. Let namespace be ? GetModuleNamespace(resolution.[[Module]]). + auto* namespace_ = TRY(resolution.module->get_module_namespace(vm)); + + // 2. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). + MUST(environment->create_immutable_binding(global_object, import_entry.local_name, true)); + + // 3. Call env.InitializeBinding(in.[[LocalName]], namespace). + MUST(environment->initialize_binding(global_object, import_entry.local_name, namespace_)); + } + // iv. Else, + else { + // 1. Call env.CreateImportBinding(in.[[LocalName]], resolution.[[Module]], resolution.[[BindingName]]). + MUST(environment->create_import_binding(import_entry.local_name, resolution.module, resolution.export_name)); + } + } + } + + // 8. Let moduleContext be a new ECMAScript code execution context. + // Note: this has already been created during the construction of this object. + + // 9. Set the Function of moduleContext to null. + + // 10. Assert: module.[[Realm]] is not undefined. + // Note: This must be true because we use a reference. + + // 11. Set the Realm of moduleContext to module.[[Realm]]. + m_execution_context.realm = &realm(); + + // 12. Set the ScriptOrModule of moduleContext to module. + m_execution_context.script_or_module = this; + + // 13. Set the VariableEnvironment of moduleContext to module.[[Environment]]. + m_execution_context.variable_environment = environment; + + // 14. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. + m_execution_context.lexical_environment = environment; + + // 15. Set the PrivateEnvironment of moduleContext to null. + + // 16. Set module.[[Context]] to moduleContext. + // Note: We're already working on that one. + + // 17. Push moduleContext onto the execution context stack; moduleContext is now the running execution context. + vm.push_execution_context(m_execution_context, realm().global_object()); + + // 18. Let code be module.[[ECMAScriptCode]]. + + // 19. Let varDeclarations be the VarScopedDeclarations of code. + // Note: We just loop through them in step 21. + + // 20. Let declaredVarNames be a new empty List. + Vector declared_var_names; + + // 21. For each element d of varDeclarations, do + // a. For each element dn of the BoundNames of d, do + m_ecmascript_code->for_each_var_declared_name([&](auto const& name) { + // i. If dn is not an element of declaredVarNames, then + if (!declared_var_names.contains_slow(name)) { + // 1. Perform ! env.CreateMutableBinding(dn, false). + MUST(environment->create_mutable_binding(global_object, name, false)); + + // 2. Call env.InitializeBinding(dn, undefined). + MUST(environment->initialize_binding(global_object, name, js_undefined())); + + // 3. Append dn to declaredVarNames. + declared_var_names.empend(name); + } + }); + + // 22. Let lexDeclarations be the LexicallyScopedDeclarations of code. + // Note: We only loop through them in step 24. + + // 23. Let privateEnv be null. + PrivateEnvironment* private_environment = nullptr; + + // 24. For each element d of lexDeclarations, do + m_ecmascript_code->for_each_lexically_scoped_declaration([&](Declaration const& declaration) { + // a. For each element dn of the BoundNames of d, do + declaration.for_each_bound_name([&](FlyString const& name) { + // i. If IsConstantDeclaration of d is true, then + if (declaration.is_constant_declaration()) { + // 1. Perform ! env.CreateImmutableBinding(dn, true). + MUST(environment->create_immutable_binding(global_object, name, true)); + } + // ii. Else, + else { + // 1. Perform ! env.CreateMutableBinding(dn, false). + MUST(environment->create_mutable_binding(global_object, name, false)); + } + + // iii. If d is a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then + if (declaration.is_function_declaration()) { + VERIFY(is(declaration)); + auto const& function_declaration = static_cast(declaration); + + // 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. + auto* function = ECMAScriptFunctionObject::create(global_object, function_declaration.name(), function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval()); + + // 2. Call env.InitializeBinding(dn, fo). + environment->initialize_binding(global_object, name, function); + } + }); + }); + + // Note: The default export name is also part of the local lexical declarations but + // instead of making that a special case in the parser we just check it here. + // This is only needed for things which are not declarations. + // For more info check Parser::parse_export_statement. + // Furthermore, that declaration is not constant. so we take 24.a.ii + if (m_default_export) { + VERIFY(m_default_export->has_statement()); + + auto const& statement = m_default_export->statement(); + if (!is(statement)) { + auto const& name = m_default_export->entries()[0].local_or_import_name; + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] Adding default export to lexical declarations: local name: {}, Expression: {}", name, statement.class_name()); + + // 1. Perform ! env.CreateMutableBinding(dn, false). + MUST(environment->create_mutable_binding(global_object, name, false)); + + // Note: Since this is not a function declaration 24.a.iii never applies + } + } + + // 25. Remove moduleContext from the execution context stack. + vm.pop_execution_context(); + + // 26. Return NormalCompletion(empty). + return normal_completion({}); +} + +// 16.2.1.6.3 ResolveExport ( exportName [ , resolveSet ] ), https://tc39.es/ecma262/#sec-resolveexport +ThrowCompletionOr SourceTextModule::resolve_export(VM& vm, FlyString const& export_name, Vector resolve_set) +{ + // 1. If resolveSet is not present, set resolveSet to a new empty List. + // Note: This is done by the default argument. + + // 2. For each Record { [[Module]], [[ExportName]] } r of resolveSet, do + for (auto& [type, module, exported_name] : resolve_set) { + // a. If module and r.[[Module]] are the same Module Record and SameValue(exportName, r.[[ExportName]]) is true, then + if (module == this && exported_name == export_name) { + // i. Assert: This is a circular import request. + + // ii. Return null. + return ResolvedBinding::null(); + } + } + + // 3. Append the Record { [[Module]]: module, [[ExportName]]: exportName } to resolveSet. + resolve_set.append({ ResolvedBinding::Type::BindingName, this, export_name }); + + // 4. For each ExportEntry Record e of module.[[LocalExportEntries]], do + for (auto& entry : m_local_export_entries) { + // a. If SameValue(exportName, e.[[ExportName]]) is true, then + if (export_name != entry.export_name) + continue; + + // i. Assert: module provides the direct binding for this export. + // FIXME: What does this mean? + + // ii. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: e.[[LocalName]] }. + return ResolvedBinding { + ResolvedBinding::Type::BindingName, + this, + entry.local_or_import_name, + }; + } + + // 5. For each ExportEntry Record e of module.[[IndirectExportEntries]], do + for (auto& entry : m_indirect_export_entries) { + // a. If SameValue(exportName, e.[[ExportName]]) is true, then + if (export_name != entry.export_name) + continue; + + // i. Let importedModule be ? HostResolveImportedModule(module, e.[[ModuleRequest]]). + auto imported_module = TRY(vm.host_resolve_imported_module(this, entry.module_request)); + + // ii. If e.[[ImportName]] is all, then + if (entry.is_all()) { + // 1. Assert: module does not provide the direct binding for this export. + // FIXME: What does this mean? / How do we check this + + // 2. Return ResolvedBinding Record { [[Module]]: importedModule, [[BindingName]]: namespace }. + return ResolvedBinding { + ResolvedBinding::Type::Namespace, + imported_module.ptr(), + {} + }; + } + // iii. Else, + else { + // 1. Assert: module imports a specific binding for this export. + // FIXME: What does this mean? / How do we check this + + // 2. Return importedModule.ResolveExport(e.[[ImportName]], resolveSet). + return imported_module->resolve_export(vm, entry.local_or_import_name, resolve_set); + } + } + + // 6. If SameValue(exportName, "default") is true, then + if (export_name == "default"sv) { + // a. Assert: A default export was not explicitly defined by this module. + // FIXME: What does this mean? / How do we check this + + // b. Return null. + return ResolvedBinding::null(); + // c. NOTE: A default export cannot be provided by an export * from "mod" declaration. + } + + // 7. Let starResolution be null. + ResolvedBinding star_resolution = ResolvedBinding::null(); + + // 8. For each ExportEntry Record e of module.[[StarExportEntries]], do + for (auto& entry : m_star_export_entries) { + // a. Let importedModule be ? HostResolveImportedModule(module, e.[[ModuleRequest]]). + auto imported_module = TRY(vm.host_resolve_imported_module(this, entry.module_request)); + + // b. Let resolution be ? importedModule.ResolveExport(exportName, resolveSet). + auto resolution = TRY(imported_module->resolve_export(vm, export_name, resolve_set)); + + // c. If resolution is ambiguous, return ambiguous. + if (resolution.is_ambiguous()) + return ResolvedBinding::ambiguous(); + + // d. If resolution is not null, then + if (resolution.type == ResolvedBinding::Null) + continue; + + // i. Assert: resolution is a ResolvedBinding Record. + VERIFY(resolution.is_valid()); + + // ii. If starResolution is null, set starResolution to resolution. + if (star_resolution.type == ResolvedBinding::Null) { + star_resolution = resolution; + } + // iii. Else, + else { + // 1. Assert: There is more than one * import that includes the requested name. + // FIXME: Assert this + + // 2. If resolution.[[Module]] and starResolution.[[Module]] are not the same Module Record, return ambiguous. + if (resolution.module != star_resolution.module) + return ResolvedBinding::ambiguous(); + + // 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. + if (resolution.is_namespace() != star_resolution.is_namespace()) + return ResolvedBinding::ambiguous(); + + // 4. If resolution.[[BindingName]] is a String, starResolution.[[BindingName]] is a String, and SameValue(resolution.[[BindingName]], starResolution.[[BindingName]]) is false, return ambiguous. + if (!resolution.is_namespace() && resolution.export_name != star_resolution.export_name) { + // Note: Because we know from the previous if that either both are namespaces or both are string we can check just one + return ResolvedBinding::ambiguous(); + } + } + } + + // 9. Return starResolution. + return star_resolution; +} + +// 16.2.1.6.5 ExecuteModule ( [ capability ] ), https://tc39.es/ecma262/#sec-source-text-module-record-execute-module +Completion SourceTextModule::execute_module(VM& vm, Optional capability) +{ + dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] SourceTextModule::execute_module({}, capability has value: {})", filename(), capability.has_value()); + + // 1. Let moduleContext be a new ECMAScript code execution context. + ExecutionContext module_context { vm.heap() }; + + // Note: This is not in the spec but we require it. + module_context.is_strict_mode = true; + + // 2. Set the Function of moduleContext to null. + + // 3. Set the Realm of moduleContext to module.[[Realm]]. + module_context.realm = &realm(); + + // 4. Set the ScriptOrModule of moduleContext to module. + module_context.script_or_module = this; + + // 5. Assert: module has been linked and declarations in its module environment have been instantiated. + VERIFY(m_status != ModuleStatus::Unlinked && m_status != ModuleStatus::Linking && environment()); + + // 6. Set the VariableEnvironment of moduleContext to module.[[Environment]]. + module_context.variable_environment = environment(); + + // 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. + module_context.lexical_environment = environment(); + + // 8. Suspend the currently running execution context. + // FIXME: We don't have suspend yet + + // 9. If module.[[HasTLA]] is false, then + if (!m_has_top_level_await) { + // a. Assert: capability is not present. + VERIFY(!capability.has_value()); + // b. Push moduleContext onto the execution context stack; moduleContext is now the running execution context. + vm.push_execution_context(module_context, realm().global_object()); + + // c. Let result be the result of evaluating module.[[ECMAScriptCode]]. + auto result = m_ecmascript_code->execute(vm.interpreter(), realm().global_object()); + + // d. Suspend moduleContext and remove it from the execution context stack. + vm.pop_execution_context(); + vm.clear_exception(); + + // e. Resume the context that is now on the top of the execution context stack as the running execution context. + // FIXME: We don't have resume yet. + + // f. Return Completion(result). + if (result.is_error()) + return result; + + // 16.2.1.11 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-module-semantics-runtime-semantics-evaluation + // -> Replace any empty value with undefined. + + result = result.update_empty(js_undefined()); + return *result.value(); + } + // 10. Else, + + // a. Assert: capability is a PromiseCapability Record. + VERIFY(capability.has_value()); + + // b. Perform ! AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleContext). + async_block_start(vm, m_ecmascript_code, capability.value(), module_context); + + // c. Return NormalCompletion(empty). + return normal_completion({}); } } diff --git a/Userland/Libraries/LibJS/SourceTextModule.h b/Userland/Libraries/LibJS/SourceTextModule.h index 4ddf1f0da85..a4e60b6ffae 100644 --- a/Userland/Libraries/LibJS/SourceTextModule.h +++ b/Userland/Libraries/LibJS/SourceTextModule.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2022, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,24 +8,56 @@ #pragma once #include +#include #include -#include #include namespace JS { // 16.2.1.6 Source Text Module Records, https://tc39.es/ecma262/#sec-source-text-module-records -class SourceTextModule final : public Module { +class SourceTextModule final : public CyclicModule { public: + using ImportEntry = ImportStatement::ImportEntry; + using ExportEntry = ExportStatement::ExportEntry; + static Result, Vector> parse(StringView source_text, Realm&, StringView filename = {}); - virtual ~SourceTextModule(); Program const& parse_node() const { return *m_ecmascript_code; } -private: - explicit SourceTextModule(Realm&, StringView filename, NonnullRefPtr); + virtual ThrowCompletionOr> get_exported_names(VM& vm, Vector export_star_set) override; + virtual ThrowCompletionOr resolve_export(VM& vm, FlyString const& export_name, Vector resolve_set = {}) override; - NonnullRefPtr m_ecmascript_code; // [[ECMAScriptCode]] + Object* import_meta() + { + if (m_import_meta.is_null()) + return nullptr; + return m_import_meta.cell(); + } + + void set_import_meta(Badge, Object* import_meta) + { + m_import_meta = make_handle(import_meta); + } + +protected: + virtual Completion initialize_environment(VM& vm) override; + virtual Completion execute_module(VM& vm, Optional capability) override; + +private: + SourceTextModule(Realm&, StringView filename, bool has_top_level_await, NonnullRefPtr body, Vector requested_modules, + Vector import_entries, Vector local_export_entries, + Vector indirect_export_entries, Vector star_export_entries, + RefPtr default_export); + + NonnullRefPtr m_ecmascript_code; // [[ECMAScriptCode]] + ExecutionContext m_execution_context; // [[Context]] + Handle m_import_meta; // [[ImportMeta]] + Vector m_import_entries; // [[ImportEntries]] + Vector m_local_export_entries; // [[LocalExportEntries]] + Vector m_indirect_export_entries; // [[IndirectExportEntries]] + Vector m_star_export_entries; // [[StarExportEntries]] + + RefPtr m_default_export; // Note: Not from the spec }; }