// Copyright 2017 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 <unordered_map>
|
#include <unordered_set>
|
|
#include "src/objects/module.h"
|
|
#include "src/accessors.h"
|
#include "src/api-inl.h"
|
#include "src/ast/modules.h"
|
#include "src/objects-inl.h"
|
#include "src/objects/hash-table-inl.h"
|
#include "src/objects/js-generator-inl.h"
|
#include "src/objects/module-inl.h"
|
|
namespace v8 {
|
namespace internal {
|
|
namespace {
|
|
struct ModuleHandleHash {
|
V8_INLINE size_t operator()(Handle<Module> module) const {
|
return module->hash();
|
}
|
};
|
|
struct ModuleHandleEqual {
|
V8_INLINE bool operator()(Handle<Module> lhs, Handle<Module> rhs) const {
|
return *lhs == *rhs;
|
}
|
};
|
|
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 UnorderedModuleSet
|
: public std::unordered_set<Handle<Module>, ModuleHandleHash,
|
ModuleHandleEqual,
|
ZoneAllocator<Handle<Module>>> {
|
public:
|
explicit UnorderedModuleSet(Zone* zone)
|
: std::unordered_set<Handle<Module>, ModuleHandleHash, ModuleHandleEqual,
|
ZoneAllocator<Handle<Module>>>(
|
2 /* bucket count */, ModuleHandleHash(), ModuleHandleEqual(),
|
ZoneAllocator<Handle<Module>>(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)) {}
|
};
|
|
} // anonymous namespace
|
|
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_;
|
};
|
|
namespace {
|
|
int ExportIndex(int cell_index) {
|
DCHECK_EQ(ModuleDescriptor::GetCellIndexKind(cell_index),
|
ModuleDescriptor::kExport);
|
return cell_index - 1;
|
}
|
|
int ImportIndex(int cell_index) {
|
DCHECK_EQ(ModuleDescriptor::GetCellIndexKind(cell_index),
|
ModuleDescriptor::kImport);
|
return -cell_index - 1;
|
}
|
|
} // anonymous namespace
|
|
void Module::CreateIndirectExport(Isolate* isolate, Handle<Module> module,
|
Handle<String> name,
|
Handle<ModuleInfoEntry> entry) {
|
Handle<ObjectHashTable> exports(module->exports(), isolate);
|
DCHECK(exports->Lookup(name)->IsTheHole(isolate));
|
exports = ObjectHashTable::Put(exports, name, entry);
|
module->set_exports(*exports);
|
}
|
|
void Module::CreateExport(Isolate* isolate, Handle<Module> 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* Module::GetCell(int cell_index) {
|
DisallowHeapAllocation no_gc;
|
Object* cell;
|
switch (ModuleDescriptor::GetCellIndexKind(cell_index)) {
|
case ModuleDescriptor::kImport:
|
cell = regular_imports()->get(ImportIndex(cell_index));
|
break;
|
case ModuleDescriptor::kExport:
|
cell = regular_exports()->get(ExportIndex(cell_index));
|
break;
|
case ModuleDescriptor::kInvalid:
|
UNREACHABLE();
|
break;
|
}
|
return Cell::cast(cell);
|
}
|
|
Handle<Object> Module::LoadVariable(Isolate* isolate, Handle<Module> module,
|
int cell_index) {
|
return handle(module->GetCell(cell_index)->value(), isolate);
|
}
|
|
void Module::StoreVariable(Handle<Module> module, int cell_index,
|
Handle<Object> value) {
|
DCHECK_EQ(ModuleDescriptor::GetCellIndexKind(cell_index),
|
ModuleDescriptor::kExport);
|
module->GetCell(cell_index)->set_value(*value);
|
}
|
|
#ifdef DEBUG
|
void Module::PrintStatusTransition(Status new_status) {
|
if (FLAG_trace_module_status) {
|
StdoutStream os;
|
os << "Changing module status from " << status() << " to " << new_status
|
<< " for ";
|
script()->GetNameOrSourceURL()->Print(os);
|
#ifndef OBJECT_PRINT
|
os << "\n";
|
#endif // OBJECT_PRINT
|
}
|
}
|
#endif // DEBUG
|
|
void Module::SetStatus(Status new_status) {
|
DisallowHeapAllocation no_alloc;
|
DCHECK_LE(status(), new_status);
|
DCHECK_NE(new_status, Module::kErrored);
|
#ifdef DEBUG
|
PrintStatusTransition(new_status);
|
#endif // DEBUG
|
set_status(new_status);
|
}
|
|
void Module::ResetGraph(Isolate* isolate, Handle<Module> module) {
|
DCHECK_NE(module->status(), kInstantiating);
|
DCHECK_NE(module->status(), kEvaluating);
|
if (module->status() != kPreInstantiating) return;
|
Handle<FixedArray> requested_modules(module->requested_modules(), isolate);
|
Reset(isolate, module);
|
for (int i = 0; i < requested_modules->length(); ++i) {
|
Handle<Object> descendant(requested_modules->get(i), isolate);
|
if (descendant->IsModule()) {
|
ResetGraph(isolate, Handle<Module>::cast(descendant));
|
} else {
|
DCHECK(descendant->IsUndefined(isolate));
|
}
|
}
|
}
|
|
void Module::Reset(Isolate* isolate, Handle<Module> module) {
|
Factory* factory = isolate->factory();
|
|
DCHECK(module->status() == kPreInstantiating ||
|
module->status() == kInstantiating);
|
DCHECK(module->exception()->IsTheHole(isolate));
|
DCHECK(module->import_meta()->IsTheHole(isolate));
|
// The namespace object cannot exist, because it would have been created
|
// by RunInitializationCode, which is called only after this module's SCC
|
// succeeds instantiation.
|
DCHECK(!module->module_namespace()->IsJSModuleNamespace());
|
|
Handle<ObjectHashTable> exports =
|
ObjectHashTable::New(isolate, module->info()->RegularExportCount());
|
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());
|
}
|
#ifdef DEBUG
|
module->PrintStatusTransition(kUninstantiated);
|
#endif // DEBUG
|
module->set_status(kUninstantiated);
|
module->set_exports(*exports);
|
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);
|
}
|
|
void Module::RecordError(Isolate* isolate) {
|
DisallowHeapAllocation no_alloc;
|
DCHECK(exception()->IsTheHole(isolate));
|
Object* the_exception = isolate->pending_exception();
|
DCHECK(!the_exception->IsTheHole(isolate));
|
|
set_code(info());
|
#ifdef DEBUG
|
PrintStatusTransition(Module::kErrored);
|
#endif // DEBUG
|
set_status(Module::kErrored);
|
set_exception(the_exception);
|
}
|
|
Object* Module::GetException() {
|
DisallowHeapAllocation no_alloc;
|
DCHECK_EQ(status(), Module::kErrored);
|
DCHECK(!exception()->IsTheHole());
|
return exception();
|
}
|
|
SharedFunctionInfo* Module::GetSharedFunctionInfo() const {
|
DisallowHeapAllocation no_alloc;
|
DCHECK_NE(status(), Module::kEvaluating);
|
DCHECK_NE(status(), Module::kEvaluated);
|
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:
|
DCHECK(code()->IsJSGeneratorObject());
|
return JSGeneratorObject::cast(code())->function()->shared();
|
case kEvaluating:
|
case kEvaluated:
|
case kErrored:
|
UNREACHABLE();
|
}
|
|
UNREACHABLE();
|
}
|
|
MaybeHandle<Cell> Module::ResolveImport(Isolate* isolate, Handle<Module> module,
|
Handle<String> name, int module_request,
|
MessageLocation loc, bool must_resolve,
|
Module::ResolveSet* resolve_set) {
|
Handle<Module> requested_module(
|
Module::cast(module->requested_modules()->get(module_request)), isolate);
|
Handle<String> specifier(
|
String::cast(module->info()->module_requests()->get(module_request)),
|
isolate);
|
MaybeHandle<Cell> result =
|
Module::ResolveExport(isolate, requested_module, specifier, name, loc,
|
must_resolve, resolve_set);
|
DCHECK_IMPLIES(isolate->has_pending_exception(), result.is_null());
|
return result;
|
}
|
|
MaybeHandle<Cell> Module::ResolveExport(Isolate* isolate, Handle<Module> module,
|
Handle<String> module_specifier,
|
Handle<String> export_name,
|
MessageLocation loc, bool must_resolve,
|
Module::ResolveSet* resolve_set) {
|
DCHECK_GE(module->status(), kPreInstantiating);
|
DCHECK_NE(module->status(), kEvaluating);
|
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 =
|
new (zone->New(sizeof(UnorderedStringSet))) UnorderedStringSet(zone);
|
} else if (name_set->count(export_name)) {
|
// Cycle detected.
|
if (must_resolve) {
|
return isolate->Throw<Cell>(
|
isolate->factory()->NewSyntaxError(
|
MessageTemplate::kCyclicModuleDependency, export_name,
|
module_specifier),
|
&loc);
|
}
|
return MaybeHandle<Cell>();
|
}
|
name_set->insert(export_name);
|
}
|
|
if (object->IsModuleInfoEntry()) {
|
// Not yet resolved indirect export.
|
Handle<ModuleInfoEntry> entry = Handle<ModuleInfoEntry>::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)->IsModuleInfoEntry());
|
|
exports = ObjectHashTable::Put(exports, export_name, cell);
|
module->set_exports(*exports);
|
return cell;
|
}
|
|
DCHECK(object->IsTheHole(isolate));
|
return Module::ResolveExportUsingStarExports(isolate, module,
|
module_specifier, export_name,
|
loc, must_resolve, resolve_set);
|
}
|
|
MaybeHandle<Cell> Module::ResolveExportUsingStarExports(
|
Isolate* isolate, Handle<Module> 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::ModuleInfoEntry> entry(
|
i::ModuleInfoEntry::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->Throw<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->Throw<Cell>(
|
isolate->factory()->NewSyntaxError(MessageTemplate::kUnresolvableExport,
|
module_specifier, export_name),
|
&loc);
|
}
|
return MaybeHandle<Cell>();
|
}
|
|
bool Module::Instantiate(Isolate* isolate, Handle<Module> module,
|
v8::Local<v8::Context> context,
|
v8::Module::ResolveCallback callback) {
|
#ifdef DEBUG
|
if (FLAG_trace_module_status) {
|
StdoutStream os;
|
os << "Instantiating module ";
|
module->script()->GetNameOrSourceURL()->Print(os);
|
#ifndef OBJECT_PRINT
|
os << "\n";
|
#endif // OBJECT_PRINT
|
}
|
#endif // DEBUG
|
|
if (!PrepareInstantiate(isolate, module, context, callback)) {
|
ResetGraph(isolate, module);
|
return false;
|
}
|
Zone zone(isolate->allocator(), ZONE_NAME);
|
ZoneForwardList<Handle<Module>> stack(&zone);
|
unsigned dfs_index = 0;
|
if (!FinishInstantiate(isolate, module, &stack, &dfs_index, &zone)) {
|
for (auto& descendant : stack) {
|
Reset(isolate, descendant);
|
}
|
DCHECK_EQ(module->status(), kUninstantiated);
|
return false;
|
}
|
DCHECK(module->status() == kInstantiated || module->status() == kEvaluated ||
|
module->status() == kErrored);
|
DCHECK(stack.empty());
|
return true;
|
}
|
|
bool Module::PrepareInstantiate(Isolate* isolate, Handle<Module> module,
|
v8::Local<v8::Context> context,
|
v8::Module::ResolveCallback callback) {
|
DCHECK_NE(module->status(), kEvaluating);
|
DCHECK_NE(module->status(), kInstantiating);
|
if (module->status() >= kPreInstantiating) return true;
|
module->SetStatus(kPreInstantiating);
|
STACK_CHECK(isolate, false);
|
|
// Obtain requested modules.
|
Handle<ModuleInfo> 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<String> specifier(String::cast(module_requests->get(i)), isolate);
|
v8::Local<v8::Module> api_requested_module;
|
if (!callback(context, v8::Utils::ToLocal(specifier),
|
v8::Utils::ToLocal(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 (!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 ModuleInfoEntry there. When we later find the correct
|
// Cell in the module that actually provides the value, we replace the
|
// ModuleInfoEntry 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<ModuleInfoEntry> entry(
|
ModuleInfoEntry::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 Module::RunInitializationCode(Isolate* isolate, Handle<Module> 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<Object> argv[] = {module};
|
MaybeHandle<Object> maybe_generator =
|
Execution::Call(isolate, function, receiver, arraysize(argv), argv);
|
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(*generator);
|
return true;
|
}
|
|
bool Module::MaybeTransitionComponent(Isolate* isolate, Handle<Module> module,
|
ZoneForwardList<Handle<Module>>* 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<Module> ancestor;
|
do {
|
ancestor = stack->front();
|
stack->pop_front();
|
DCHECK_EQ(ancestor->status(),
|
new_status == kInstantiated ? kInstantiating : kEvaluating);
|
if (new_status == kInstantiated) {
|
if (!RunInitializationCode(isolate, ancestor)) return false;
|
}
|
ancestor->SetStatus(new_status);
|
} while (*ancestor != *module);
|
}
|
return true;
|
}
|
|
bool Module::FinishInstantiate(Isolate* isolate, Handle<Module> module,
|
ZoneForwardList<Handle<Module>>* stack,
|
unsigned* dfs_index, Zone* zone) {
|
DCHECK_NE(module->status(), kEvaluating);
|
if (module->status() >= kInstantiating) return true;
|
DCHECK_EQ(module->status(), kPreInstantiating);
|
STACK_CHECK(isolate, false);
|
|
// 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 (!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) {
|
module->set_dfs_ancestor_index(
|
std::min(module->dfs_ancestor_index(),
|
requested_module->dfs_ancestor_index()));
|
}
|
}
|
|
Handle<Script> script(module->script(), isolate);
|
Handle<ModuleInfo> 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<ModuleInfoEntry> entry(
|
ModuleInfoEntry::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<ModuleInfoEntry> entry(
|
ModuleInfoEntry::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);
|
}
|
|
MaybeHandle<Object> Module::Evaluate(Isolate* isolate, Handle<Module> module) {
|
#ifdef DEBUG
|
if (FLAG_trace_module_status) {
|
StdoutStream os;
|
os << "Evaluating module ";
|
module->script()->GetNameOrSourceURL()->Print(os);
|
#ifndef OBJECT_PRINT
|
os << "\n";
|
#endif // OBJECT_PRINT
|
}
|
#endif // DEBUG
|
if (module->status() == kErrored) {
|
isolate->Throw(module->GetException());
|
return MaybeHandle<Object>();
|
}
|
DCHECK_NE(module->status(), kEvaluating);
|
DCHECK_GE(module->status(), kInstantiated);
|
Zone zone(isolate->allocator(), ZONE_NAME);
|
|
ZoneForwardList<Handle<Module>> stack(&zone);
|
unsigned dfs_index = 0;
|
Handle<Object> result;
|
if (!Evaluate(isolate, module, &stack, &dfs_index).ToHandle(&result)) {
|
for (auto& descendant : stack) {
|
DCHECK_EQ(descendant->status(), kEvaluating);
|
descendant->RecordError(isolate);
|
}
|
DCHECK_EQ(module->GetException(), isolate->pending_exception());
|
return MaybeHandle<Object>();
|
}
|
DCHECK_EQ(module->status(), kEvaluated);
|
DCHECK(stack.empty());
|
return result;
|
}
|
|
MaybeHandle<Object> Module::Evaluate(Isolate* isolate, Handle<Module> module,
|
ZoneForwardList<Handle<Module>>* stack,
|
unsigned* dfs_index) {
|
if (module->status() == kErrored) {
|
isolate->Throw(module->GetException());
|
return MaybeHandle<Object>();
|
}
|
if (module->status() >= kEvaluating) {
|
return isolate->factory()->undefined_value();
|
}
|
DCHECK_EQ(module->status(), kInstantiated);
|
STACK_CHECK(isolate, MaybeHandle<Object>());
|
|
Handle<JSGeneratorObject> generator(JSGeneratorObject::cast(module->code()),
|
isolate);
|
module->set_code(
|
generator->function()->shared()->scope_info()->ModuleDescriptorInfo());
|
module->SetStatus(kEvaluating);
|
module->set_dfs_index(*dfs_index);
|
module->set_dfs_ancestor_index(*dfs_index);
|
stack->push_front(module);
|
(*dfs_index)++;
|
|
// Recursion.
|
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);
|
RETURN_ON_EXCEPTION(
|
isolate, Evaluate(isolate, requested_module, stack, dfs_index), Object);
|
|
DCHECK_GE(requested_module->status(), kEvaluating);
|
DCHECK_NE(requested_module->status(), kErrored);
|
SLOW_DCHECK(
|
// {requested_module} is evaluating iff it's on the {stack}.
|
(requested_module->status() == kEvaluating) ==
|
std::count_if(stack->begin(), stack->end(), [&](Handle<Module> m) {
|
return *m == *requested_module;
|
}));
|
|
if (requested_module->status() == kEvaluating) {
|
module->set_dfs_ancestor_index(
|
std::min(module->dfs_ancestor_index(),
|
requested_module->dfs_ancestor_index()));
|
}
|
}
|
|
// Evaluation of module body.
|
Handle<JSFunction> resume(
|
isolate->native_context()->generator_next_internal(), isolate);
|
Handle<Object> result;
|
ASSIGN_RETURN_ON_EXCEPTION(
|
isolate, result, Execution::Call(isolate, resume, generator, 0, nullptr),
|
Object);
|
DCHECK(static_cast<JSIteratorResult*>(JSObject::cast(*result))
|
->done()
|
->BooleanValue(isolate));
|
|
CHECK(MaybeTransitionComponent(isolate, module, stack, kEvaluated));
|
return handle(
|
static_cast<JSIteratorResult*>(JSObject::cast(*result))->value(),
|
isolate);
|
}
|
|
namespace {
|
|
void FetchStarExports(Isolate* isolate, Handle<Module> 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<ModuleInfoEntry> entry(
|
ModuleInfoEntry::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.
|
FetchStarExports(isolate, 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 (int i = 0, n = requested_exports->Capacity(); i < n; ++i) {
|
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);
|
}
|
|
} // anonymous namespace
|
|
Handle<JSModuleNamespace> Module::GetModuleNamespace(Isolate* isolate,
|
Handle<Module> module,
|
int module_request) {
|
Handle<Module> requested_module(
|
Module::cast(module->requested_modules()->get(module_request)), isolate);
|
return Module::GetModuleNamespace(isolate, requested_module);
|
}
|
|
Handle<JSModuleNamespace> Module::GetModuleNamespace(Isolate* isolate,
|
Handle<Module> module) {
|
Handle<HeapObject> object(module->module_namespace(), isolate);
|
ReadOnlyRoots roots(isolate);
|
if (!object->IsUndefined(roots)) {
|
// Namespace object already exists.
|
return Handle<JSModuleNamespace>::cast(object);
|
}
|
|
// Collect the export names.
|
Zone zone(isolate->allocator(), ZONE_NAME);
|
UnorderedModuleSet visited(&zone);
|
FetchStarExports(isolate, module, &zone, &visited);
|
Handle<ObjectHashTable> exports(module->exports(), isolate);
|
ZoneVector<Handle<String>> names(&zone);
|
names.reserve(exports->NumberOfElements());
|
for (int i = 0, n = exports->Capacity(); i < n; ++i) {
|
Object* key;
|
if (!exports->ToKey(roots, i, &key)) continue;
|
names.push_back(handle(String::cast(key), isolate));
|
}
|
DCHECK_EQ(static_cast<int>(names.size()), exports->NumberOfElements());
|
|
// Sort them alphabetically.
|
std::sort(names.begin(), names.end(),
|
[&isolate](Handle<String> a, Handle<String> b) {
|
return String::Compare(isolate, a, b) ==
|
ComparisonResult::kLessThan;
|
});
|
|
// Create the namespace object (initially empty).
|
Handle<JSModuleNamespace> ns = isolate->factory()->NewJSModuleNamespace();
|
ns->set_module(*module);
|
module->set_module_namespace(*ns);
|
|
// Create the properties in the namespace object. Transition the object
|
// to dictionary mode so that property addition is faster.
|
PropertyAttributes attr = DONT_DELETE;
|
JSObject::NormalizeProperties(ns, CLEAR_INOBJECT_PROPERTIES,
|
static_cast<int>(names.size()),
|
"JSModuleNamespace");
|
for (const auto& name : names) {
|
JSObject::SetNormalizedProperty(
|
ns, name, Accessors::MakeModuleNamespaceEntryInfo(isolate, name),
|
PropertyDetails(kAccessor, attr, PropertyCellType::kMutable));
|
}
|
JSObject::PreventExtensions(ns, kThrowOnError).ToChecked();
|
|
// Optimize the namespace object as a prototype, for two reasons:
|
// - The object's map is guaranteed not to be shared. ICs rely on this.
|
// - We can store a pointer from the map back to the namespace object.
|
// Turbofan can use this for inlining the access.
|
JSObject::OptimizeAsPrototype(ns);
|
|
Handle<PrototypeInfo> proto_info =
|
Map::GetOrCreatePrototypeInfo(Handle<JSObject>::cast(ns), isolate);
|
proto_info->set_module_namespace(*ns);
|
return ns;
|
}
|
|
MaybeHandle<Object> JSModuleNamespace::GetExport(Isolate* isolate,
|
Handle<String> name) {
|
Handle<Object> object(module()->exports()->Lookup(name), isolate);
|
if (object->IsTheHole(isolate)) {
|
return isolate->factory()->undefined_value();
|
}
|
|
Handle<Object> value(Handle<Cell>::cast(object)->value(), isolate);
|
if (value->IsTheHole(isolate)) {
|
THROW_NEW_ERROR(
|
isolate, NewReferenceError(MessageTemplate::kNotDefined, name), Object);
|
}
|
|
return value;
|
}
|
|
Maybe<PropertyAttributes> JSModuleNamespace::GetPropertyAttributes(
|
LookupIterator* it) {
|
Handle<JSModuleNamespace> object = it->GetHolder<JSModuleNamespace>();
|
Handle<String> name = Handle<String>::cast(it->GetName());
|
DCHECK_EQ(it->state(), LookupIterator::ACCESSOR);
|
|
Isolate* isolate = it->isolate();
|
|
Handle<Object> lookup(object->module()->exports()->Lookup(name), isolate);
|
if (lookup->IsTheHole(isolate)) {
|
return Just(ABSENT);
|
}
|
|
Handle<Object> value(Handle<Cell>::cast(lookup)->value(), isolate);
|
if (value->IsTheHole(isolate)) {
|
isolate->Throw(*isolate->factory()->NewReferenceError(
|
MessageTemplate::kNotDefined, name));
|
return Nothing<PropertyAttributes>();
|
}
|
|
return Just(it->property_attributes());
|
}
|
|
} // namespace internal
|
} // namespace v8
|