blob: 25edc41ff4b1f24783600a764bc30f6bc39fa037 [file]
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/objects/source-text-module.h"
#include "src/api/api-inl.h"
#include "src/ast/modules.h"
#include "src/builtins/accessors.h"
#include "src/common/assert-scope.h"
#include "src/objects/js-generator-inl.h"
#include "src/objects/module-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/shared-function-info.h"
#include "src/utils/ostreams.h"
namespace v8 {
namespace internal {
struct StringHandleHash {
V8_INLINE size_t operator()(DirectHandle<String> string) const {
return string->EnsureHash();
}
};
struct StringHandleEqual {
V8_INLINE bool operator()(DirectHandle<String> lhs,
DirectHandle<String> rhs) const {
return lhs->Equals(*rhs);
}
};
class UnorderedStringSet
: public std::unordered_set<Handle<String>, StringHandleHash,
StringHandleEqual,
ZoneAllocator<Handle<String>>> {
public:
explicit UnorderedStringSet(Zone* zone)
: std::unordered_set<Handle<String>, StringHandleHash, StringHandleEqual,
ZoneAllocator<Handle<String>>>(
2 /* bucket count */, StringHandleHash(), StringHandleEqual(),
ZoneAllocator<Handle<String>>(zone)) {}
};
class UnorderedStringMap
: public std::unordered_map<
Handle<String>, Handle<Object>, StringHandleHash, StringHandleEqual,
ZoneAllocator<std::pair<const Handle<String>, Handle<Object>>>> {
public:
explicit UnorderedStringMap(Zone* zone)
: std::unordered_map<
Handle<String>, Handle<Object>, StringHandleHash, StringHandleEqual,
ZoneAllocator<std::pair<const Handle<String>, Handle<Object>>>>(
2 /* bucket count */, StringHandleHash(), StringHandleEqual(),
ZoneAllocator<std::pair<const Handle<String>, Handle<Object>>>(
zone)) {}
};
class Module::ResolveSet
: public std::unordered_map<
Handle<Module>, UnorderedStringSet*, ModuleHandleHash,
ModuleHandleEqual,
ZoneAllocator<std::pair<const Handle<Module>, UnorderedStringSet*>>> {
public:
explicit ResolveSet(Zone* zone)
: std::unordered_map<Handle<Module>, UnorderedStringSet*,
ModuleHandleHash, ModuleHandleEqual,
ZoneAllocator<std::pair<const Handle<Module>,
UnorderedStringSet*>>>(
2 /* bucket count */, ModuleHandleHash(), ModuleHandleEqual(),
ZoneAllocator<std::pair<const Handle<Module>, UnorderedStringSet*>>(
zone)),
zone_(zone) {}
Zone* zone() const { return zone_; }
private:
Zone* zone_;
};
struct SourceTextModule::AsyncEvaluationOrdinalCompare {
bool operator()(DirectHandle<SourceTextModule> lhs,
DirectHandle<SourceTextModule> rhs) const {
DCHECK(lhs->HasAsyncEvaluationOrdinal());
DCHECK(rhs->HasAsyncEvaluationOrdinal());
return lhs->async_evaluation_ordinal() < rhs->async_evaluation_ordinal();
}
};
Tagged<SharedFunctionInfo> SourceTextModule::GetSharedFunctionInfo() const {
DisallowGarbageCollection no_gc;
switch (status()) {
case kUnlinked:
case kPreLinking:
return Cast<SharedFunctionInfo>(code());
case kLinking:
return Cast<JSFunction>(code())->shared();
case kLinked:
case kEvaluating:
case kEvaluatingAsync:
case kEvaluated:
return Cast<JSGeneratorObject>(code())->function()->shared();
case kErrored:
return Cast<SharedFunctionInfo>(code());
}
UNREACHABLE();
}
Tagged<Script> SourceTextModule::GetScript() const {
DisallowGarbageCollection no_gc;
return Cast<Script>(GetSharedFunctionInfo()->script());
}
int SourceTextModule::ExportIndex(int cell_index) {
DCHECK_EQ(SourceTextModuleDescriptor::GetCellIndexKind(cell_index),
SourceTextModuleDescriptor::kExport);
return cell_index - 1;
}
int SourceTextModule::ImportIndex(int cell_index) {
DCHECK_EQ(SourceTextModuleDescriptor::GetCellIndexKind(cell_index),
SourceTextModuleDescriptor::kImport);
return -cell_index - 1;
}
void SourceTextModule::CreateIndirectExport(
Isolate* isolate, DirectHandle<SourceTextModule> module,
DirectHandle<String> name, DirectHandle<SourceTextModuleInfoEntry> entry) {
Handle<ObjectHashTable> exports(module->exports(), isolate);
DCHECK(IsTheHole(exports->Lookup(name)));
exports = ObjectHashTable::Put(isolate, exports, name, entry);
module->set_exports(*exports);
}
void SourceTextModule::CreateExport(Isolate* isolate,
DirectHandle<SourceTextModule> module,
int cell_index,
DirectHandle<FixedArray> names) {
const uint32_t names_len = names->ulength().value();
DCHECK_LT(0, names_len);
DirectHandle<Cell> cell = isolate->factory()->NewCell();
module->regular_exports()->set(ExportIndex(cell_index), *cell);
Handle<ObjectHashTable> exports(module->exports(), isolate);
for (uint32_t i = 0; i < names_len; ++i) {
DirectHandle<String> name(Cast<String>(names->get(i)), isolate);
DCHECK(IsTheHole(exports->Lookup(name)));
exports = ObjectHashTable::Put(isolate, exports, name, cell);
}
module->set_exports(*exports);
}
Tagged<Cell> SourceTextModule::GetCell(int cell_index) {
DisallowGarbageCollection no_gc;
Tagged<Object> cell;
switch (SourceTextModuleDescriptor::GetCellIndexKind(cell_index)) {
case SourceTextModuleDescriptor::kImport:
cell = regular_imports()->get(ImportIndex(cell_index));
break;
case SourceTextModuleDescriptor::kExport:
cell = regular_exports()->get(ExportIndex(cell_index));
break;
case SourceTextModuleDescriptor::kInvalid:
UNREACHABLE();
}
return Cast<Cell>(cell);
}
Handle<Object> SourceTextModule::LoadVariable(
Isolate* isolate, DirectHandle<SourceTextModule> module, int cell_index) {
return handle(module->GetCell(cell_index)->value(), isolate);
}
void SourceTextModule::StoreVariable(DirectHandle<SourceTextModule> module,
int cell_index,
DirectHandle<Object> value) {
DisallowGarbageCollection no_gc;
DCHECK_EQ(SourceTextModuleDescriptor::GetCellIndexKind(cell_index),
SourceTextModuleDescriptor::kExport);
module->GetCell(cell_index)->set_value(*value);
}
MaybeHandle<Cell> SourceTextModule::ResolveExport(
Isolate* isolate, Handle<SourceTextModule> module,
DirectHandle<String> module_specifier, Handle<String> export_name,
MessageLocation loc, bool must_resolve, Module::ResolveSet* resolve_set) {
DCHECK(!export_name.is_null());
Handle<Object> object(module->exports()->Lookup(export_name), isolate);
if (IsCell(*object)) {
// Already resolved (e.g. because it's a local export).
return Cast<Cell>(object);
}
// Check for cycle before recursing.
{
// Attempt insertion with a null string set.
auto result = resolve_set->insert({module, nullptr});
UnorderedStringSet*& name_set = result.first->second;
if (result.second) {
// |module| wasn't in the map previously, so allocate a new name set.
Zone* zone = resolve_set->zone();
name_set = zone->New<UnorderedStringSet>(zone);
} else if (name_set->count(export_name)) {
// Cycle detected.
if (must_resolve) {
isolate->ThrowAt(isolate->factory()->NewSyntaxError(
MessageTemplate::kCyclicModuleDependency,
export_name, module_specifier),
&loc);
return MaybeHandle<Cell>();
}
return MaybeHandle<Cell>();
}
name_set->insert(export_name);
}
if (IsTheHole(*object)) {
return SourceTextModule::ResolveExportUsingStarExports(
isolate, module, module_specifier, export_name, loc, must_resolve,
resolve_set);
}
DCHECK(IsSourceTextModuleInfoEntry(*object));
// Not yet resolved indirect export.
auto entry = Cast<SourceTextModuleInfoEntry>(object);
MaybeHandle<String> import_name =
IsUndefined(entry->import_name())
? MaybeHandle<String>()
: handle(Cast<String>(entry->import_name()), isolate);
Handle<Script> script(module->GetScript(), isolate);
MessageLocation new_loc(script, entry->beg_pos(), entry->end_pos());
Handle<Cell> cell;
if (!ResolveImport(isolate, module, import_name, entry->module_request(),
new_loc, must_resolve, resolve_set)
.ToHandle(&cell)) {
return MaybeHandle<Cell>();
}
// The export table may have changed but the entry in question should be
// unchanged.
Handle<ObjectHashTable> exports(module->exports(), isolate);
DCHECK(IsSourceTextModuleInfoEntry(exports->Lookup(export_name)));
exports = ObjectHashTable::Put(isolate, exports, export_name, cell);
module->set_exports(*exports);
return cell;
}
MaybeHandle<Cell> SourceTextModule::ResolveImport(
Isolate* isolate, DirectHandle<SourceTextModule> module,
MaybeHandle<String> maybe_name, int module_request_index,
MessageLocation loc, bool must_resolve, Module::ResolveSet* resolve_set) {
DirectHandle<ModuleRequest> module_request(
Cast<ModuleRequest>(
module->info()->module_requests()->get(module_request_index)),
isolate);
ModuleImportPhase phase = module_request->phase();
switch (phase) {
case ModuleImportPhase::kSource: {
DCHECK(v8_flags.js_source_phase_imports);
// https://tc39.es/proposal-source-phase-imports/#sec-source-text-module-record-initialize-environment
// InitializeEnvironment
// 7.c. Else if in.[[ImportName]] is source, then
// 7.c.i. Let moduleSourceObject be ? importedModule.GetModuleSource().
// 7.c.ii. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true).
// 7.c.iii. Perform ! env.InitializeBinding(in.[[LocalName]],
// moduleSourceObject).
Handle<Cell> cell = isolate->factory()->NewCell();
cell->set_value(module->requested_modules()->get(module_request_index));
return cell;
}
case ModuleImportPhase::kDefer:
case ModuleImportPhase::kEvaluation: {
Handle<Module> requested_module(
Cast<Module>(module->requested_modules()->get(module_request_index)),
isolate);
DirectHandle<String> module_specifier(
Cast<String>(module_request->specifier()), isolate);
Handle<String> name;
if (maybe_name.ToHandle(&name)) {
MaybeHandle<Cell> result =
Module::ResolveExport(isolate, requested_module, module_specifier,
name, loc, must_resolve, resolve_set);
DCHECK_IMPLIES(isolate->has_exception(), result.is_null());
return result;
} else {
// This is to resolve an indirect include of the * as namespace.
// b. If in.[[ImportName]] is namespace-object, then
// i. Let namespace be GetModuleNamespace(importedModule,
// in.[[ModuleRequest]].[[Phase]]).
return GetModuleNamespaceCell(isolate, requested_module, phase);
}
}
default:
UNREACHABLE();
}
}
MaybeHandle<Cell> SourceTextModule::ResolveExportUsingStarExports(
Isolate* isolate, DirectHandle<SourceTextModule> module,
DirectHandle<String> module_specifier, Handle<String> export_name,
MessageLocation loc, bool must_resolve, Module::ResolveSet* resolve_set) {
if (!export_name->Equals(ReadOnlyRoots(isolate).default_string())) {
// Go through all star exports looking for the given name. If multiple star
// exports provide the name, make sure they all map it to the same cell.
Handle<Cell> unique_cell;
DirectHandle<FixedArray> special_exports(module->info()->special_exports(),
isolate);
const uint32_t special_exports_len = special_exports->ulength().value();
for (uint32_t i = 0; i < special_exports_len; ++i) {
i::DirectHandle<i::SourceTextModuleInfoEntry> entry(
i::Cast<i::SourceTextModuleInfoEntry>(special_exports->get(i)),
isolate);
if (!IsUndefined(entry->export_name())) {
continue; // Indirect export.
}
Handle<Script> script(module->GetScript(), isolate);
MessageLocation new_loc(script, entry->beg_pos(), entry->end_pos());
Handle<Cell> cell;
if (ResolveImport(isolate, module, export_name, entry->module_request(),
new_loc, false, resolve_set)
.ToHandle(&cell)) {
if (unique_cell.is_null()) unique_cell = cell;
if (*unique_cell != *cell) {
isolate->ThrowAt(isolate->factory()->NewSyntaxError(
MessageTemplate::kAmbiguousExport,
module_specifier, export_name),
&loc);
return MaybeHandle<Cell>();
}
} else if (isolate->has_exception()) {
return MaybeHandle<Cell>();
}
}
if (!unique_cell.is_null()) {
// Found a unique star export for this name.
Handle<ObjectHashTable> exports(module->exports(), isolate);
DCHECK(IsTheHole(exports->Lookup(export_name)));
exports =
ObjectHashTable::Put(isolate, exports, export_name, unique_cell);
module->set_exports(*exports);
return unique_cell;
}
}
// Unresolvable.
if (must_resolve) {
isolate->ThrowAt(
isolate->factory()->NewSyntaxError(MessageTemplate::kUnresolvableExport,
module_specifier, export_name),
&loc);
return MaybeHandle<Cell>();
}
return MaybeHandle<Cell>();
}
bool SourceTextModule::PrepareInstantiate(
Isolate* isolate, DirectHandle<SourceTextModule> module,
v8::Local<v8::Context> context,
const Module::UserResolveCallbacks& callbacks) {
// One of the callbacks must be set, otherwise we cannot resolve.
DCHECK_IMPLIES(callbacks.module_callback == nullptr,
callbacks.module_callback_by_index != nullptr);
// Obtain requested modules.
DirectHandle<SourceTextModuleInfo> module_info(module->info(), isolate);
DirectHandle<FixedArray> module_requests(module_info->module_requests(),
isolate);
DirectHandle<FixedArray> requested_modules(module->requested_modules(),
isolate);
const uint32_t module_requests_len = module_requests->ulength().value();
for (uint32_t i = 0; i < module_requests_len; ++i) {
DirectHandle<ModuleRequest> module_request(
Cast<ModuleRequest>(module_requests->get(i)), isolate);
DirectHandle<String> specifier(module_request->specifier(), isolate);
DirectHandle<FixedArray> import_attributes(
module_request->import_attributes(), isolate);
switch (module_request->phase()) {
case ModuleImportPhase::kDefer:
case ModuleImportPhase::kEvaluation: {
v8::Local<v8::Module> api_requested_module;
if (callbacks.module_callback != nullptr) {
if (!callbacks
.module_callback(
context, v8::Utils::ToLocal(specifier),
v8::Utils::FixedArrayToLocal(import_attributes),
v8::Utils::ToLocal(Cast<Module>(module)))
.ToLocal(&api_requested_module)) {
return false;
}
} else {
DCHECK_NOT_NULL(callbacks.module_callback_by_index);
if (!callbacks
.module_callback_by_index(
context, i, v8::Utils::ToLocal(Cast<Module>(module)))
.ToLocal(&api_requested_module)) {
return false;
}
}
DirectHandle<Module> requested_module =
Utils::OpenDirectHandle(*api_requested_module);
requested_modules->set(i, *requested_module);
break;
}
case ModuleImportPhase::kSource: {
DCHECK(v8_flags.js_source_phase_imports);
v8::Local<v8::Object> api_requested_module_source;
if (callbacks.source_callback != nullptr) {
if (!callbacks
.source_callback(
context, v8::Utils::ToLocal(specifier),
v8::Utils::FixedArrayToLocal(import_attributes),
v8::Utils::ToLocal(Cast<Module>(module)))
.ToLocal(&api_requested_module_source)) {
return false;
}
} else {
DCHECK_NOT_NULL(callbacks.source_callback_by_index);
if (!callbacks
.source_callback_by_index(
context, i, v8::Utils::ToLocal(Cast<Module>(module)))
.ToLocal(&api_requested_module_source)) {
return false;
}
}
DirectHandle<JSReceiver> requested_module_source =
Utils::OpenDirectHandle(*api_requested_module_source);
requested_modules->set(i, *requested_module_source);
break;
}
default:
UNREACHABLE();
}
}
// Recurse.
const uint32_t requested_modules_len = requested_modules->ulength().value();
for (uint32_t i = 0; i < requested_modules_len; ++i) {
DirectHandle<ModuleRequest> module_request(
Cast<ModuleRequest>(module_requests->get(i)), isolate);
if (module_request->phase() == ModuleImportPhase::kSource) {
continue;
}
DirectHandle<Module> requested_module(
Cast<Module>(requested_modules->get(i)), isolate);
if (!Module::PrepareInstantiate(isolate, requested_module, context,
callbacks)) {
return false;
}
}
// Set up local exports.
// TODO(neis): Create regular_exports array here instead of in factory method?
for (uint32_t i = 0, n = module_info->RegularExportCount(); i < n; ++i) {
int cell_index = module_info->RegularExportCellIndex(i);
DirectHandle<FixedArray> export_names(
module_info->RegularExportExportNames(i), isolate);
CreateExport(isolate, module, cell_index, export_names);
}
// Partially set up indirect exports.
// For each indirect export, we create the appropriate slot in the export
// table and store its SourceTextModuleInfoEntry there. When we later find
// the correct Cell in the module that actually provides the value, we replace
// the SourceTextModuleInfoEntry by that Cell (see ResolveExport).
DirectHandle<FixedArray> special_exports(module_info->special_exports(),
isolate);
const uint32_t special_exports_len = special_exports->ulength().value();
for (uint32_t i = 0; i < special_exports_len; ++i) {
DirectHandle<SourceTextModuleInfoEntry> entry(
Cast<SourceTextModuleInfoEntry>(special_exports->get(i)), isolate);
DirectHandle<Object> export_name(entry->export_name(), isolate);
if (IsUndefined(*export_name)) continue; // Star export.
CreateIndirectExport(isolate, module, Cast<String>(export_name), entry);
}
DCHECK_EQ(module->status(), kPreLinking);
return true;
}
bool SourceTextModule::RunInitializationCode(
Isolate* isolate, DirectHandle<SourceTextModule> module) {
DCHECK_EQ(module->status(), kLinking);
DirectHandle<JSFunction> function(Cast<JSFunction>(module->code()), isolate);
DCHECK_EQ(MODULE_SCOPE, function->shared()->scope_info()->scope_type());
DirectHandle<Object> receiver = isolate->factory()->undefined_value();
DirectHandle<ScopeInfo> scope_info(function->shared()->scope_info(), isolate);
DirectHandle<Context> context = isolate->factory()->NewModuleContext(
module, isolate->native_context(), scope_info);
function->set_context(*context);
MaybeDirectHandle<Object> maybe_generator =
Execution::Call(isolate, function, receiver, {});
DirectHandle<Object> generator;
if (!maybe_generator.ToHandle(&generator)) {
DCHECK(isolate->has_exception());
return false;
}
DCHECK_EQ(*function, Cast<JSGeneratorObject>(generator)->function());
module->set_code(Cast<JSGeneratorObject>(*generator));
return true;
}
// https://tc39.es/ecma262/#sec-innermoduleevaluation and
// https://tc39.es/ecma262/#sec-innermodulelinking
bool SourceTextModule::MaybeTransitionComponent(
Isolate* isolate, DirectHandle<SourceTextModule> module,
ZoneForwardList<Handle<SourceTextModule>>* stack, Status new_status) {
DCHECK(new_status == kLinked || new_status == kEvaluated);
#ifdef DEBUG
if (v8_flags.trace_module_status) {
StdoutStream os;
os << "Transitioning strongly connected module graph component to "
<< Module::StatusString(new_status) << " {\n";
}
#endif // DEBUG
// Below, N/M means step N in InnerModuleEvaluation and step M in
// InnerModuleLinking.
// 14/11. Assert: module occurs exactly once in stack.
SLOW_DCHECK(
// {module} is on the {stack}.
std::count_if(stack->begin(), stack->end(), [&](DirectHandle<Module> m) {
return *m == *module;
}) == 1);
// 15/12. Assert: module.[[DFSAncestorIndex]] ≤ module.[[DFSIndex]].
DCHECK_LE(module->dfs_ancestor_index(), module->dfs_index());
// 16/13. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then
if (module->dfs_ancestor_index() == module->dfs_index()) {
// This is the root of its strongly connected component.
DirectHandle<SourceTextModule> cycle_root = module;
DirectHandle<SourceTextModule> ancestor;
// This loop handles the loops in both InnerModuleEvaluation and
// InnerModuleLinking.
//
// InnerModuleEvaluation
//
// a. Let done be false.
// b. Repeat, while done is false,
// i. Let requiredModule be the last element of stack.
// ii. Remove the last element of stack.
// iii. Assert: requiredModule is a Cyclic Module Record.
// iv. Assert: requiredModule.[[AsyncEvaluationOrder]] is either an
// integer or unset.
// v. If requiredModule.[[AsyncEvaluation]] is false, set
// requiredModule.[[Status]] to EVALUATED.
// vi. Else, set requiredModule.[[Status]] to EVALUATING-ASYNC.
// vii. If requiredModule and module are the same Module Record, set done
// to true.
// viii. Assert: requiredModule.[[CycleRoot]] is empty.
// ix. Set requiredModule.[[CycleRoot]] to module.
//
// InnerModuleLinking
//
// a. Let done be false.
// b. Repeat, while done is false,
// i. Let requiredModule be the last element of stack.
// ii. Remove the last element of stack.
// iii. Assert: requiredModule is a Cyclic Module Record.
// iv. Set requiredModule.[[Status]] to LINKED.
// v. If requiredModule and module are the same Module Record, set done
// to true.
do {
ancestor = stack->front();
stack->pop_front();
DCHECK_EQ(ancestor->status(),
new_status == kLinked ? kLinking : kEvaluating);
if (new_status == kLinked) {
if (!SourceTextModule::RunInitializationCode(isolate, ancestor)) {
return false;
}
ancestor->SetStatus(kLinked);
} else {
DCHECK(ancestor->async_evaluation_ordinal() == kNotAsyncEvaluated ||
ancestor->HasAsyncEvaluationOrdinal());
DCHECK(IsTheHole(ancestor->cycle_root()));
ancestor->set_cycle_root(*cycle_root);
ancestor->SetStatus(ancestor->HasAsyncEvaluationOrdinal()
? kEvaluatingAsync
: kEvaluated);
}
} while (*ancestor != *module);
}
#ifdef DEBUG
if (v8_flags.trace_module_status) {
StdoutStream os;
os << "}\n";
}
#endif // DEBUG
return true;
}
bool SourceTextModule::FinishInstantiate(
Isolate* isolate, Handle<SourceTextModule> module,
ZoneForwardList<Handle<SourceTextModule>>* stack, unsigned* dfs_index,
Zone* zone, unsigned depth, unsigned* max_depth) {
// Instantiate SharedFunctionInfo and mark module as instantiating for
// the recursion.
DirectHandle<SharedFunctionInfo> shared(
Cast<SharedFunctionInfo>(module->code()), isolate);
DirectHandle<JSFunction> function =
Factory::JSFunctionBuilder{isolate, shared, isolate->native_context()}
.Build();
module->set_code(*function);
module->SetStatus(kLinking);
module->set_dfs_index(*dfs_index);
module->set_dfs_ancestor_index(*dfs_index);
stack->push_front(module);
(*dfs_index)++;
// Recurse.
DirectHandle<FixedArray> module_requests(module->info()->module_requests(),
isolate);
DirectHandle<FixedArray> requested_modules(module->requested_modules(),
isolate);
const uint32_t requested_modules_len = requested_modules->ulength().value();
for (uint32_t i = 0; i < requested_modules_len; ++i) {
DirectHandle<ModuleRequest> module_request(
Cast<ModuleRequest>(module_requests->get(i)), isolate);
if (module_request->phase() == ModuleImportPhase::kSource) {
continue;
}
Handle<Module> requested_module(Cast<Module>(requested_modules->get(i)),
isolate);
if (!Module::FinishInstantiate(isolate, requested_module, stack, dfs_index,
zone, depth + 1, max_depth)) {
return false;
}
DCHECK_NE(requested_module->status(), kEvaluating);
DCHECK_GE(requested_module->status(), kLinking);
SLOW_DCHECK(
// {requested_module} is instantiating iff it's on the {stack}.
(requested_module->status() == kLinking) ==
std::count_if(
stack->begin(), stack->end(),
[&](DirectHandle<Module> m) { return *m == *requested_module; }));
if (requested_module->status() == kLinking) {
// SyntheticModules go straight to kLinked so this must be a
// SourceTextModule
module->set_dfs_ancestor_index(std::min(
module->dfs_ancestor_index(),
Cast<SourceTextModule>(*requested_module)->dfs_ancestor_index()));
}
}
Handle<Script> script(module->GetScript(), isolate);
DirectHandle<SourceTextModuleInfo> module_info(module->info(), isolate);
// Resolve imports.
DirectHandle<FixedArray> regular_imports(module_info->regular_imports(),
isolate);
const uint32_t regular_imports_len = regular_imports->ulength().value();
for (uint32_t i = 0; i < regular_imports_len; ++i) {
DirectHandle<SourceTextModuleInfoEntry> entry(
Cast<SourceTextModuleInfoEntry>(regular_imports->get(i)), isolate);
Handle<String> name(Cast<String>(entry->import_name()), isolate);
MessageLocation loc(script, entry->beg_pos(), entry->end_pos());
ResolveSet resolve_set(zone);
DirectHandle<Cell> cell;
if (!ResolveImport(isolate, module, name, entry->module_request(), loc,
true, &resolve_set)
.ToHandle(&cell)) {
return false;
}
module->regular_imports()->set(ImportIndex(entry->cell_index()), *cell);
}
// Resolve indirect exports.
DirectHandle<FixedArray> special_exports(module_info->special_exports(),
isolate);
const uint32_t special_exports_len = special_exports->ulength().value();
for (uint32_t i = 0; i < special_exports_len; ++i) {
DirectHandle<SourceTextModuleInfoEntry> entry(
Cast<SourceTextModuleInfoEntry>(special_exports->get(i)), isolate);
Handle<Object> name(entry->export_name(), isolate);
if (IsUndefined(*name)) continue; // Star export.
MessageLocation loc(script, entry->beg_pos(), entry->end_pos());
ResolveSet resolve_set(zone);
if (ResolveExport(isolate, module, {}, Cast<String>(name), loc, true,
&resolve_set)
.is_null()) {
return false;
}
}
return MaybeTransitionComponent(isolate, module, stack, kLinked);
}
void SourceTextModule::FetchStarExports(Isolate* isolate,
Handle<SourceTextModule> module,
Zone* zone,
UnorderedModuleSet* visited) {
DCHECK_GE(module->status(), Module::kLinking);
// Shortcut.
if (!IsUndefined(module->module_namespace()) &&
IsJSModuleNamespace(Cast<Cell>(module->module_namespace())->value())) {
return;
}
bool cycle = !visited->insert(module).second;
if (cycle) return;
Handle<ObjectHashTable> exports(module->exports(), isolate);
UnorderedStringMap more_exports(zone);
// TODO(neis): Only allocate more_exports if there are star exports.
// Maybe split special_exports into indirect_exports and star_exports.
ReadOnlyRoots roots(isolate);
DirectHandle<FixedArray> special_exports(module->info()->special_exports(),
isolate);
const uint32_t special_exports_len = special_exports->ulength().value();
for (uint32_t i = 0; i < special_exports_len; ++i) {
DirectHandle<SourceTextModuleInfoEntry> entry(
Cast<SourceTextModuleInfoEntry>(special_exports->get(i)), isolate);
if (!IsUndefined(entry->export_name())) {
continue; // Indirect export.
}
// Source phase imports store a JSReceiver (not a Module) in
// requested_modules. Guard against type confusion if a future change
// breaks the invariant that star exports only reference evaluation-phase
// imports.
CHECK_EQ(Cast<ModuleRequest>(module->info()->module_requests()->get(
entry->module_request()))
->phase(),
ModuleImportPhase::kEvaluation);
Handle<Module> requested_module(
Cast<Module>(module->requested_modules()->get(entry->module_request())),
isolate);
// Recurse.
if (IsSourceTextModule(*requested_module)) {
FetchStarExports(isolate, Cast<SourceTextModule>(requested_module), zone,
visited);
}
// Collect all of [requested_module]'s exports that must be added to
// [module]'s exports (i.e. to [exports]). We record these in
// [more_exports]. Ambiguities (conflicting exports) are marked by mapping
// the name to undefined instead of a Cell.
DirectHandle<ObjectHashTable> requested_exports(requested_module->exports(),
isolate);
for (InternalIndex index : requested_exports->IterateEntries()) {
Tagged<Object> key;
if (!requested_exports->ToKey(roots, index, &key)) continue;
Handle<String> name(Cast<String>(key), isolate);
if (name->Equals(roots.default_string())) continue;
if (!IsTheHole(exports->Lookup(name))) continue;
Handle<Cell> cell(Cast<Cell>(requested_exports->ValueAt(index)), isolate);
auto insert_result = more_exports.insert(std::make_pair(name, cell));
if (!insert_result.second) {
auto it = insert_result.first;
if (*it->second == *cell || IsUndefined(*it->second)) {
// We already recorded this mapping before, or the name is already
// known to be ambiguous. In either case, there's nothing to do.
} else {
DCHECK(IsCell(*it->second));
// Different star exports provide different cells for this name, hence
// mark the name as ambiguous.
it->second = isolate->factory()->undefined_value();
}
}
}
}
// Copy [more_exports] into [exports].
for (const auto& elem : more_exports) {
if (IsUndefined(*elem.second)) continue; // Ambiguous export.
DCHECK(!elem.first->Equals(ReadOnlyRoots(isolate).default_string()));
DCHECK(IsCell(*elem.second));
exports = ObjectHashTable::Put(isolate, exports, elem.first, elem.second);
}
module->set_exports(*exports);
}
void SourceTextModule::GatherAvailableAncestors(
Isolate* isolate, Zone* zone, Handle<SourceTextModule> start,
AvailableAncestorsSet* exec_list) {
// The spec algorithm is recursive. It is transformed to an equivalent
// iterative one here.
ZoneStack<Handle<SourceTextModule>> worklist(zone);
worklist.push(start);
while (!worklist.empty()) {
DirectHandle<SourceTextModule> module = worklist.top();
worklist.pop();
// 1. For each Cyclic Module Record m of module.[[AsyncParentModules]], do
const uint32_t module_count = module->AsyncParentModuleCount();
DCHECK_LE(module_count, kMaxInt);
for (int i = static_cast<int>(module_count); i-- > 0;) {
Handle<SourceTextModule> m = module->GetAsyncParentModule(isolate, i);
// a. If execList does not contain m and m.[[EvaluationError]] is empty,
// then
if (m->status() != kErrored && exec_list->find(m) == exec_list->end()) {
// i. Assert: m.[[Status]] is EVALUATING-ASYNC.
DCHECK_EQ(m->status(), kEvaluatingAsync);
// ii. Assert: m.[[CycleRoot]] is not empty.
DCHECK(!IsTheHole(m->cycle_root()));
// iii. If m.[[CycleRoot]].[[EvaluationError]] is empty, then
if (m->GetCycleRoot(isolate)->status() != kErrored) {
// 1. Assert: m.[[AsyncEvaluation]] is true.
DCHECK(m->HasAsyncEvaluationOrdinal());
// 2. Assert: m.[[PendingAsyncDependencies]] > 0.
DCHECK(m->HasPendingAsyncDependencies());
// 3. Set m.[[PendingAsyncDependencies]] to
// m.[[PendingAsyncDependencies]] - 1.
m->DecrementPendingAsyncDependencies();
// 4. If m.[[PendingAsyncDependencies]] = 0, then
if (!m->HasPendingAsyncDependencies()) {
// a. Append m to execList.
exec_list->insert(m);
// b. If m.[[HasTLA]] is false,
// perform ! GatherAvailableAncestors(m, execList).
if (!m->has_toplevel_await()) worklist.push(m);
}
}
}
}
}
// 2. Return UNUSED.
}
DirectHandle<JSModuleNamespace> SourceTextModule::GetModuleNamespace(
Isolate* isolate, DirectHandle<SourceTextModule> module,
int module_request_index) {
Tagged<ModuleRequest> module_request = Cast<ModuleRequest>(
module->info()->module_requests()->get(module_request_index));
// Source phase imports store a JSReceiver (not a Module) in
// requested_modules. Guard against type confusion if a future change
// routes a source-phase request through GetModuleNamespace.
CHECK_NE(module_request->phase(), ModuleImportPhase::kSource);
Handle<Module> requested_module(
Cast<Module>(module->requested_modules()->get(module_request_index)),
isolate);
return Module::GetModuleNamespace(isolate, requested_module,
module_request->phase());
}
MaybeHandle<JSObject> SourceTextModule::GetImportMeta(
Isolate* isolate, DirectHandle<SourceTextModule> module) {
Handle<UnionOf<JSObject, TheHole>> import_meta(
module->import_meta(kAcquireLoad), isolate);
if (IsTheHole(*import_meta)) {
if (!isolate->RunHostInitializeImportMetaObjectCallback(module).ToHandle(
&import_meta)) {
return {};
}
module->set_import_meta(*import_meta, kReleaseStore);
}
return Cast<JSObject>(import_meta);
}
// https://tc39.es/ecma262/#sec-moduleevaluation
bool SourceTextModule::MaybeHandleEvaluationException(
Isolate* isolate, ZoneForwardList<Handle<SourceTextModule>>* stack) {
DisallowGarbageCollection no_gc;
Tagged<Object> exception = isolate->exception();
// Step 9.
if (isolate->is_catchable_by_javascript(exception)) {
// a. For each Cyclic Module Record m in stack, do
for (DirectHandle<SourceTextModule> descendant : *stack) {
// i. Assert: m.[[Status]] is EVALUATING.
CHECK_EQ(descendant->status(), kEvaluating);
// ii. Set m.[[Status]] to EVALUATED.
// iii. Set m.[[EvaluationError]] to result.
descendant->RecordError(isolate, exception);
}
// A stack overflow at the InnerModuleEvaluation entry STACK_CHECK can throw
// before this module was appended to `stack`, leaving its
// [[EvaluationError]] empty. Record it directly so the top-level capability
// rejects with the actual exception rather than the EMPTY (TheHole)
// sentinel.
if (IsTheHole(this->exception())) {
RecordError(isolate, exception);
}
return true;
}
// If the exception was a termination exception, rejecting the promise
// would resume execution, and our API contract is to return an empty
// handle. The module's status should be set to kErrored and the
// exception field should be set to `null`.
RecordError(isolate, exception);
for (DirectHandle<SourceTextModule> descendant : *stack) {
descendant->RecordError(isolate, exception);
}
CHECK_EQ(status(), kErrored);
CHECK_EQ(this->exception(), *isolate->factory()->null_value());
return false;
}
// https://tc39.es/ecma262/#sec-moduleevaluation
MaybeDirectHandle<JSPromise> SourceTextModule::Evaluate(
Isolate* isolate, Handle<SourceTextModule> module) {
CHECK(module->status() == kLinked || module->status() == kEvaluatingAsync ||
module->status() == kEvaluated || module->status() == kErrored);
// An errored module can only reach here if it was never an evaluation
// entry point; otherwise Module::Evaluate would have returned its
// already-rejected top-level capability.
CHECK_IMPLIES(module->status() == kErrored,
IsUndefined(module->top_level_capability()));
// 5. Let stack be a new empty List.
Zone zone(isolate->allocator(), ZONE_NAME);
ZoneForwardList<Handle<SourceTextModule>> stack(&zone);
unsigned dfs_index = 0;
// 6. Let capability be ! NewPromiseCapability(%Promise%).
DirectHandle<JSPromise> capability = isolate->factory()->NewJSPromise();
// 7. Set module.[[TopLevelCapability]] to capability.
module->set_top_level_capability(*capability);
DCHECK(IsJSPromise(module->top_level_capability()));
// 8. Let result be InnerModuleEvaluation(module, stack, 0).
// 9. If result is an abrupt completion, then
v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
try_catch.SetVerbose(false);
try_catch.SetCaptureMessage(false);
// TODO(verwaest): Return a bool from InnerModuleEvaluation instead?
if (InnerModuleEvaluation(isolate, module, &stack, &dfs_index).is_null()) {
if (!module->MaybeHandleEvaluationException(isolate, &stack)) return {};
CHECK(try_catch.HasCaught());
// We are clearing the internal exception here because JSPromise::Reject can
// call Isolate::ReportPromiseReject that will trap to a host defined hook
// and given we are already registering this exception on promise
// capability, we shouldn't keep it as pending on isolate. This is important
// because host might have paths that call `Isolate::ReportPendingMessages`
// while `AllowExceptions::IsAllowed` is `false` and leaving such exception
// pending will cause a DCHECK failure.
isolate->clear_internal_exception();
// d. Perform ! Call(capability.[[Reject]], undefined,
// «result.[[Value]]»).
JSPromise::Reject(capability, direct_handle(module->exception(), isolate));
} else { // 10. Else,
// a. Assert: module.[[Status]] is either EVALUATING-ASYNC or EVALUATED.
CHECK_GE(module->status(), kEvaluatingAsync);
// c. If module.[[AsyncEvaluation]] is false, then
if (!module->HasAsyncEvaluationOrdinal()) {
// i. Assert: module.[[Status]] is EVALUATED.
DCHECK_EQ(module->status(), kEvaluated);
// ii. Perform ! Call(capability.[[Resolve]], undefined,
// «undefined»).
JSPromise::Resolve(capability, isolate->factory()->undefined_value())
.ToHandleChecked();
}
// d. Assert: stack is empty.
DCHECK(stack.empty());
}
// 11. Return capability.[[Promise]].
return capability;
}
// https://tc39.es/ecma262/#sec-async-module-execution-fulfilled
Maybe<bool> SourceTextModule::AsyncModuleExecutionFulfilled(
Isolate* isolate, Handle<SourceTextModule> module) {
// 1. If module.[[Status]] is EVALUATED, then
if (module->status() == kErrored) {
// a. Assert: module.[[EvaluationError]] is not EMPTY.
DCHECK(!IsTheHole(module->exception()));
// b. Return UNUSED.
return Just(true);
}
// 2. Assert: module.[[Status]] is EVALUATING-ASYNC.
DCHECK_EQ(module->status(), kEvaluatingAsync);
// 3. Assert: module.[[AsyncEvaluation]] is true.
DCHECK(module->HasAsyncEvaluationOrdinal());
// 4. Assert: module.[[EvaluationError]] is EMPTY.
// (Done by step 2.)
// 5. Set module.[[AsyncEvaluation]] to false.
module->set_async_evaluation_ordinal(kAsyncEvaluateDidFinish);
// 6. Set module.[[Status]] to EVALUATED.
module->SetStatus(kEvaluated);
// 7. If module.[[TopLevelCapability]] is not EMPTY, then
if (!IsUndefined(module->top_level_capability())) {
// a. Assert: module.[[CycleRoot]] is equal to module.
DCHECK_EQ(*module->GetCycleRoot(isolate), *module);
// i. Perform ! Call(module.[[TopLevelCapability]].[[Resolve]], undefined,
// «undefined»).
DirectHandle<JSPromise> capability(
Cast<JSPromise>(module->top_level_capability()), isolate);
JSPromise::Resolve(capability, isolate->factory()->undefined_value())
.ToHandleChecked();
}
// 8. Let execList be a new empty List.
Zone zone(isolate->allocator(), ZONE_NAME);
AvailableAncestorsSet exec_list(&zone);
// 9. Perform GatherAvailableAncestors(module, execList).
GatherAvailableAncestors(isolate, &zone, module, &exec_list);
// 10. Let sortedExecList be a List of elements that are the elements of
// execList, in the order in which they had their [[AsyncEvaluation]]
// fields set to true in InnerModuleEvaluation.
//
// This step is implemented by AvailableAncestorsSet, which is a set
// ordered on async_evaluation_ordinal.
// 11. Assert: All elements of sortedExecList have their [[AsyncEvaluation]]
// field set to true, [[PendingAsyncDependencies]] field set to 0 and
// [[EvaluationError]] field set to undefined.
#ifdef DEBUG
for (DirectHandle<SourceTextModule> m : exec_list) {
DCHECK(m->HasAsyncEvaluationOrdinal());
DCHECK(!m->HasPendingAsyncDependencies());
DCHECK_NE(m->status(), kErrored);
}
#endif
// 12. For each Module m of sortedExecList, do
for (DirectHandle<SourceTextModule> m : exec_list) {
if (m->status() == kErrored) { // a. If m.[[Status]] is EVALUATED, then
// i. Assert: m.[[EvaluationError]] is not EMPTY.
DCHECK(!IsTheHole(m->exception()));
} else if (m->has_toplevel_await()) { // b. Else if m.[[HasTLA]] is true,
// then
// i. Perform ExecuteAsyncModule(m).
//
// The execution may have been terminated and can not be resumed, so just
// raise the exception.
MAYBE_RETURN(ExecuteAsyncModule(isolate, m), Nothing<bool>());
} else { // c. Else,
// i. Let result be m.ExecuteModule().
DirectHandle<Object> unused_result;
MaybeDirectHandle<Object> exception;
// ii. If result is an abrupt completion, then
if (!ExecuteModule(isolate, m, &exception).ToHandle(&unused_result)) {
DCHECK_IMPLIES(exception.IsEmpty(),
isolate->is_execution_terminating());
if (isolate->is_execution_terminating()) return {};
// 1. Perform AsyncModuleExecutionRejected(m, result.[[Value]]).
AsyncModuleExecutionRejected(isolate, m, exception.ToHandleChecked());
} else { // iii. Else,
// 1. Set m.[[AsyncEvaluation]] to false.
m->set_async_evaluation_ordinal(kAsyncEvaluateDidFinish);
// 2. Set m.[[Status]] to EVALUATED.
m->SetStatus(kEvaluated);
// 3. If m.[[TopLevelCapability]] is not EMPTY, then
if (!IsUndefined(m->top_level_capability())) {
// a. Assert: m.[[CycleRoot]] and m are the same Module Record.
DCHECK_EQ(*m->GetCycleRoot(isolate), *m);
// b. Perform ! Call(m.[[TopLevelCapability]].[[Resolve]], undefined,
// « undefined »).
DirectHandle<JSPromise> capability(
Cast<JSPromise>(m->top_level_capability()), isolate);
JSPromise::Resolve(capability, isolate->factory()->undefined_value())
.ToHandleChecked();
}
}
}
}
// Return UNUSED.
return Just(true);
}
// https://tc39.es/ecma262/#sec-async-module-execution-rejected
void SourceTextModule::AsyncModuleExecutionRejected(
Isolate* isolate, DirectHandle<SourceTextModule> module,
DirectHandle<Object> exception) {
// 1. If module.[[Status]] is EVALUATED, then
if (module->status() == kErrored) {
// a. Assert: module.[[EvaluationError]] is not empty.
DCHECK(!IsTheHole(module->exception()));
// b. Return UNUSED.
return;
}
DCHECK(isolate->is_catchable_by_javascript(*exception));
// 2. Assert: module.[[Status]] is EVALUATING-ASYNC.
CHECK_EQ(module->status(), kEvaluatingAsync);
// 3. Assert: module.[[AsyncEvaluation]] is true.
DCHECK(module->HasAsyncEvaluationOrdinal());
// 4. Assert: module.[[EvaluationError]] is EMPTY.
DCHECK(IsTheHole(module->exception()));
// 5. Set module.[[EvaluationError]] to ThrowCompletion(error).
module->RecordError(isolate, *exception);
// 6. Set module.[[Status]] to EVALUATED.
// (We have a status for kErrored, so don't set to kEvaluated.)
module->set_async_evaluation_ordinal(kAsyncEvaluateDidFinish);
// 7. If module.[[TopLevelCapability]] is not EMPTY, then
if (!IsUndefined(module->top_level_capability())) {
// a. Assert: module.[[CycleRoot]] and module are the same Module Record.
DCHECK_EQ(*module->GetCycleRoot(isolate), *module);
// b. Perform ! Call(module.[[TopLevelCapability]].[[Reject]],
// undefined, «error»).
DirectHandle<JSPromise> capability(
Cast<JSPromise>(module->top_level_capability()), isolate);
JSPromise::Reject(capability, exception);
}
// 8. For each Cyclic Module Record m of module.[[AsyncParentModules]], do
const uint32_t module_count = module->AsyncParentModuleCount();
for (uint32_t i = 0; i < module_count; i++) {
// a. Perform AsyncModuleExecutionRejected(m, error).
DirectHandle<SourceTextModule> m = module->GetAsyncParentModule(isolate, i);
AsyncModuleExecutionRejected(isolate, m, exception);
}
// 9. Return UNUSED.
}
// static
Maybe<bool> SourceTextModule::ExecuteAsyncModule(
Isolate* isolate, DirectHandle<SourceTextModule> module) {
// 1. Assert: module.[[Status]] is either EVALUATING or EVALUATING-ASYNC.
CHECK(module->status() == kEvaluating ||
module->status() == kEvaluatingAsync);
// 2. Assert: module.[[HasTLA]] is true.
DCHECK(module->has_toplevel_await());
// 3. Let capability be ! NewPromiseCapability(%Promise%).
DirectHandle<JSPromise> capability = isolate->factory()->NewJSPromise();
DirectHandle<Context> execute_async_module_context =
isolate->factory()->NewBuiltinContext(
isolate->native_context(),
ExecuteAsyncModuleContextSlots::kContextLength);
execute_async_module_context->SetNoCell(
ExecuteAsyncModuleContextSlots::kModule, *module);
// 4. Let fulfilledClosure be a new Abstract Closure with no parameters that
// captures module and performs the following steps when called:
// a. Perform AsyncModuleExecutionFulfilled(module).
// b. Return undefined.
// 5. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »).
DirectHandle<JSFunction> on_fulfilled =
Factory::JSFunctionBuilder{
isolate,
isolate->factory()
->source_text_module_execute_async_module_fulfilled_sfi(),
execute_async_module_context}
.Build();
// 6. Let rejectedClosure be a new Abstract Closure with parameters (error)
// that captures module and performs the following steps when called:
// a. Perform AsyncModuleExecutionRejected(module, error).
// b. Return undefined.
// 7. Let onRejected be CreateBuiltinFunction(rejectedClosure, 0, "", « »).
DirectHandle<JSFunction> on_rejected =
Factory::JSFunctionBuilder{
isolate,
isolate->factory()
->source_text_module_execute_async_module_rejected_sfi(),
execute_async_module_context}
.Build();
// 8. Perform PerformPromiseThen(capability.[[Promise]],
// onFulfilled, onRejected).
DirectHandle<Object> args[] = {on_fulfilled, on_rejected};
if (V8_UNLIKELY(Execution::CallBuiltin(isolate,
isolate->perform_promise_then(),
capability, base::VectorOf(args))
.is_null())) {
// This may only fail with a termination exception.
CHECK(isolate->is_execution_terminating());
return Nothing<bool>();
}
// 9. Perform ! module.ExecuteModule(capability).
// Note: In V8 we have broken module.ExecuteModule into
// ExecuteModule for synchronous module execution and
// InnerExecuteAsyncModule for asynchronous execution.
MaybeDirectHandle<Object> ret =
InnerExecuteAsyncModule(isolate, module, capability);
if (ret.is_null()) {
// The evaluation of async module cannot throw a JavaScript observable
// exception.
DCHECK_IMPLIES(v8_flags.strict_termination_checks,
isolate->is_execution_terminating());
return Nothing<bool>();
}
// 10. Return UNUSED.
return Just<bool>(true);
}
MaybeDirectHandle<Object> SourceTextModule::InnerExecuteAsyncModule(
Isolate* isolate, DirectHandle<SourceTextModule> module,
DirectHandle<JSPromise> capability) {
// If we have an async module, then it has an associated
// JSAsyncFunctionObject, which we then evaluate with the passed in promise
// capability.
DirectHandle<JSAsyncFunctionObject> async_function_object(
Cast<JSAsyncFunctionObject>(module->code()), isolate);
async_function_object->set_promise(*capability);
DirectHandle<JSFunction> resume(
isolate->native_context()->async_module_evaluate_internal(), isolate);
return Execution::TryCall(isolate, resume, async_function_object, {},
Execution::MessageHandling::kKeepPending, nullptr);
}
MaybeDirectHandle<Object> SourceTextModule::ExecuteModule(
Isolate* isolate, DirectHandle<SourceTextModule> module,
MaybeDirectHandle<Object>* exception_out) {
// Synchronous modules have an associated JSGeneratorObject.
DirectHandle<JSGeneratorObject> generator(
Cast<JSGeneratorObject>(module->code()), isolate);
DirectHandle<JSFunction> resume(
isolate->native_context()->generator_next_internal(), isolate);
DirectHandle<Object> result;
if (!Execution::TryCall(isolate, resume, generator, {},
Execution::MessageHandling::kKeepPending,
exception_out)
.ToHandle(&result)) {
return {};
}
DCHECK(
Object::BooleanValue(Cast<JSIteratorResult>(*result)->done(), isolate));
return direct_handle(Cast<JSIteratorResult>(*result)->value(), isolate);
}
MaybeDirectHandle<Object> SourceTextModule::InnerModuleEvaluation(
Isolate* isolate, Handle<SourceTextModule> module,
ZoneForwardList<Handle<SourceTextModule>>* stack, unsigned* dfs_index) {
STACK_CHECK(isolate, MaybeDirectHandle<Object>());
int module_status = module->status();
// InnerModuleEvaluation(module, stack, index)
// 2. If module.[[Status]] is either EVALUATING-ASYNC or EVALUATED, then
if (module_status == kEvaluatingAsync || module_status == kEvaluating ||
module_status == kEvaluated) {
// a. If module.[[EvaluationError]] is undefined, return index.
// (We return undefined instead)
//
// 3. If module.[[Status]] is EVALUATING, return index.
// (Out of order)
return isolate->factory()->undefined_value();
} else if (module_status == kErrored) {
// b. Otherwise return module.[[EvaluationError]].
// (We throw on isolate and return a MaybeHandle<Object> instead)
isolate->Throw(module->exception());
return MaybeDirectHandle<Object>();
}
// 4. Assert: module.[[Status]] is LINKED.
CHECK_EQ(module_status, kLinked);
DirectHandle<FixedArray> module_requests;
DirectHandle<FixedArray> requested_modules;
{
DisallowGarbageCollection no_gc;
Tagged<SourceTextModule> raw_module = *module;
// 5. Set module.[[Status]] to EVALUATING.
raw_module->SetStatus(kEvaluating);
// 6. Set module.[[DFSIndex]] to index.
raw_module->set_dfs_index(*dfs_index);
// 7. Set module.[[DFSAncestorIndex]] to index.
raw_module->set_dfs_ancestor_index(*dfs_index);
// 8. Set module.[[PendingAsyncDependencies]] to 0.
DCHECK(!raw_module->HasPendingAsyncDependencies());
// 9. Set index to index + 1.
(*dfs_index)++;
// 10. Append module to stack.
stack->push_front(module);
// Recursion.
module_requests =
direct_handle(raw_module->info()->module_requests(), isolate);
requested_modules = direct_handle(raw_module->requested_modules(), isolate);
}
Zone zone(isolate->allocator(), ZONE_NAME);
// There's an evaluation set to perform optimized check if a module is already
// in evaluation_list. It's necessary to keep evaluation order as it's seen to
// be spec compliant.
UnorderedModuleSet evaluation_set(&zone);
ZoneVector<Handle<Module>> evaluation_list(&zone);
UnorderedModuleSet seen_modules(&zone);
const uint32_t requested_modules_len = requested_modules->ulength().value();
for (uint32_t i = 0; i < requested_modules_len; ++i) {
DirectHandle<ModuleRequest> module_request(
Cast<ModuleRequest>(module_requests->get(i)), isolate);
if (module_request->phase() == ModuleImportPhase::kSource) {
continue;
}
Handle<Module> requested_module(Cast<Module>(requested_modules->get(i)),
isolate);
if (module_request->phase() == ModuleImportPhase::kDefer) {
ZoneVector<Handle<SourceTextModule>> async_evaluation_list(&zone);
GatherAsynchronousTransitiveDependencies(
isolate, requested_module, &evaluation_set, &async_evaluation_list,
&seen_modules);
for (auto async_module : async_evaluation_list) {
evaluation_list.push_back(async_module);
}
} else if (evaluation_set.insert(requested_module).second) {
evaluation_list.push_back(requested_module);
}
}
// 11. For each ModuleRequest Record required of module.[[RequestedModules]],
for (size_t i = 0, length = evaluation_list.size(); i < length; ++i) {
// b. If requiredModule.[[Phase]] is evaluation, then
Handle<Module> requested_module = evaluation_list[i];
// c. If requiredModule is a Cyclic Module Record, then
if (IsSourceTextModule(*requested_module)) {
// b. Set index to ? InnerModuleEvaluation(requiredModule, stack, index).
// (Out of order because InnerModuleEvaluation is type-driven.)
Handle<SourceTextModule> required_module(
Cast<SourceTextModule>(*requested_module), isolate);
RETURN_ON_EXCEPTION(
isolate,
InnerModuleEvaluation(isolate, required_module, stack, dfs_index));
int required_module_status = required_module->status();
// i. Assert: requiredModule.[[Status]] is one of EVALUATING,
// EVALUATING-ASYNC, or EVALUATED.
// (We also assert the module cannot be errored, because if it was
// we would have already returned from InnerModuleEvaluation)
CHECK_GE(required_module_status, kEvaluating);
CHECK_NE(required_module_status, kErrored);
// ii. Assert: requiredModule.[[Status]] is EVALUATING if and only if
// requiredModule is in stack.
SLOW_DCHECK((requested_module->status() == kEvaluating) ==
std::count_if(stack->begin(), stack->end(),
[&](DirectHandle<Module> m) {
return *m == *requested_module;
}));
// iii. If requiredModule.[[Status]] is EVALUATING, then
if (required_module_status == kEvaluating) {
// 1. Set module.[[DFSAncestorIndex]] to
// min(module.[[DFSAncestorIndex]],
// requiredModule.[[DFSAncestorIndex]]).
module->set_dfs_ancestor_index(
std::min(module->dfs_ancestor_index(),
required_module->dfs_ancestor_index()));
} else { // iv. Else,
// 1. Assert: requiredModule.[[CycleRoot]] is not empty.
DCHECK(!IsTheHole(required_module->cycle_root()));
// 2. Set requiredModule to requiredModule.[[CycleRoot]].
required_module = required_module->GetCycleRoot(isolate);
required_module_status = required_module->status();
// 3. Assert: requiredModule.[[Status]] is either EVALUATING-ASYNC or
// EVALUATED.
CHECK_GE(required_module_status, kEvaluatingAsync);
// 4. If requiredModule.[[EvaluationError]] is not EMPTY,
// return ? module.[[EvaluationError]].
// (If there was an exception on the original required module we would
// have already returned. This check handles the case where the
// AsyncCycleRoot has an error. Instead of returning the exception, we
// throw on isolate and return a MaybeHandle<Object>.)
if (required_module_status == kErrored) {
isolate->Throw(required_module->exception());
return MaybeDirectHandle<Object>();
}
}
// v. If requiredModule.[[AsyncEvaluation]] is true, then
if (required_module->HasAsyncEvaluationOrdinal()) {
// 1. Set module.[[PendingAsyncDependencies]] to
// module.[[PendingAsyncDependencies]] + 1.
module->IncrementPendingAsyncDependencies();
// 2. Append module to requiredModule.[[AsyncParentModules]].
AddAsyncParentModule(isolate, required_module, module);
}
} else {
// b. Set index to ? InnerModuleEvaluation(requiredModule, stack, index).
// (Out of order because InnerModuleEvaluation is type-driven.)
RETURN_ON_EXCEPTION(isolate, Module::Evaluate(isolate, requested_module));
}
}
// The spec returns the module index for proper numbering of dependencies.
// However, we pass the module index by pointer instead.
//
// Before async modules v8 returned the value result from calling next
// on the module's implicit iterator. We preserve this behavior for
// synchronous modules, but return undefined for AsyncModules.
DirectHandle<Object> result = isolate->factory()->undefined_value();
// 12. If module.[[PendingAsyncDependencies]] > 0 or module.[[HasTLA]] is
// true, then
if (module->HasPendingAsyncDependencies() || module->has_toplevel_await()) {
// a. Assert: module.[[AsyncEvaluation]] is false and was never previously
// set to true.
DCHECK_EQ(module->async_evaluation_ordinal(), kNotAsyncEvaluated);
// b. Set module.[[AsyncEvaluation]] to true.
// c. NOTE: The order in which module records have their [[AsyncEvaluation]]
// fields transition to true is significant.
module->set_async_evaluation_ordinal(
isolate->NextModuleAsyncEvaluationOrdinal());
// c. If module.[[PendingAsyncDependencies]] = 0, perform
// ExecuteAsyncModule(module).
// The execution may have been terminated and can not be resumed, so just
// raise the exception.
if (!module->HasPendingAsyncDependencies()) {
MAYBE_RETURN(SourceTextModule::ExecuteAsyncModule(isolate, module),
MaybeDirectHandle<Object>());
}
} else { // 13. Else,
// a. Perform ? module.ExecuteModule().
MaybeDirectHandle<Object> exception;
DirectHandle<Object> maybe_result;
if (!ExecuteModule(isolate, module, &exception).ToHandle(&maybe_result)) {
if (!isolate->is_execution_terminating()) {
isolate->Throw(*exception.ToHandleChecked());
}
return maybe_result;
}
}
CHECK(MaybeTransitionComponent(isolate, module, stack, kEvaluated));
return result;
}
bool SourceTextModule::IsModuleSCCEvaluated(Handle<SourceTextModule> module) {
// It's necessary to check if [[CycleRoot]] is not empty here because:
// 1. A module starts with its [[CycleRoot]] as `TheHole` and it's set
// once the cycle is detected, or when the module finishes its evaluation
// without errors.
// 2. GatherAsynchronousTransitiveDependencies can be called with a module
// where it's `[[CycleRoot]]` is not set yet, and since it depends on
// `IsModuleSCCEvaluated`, we need such guard. A later call from
// `ReadyForSyncExecution` for the same module will have its `[[CycleRoot]]`
// set, unless its evaluation errored.
if (!IsTheHole(module->cycle_root())) {
Tagged<SourceTextModule> cycle_root =
Cast<SourceTextModule>(module->cycle_root());
return cycle_root->status() == Module::kEvaluated ||
cycle_root->status() == Module::kErrored;
}
return module->status() == Module::kEvaluated ||
module->status() == Module::kErrored;
}
// https://tc39.es/proposal-defer-import-eval/#sec-GatherAsynchronousTransitiveDependencies
void SourceTextModule::GatherAsynchronousTransitiveDependencies(
Isolate* isolate, Handle<Module> module, UnorderedModuleSet* evaluation_set,
ZoneVector<Handle<SourceTextModule>>* evaluation_list,
UnorderedModuleSet* seen_set) {
if (!seen_set->insert(module).second) {
return;
}
if (!IsSourceTextModule(*module)) {
return;
}
Handle<SourceTextModule> source_text_module = Cast<SourceTextModule>(module);
if (source_text_module->status() == kEvaluating ||
IsModuleSCCEvaluated(source_text_module)) {
return;
}
if (source_text_module->has_toplevel_await()) {
if (evaluation_set->insert(source_text_module).second) {
evaluation_list->push_back(source_text_module);
}
return;
}
DirectHandle<FixedArray> module_requests(
source_text_module->info()->module_requests(), isolate);
DirectHandle<FixedArray> requested_modules(
source_text_module->requested_modules(), isolate);
const uint32_t requested_modules_len = requested_modules->ulength().value();
for (uint32_t i = 0; i < requested_modules_len; ++i) {
DirectHandle<ModuleRequest> module_request(
Cast<ModuleRequest>(module_requests->get(i)), isolate);
// Only process evaluation phase modules (skip source phase)
if (module_request->phase() == ModuleImportPhase::kSource) {
continue;
}
Handle<Module> requested_module(Cast<Module>(requested_modules->get(i)),
isolate);
GatherAsynchronousTransitiveDependencies(
isolate, requested_module, evaluation_set, evaluation_list, seen_set);
}
}
// https://tc39.es/proposal-defer-import-eval/#sec-ReadyForSyncExecution
bool SourceTextModule::ReadyForSyncExecution(Isolate* isolate,
Handle<Module> module,
UnorderedModuleSet* seen) {
if (!seen->insert(module).second) {
return true;
}
if (!IsSourceTextModule(*module)) {
return true;
}
Handle<SourceTextModule> source_text_module = Cast<SourceTextModule>(module);
if (IsModuleSCCEvaluated(source_text_module)) {
return true;
}
if (source_text_module->status() == kEvaluating ||
source_text_module->status() == kEvaluatingAsync) {
return false;
}
if (source_text_module->has_toplevel_await()) {
return false;
}
DirectHandle<FixedArray> module_requests(
source_text_module->info()->module_requests(), isolate);
DirectHandle<FixedArray> requested_modules(
source_text_module->requested_modules(), isolate);
const uint32_t requested_modules_len = requested_modules->ulength().value();
for (uint32_t i = 0; i < requested_modules_len; ++i) {
DirectHandle<ModuleRequest> module_request(
Cast<ModuleRequest>(module_requests->get(i)), isolate);
if (module_request->phase() == ModuleImportPhase::kSource) {
continue;
}
Handle<Module> requested_module(Cast<Module>(requested_modules->get(i)),
isolate);
if (!ReadyForSyncExecution(isolate, requested_module, seen)) {
return false;
}
}
return true;
}
void SourceTextModule::Reset(Isolate* isolate,
DirectHandle<SourceTextModule> module) {
Factory* factory = isolate->factory();
DCHECK(IsTheHole(module->import_meta(kAcquireLoad)));
DirectHandle<FixedArray> regular_exports =
factory->NewFixedArray(module->regular_exports()->length().value());
DirectHandle<FixedArray> regular_imports =
factory->NewFixedArray(module->regular_imports()->length().value());
DirectHandle<FixedArray> requested_modules =
factory->NewFixedArray(module->requested_modules()->length().value());
DisallowGarbageCollection no_gc;
Tagged<SourceTextModule> raw_module = *module;
if (raw_module->status() == kLinking) {
raw_module->set_code(Cast<JSFunction>(raw_module->code())->shared());
}
raw_module->set_regular_exports(*regular_exports);
raw_module->set_regular_imports(*regular_imports);
raw_module->set_requested_modules(*requested_modules);
raw_module->set_dfs_index(-1);
raw_module->set_dfs_ancestor_index(-1);
}
std::pair<DirectHandleVector<SourceTextModule>,
DirectHandleVector<JSMessageObject>>
SourceTextModule::GetStalledTopLevelAwaitMessages(Isolate* isolate) {
Zone zone(isolate->allocator(), ZONE_NAME);
UnorderedModuleSet visited(&zone);
DirectHandleVector<SourceTextModule> stalled_modules(isolate);
DirectHandleVector<JSMessageObject> messages(isolate);
InnerGetStalledTopLevelAwaitModule(isolate, &visited, &stalled_modules);
size_t stalled_modules_size = stalled_modules.size();
if (stalled_modules_size == 0) return {stalled_modules, messages};
messages.reserve(stalled_modules_size);
for (DirectHandle<SourceTextModule> found : stalled_modules) {
CHECK(IsJSGeneratorObject(found->code()));
DirectHandle<JSGeneratorObject> code(Cast<JSGeneratorObject>(found->code()),
isolate);
Handle<SharedFunctionInfo> shared(found->GetSharedFunctionInfo(), isolate);
Handle<Object> script(shared->script(), isolate);
MessageLocation location =
MessageLocation(Cast<Script>(script), shared, code->code_offset());
DirectHandle<JSMessageObject> message = MessageHandler::MakeMessageObject(
isolate, MessageTemplate::kTopLevelAwaitStalled, &location,
isolate->factory()->null_value());
messages.push_back(message);
}
return {stalled_modules, messages};
}
void SourceTextModule::InnerGetStalledTopLevelAwaitModule(
Isolate* isolate, UnorderedModuleSet* visited,
DirectHandleVector<SourceTextModule>* result) {
DisallowGarbageCollection no_gc;
// If it's a module that is waiting for no other modules but itself,
// it's what we are looking for. Add it to the results.
if (!HasPendingAsyncDependencies() && HasAsyncEvaluationOrdinal()) {
DCHECK(HasAsyncEvaluationOrdinal());
result->push_back(direct_handle(Tagged<SourceTextModule>(this), isolate));
return;
}
// The module isn't what we are looking for, continue looking in the graph.
Tagged<FixedArray> requests = info()->module_requests();
Tagged<FixedArray> requested = requested_modules();
const uint32_t length = requested->ulength().value();
for (uint32_t i = 0; i < length; ++i) {
Tagged<ModuleRequest> request = Cast<ModuleRequest>(requests->get(i));
if (request->phase() != ModuleImportPhase::kEvaluation) {
continue;
}
Tagged<Module> requested_module = Cast<Module>(requested->get(i));
if (IsSourceTextModule(requested_module) &&
visited->insert(handle(requested_module, isolate)).second) {
Tagged<SourceTextModule> source_text_module =
Cast<SourceTextModule>(requested_module);
source_text_module->InnerGetStalledTopLevelAwaitModule(isolate, visited,
result);
}
}
}
} // namespace internal
} // namespace v8