// 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 "src/objects/module.h" #include #include #include "src/api/api-inl.h" #include "src/ast/modules.h" #include "src/builtins/accessors.h" #include "src/common/assert-scope.h" #include "src/heap/heap-inl.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/objects/source-text-module.h" #include "src/objects/synthetic-module-inl.h" #include "src/utils/ostreams.h" namespace v8 { namespace internal { namespace { #ifdef DEBUG void PrintModuleName(Module module, std::ostream& os) { if (module.IsSourceTextModule()) { SourceTextModule::cast(module).GetScript().GetNameOrSourceURL().Print(os); } else { SyntheticModule::cast(module).name().Print(os); } #ifndef OBJECT_PRINT os << "\n"; #endif // OBJECT_PRINT } void PrintStatusTransition(Module module, Module::Status new_status) { if (!FLAG_trace_module_status) return; StdoutStream os; os << "Changing module status from " << module.status() << " to " << new_status << " for "; PrintModuleName(module, os); } void PrintStatusMessage(Module module, const char* message) { if (!FLAG_trace_module_status) return; StdoutStream os; os << "Instantiating module "; PrintModuleName(module, os); } #endif // DEBUG void SetStatusInternal(Module module, Module::Status new_status) { DisallowGarbageCollection no_gc; #ifdef DEBUG PrintStatusTransition(module, new_status); #endif // DEBUG module.set_status(new_status); } } // end namespace void Module::SetStatus(Status new_status) { DisallowGarbageCollection no_gc; DCHECK_LE(status(), new_status); DCHECK_NE(new_status, Module::kErrored); SetStatusInternal(*this, new_status); } // static void Module::RecordErrorUsingPendingException(Isolate* isolate, Handle module) { Handle the_exception(isolate->pending_exception(), isolate); RecordError(isolate, module, the_exception); } // static void Module::RecordError(Isolate* isolate, Handle module, Handle error) { DisallowGarbageCollection no_gc; DCHECK(module->exception().IsTheHole(isolate)); DCHECK(!error->IsTheHole(isolate)); if (module->IsSourceTextModule()) { // Revert to minmal SFI in case we have already been instantiating or // evaluating. auto self = SourceTextModule::cast(*module); self.set_code(self.GetSharedFunctionInfo()); } SetStatusInternal(*module, Module::kErrored); if (isolate->is_catchable_by_javascript(*error)) { module->set_exception(*error); } else { // v8::TryCatch uses `null` for termination exceptions. module->set_exception(*isolate->factory()->null_value()); } } void Module::ResetGraph(Isolate* isolate, Handle module) { DCHECK_NE(module->status(), kEvaluating); if (module->status() != kPreInstantiating && module->status() != kInstantiating) { return; } Handle requested_modules = module->IsSourceTextModule() ? Handle( SourceTextModule::cast(*module).requested_modules(), isolate) : Handle(); Reset(isolate, module); if (!module->IsSourceTextModule()) { DCHECK(module->IsSyntheticModule()); return; } for (int i = 0; i < requested_modules->length(); ++i) { Handle descendant(requested_modules->get(i), isolate); if (descendant->IsModule()) { ResetGraph(isolate, Handle::cast(descendant)); } else { DCHECK(descendant->IsUndefined(isolate)); } } } void Module::Reset(Isolate* isolate, Handle 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()); const int export_count = module->IsSourceTextModule() ? SourceTextModule::cast(*module).regular_exports().length() : SyntheticModule::cast(*module).export_names().length(); Handle exports = ObjectHashTable::New(isolate, export_count); if (module->IsSourceTextModule()) { SourceTextModule::Reset(isolate, Handle::cast(module)); } module->set_exports(*exports); SetStatusInternal(*module, kUninstantiated); } Object Module::GetException() { DisallowGarbageCollection no_gc; DCHECK_EQ(status(), Module::kErrored); DCHECK(!exception().IsTheHole()); return exception(); } MaybeHandle Module::ResolveExport(Isolate* isolate, Handle module, Handle module_specifier, Handle 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::cast(module), module_specifier, export_name, loc, must_resolve, resolve_set); } else { return SyntheticModule::ResolveExport( isolate, Handle::cast(module), module_specifier, export_name, loc, must_resolve); } } bool Module::Instantiate( Isolate* isolate, Handle module, v8::Local context, v8::Module::ResolveModuleCallback callback, DeprecatedResolveCallback callback_without_import_assertions) { #ifdef DEBUG PrintStatusMessage(*module, "Instantiating module "); #endif // DEBUG if (!PrepareInstantiate(isolate, module, context, callback, callback_without_import_assertions)) { ResetGraph(isolate, module); DCHECK_EQ(module->status(), kUninstantiated); return false; } Zone zone(isolate->allocator(), ZONE_NAME); ZoneForwardList> stack(&zone); unsigned dfs_index = 0; if (!FinishInstantiate(isolate, module, &stack, &dfs_index, &zone)) { ResetGraph(isolate, module); 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, v8::Local context, v8::Module::ResolveModuleCallback callback, DeprecatedResolveCallback callback_without_import_assertions) { 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::cast(module), context, callback, callback_without_import_assertions); } else { return SyntheticModule::PrepareInstantiate( isolate, Handle::cast(module), context); } } bool Module::FinishInstantiate(Isolate* isolate, Handle module, ZoneForwardList>* 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::cast(module), stack, dfs_index, zone); } else { return SyntheticModule::FinishInstantiate( isolate, Handle::cast(module)); } } MaybeHandle Module::Evaluate(Isolate* isolate, Handle module) { #ifdef DEBUG PrintStatusMessage(*module, "Evaluating module "); #endif // DEBUG STACK_CHECK(isolate, MaybeHandle()); if (FLAG_harmony_top_level_await) { return Module::EvaluateMaybeAsync(isolate, module); } else { return Module::InnerEvaluate(isolate, module); } } MaybeHandle Module::EvaluateMaybeAsync(Isolate* isolate, Handle 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 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 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 // module.[[CycleRoot]]. // A Synthetic Module has no children so it is its own cycle root. if (module->status() == kEvaluated && module->IsSourceTextModule()) { module = Handle::cast(module)->GetCycleRoot(isolate); } // 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()); if (module->IsSourceTextModule()) { return SourceTextModule::EvaluateMaybeAsync( isolate, Handle::cast(module)); } else { return SyntheticModule::Evaluate(isolate, Handle::cast(module)); } } MaybeHandle Module::InnerEvaluate(Isolate* isolate, Handle module) { if (module->status() == kErrored) { isolate->Throw(module->GetException()); return MaybeHandle(); } else if (module->status() == kEvaluated) { return isolate->factory()->undefined_value(); } // InnerEvaluate can be called both to evaluate top level modules without // the harmony_top_level_await flag and recursively to evaluate // SyntheticModules in the dependency graphs of SourceTextModules. // // However, SyntheticModules transition directly to 'Evaluated,' so we should // never see an 'Evaluating' module at this point. CHECK_EQ(module->status(), kInstantiated); if (module->IsSourceTextModule()) { return SourceTextModule::Evaluate(isolate, Handle::cast(module)); } else { return SyntheticModule::Evaluate(isolate, Handle::cast(module)); } } Handle Module::GetModuleNamespace(Isolate* isolate, Handle module) { Handle object(module->module_namespace(), isolate); ReadOnlyRoots roots(isolate); if (!object->IsUndefined(roots)) { // Namespace object already exists. return Handle::cast(object); } // Collect the export names. Zone zone(isolate->allocator(), ZONE_NAME); UnorderedModuleSet visited(&zone); if (module->IsSourceTextModule()) { SourceTextModule::FetchStarExports( isolate, Handle::cast(module), &zone, &visited); } Handle exports(module->exports(), isolate); ZoneVector> names(&zone); names.reserve(exports->NumberOfElements()); for (InternalIndex i : exports->IterateEntries()) { Object key; if (!exports->ToKey(roots, i, &key)) continue; names.push_back(handle(String::cast(key), isolate)); } DCHECK_EQ(static_cast(names.size()), exports->NumberOfElements()); // Sort them alphabetically. std::sort(names.begin(), names.end(), [&isolate](Handle a, Handle b) { return String::Compare(isolate, a, b) == ComparisonResult::kLessThan; }); // Create the namespace object (initially empty). Handle 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(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 proto_info = Map::GetOrCreatePrototypeInfo(Handle::cast(ns), isolate); proto_info->set_module_namespace(*ns); return ns; } MaybeHandle JSModuleNamespace::GetExport(Isolate* isolate, Handle name) { Handle object(module().exports().Lookup(name), isolate); if (object->IsTheHole(isolate)) { return isolate->factory()->undefined_value(); } Handle value(Cell::cast(*object).value(), isolate); if (value->IsTheHole(isolate)) { THROW_NEW_ERROR( isolate, NewReferenceError(MessageTemplate::kNotDefined, name), Object); } return value; } Maybe JSModuleNamespace::GetPropertyAttributes( LookupIterator* it) { Handle object = it->GetHolder(); Handle name = Handle::cast(it->GetName()); DCHECK_EQ(it->state(), LookupIterator::ACCESSOR); Isolate* isolate = it->isolate(); Handle lookup(object->module().exports().Lookup(name), isolate); if (lookup->IsTheHole(isolate)) return Just(ABSENT); Handle value(Handle::cast(lookup)->value(), isolate); if (value->IsTheHole(isolate)) { isolate->Throw(*isolate->factory()->NewReferenceError( MessageTemplate::kNotDefined, name)); return Nothing(); } return Just(it->property_attributes()); } bool Module::IsGraphAsync(Isolate* isolate) const { DisallowGarbageCollection no_gc; // Only SourceTextModules may be async. if (!IsSourceTextModule()) return false; SourceTextModule root = SourceTextModule::cast(*this); Zone zone(isolate->allocator(), ZONE_NAME); const size_t bucket_count = 2; ZoneUnorderedSet visited(&zone, bucket_count); ZoneVector worklist(&zone); visited.insert(root); worklist.push_back(root); do { SourceTextModule current = worklist.back(); worklist.pop_back(); DCHECK_GE(current.status(), kInstantiated); if (current.async()) return true; FixedArray requested_modules = current.requested_modules(); for (int i = 0, length = requested_modules.length(); i < length; ++i) { Module descendant = Module::cast(requested_modules.get(i)); if (descendant.IsSourceTextModule()) { const bool cycle = !visited.insert(descendant).second; if (!cycle) worklist.push_back(SourceTextModule::cast(descendant)); } } } while (!worklist.empty()); return false; } } // namespace internal } // namespace v8