blob: e3c7a2d6cd5f0cf92ba8f945766ee0a5bcbb05fa [file] [log] [blame]
// 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/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()(Handle<String> string) const {
return string->Hash();
}
};
struct StringHandleEqual {
V8_INLINE bool operator()(Handle<String> lhs, Handle<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_;
};
SharedFunctionInfo SourceTextModule::GetSharedFunctionInfo() const {
DisallowHeapAllocation no_alloc;
switch (status()) {
case kUninstantiated:
case kPreInstantiating:
DCHECK(code().IsSharedFunctionInfo());
return SharedFunctionInfo::cast(code());
case kInstantiating:
DCHECK(code().IsJSFunction());
return JSFunction::cast(code()).shared();
case kInstantiated:
case kEvaluating:
case kEvaluated:
DCHECK(code().IsJSGeneratorObject());
return JSGeneratorObject::cast(code()).function().shared();
case kErrored:
UNREACHABLE();
}
UNREACHABLE();
}
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, Handle<SourceTextModule> module, Handle<String> name,
Handle<SourceTextModuleInfoEntry> entry) {
Handle<ObjectHashTable> exports(module->exports(), isolate);
DCHECK(exports->Lookup(name).IsTheHole(isolate));
exports = ObjectHashTable::Put(exports, name, entry);
module->set_exports(*exports);
}
void SourceTextModule::CreateExport(Isolate* isolate,
Handle<SourceTextModule> module,
int cell_index, Handle<FixedArray> names) {
DCHECK_LT(0, names->length());
Handle<Cell> cell =
isolate->factory()->NewCell(isolate->factory()->undefined_value());
module->regular_exports().set(ExportIndex(cell_index), *cell);
Handle<ObjectHashTable> exports(module->exports(), isolate);
for (int i = 0, n = names->length(); i < n; ++i) {
Handle<String> name(String::cast(names->get(i)), isolate);
DCHECK(exports->Lookup(name).IsTheHole(isolate));
exports = ObjectHashTable::Put(exports, name, cell);
}
module->set_exports(*exports);
}
Cell SourceTextModule::GetCell(int cell_index) {
DisallowHeapAllocation no_gc;
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();
break;
}
return Cell::cast(cell);
}
Handle<Object> SourceTextModule::LoadVariable(Isolate* isolate,
Handle<SourceTextModule> module,
int cell_index) {
return handle(module->GetCell(cell_index).value(), isolate);
}
void SourceTextModule::StoreVariable(Handle<SourceTextModule> module,
int cell_index, Handle<Object> value) {
DisallowHeapAllocation 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,
Handle<String> module_specifier, Handle<String> export_name,
MessageLocation loc, bool must_resolve, Module::ResolveSet* resolve_set) {
Handle<Object> object(module->exports().Lookup(export_name), isolate);
if (object->IsCell()) {
// Already resolved (e.g. because it's a local export).
return Handle<Cell>::cast(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) {
return isolate->ThrowAt<Cell>(
isolate->factory()->NewSyntaxError(
MessageTemplate::kCyclicModuleDependency, export_name,
module_specifier),
&loc);
}
return MaybeHandle<Cell>();
}
name_set->insert(export_name);
}
if (object->IsSourceTextModuleInfoEntry()) {
// Not yet resolved indirect export.
Handle<SourceTextModuleInfoEntry> entry =
Handle<SourceTextModuleInfoEntry>::cast(object);
Handle<String> import_name(String::cast(entry->import_name()), isolate);
Handle<Script> script(module->script(), 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, true, resolve_set)
.ToHandle(&cell)) {
DCHECK(isolate->has_pending_exception());
return MaybeHandle<Cell>();
}
// The export table may have changed but the entry in question should be
// unchanged.
Handle<ObjectHashTable> exports(module->exports(), isolate);
DCHECK(exports->Lookup(export_name).IsSourceTextModuleInfoEntry());
exports = ObjectHashTable::Put(exports, export_name, cell);
module->set_exports(*exports);
return cell;
}
DCHECK(object->IsTheHole(isolate));
return SourceTextModule::ResolveExportUsingStarExports(
isolate, module, module_specifier, export_name, loc, must_resolve,
resolve_set);
}
MaybeHandle<Cell> SourceTextModule::ResolveImport(
Isolate* isolate, Handle<SourceTextModule> module, Handle<String> name,
int module_request_index, MessageLocation loc, bool must_resolve,
Module::ResolveSet* resolve_set) {
Handle<Module> requested_module(
Module::cast(module->requested_modules().get(module_request_index)),
isolate);
Handle<ModuleRequest> module_request(
ModuleRequest::cast(
module->info().module_requests().get(module_request_index)),
isolate);
Handle<String> module_specifier(String::cast(module_request->specifier()),
isolate);
MaybeHandle<Cell> result =
Module::ResolveExport(isolate, requested_module, module_specifier, name,
loc, must_resolve, resolve_set);
DCHECK_IMPLIES(isolate->has_pending_exception(), result.is_null());
return result;
}
MaybeHandle<Cell> SourceTextModule::ResolveExportUsingStarExports(
Isolate* isolate, Handle<SourceTextModule> module,
Handle<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;
Handle<FixedArray> special_exports(module->info().special_exports(),
isolate);
for (int i = 0, n = special_exports->length(); i < n; ++i) {
i::Handle<i::SourceTextModuleInfoEntry> entry(
i::SourceTextModuleInfoEntry::cast(special_exports->get(i)), isolate);
if (!entry->export_name().IsUndefined(isolate)) {
continue; // Indirect export.
}
Handle<Script> script(module->script(), 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) {
return isolate->ThrowAt<Cell>(isolate->factory()->NewSyntaxError(
MessageTemplate::kAmbiguousExport,
module_specifier, export_name),
&loc);
}
} else if (isolate->has_pending_exception()) {
return MaybeHandle<Cell>();
}
}
if (!unique_cell.is_null()) {
// Found a unique star export for this name.
Handle<ObjectHashTable> exports(module->exports(), isolate);
DCHECK(exports->Lookup(export_name).IsTheHole(isolate));
exports = ObjectHashTable::Put(exports, export_name, unique_cell);
module->set_exports(*exports);
return unique_cell;
}
}
// Unresolvable.
if (must_resolve) {
return isolate->ThrowAt<Cell>(
isolate->factory()->NewSyntaxError(MessageTemplate::kUnresolvableExport,
module_specifier, export_name),
&loc);
}
return MaybeHandle<Cell>();
}
bool SourceTextModule::PrepareInstantiate(
Isolate* isolate, Handle<SourceTextModule> module,
v8::Local<v8::Context> context, v8::Module::ResolveCallback callback) {
// Obtain requested modules.
Handle<SourceTextModuleInfo> module_info(module->info(), isolate);
Handle<FixedArray> module_requests(module_info->module_requests(), isolate);
Handle<FixedArray> requested_modules(module->requested_modules(), isolate);
for (int i = 0, length = module_requests->length(); i < length; ++i) {
Handle<ModuleRequest> module_request(
ModuleRequest::cast(module_requests->get(i)), isolate);
Handle<String> specifier(module_request->specifier(), isolate);
// TODO(v8:10958) Pass import assertions to the callback
v8::Local<v8::Module> api_requested_module;
if (!callback(context, v8::Utils::ToLocal(specifier),
v8::Utils::ToLocal(Handle<Module>::cast(module)))
.ToLocal(&api_requested_module)) {
isolate->PromoteScheduledException();
return false;
}
Handle<Module> requested_module = Utils::OpenHandle(*api_requested_module);
requested_modules->set(i, *requested_module);
}
// Recurse.
for (int i = 0, length = requested_modules->length(); i < length; ++i) {
Handle<Module> requested_module(Module::cast(requested_modules->get(i)),
isolate);
if (!Module::PrepareInstantiate(isolate, requested_module, context,
callback)) {
return false;
}
}
// Set up local exports.
// TODO(neis): Create regular_exports array here instead of in factory method?
for (int i = 0, n = module_info->RegularExportCount(); i < n; ++i) {
int cell_index = module_info->RegularExportCellIndex(i);
Handle<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).
Handle<FixedArray> special_exports(module_info->special_exports(), isolate);
for (int i = 0, n = special_exports->length(); i < n; ++i) {
Handle<SourceTextModuleInfoEntry> entry(
SourceTextModuleInfoEntry::cast(special_exports->get(i)), isolate);
Handle<Object> export_name(entry->export_name(), isolate);
if (export_name->IsUndefined(isolate)) continue; // Star export.
CreateIndirectExport(isolate, module, Handle<String>::cast(export_name),
entry);
}
DCHECK_EQ(module->status(), kPreInstantiating);
return true;
}
bool SourceTextModule::RunInitializationCode(Isolate* isolate,
Handle<SourceTextModule> module) {
DCHECK_EQ(module->status(), kInstantiating);
Handle<JSFunction> function(JSFunction::cast(module->code()), isolate);
DCHECK_EQ(MODULE_SCOPE, function->shared().scope_info().scope_type());
Handle<Object> receiver = isolate->factory()->undefined_value();
Handle<ScopeInfo> scope_info(function->shared().scope_info(), isolate);
Handle<Context> context = isolate->factory()->NewModuleContext(
module, isolate->native_context(), scope_info);
function->set_context(*context);
MaybeHandle<Object> maybe_generator =
Execution::Call(isolate, function, receiver, 0, {});
Handle<Object> generator;
if (!maybe_generator.ToHandle(&generator)) {
DCHECK(isolate->has_pending_exception());
return false;
}
DCHECK_EQ(*function, Handle<JSGeneratorObject>::cast(generator)->function());
module->set_code(JSGeneratorObject::cast(*generator));
return true;
}
bool SourceTextModule::MaybeTransitionComponent(
Isolate* isolate, Handle<SourceTextModule> module,
ZoneForwardList<Handle<SourceTextModule>>* stack, Status new_status) {
DCHECK(new_status == kInstantiated || new_status == kEvaluated);
SLOW_DCHECK(
// {module} is on the {stack}.
std::count_if(stack->begin(), stack->end(),
[&](Handle<Module> m) { return *m == *module; }) == 1);
DCHECK_LE(module->dfs_ancestor_index(), module->dfs_index());
if (module->dfs_ancestor_index() == module->dfs_index()) {
// This is the root of its strongly connected component.
Handle<SourceTextModule> ancestor;
do {
ancestor = stack->front();
stack->pop_front();
DCHECK_EQ(ancestor->status(),
new_status == kInstantiated ? kInstantiating : kEvaluating);
if (new_status == kInstantiated) {
if (!SourceTextModule::RunInitializationCode(isolate, ancestor))
return false;
}
ancestor->SetStatus(new_status);
} while (*ancestor != *module);
}
return true;
}
bool SourceTextModule::FinishInstantiate(
Isolate* isolate, Handle<SourceTextModule> module,
ZoneForwardList<Handle<SourceTextModule>>* stack, unsigned* dfs_index,
Zone* zone) {
// Instantiate SharedFunctionInfo and mark module as instantiating for
// the recursion.
Handle<SharedFunctionInfo> shared(SharedFunctionInfo::cast(module->code()),
isolate);
Handle<JSFunction> function =
isolate->factory()->NewFunctionFromSharedFunctionInfo(
shared, isolate->native_context());
module->set_code(*function);
module->SetStatus(kInstantiating);
module->set_dfs_index(*dfs_index);
module->set_dfs_ancestor_index(*dfs_index);
stack->push_front(module);
(*dfs_index)++;
// Recurse.
Handle<FixedArray> requested_modules(module->requested_modules(), isolate);
for (int i = 0, length = requested_modules->length(); i < length; ++i) {
Handle<Module> requested_module(Module::cast(requested_modules->get(i)),
isolate);
if (!Module::FinishInstantiate(isolate, requested_module, stack, dfs_index,
zone)) {
return false;
}
DCHECK_NE(requested_module->status(), kEvaluating);
DCHECK_GE(requested_module->status(), kInstantiating);
SLOW_DCHECK(
// {requested_module} is instantiating iff it's on the {stack}.
(requested_module->status() == kInstantiating) ==
std::count_if(stack->begin(), stack->end(), [&](Handle<Module> m) {
return *m == *requested_module;
}));
if (requested_module->status() == kInstantiating) {
// SyntheticModules go straight to kInstantiated so this must be a
// SourceTextModule
module->set_dfs_ancestor_index(std::min(
module->dfs_ancestor_index(),
SourceTextModule::cast(*requested_module).dfs_ancestor_index()));
}
}
Handle<Script> script(module->script(), isolate);
Handle<SourceTextModuleInfo> module_info(module->info(), isolate);
// Resolve imports.
Handle<FixedArray> regular_imports(module_info->regular_imports(), isolate);
for (int i = 0, n = regular_imports->length(); i < n; ++i) {
Handle<SourceTextModuleInfoEntry> entry(
SourceTextModuleInfoEntry::cast(regular_imports->get(i)), isolate);
Handle<String> name(String::cast(entry->import_name()), isolate);
MessageLocation loc(script, entry->beg_pos(), entry->end_pos());
ResolveSet resolve_set(zone);
Handle<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.
Handle<FixedArray> special_exports(module_info->special_exports(), isolate);
for (int i = 0, n = special_exports->length(); i < n; ++i) {
Handle<SourceTextModuleInfoEntry> entry(
SourceTextModuleInfoEntry::cast(special_exports->get(i)), isolate);
Handle<Object> name(entry->export_name(), isolate);
if (name->IsUndefined(isolate)) continue; // Star export.
MessageLocation loc(script, entry->beg_pos(), entry->end_pos());
ResolveSet resolve_set(zone);
if (ResolveExport(isolate, module, Handle<String>(),
Handle<String>::cast(name), loc, true, &resolve_set)
.is_null()) {
return false;
}
}
return MaybeTransitionComponent(isolate, module, stack, kInstantiated);
}
void SourceTextModule::FetchStarExports(Isolate* isolate,
Handle<SourceTextModule> module,
Zone* zone,
UnorderedModuleSet* visited) {
DCHECK_GE(module->status(), Module::kInstantiating);
if (module->module_namespace().IsJSModuleNamespace()) return; // Shortcut.
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);
Handle<FixedArray> special_exports(module->info().special_exports(), isolate);
for (int i = 0, n = special_exports->length(); i < n; ++i) {
Handle<SourceTextModuleInfoEntry> entry(
SourceTextModuleInfoEntry::cast(special_exports->get(i)), isolate);
if (!entry->export_name().IsUndefined(roots)) {
continue; // Indirect export.
}
Handle<Module> requested_module(
Module::cast(module->requested_modules().get(entry->module_request())),
isolate);
// Recurse.
if (requested_module->IsSourceTextModule())
FetchStarExports(isolate,
Handle<SourceTextModule>::cast(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.
Handle<ObjectHashTable> requested_exports(requested_module->exports(),
isolate);
for (InternalIndex i : requested_exports->IterateEntries()) {
Object key;
if (!requested_exports->ToKey(roots, i, &key)) continue;
Handle<String> name(String::cast(key), isolate);
if (name->Equals(roots.default_string())) continue;
if (!exports->Lookup(name).IsTheHole(roots)) continue;
Handle<Cell> cell(Cell::cast(requested_exports->ValueAt(i)), 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 || it->second->IsUndefined(roots)) {
// 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(it->second->IsCell());
// Different star exports provide different cells for this name, hence
// mark the name as ambiguous.
it->second = roots.undefined_value_handle();
}
}
}
}
// Copy [more_exports] into [exports].
for (const auto& elem : more_exports) {
if (elem.second->IsUndefined(isolate)) continue; // Ambiguous export.
DCHECK(!elem.first->Equals(ReadOnlyRoots(isolate).default_string()));
DCHECK(elem.second->IsCell());
exports = ObjectHashTable::Put(exports, elem.first, elem.second);
}
module->set_exports(*exports);
}
Handle<JSModuleNamespace> SourceTextModule::GetModuleNamespace(
Isolate* isolate, Handle<SourceTextModule> module, int module_request) {
Handle<Module> requested_module(
Module::cast(module->requested_modules().get(module_request)), isolate);
return Module::GetModuleNamespace(isolate, requested_module);
}
MaybeHandle<JSObject> SourceTextModule::GetImportMeta(
Isolate* isolate, Handle<SourceTextModule> module) {
Handle<HeapObject> import_meta(module->import_meta(), isolate);
if (import_meta->IsTheHole(isolate)) {
if (!isolate->RunHostInitializeImportMetaObjectCallback(module).ToHandle(
&import_meta)) {
return {};
}
module->set_import_meta(*import_meta);
}
return Handle<JSObject>::cast(import_meta);
}
MaybeHandle<Object> SourceTextModule::EvaluateMaybeAsync(
Isolate* isolate, Handle<SourceTextModule> module) {
// In the event of errored evaluation, return a rejected promise.
if (module->status() == kErrored) {
// If we have a top level capability we assume it has already been
// rejected, and return it here. Otherwise create a new promise and
// reject it with the module's exception.
if (module->top_level_capability().IsJSPromise()) {
Handle<JSPromise> top_level_capability(
JSPromise::cast(module->top_level_capability()), isolate);
DCHECK(top_level_capability->status() == Promise::kRejected &&
top_level_capability->result() == module->exception());
return top_level_capability;
}
Handle<JSPromise> capability = isolate->factory()->NewJSPromise();
JSPromise::Reject(capability, handle(module->exception(), isolate));
return capability;
}
// Start of Evaluate () Concrete Method
// 2. Assert: module.[[Status]] is "linked" or "evaluated".
CHECK(module->status() == kInstantiated || module->status() == kEvaluated);
// 3. If module.[[Status]] is "evaluated", set module to
// GetAsyncCycleRoot(module).
if (module->status() == kEvaluated) {
module = GetAsyncCycleRoot(isolate, module);
}
// 4. If module.[[TopLevelCapability]] is not undefined, then
// a. Return module.[[TopLevelCapability]].[[Promise]].
if (module->top_level_capability().IsJSPromise()) {
return handle(JSPromise::cast(module->top_level_capability()), isolate);
}
DCHECK(module->top_level_capability().IsUndefined());
// 6. Let capability be ! NewPromiseCapability(%Promise%).
Handle<JSPromise> capability = isolate->factory()->NewJSPromise();
// 7. Set module.[[TopLevelCapability]] to capability.
module->set_top_level_capability(*capability);
DCHECK(module->top_level_capability().IsJSPromise());
// 9. If result is an abrupt completion, then
Handle<Object> unused_result;
if (!Evaluate(isolate, module).ToHandle(&unused_result)) {
// 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`.
if (!isolate->is_catchable_by_javascript(isolate->pending_exception())) {
DCHECK_EQ(module->status(), kErrored);
DCHECK_EQ(module->exception(), *isolate->factory()->null_value());
return {};
}
// d. Perform ! Call(capability.[[Reject]], undefined,
// «result.[[Value]]»).
isolate->clear_pending_exception();
JSPromise::Reject(capability, handle(module->exception(), isolate));
} else {
// 10. Otherwise,
// a. Assert: module.[[Status]] is "evaluated"...
CHECK_EQ(module->status(), kEvaluated);
// b. If module.[[AsyncEvaluating]] is false, then
if (!module->async_evaluating()) {
// i. Perform ! Call(capability.[[Resolve]], undefined,
// «undefined»).
JSPromise::Resolve(capability, isolate->factory()->undefined_value())
.ToHandleChecked();
}
}
// 11. Return capability.[[Promise]].
return capability;
}
MaybeHandle<Object> SourceTextModule::Evaluate(
Isolate* isolate, Handle<SourceTextModule> module) {
// Evaluate () Concrete Method continued from EvaluateMaybeAsync.
CHECK(module->status() == kInstantiated || module->status() == kEvaluated);
// 5. Let stack be a new empty List.
Zone zone(isolate->allocator(), ZONE_NAME);
ZoneForwardList<Handle<SourceTextModule>> stack(&zone);
unsigned dfs_index = 0;
// 8. Let result be InnerModuleEvaluation(module, stack, 0).
// 9. If result is an abrupt completion, then
Handle<Object> result;
if (!InnerModuleEvaluation(isolate, module, &stack, &dfs_index)
.ToHandle(&result)) {
// a. For each Cyclic Module Record m in stack, do
for (auto& 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.
Module::RecordErrorUsingPendingException(isolate, descendant);
}
#ifdef DEBUG
if (isolate->is_catchable_by_javascript(isolate->pending_exception())) {
CHECK_EQ(module->exception(), isolate->pending_exception());
} else {
CHECK_EQ(module->exception(), *isolate->factory()->null_value());
}
#endif // DEBUG
} else {
// 10. Otherwise,
// c. Assert: stack is empty.
DCHECK(stack.empty());
}
return result;
}
void SourceTextModule::AsyncModuleExecutionFulfilled(
Isolate* isolate, Handle<SourceTextModule> module) {
// 1. Assert: module.[[Status]] is "evaluated".
CHECK(module->status() == kEvaluated || module->status() == kErrored);
// 2. If module.[[AsyncEvaluating]] is false,
if (!module->async_evaluating()) {
// a. Assert: module.[[EvaluationError]] is not undefined.
CHECK_EQ(module->status(), kErrored);
// b. Return undefined.
return;
}
// 3. Assert: module.[[EvaluationError]] is undefined.
CHECK_EQ(module->status(), kEvaluated);
// 4. Set module.[[AsyncEvaluating]] to false.
module->set_async_evaluating(false);
// 5. For each Module m of module.[[AsyncParentModules]], do
for (int i = 0; i < module->AsyncParentModuleCount(); i++) {
Handle<SourceTextModule> m = module->GetAsyncParentModule(isolate, i);
// a. If module.[[DFSIndex]] is not equal to module.[[DFSAncestorIndex]],
// then
if (module->dfs_index() != module->dfs_ancestor_index()) {
// i. Assert: m.[[DFSAncestorIndex]] is equal to
// module.[[DFSAncestorIndex]].
DCHECK_LE(m->dfs_ancestor_index(), module->dfs_ancestor_index());
}
// b. Decrement m.[[PendingAsyncDependencies]] by 1.
m->DecrementPendingAsyncDependencies();
// c. If m.[[PendingAsyncDependencies]] is 0 and m.[[EvaluationError]] is
// undefined, then
if (!m->HasPendingAsyncDependencies() && m->status() == kEvaluated) {
// i. Assert: m.[[AsyncEvaluating]] is true.
DCHECK(m->async_evaluating());
// ii. Let cycleRoot be ! GetAsyncCycleRoot(m).
auto cycle_root = GetAsyncCycleRoot(isolate, m);
// iii. If cycleRoot.[[EvaluationError]] is not undefined,
// return undefined.
if (cycle_root->status() == kErrored) {
return;
}
// iv. If m.[[Async]] is true, then
if (m->async()) {
// 1. Perform ! ExecuteAsyncModule(m).
ExecuteAsyncModule(isolate, m);
} else {
// v. Otherwise,
// 1. Let result be m.ExecuteModule().
// 2. If result is a normal completion,
Handle<Object> unused_result;
if (ExecuteModule(isolate, m).ToHandle(&unused_result)) {
// a. Perform ! AsyncModuleExecutionFulfilled(m).
AsyncModuleExecutionFulfilled(isolate, m);
} else {
// 3. Otherwise,
// a. Perform ! AsyncModuleExecutionRejected(m,
// result.[[Value]]).
Handle<Object> exception(isolate->pending_exception(), isolate);
isolate->clear_pending_exception();
AsyncModuleExecutionRejected(isolate, m, exception);
}
}
}
}
// 6. If module.[[TopLevelCapability]] is not undefined, then
if (!module->top_level_capability().IsUndefined(isolate)) {
// a. Assert: module.[[DFSIndex]] is equal to module.[[DFSAncestorIndex]].
DCHECK_EQ(module->dfs_index(), module->dfs_ancestor_index());
// b. Perform ! Call(module.[[TopLevelCapability]].[[Resolve]],
// undefined, «undefined»).
Handle<JSPromise> capability(
JSPromise::cast(module->top_level_capability()), isolate);
JSPromise::Resolve(capability, isolate->factory()->undefined_value())
.ToHandleChecked();
}
// 7. Return undefined.
}
void SourceTextModule::AsyncModuleExecutionRejected(
Isolate* isolate, Handle<SourceTextModule> module,
Handle<Object> exception) {
DCHECK(isolate->is_catchable_by_javascript(*exception));
// 1. Assert: module.[[Status]] is "evaluated".
CHECK(module->status() == kEvaluated || module->status() == kErrored);
// 2. If module.[[AsyncEvaluating]] is false,
if (!module->async_evaluating()) {
// a. Assert: module.[[EvaluationError]] is not undefined.
CHECK_EQ(module->status(), kErrored);
// b. Return undefined.
return;
}
// 4. Set module.[[EvaluationError]] to ThrowCompletion(error).
Module::RecordError(isolate, module, exception);
// 5. Set module.[[AsyncEvaluating]] to false.
module->set_async_evaluating(false);
// 6. For each Module m of module.[[AsyncParentModules]], do
for (int i = 0; i < module->AsyncParentModuleCount(); i++) {
Handle<SourceTextModule> m = module->GetAsyncParentModule(isolate, i);
// a. If module.[[DFSIndex]] is not equal to module.[[DFSAncestorIndex]],
// then
if (module->dfs_index() != module->dfs_ancestor_index()) {
// i. Assert: m.[[DFSAncestorIndex]] is equal to
// module.[[DFSAncestorIndex]].
DCHECK_EQ(m->dfs_ancestor_index(), module->dfs_ancestor_index());
}
// b. Perform ! AsyncModuleExecutionRejected(m, error).
AsyncModuleExecutionRejected(isolate, m, exception);
}
// 7. If module.[[TopLevelCapability]] is not undefined, then
if (!module->top_level_capability().IsUndefined(isolate)) {
// a. Assert: module.[[DFSIndex]] is equal to module.[[DFSAncestorIndex]].
DCHECK(module->dfs_index() == module->dfs_ancestor_index());
// b. Perform ! Call(module.[[TopLevelCapability]].[[Reject]],
// undefined, «error»).
Handle<JSPromise> capability(
JSPromise::cast(module->top_level_capability()), isolate);
JSPromise::Reject(capability, exception);
}
// 8. Return undefined.
}
void SourceTextModule::ExecuteAsyncModule(Isolate* isolate,
Handle<SourceTextModule> module) {
// 1. Assert: module.[[Status]] is "evaluating" or "evaluated".
CHECK(module->status() == kEvaluating || module->status() == kEvaluated);
// 2. Assert: module.[[Async]] is true.
DCHECK(module->async());
// 3. Set module.[[AsyncEvaluating]] to true.
module->set_async_evaluating(true);
// 4. Let capability be ! NewPromiseCapability(%Promise%).
Handle<JSPromise> capability = isolate->factory()->NewJSPromise();
// 5. Let stepsFulfilled be the steps of a CallAsyncModuleFulfilled
Handle<JSFunction> steps_fulfilled(
isolate->native_context()->call_async_module_fulfilled(), isolate);
ScopedVector<Handle<Object>> empty_argv(0);
// 6. Let onFulfilled be CreateBuiltinFunction(stepsFulfilled,
// «[[Module]]»).
// 7. Set onFulfilled.[[Module]] to module.
Handle<JSBoundFunction> on_fulfilled =
isolate->factory()
->NewJSBoundFunction(steps_fulfilled, module, empty_argv)
.ToHandleChecked();
// 8. Let stepsRejected be the steps of a CallAsyncModuleRejected.
Handle<JSFunction> steps_rejected(
isolate->native_context()->call_async_module_rejected(), isolate);
// 9. Let onRejected be CreateBuiltinFunction(stepsRejected, «[[Module]]»).
// 10. Set onRejected.[[Module]] to module.
Handle<JSBoundFunction> on_rejected =
isolate->factory()
->NewJSBoundFunction(steps_rejected, module, empty_argv)
.ToHandleChecked();
// 11. Perform ! PerformPromiseThen(capability.[[Promise]],
// onFulfilled, onRejected).
Handle<Object> argv[] = {on_fulfilled, on_rejected};
Execution::CallBuiltin(isolate, isolate->promise_then(), capability,
arraysize(argv), argv)
.ToHandleChecked();
// 12. Perform ! module.ExecuteModule(capability).
// Note: In V8 we have broken module.ExecuteModule into
// ExecuteModule for synchronous module execution and
// InnerExecuteAsyncModule for asynchronous execution.
InnerExecuteAsyncModule(isolate, module, capability).ToHandleChecked();
// 13. Return.
}
MaybeHandle<Object> SourceTextModule::InnerExecuteAsyncModule(
Isolate* isolate, Handle<SourceTextModule> module,
Handle<JSPromise> capability) {
// If we have an async module, then it has an associated
// JSAsyncFunctionObject, which we then evaluate with the passed in promise
// capability.
Handle<JSAsyncFunctionObject> async_function_object(
JSAsyncFunctionObject::cast(module->code()), isolate);
async_function_object->set_promise(*capability);
Handle<JSFunction> resume(
isolate->native_context()->async_module_evaluate_internal(), isolate);
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
Execution::TryCall(isolate, resume, async_function_object, 0, nullptr,
Execution::MessageHandling::kKeepPending, nullptr,
false),
Object);
return result;
}
MaybeHandle<Object> SourceTextModule::ExecuteModule(
Isolate* isolate, Handle<SourceTextModule> module) {
// Synchronous modules have an associated JSGeneratorObject.
Handle<JSGeneratorObject> generator(JSGeneratorObject::cast(module->code()),
isolate);
Handle<JSFunction> resume(
isolate->native_context()->generator_next_internal(), isolate);
Handle<Object> result;
// With top_level_await, we need to catch any exceptions and reject
// the top level capability.
if (FLAG_harmony_top_level_await) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
Execution::TryCall(isolate, resume, generator, 0, nullptr,
Execution::MessageHandling::kKeepPending, nullptr,
false),
Object);
} else {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
Execution::Call(isolate, resume, generator, 0, nullptr), Object);
}
DCHECK(JSIteratorResult::cast(*result).done().BooleanValue(isolate));
return handle(JSIteratorResult::cast(*result).value(), isolate);
}
MaybeHandle<Object> SourceTextModule::InnerModuleEvaluation(
Isolate* isolate, Handle<SourceTextModule> module,
ZoneForwardList<Handle<SourceTextModule>>* stack, unsigned* dfs_index) {
STACK_CHECK(isolate, MaybeHandle<Object>());
// InnerModuleEvaluation(module, stack, index)
// 2. If module.[[Status]] is "evaluated", then
// a. If module.[[EvaluationError]] is undefined, return index.
// (We return undefined instead)
if (module->status() == kEvaluated || module->status() == kEvaluating) {
return isolate->factory()->undefined_value();
}
// b. Otherwise return module.[[EvaluationError]].
// (We throw on isolate and return a MaybeHandle<Object>
// instead)
if (module->status() == kErrored) {
isolate->Throw(module->exception());
return MaybeHandle<Object>();
}
// 4. Assert: module.[[Status]] is "linked".
CHECK_EQ(module->status(), kInstantiated);
// 5. Set module.[[Status]] to "evaluating".
module->SetStatus(kEvaluating);
// 6. Set module.[[DFSIndex]] to index.
module->set_dfs_index(*dfs_index);
// 7. Set module.[[DFSAncestorIndex]] to index.
module->set_dfs_ancestor_index(*dfs_index);
// 8. Set module.[[PendingAsyncDependencies]] to 0.
DCHECK(!module->HasPendingAsyncDependencies());
// 9. Set module.[[AsyncParentModules]] to a new empty List.
Handle<ArrayList> async_parent_modules = ArrayList::New(isolate, 0);
module->set_async_parent_modules(*async_parent_modules);
// 10. Set index to index + 1.
(*dfs_index)++;
// 11. Append module to stack.
stack->push_front(module);
// Recursion.
Handle<FixedArray> requested_modules(module->requested_modules(), isolate);
// 12. For each String required that is an element of
// module.[[RequestedModules]], do
for (int i = 0, length = requested_modules->length(); i < length; ++i) {
Handle<Module> requested_module(Module::cast(requested_modules->get(i)),
isolate);
// d. If requiredModule is a Cyclic Module Record, then
if (requested_module->IsSourceTextModule()) {
Handle<SourceTextModule> required_module(
SourceTextModule::cast(*requested_module), isolate);
RETURN_ON_EXCEPTION(
isolate,
InnerModuleEvaluation(isolate, required_module, stack, dfs_index),
Object);
// i. Assert: requiredModule.[[Status]] is either "evaluating" 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(), [&](Handle<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. Otherwise,
// 1. Set requiredModule to GetAsyncCycleRoot(requiredModule).
required_module = GetAsyncCycleRoot(isolate, required_module);
// 2. Assert: requiredModule.[[Status]] is "evaluated".
CHECK_GE(required_module->status(), kEvaluated);
// 3. If requiredModule.[[EvaluationError]] is not undefined,
// 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 MaybeHandle<Object>();
}
}
// v. If requiredModule.[[AsyncEvaluating]] is true, then
if (required_module->async_evaluating()) {
// 1. Set module.[[PendingAsyncDependencies]] to
// module.[[PendingAsyncDependencies]] + 1.
module->IncrementPendingAsyncDependencies();
// 2. Append module to requiredModule.[[AsyncParentModules]].
AddAsyncParentModule(isolate, required_module, module);
}
} else {
RETURN_ON_EXCEPTION(isolate, Module::Evaluate(isolate, requested_module),
Object);
}
}
// 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.
Handle<Object> result = isolate->factory()->undefined_value();
// 14. If module.[[PendingAsyncDependencies]] is > 0, set
// module.[[AsyncEvaluating]] to true.
if (module->HasPendingAsyncDependencies()) {
module->set_async_evaluating(true);
} else if (module->async()) {
// 15. Otherwise, if module.[[Async]] is true,
// perform ! ExecuteAsyncModule(module).
SourceTextModule::ExecuteAsyncModule(isolate, module);
} else {
// 16. Otherwise, perform ? module.ExecuteModule().
ASSIGN_RETURN_ON_EXCEPTION(isolate, result, ExecuteModule(isolate, module),
Object);
}
CHECK(MaybeTransitionComponent(isolate, module, stack, kEvaluated));
return result;
}
Handle<SourceTextModule> SourceTextModule::GetAsyncCycleRoot(
Isolate* isolate, Handle<SourceTextModule> module) {
// 1. Assert: module.[[Status]] is "evaluated".
CHECK_GE(module->status(), kEvaluated);
// 2. If module.[[AsyncParentModules]] is an empty List, return module.
if (module->AsyncParentModuleCount() == 0) {
return module;
}
// 3. Repeat, while module.[[DFSIndex]] is greater than
// module.[[DFSAncestorIndex]],
while (module->dfs_index() > module->dfs_ancestor_index()) {
// a. Assert: module.[[AsyncParentModules]] is a non-empty List.
DCHECK_GT(module->AsyncParentModuleCount(), 0);
// b. Let nextCycleModule be the first element of
// module.[[AsyncParentModules]].
Handle<SourceTextModule> next_cycle_module =
module->GetAsyncParentModule(isolate, 0);
// c. Assert: nextCycleModule.[[DFSAncestorIndex]] is less than or equal
// to module.[[DFSAncestorIndex]].
DCHECK_LE(next_cycle_module->dfs_ancestor_index(),
module->dfs_ancestor_index());
// d. Set module to nextCycleModule
module = next_cycle_module;
}
// 4. Assert: module.[[DFSIndex]] is equal to module.[[DFSAncestorIndex]].
DCHECK_EQ(module->dfs_index(), module->dfs_ancestor_index());
// 5. Return module.
return module;
}
void SourceTextModule::Reset(Isolate* isolate,
Handle<SourceTextModule> module) {
Factory* factory = isolate->factory();
DCHECK(module->import_meta().IsTheHole(isolate));
Handle<FixedArray> regular_exports =
factory->NewFixedArray(module->regular_exports().length());
Handle<FixedArray> regular_imports =
factory->NewFixedArray(module->regular_imports().length());
Handle<FixedArray> requested_modules =
factory->NewFixedArray(module->requested_modules().length());
if (module->status() == kInstantiating) {
module->set_code(JSFunction::cast(module->code()).shared());
}
module->set_regular_exports(*regular_exports);
module->set_regular_imports(*regular_imports);
module->set_requested_modules(*requested_modules);
module->set_dfs_index(-1);
module->set_dfs_ancestor_index(-1);
}
} // namespace internal
} // namespace v8