| // 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 |