mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
LibJS: Implement Module linking and evaluating
This commit is contained in:
parent
be9d478d92
commit
1b8ccf9a66
Notes:
sideshowbarker
2024-07-17 20:28:34 +09:00
Author: https://github.com/davidot Commit: https://github.com/SerenityOS/serenity/commit/1b8ccf9a668 Pull-request: https://github.com/SerenityOS/serenity/pull/11957 Reviewed-by: https://github.com/emanuele6 Reviewed-by: https://github.com/linusg
9 changed files with 1569 additions and 22 deletions
|
@ -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
|
||||
|
|
658
Userland/Libraries/LibJS/CyclicModule.cpp
Normal file
658
Userland/Libraries/LibJS/CyclicModule.cpp
Normal file
|
@ -0,0 +1,658 @@
|
|||
/*
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/CyclicModule.h>
|
||||
#include <LibJS/Runtime/PromiseConstructor.h>
|
||||
#include <LibJS/Runtime/PromiseReaction.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
CyclicModule::CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<FlyString> 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<void> 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<Module*> 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<CyclicModule>(module)) {
|
||||
auto& cyclic_module = static_cast<CyclicModule&>(*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<u32> CyclicModule::inner_module_linking(VM& vm, Vector<Module*>& 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<CyclicModule>(*required_module)) {
|
||||
auto& cyclic_module = static_cast<CyclicModule&>(*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<CyclicModule>(*required_module));
|
||||
|
||||
// iv. Set requiredModule.[[Status]] to linked.
|
||||
static_cast<CyclicModule&>(*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<Promise*> 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<Promise>(*m_top_level_capability->promise));
|
||||
return static_cast<Promise*>(m_top_level_capability->promise);
|
||||
}
|
||||
|
||||
// 5. Let stack be a new empty List.
|
||||
Vector<Module*> 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<CyclicModule>(*mod))
|
||||
continue;
|
||||
|
||||
auto& cyclic_module = static_cast<CyclicModule&>(*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<Promise>(*m_top_level_capability->promise));
|
||||
return static_cast<Promise*>(m_top_level_capability->promise);
|
||||
}
|
||||
|
||||
// 16.2.1.5.2.1 InnerModuleEvaluation ( module, stack, index ), https://tc39.es/ecma262/#sec-innermoduleevaluation
|
||||
ThrowCompletionOr<u32> CyclicModule::inner_module_evaluation(VM& vm, Vector<Module*>& 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<CyclicModule>(*required_module))
|
||||
continue;
|
||||
|
||||
auto* cyclic_module = static_cast<CyclicModule*>(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<CyclicModule>(*required_module));
|
||||
|
||||
auto& cyclic_module = static_cast<CyclicModule&>(*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<PromiseCapability>)
|
||||
{
|
||||
// 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<void> 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<Value> {
|
||||
// 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<Value> {
|
||||
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<Promise>(*capability.promise));
|
||||
|
||||
// 8. Perform ! PerformPromiseThen(capability.[[Promise]], onFulfilled, onRejected).
|
||||
static_cast<Promise*>(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<void> CyclicModule::gather_available_ancestors(Vector<CyclicModule*>& 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<void> 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<CyclicModule*> 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<bool> 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<void> 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 {};
|
||||
}
|
||||
|
||||
}
|
61
Userland/Libraries/LibJS/CyclicModule.h
Normal file
61
Userland/Libraries/LibJS/CyclicModule.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/AST.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Module.h>
|
||||
#include <LibJS/Parser.h>
|
||||
#include <LibJS/Runtime/PromiseReaction.h>
|
||||
|
||||
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<void> link(VM& vm) override;
|
||||
virtual ThrowCompletionOr<Promise*> evaluate(VM& vm) override;
|
||||
|
||||
protected:
|
||||
CyclicModule(Realm& realm, StringView filename, bool has_top_level_await, Vector<FlyString> requested_modules);
|
||||
|
||||
virtual ThrowCompletionOr<u32> inner_module_linking(VM& vm, Vector<Module*>& stack, u32 index) override;
|
||||
virtual ThrowCompletionOr<u32> inner_module_evaluation(VM& vm, Vector<Module*>& stack, u32 index) override;
|
||||
|
||||
virtual Completion initialize_environment(VM& vm);
|
||||
virtual Completion execute_module(VM& vm, Optional<PromiseCapability> capability = {});
|
||||
|
||||
ThrowCompletionOr<void> execute_async_module(VM& vm);
|
||||
ThrowCompletionOr<void> gather_available_ancestors(Vector<CyclicModule*>& exec_list);
|
||||
ThrowCompletionOr<void> async_module_execution_fulfilled(VM& vm);
|
||||
ThrowCompletionOr<void> async_module_execution_rejected(VM& vm, Value error);
|
||||
|
||||
ModuleStatus m_status { ModuleStatus::Unlinked }; // [[Status]]
|
||||
ThrowCompletionOr<void> m_evaluation_error; // [[EvaluationError]]
|
||||
Optional<u32> m_dfs_index; // [[DFSIndex]]
|
||||
Optional<u32> m_dfs_ancestor_index; // [[DFSAncestorIndex]]
|
||||
Vector<FlyString> m_requested_modules; // [[RequestedModules]]
|
||||
CyclicModule* m_cycle_root; // [[CycleRoot]]
|
||||
bool m_has_top_level_await { false }; // [[HasTLA]]
|
||||
bool m_async_evaluation { false }; // [[AsyncEvaluation]]
|
||||
Optional<PromiseCapability> m_top_level_capability; // [[TopLevelCapability]]
|
||||
Vector<CyclicModule*> m_async_parent_modules; // [[AsyncParentModules]]
|
||||
Optional<u32> m_pending_async_dependencies; // [[PendingAsyncDependencies]]
|
||||
};
|
||||
|
||||
}
|
|
@ -1,17 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/CyclicModule.h>
|
||||
#include <LibJS/Module.h>
|
||||
#include <LibJS/Runtime/ModuleNamespaceObject.h>
|
||||
|
||||
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<u32> Module::inner_module_linking(VM& vm, Vector<Module*>&, 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<u32> Module::inner_module_evaluation(VM& vm, Vector<Module*>&, 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<Object*> 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<FlyString> 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<FlyString> 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<ModuleNamespaceObject>(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
*
|
||||
* 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<Module> {
|
||||
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<Object*> get_module_namespace(VM& vm);
|
||||
|
||||
virtual ThrowCompletionOr<void> link(VM& vm) = 0;
|
||||
virtual ThrowCompletionOr<Promise*> evaluate(VM& vm) = 0;
|
||||
|
||||
virtual ThrowCompletionOr<Vector<FlyString>> get_exported_names(VM& vm, Vector<Module*> export_star_set = {}) = 0;
|
||||
virtual ThrowCompletionOr<ResolvedBinding> resolve_export(VM& vm, FlyString const& export_name, Vector<ResolvedBinding> resolve_set = {}) = 0;
|
||||
|
||||
virtual ThrowCompletionOr<u32> inner_module_linking(VM& vm, Vector<Module*>& stack, u32 index);
|
||||
virtual ThrowCompletionOr<u32> inner_module_evaluation(VM& vm, Vector<Module*>& 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<VM> m_vm;
|
||||
Object* module_namespace_create(VM& vm, Vector<FlyString> 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<Realm> m_realm; // [[Realm]]
|
||||
Handle<Environment> m_environment; // [[Environment]]
|
||||
Handle<Object> m_namespace; // [[Namespace]]
|
||||
|
|
|
@ -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") \
|
||||
|
|
|
@ -150,8 +150,32 @@ ThrowCompletionOr<Value> 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<ReferenceError>(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<ReferenceError>(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
|
||||
|
|
|
@ -1,13 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/QuickSort.h>
|
||||
#include <LibJS/Interpreter.h>
|
||||
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
|
||||
#include <LibJS/Runtime/ModuleEnvironment.h>
|
||||
#include <LibJS/SourceTextModule.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
// 16.2.1.3 Static Semantics: ModuleRequests, https://tc39.es/ecma262/#sec-static-semantics-modulerequests
|
||||
static Vector<FlyString> 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<RequestedModuleAndSourceIndex> 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<FlyString> 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<Program> body, Vector<FlyString> requested_modules,
|
||||
Vector<ImportEntry> import_entries, Vector<ExportEntry> local_export_entries,
|
||||
Vector<ExportEntry> indirect_export_entries, Vector<ExportEntry> star_export_entries,
|
||||
RefPtr<ExportStatement> 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<NonnullRefPtr<SourceTextModule>, Vector<Parser::Error>> SourceTextModule::parse(StringView source_text, Realm& realm, StringView filename)
|
||||
{
|
||||
|
@ -19,18 +81,567 @@ Result<NonnullRefPtr<SourceTextModule>, Vector<Parser::Error>> 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<ImportEntry> 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<ExportEntry> indirect_export_entries;
|
||||
|
||||
// 7. Let localExportEntries be a new empty List.
|
||||
Vector<ExportEntry> local_export_entries;
|
||||
|
||||
// 8. Let starExportEntries be a new empty List.
|
||||
Vector<ExportEntry> star_export_entries;
|
||||
|
||||
// Note: Not in the spec but makes it easier to find the default.
|
||||
RefPtr<ExportStatement> 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> program)
|
||||
: Module(realm, filename)
|
||||
, m_ecmascript_code(move(program))
|
||||
// 16.2.1.6.2 GetExportedNames ( [ exportStarSet ] ), https://tc39.es/ecma262/#sec-getexportednames
|
||||
ThrowCompletionOr<Vector<FlyString>> SourceTextModule::get_exported_names(VM& vm, Vector<Module*> 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<FlyString> {};
|
||||
}
|
||||
|
||||
// 3. Append module to exportStarSet.
|
||||
export_star_set.append(this);
|
||||
|
||||
// 4. Let exportedNames be a new empty List.
|
||||
Vector<FlyString> 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<SyntaxError>(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<ModuleEnvironment>(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<InternalError>(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<SyntaxError>(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<FlyString> 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<FunctionDeclaration>(declaration));
|
||||
auto const& function_declaration = static_cast<FunctionDeclaration const&>(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<Declaration>(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<ResolvedBinding> SourceTextModule::resolve_export(VM& vm, FlyString const& export_name, Vector<ResolvedBinding> 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<PromiseCapability> 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({});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -7,24 +8,56 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibJS/AST.h>
|
||||
#include <LibJS/CyclicModule.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Module.h>
|
||||
#include <LibJS/Parser.h>
|
||||
|
||||
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<NonnullRefPtr<SourceTextModule>, Vector<Parser::Error>> 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<Program>);
|
||||
virtual ThrowCompletionOr<Vector<FlyString>> get_exported_names(VM& vm, Vector<Module*> export_star_set) override;
|
||||
virtual ThrowCompletionOr<ResolvedBinding> resolve_export(VM& vm, FlyString const& export_name, Vector<ResolvedBinding> resolve_set = {}) override;
|
||||
|
||||
NonnullRefPtr<Program> 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<MetaProperty>, 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<PromiseCapability> capability) override;
|
||||
|
||||
private:
|
||||
SourceTextModule(Realm&, StringView filename, bool has_top_level_await, NonnullRefPtr<Program> body, Vector<FlyString> requested_modules,
|
||||
Vector<ImportEntry> import_entries, Vector<ExportEntry> local_export_entries,
|
||||
Vector<ExportEntry> indirect_export_entries, Vector<ExportEntry> star_export_entries,
|
||||
RefPtr<ExportStatement> default_export);
|
||||
|
||||
NonnullRefPtr<Program> m_ecmascript_code; // [[ECMAScriptCode]]
|
||||
ExecutionContext m_execution_context; // [[Context]]
|
||||
Handle<Object> m_import_meta; // [[ImportMeta]]
|
||||
Vector<ImportEntry> m_import_entries; // [[ImportEntries]]
|
||||
Vector<ExportEntry> m_local_export_entries; // [[LocalExportEntries]]
|
||||
Vector<ExportEntry> m_indirect_export_entries; // [[IndirectExportEntries]]
|
||||
Vector<ExportEntry> m_star_export_entries; // [[StarExportEntries]]
|
||||
|
||||
RefPtr<ExportStatement> m_default_export; // Note: Not from the spec
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue