| // 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/api/api-inl.h" |
| #include "src/ast/modules.h" |
| #include "src/builtins/accessors.h" |
| #include "src/objects/cell-inl.h" |
| #include "src/objects/hash-table-inl.h" |
| #include "src/objects/js-generator-inl.h" |
| #include "src/objects/module-inl.h" |
| #include "src/objects/objects-inl.h" |
| #include "src/utils/ostreams.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| #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 "; |
| if (this->IsSourceTextModule()) { |
| Handle<Script> script(SourceTextModule::cast(*this).script(), |
| GetIsolate()); |
| script->GetNameOrSourceURL().Print(os); |
| } else { |
| SyntheticModule::cast(*this).name().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::RecordError(Isolate* isolate) { |
| DisallowHeapAllocation no_alloc; |
| DCHECK(exception().IsTheHole(isolate)); |
| Object the_exception = isolate->pending_exception(); |
| DCHECK(!the_exception.IsTheHole(isolate)); |
| |
| if (this->IsSourceTextModule()) { |
| Handle<SourceTextModule> self(SourceTextModule::cast(*this), GetIsolate()); |
| self->set_code(self->info()); |
| } |
| #ifdef DEBUG |
| PrintStatusTransition(Module::kErrored); |
| #endif // DEBUG |
| set_status(Module::kErrored); |
| set_exception(the_exception); |
| } |
| |
| 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->IsSourceTextModule() |
| ? Handle<FixedArray>( |
| Handle<SourceTextModule>::cast(module)->requested_modules(), |
| isolate) |
| : Handle<FixedArray>(); |
| Reset(isolate, module); |
| if (module->IsSourceTextModule()) { |
| 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)); |
| } |
| } |
| } else { |
| DCHECK(module->IsSyntheticModule()); |
| // Nothing else to do here. |
| } |
| } |
| |
| void Module::Reset(Isolate* isolate, Handle<Module> module) { |
| DCHECK(module->status() == kPreInstantiating || |
| module->status() == kInstantiating); |
| DCHECK(module->exception().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()); |
| |
| #ifdef DEBUG |
| module->PrintStatusTransition(kUninstantiated); |
| #endif // DEBUG |
| |
| int export_count; |
| |
| if (module->IsSourceTextModule()) { |
| Handle<SourceTextModule> source_text_module = |
| Handle<SourceTextModule>::cast(module); |
| export_count = source_text_module->regular_exports().length(); |
| SourceTextModule::Reset(isolate, source_text_module); |
| } else { |
| export_count = |
| Handle<SyntheticModule>::cast(module)->export_names().length(); |
| // Nothing to do here. |
| } |
| |
| Handle<ObjectHashTable> exports = ObjectHashTable::New(isolate, export_count); |
| |
| module->set_exports(*exports); |
| module->set_status(kUninstantiated); |
| } |
| |
| Object Module::GetException() { |
| DisallowHeapAllocation no_alloc; |
| DCHECK_EQ(status(), Module::kErrored); |
| DCHECK(!exception().IsTheHole()); |
| return exception(); |
| } |
| |
| 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); |
| |
| if (module->IsSourceTextModule()) { |
| return SourceTextModule::ResolveExport( |
| isolate, Handle<SourceTextModule>::cast(module), module_specifier, |
| export_name, loc, must_resolve, resolve_set); |
| } else { |
| return SyntheticModule::ResolveExport( |
| isolate, Handle<SyntheticModule>::cast(module), module_specifier, |
| export_name, loc, must_resolve); |
| } |
| } |
| |
| 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 "; |
| if (module->IsSourceTextModule()) { |
| Handle<SourceTextModule>::cast(module) |
| ->script() |
| .GetNameOrSourceURL() |
| .Print(os); |
| } else { |
| Handle<SyntheticModule>::cast(module)->name().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<SourceTextModule>> 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); |
| |
| if (module->IsSourceTextModule()) { |
| return SourceTextModule::PrepareInstantiate( |
| isolate, Handle<SourceTextModule>::cast(module), context, callback); |
| } else { |
| return SyntheticModule::PrepareInstantiate( |
| isolate, Handle<SyntheticModule>::cast(module), context, callback); |
| } |
| } |
| |
| bool Module::FinishInstantiate(Isolate* isolate, Handle<Module> module, |
| ZoneForwardList<Handle<SourceTextModule>>* 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); |
| |
| if (module->IsSourceTextModule()) { |
| return SourceTextModule::FinishInstantiate( |
| isolate, Handle<SourceTextModule>::cast(module), stack, dfs_index, |
| zone); |
| } else { |
| return SyntheticModule::FinishInstantiate( |
| isolate, Handle<SyntheticModule>::cast(module)); |
| } |
| } |
| |
| MaybeHandle<Object> Module::Evaluate(Isolate* isolate, Handle<Module> module) { |
| #ifdef DEBUG |
| if (FLAG_trace_module_status) { |
| StdoutStream os; |
| os << "Evaluating module "; |
| if (module->IsSourceTextModule()) { |
| Handle<SourceTextModule>::cast(module) |
| ->script() |
| .GetNameOrSourceURL() |
| .Print(os); |
| } else { |
| Handle<SyntheticModule>::cast(module)->name().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<SourceTextModule>> 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<SourceTextModule>>* 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>()); |
| |
| if (module->IsSourceTextModule()) { |
| return SourceTextModule::Evaluate( |
| isolate, Handle<SourceTextModule>::cast(module), stack, dfs_index); |
| } else { |
| return SyntheticModule::Evaluate(isolate, |
| Handle<SyntheticModule>::cast(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); |
| |
| if (module->IsSourceTextModule()) { |
| SourceTextModule::FetchStarExports( |
| isolate, Handle<SourceTextModule>::cast(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(isolate, 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 |