#include "module_wrap.h" #include "env.h" #include "memory_tracker-inl.h" #include "node_contextify.h" #include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" #include "node_process-inl.h" #include "node_watchdog.h" #include "util-inl.h" #include // S_IFDIR #include namespace node { namespace loader { using errors::TryCatchScope; using node::contextify::ContextifyContext; using v8::Array; using v8::ArrayBufferView; using v8::Context; using v8::Data; using v8::EscapableHandleScope; using v8::Exception; using v8::FixedArray; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Global; using v8::HandleScope; using v8::Int32; using v8::Integer; using v8::Isolate; using v8::Just; using v8::JustVoid; using v8::Local; using v8::LocalVector; using v8::Maybe; using v8::MaybeLocal; using v8::MemorySpan; using v8::Message; using v8::MicrotaskQueue; using v8::Module; using v8::ModuleImportPhase; using v8::ModuleRequest; using v8::Name; using v8::Nothing; using v8::Null; using v8::Object; using v8::ObjectTemplate; using v8::PrimitiveArray; using v8::Promise; using v8::PromiseRejectEvent; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; using v8::Symbol; using v8::UnboundModuleScript; using v8::Undefined; using v8::Value; void ModuleCacheKey::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("specifier", specifier); tracker->TrackField("import_attributes", import_attributes); } template ModuleCacheKey ModuleCacheKey::From(Local context, Local specifier, Local import_attributes) { CHECK_EQ(import_attributes->Length() % elements_per_attribute, 0); Isolate* isolate = context->GetIsolate(); std::size_t h1 = specifier->GetIdentityHash(); size_t num_attributes = import_attributes->Length() / elements_per_attribute; ImportAttributeVector attributes; attributes.reserve(num_attributes); std::size_t h2 = 0; for (int i = 0; i < import_attributes->Length(); i += elements_per_attribute) { Local v8_key = import_attributes->Get(context, i).As(); Local v8_value = import_attributes->Get(context, i + 1).As(); Utf8Value key_utf8(isolate, v8_key); Utf8Value value_utf8(isolate, v8_value); attributes.emplace_back(key_utf8.ToString(), value_utf8.ToString()); h2 ^= v8_key->GetIdentityHash(); h2 ^= v8_value->GetIdentityHash(); } // Combine the hashes using a simple XOR and bit shift to reduce // collisions. Note that the hash does not guarantee uniqueness. std::size_t hash = h1 ^ (h2 << 1); Utf8Value utf8_specifier(isolate, specifier); return ModuleCacheKey{utf8_specifier.ToString(), attributes, hash}; } ModuleCacheKey ModuleCacheKey::From(Local context, Local v8_request) { return From( context, v8_request->GetSpecifier(), v8_request->GetImportAttributes()); } ModuleWrap::ModuleWrap(Realm* realm, Local object, Local module, Local url, Local context_object, Local synthetic_evaluation_step) : BaseObject(realm, object), module_(realm->isolate(), module), module_hash_(module->GetIdentityHash()) { realm->env()->hash_to_module_map.emplace(module_hash_, this); object->SetInternalField(kModuleSlot, module); object->SetInternalField(kURLSlot, url); object->SetInternalField(kModuleSourceObjectSlot, v8::Undefined(realm->isolate())); object->SetInternalField(kSyntheticEvaluationStepsSlot, synthetic_evaluation_step); object->SetInternalField(kContextObjectSlot, context_object); if (!synthetic_evaluation_step->IsUndefined()) { synthetic_ = true; } MakeWeak(); module_.SetWeak(); } ModuleWrap::~ModuleWrap() { auto range = env()->hash_to_module_map.equal_range(module_hash_); for (auto it = range.first; it != range.second; ++it) { if (it->second == this) { env()->hash_to_module_map.erase(it); break; } } } Local ModuleWrap::context() const { Local obj = object()->GetInternalField(kContextObjectSlot).As(); // If this fails, there is likely a bug e.g. ModuleWrap::context() is accessed // before the ModuleWrap constructor completes. CHECK(obj->IsObject()); return obj.As()->GetCreationContextChecked(); } ModuleWrap* ModuleWrap::GetFromModule(Environment* env, Local module) { auto range = env->hash_to_module_map.equal_range(module->GetIdentityHash()); for (auto it = range.first; it != range.second; ++it) { if (it->second->module_ == module) { return it->second; } } return nullptr; } Maybe ModuleWrap::CheckUnsettledTopLevelAwait() { Isolate* isolate = env()->isolate(); Local context = env()->context(); // This must be invoked when the environment is shutting down, and the module // is kept alive by the module wrap via an internal field. CHECK(env()->exiting()); CHECK(!module_.IsEmpty()); Local module = module_.Get(isolate); // It's a synthetic module, likely a facade wrapping CJS. if (!module->IsSourceTextModule()) { return Just(true); } if (!module->IsGraphAsync()) { // There is no TLA, no need to check. return Just(true); } auto stalled_messages = std::get<1>(module->GetStalledTopLevelAwaitMessages(isolate)); if (stalled_messages.empty()) { return Just(true); } if (env()->options()->warnings) { for (auto& message : stalled_messages) { std::string reason = "Warning: Detected unsettled top-level await at "; std::string info = FormatErrorMessage(isolate, context, "", message, true); reason += info; FPrintF(stderr, "%s\n", reason); } } return Just(false); } Local ModuleWrap::GetHostDefinedOptions( Isolate* isolate, Local id_symbol) { Local host_defined_options = PrimitiveArray::New(isolate, HostDefinedOptions::kLength); host_defined_options->Set(isolate, HostDefinedOptions::kID, id_symbol); return host_defined_options; } // new ModuleWrap(url, context, source, lineOffset, columnOffset[, cachedData]); // new ModuleWrap(url, context, source, lineOffset, columnOffset, // idSymbol); // new ModuleWrap(url, context, exportNames, evaluationCallback[, cjsModule]) void ModuleWrap::New(const FunctionCallbackInfo& args) { CHECK(args.IsConstructCall()); CHECK_GE(args.Length(), 3); Realm* realm = Realm::GetCurrent(args); Isolate* isolate = realm->isolate(); Local that = args.This(); CHECK(args[0]->IsString()); Local url = args[0].As(); Local context; ContextifyContext* contextify_context = nullptr; if (args[1]->IsUndefined()) { context = that->GetCreationContextChecked(); } else { CHECK(args[1]->IsObject()); contextify_context = ContextifyContext::ContextFromContextifiedSandbox( realm->env(), args[1].As()); CHECK_NOT_NULL(contextify_context); context = contextify_context->context(); } int line_offset = 0; int column_offset = 0; bool synthetic = args[2]->IsArray(); bool can_use_builtin_cache = false; Local host_defined_options = PrimitiveArray::New(isolate, HostDefinedOptions::kLength); Local id_symbol; if (synthetic) { // new ModuleWrap(url, context, exportNames, evaluationCallback[, // cjsModule]) CHECK(args[3]->IsFunction()); } else { // new ModuleWrap(url, context, source, lineOffset, columnOffset[, // cachedData]); // new ModuleWrap(url, context, source, lineOffset, columnOffset, // idSymbol); CHECK(args[2]->IsString()); CHECK(args[3]->IsNumber()); line_offset = args[3].As()->Value(); CHECK(args[4]->IsNumber()); column_offset = args[4].As()->Value(); if (args[5]->IsSymbol()) { id_symbol = args[5].As(); can_use_builtin_cache = (id_symbol == realm->isolate_data()->source_text_module_default_hdo()); } else { id_symbol = Symbol::New(isolate, url); } host_defined_options = GetHostDefinedOptions(isolate, id_symbol); if (that->SetPrivate(context, realm->isolate_data()->host_defined_option_symbol(), id_symbol) .IsNothing()) { return; } } ShouldNotAbortOnUncaughtScope no_abort_scope(realm->env()); TryCatchScope try_catch(realm->env()); Local module; { Context::Scope context_scope(context); if (synthetic) { CHECK(args[2]->IsArray()); Local export_names_arr = args[2].As(); uint32_t len = export_names_arr->Length(); LocalVector export_names(realm->isolate(), len); for (uint32_t i = 0; i < len; i++) { Local export_name_val; if (!export_names_arr->Get(context, i).ToLocal(&export_name_val)) { return; } CHECK(export_name_val->IsString()); export_names[i] = export_name_val.As(); } const MemorySpan> span(export_names.begin(), export_names.size()); module = Module::CreateSyntheticModule( isolate, url, span, SyntheticModuleEvaluationStepsCallback); } else { // When we are compiling for the default loader, this will be // std::nullopt, and CompileSourceTextModule() should use // on-disk cache. std::optional user_cached_data; if (id_symbol != realm->isolate_data()->source_text_module_default_hdo()) { user_cached_data = nullptr; } if (args[5]->IsArrayBufferView()) { CHECK(!can_use_builtin_cache); // We don't use this option internally. Local cached_data_buf = args[5].As(); uint8_t* data = static_cast(cached_data_buf->Buffer()->Data()); user_cached_data = new ScriptCompiler::CachedData(data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength()); } Local source_text = args[2].As(); bool cache_rejected = false; if (!CompileSourceTextModule(realm, source_text, url, line_offset, column_offset, host_defined_options, user_cached_data, &cache_rejected) .ToLocal(&module)) { if (try_catch.HasCaught() && !try_catch.HasTerminated()) { CHECK(!try_catch.Message().IsEmpty()); CHECK(!try_catch.Exception().IsEmpty()); AppendExceptionLine(realm->env(), try_catch.Exception(), try_catch.Message(), ErrorHandlingMode::MODULE_ERROR); try_catch.ReThrow(); } return; } if (user_cached_data.has_value() && user_cached_data.value() != nullptr && cache_rejected) { THROW_ERR_VM_MODULE_CACHED_DATA_REJECTED( realm, "cachedData buffer was rejected"); try_catch.ReThrow(); return; } if (that->Set(context, realm->env()->source_url_string(), module->GetUnboundModuleScript()->GetSourceURL()) .IsNothing()) { return; } if (that->Set(context, realm->env()->source_map_url_string(), module->GetUnboundModuleScript()->GetSourceMappingURL()) .IsNothing()) { return; } } } if (!that->Set(context, realm->isolate_data()->url_string(), url) .FromMaybe(false)) { return; } if (synthetic && args[4]->IsObject() && that->Set(context, realm->isolate_data()->imported_cjs_symbol(), args[4]) .IsNothing()) { return; } // Initialize an empty slot for source map cache before the object is frozen. if (that->SetPrivate(context, realm->isolate_data()->source_map_data_private_symbol(), Undefined(isolate)) .IsNothing()) { return; } // Use the extras object as an object whose GetCreationContext() will be the // original `context`, since the `Context` itself strictly speaking cannot // be stored in an internal field. Local context_object = context->GetExtrasBindingObject(); Local synthetic_evaluation_step = synthetic ? args[3] : Undefined(realm->isolate()).As(); ModuleWrap* obj = new ModuleWrap( realm, that, module, url, context_object, synthetic_evaluation_step); obj->contextify_context_ = contextify_context; args.GetReturnValue().Set(that); } MaybeLocal ModuleWrap::CompileSourceTextModule( Realm* realm, Local source_text, Local url, int line_offset, int column_offset, Local host_defined_options, std::optional user_cached_data, bool* cache_rejected) { Isolate* isolate = realm->isolate(); EscapableHandleScope scope(isolate); ScriptOrigin origin(url, line_offset, column_offset, true, // is cross origin -1, // script id Local(), // source map URL false, // is opaque (?) false, // is WASM true, // is ES Module host_defined_options); ScriptCompiler::CachedData* cached_data = nullptr; CompileCacheEntry* cache_entry = nullptr; // When compiling for the default loader, user_cached_data is std::nullptr. // When compiling for vm.Module, it's either nullptr or a pointer to the // cached data. if (user_cached_data.has_value()) { cached_data = user_cached_data.value(); } else if (realm->env()->use_compile_cache()) { cache_entry = realm->env()->compile_cache_handler()->GetOrInsert( source_text, url, CachedCodeType::kESM); } if (cache_entry != nullptr && cache_entry->cache != nullptr) { // source will take ownership of cached_data. cached_data = cache_entry->CopyCache(); } ScriptCompiler::Source source(source_text, origin, cached_data); ScriptCompiler::CompileOptions options; if (cached_data == nullptr) { options = ScriptCompiler::kNoCompileOptions; } else { options = ScriptCompiler::kConsumeCodeCache; } Local module; if (!ScriptCompiler::CompileModule(isolate, &source, options) .ToLocal(&module)) { return scope.EscapeMaybe(MaybeLocal()); } if (options == ScriptCompiler::kConsumeCodeCache) { *cache_rejected = source.GetCachedData()->rejected; } if (cache_entry != nullptr) { realm->env()->compile_cache_handler()->MaybeSave( cache_entry, module, *cache_rejected); } return scope.Escape(module); } ModulePhase to_phase_constant(ModuleImportPhase phase) { switch (phase) { case ModuleImportPhase::kEvaluation: return kEvaluationPhase; case ModuleImportPhase::kSource: return kSourcePhase; } UNREACHABLE(); } static Local createImportAttributesContainer( Realm* realm, Isolate* isolate, Local raw_attributes, const int elements_per_attribute) { CHECK_EQ(raw_attributes->Length() % elements_per_attribute, 0); size_t num_attributes = raw_attributes->Length() / elements_per_attribute; LocalVector names(isolate, num_attributes); LocalVector values(isolate, num_attributes); for (int i = 0; i < raw_attributes->Length(); i += elements_per_attribute) { int idx = i / elements_per_attribute; names[idx] = raw_attributes->Get(realm->context(), i).As(); values[idx] = raw_attributes->Get(realm->context(), i + 1).As(); } Local attributes = Object::New( isolate, Null(isolate), names.data(), values.data(), num_attributes); attributes->SetIntegrityLevel(realm->context(), v8::IntegrityLevel::kFrozen) .Check(); return attributes; } static Local createModuleRequestsContainer( Realm* realm, Isolate* isolate, Local raw_requests) { EscapableHandleScope scope(isolate); Local context = realm->context(); LocalVector requests(isolate, raw_requests->Length()); for (int i = 0; i < raw_requests->Length(); i++) { Local module_request = raw_requests->Get(realm->context(), i).As(); Local specifier = module_request->GetSpecifier(); // Contains the import attributes for this request in the form: // [key1, value1, source_offset1, key2, value2, source_offset2, ...]. Local raw_attributes = module_request->GetImportAttributes(); Local attributes = createImportAttributesContainer(realm, isolate, raw_attributes, 3); ModuleImportPhase phase = module_request->GetPhase(); Local names[] = { realm->isolate_data()->specifier_string(), realm->isolate_data()->attributes_string(), realm->isolate_data()->phase_string(), }; Local values[] = { specifier, attributes, Integer::New(isolate, to_phase_constant(phase)), }; DCHECK_EQ(arraysize(names), arraysize(values)); Local request = Object::New(isolate, Null(isolate), names, values, arraysize(names)); request->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen).Check(); requests[i] = request; } return scope.Escape(Array::New(isolate, requests.data(), requests.size())); } void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); Local that = args.This(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, that); Local module = obj->module_.Get(isolate); args.GetReturnValue().Set(createModuleRequestsContainer( realm, isolate, module->GetModuleRequests())); } // moduleWrap.link(moduleWraps) void ModuleWrap::Link(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); Local context = realm->context(); ModuleWrap* dependent; ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This()); CHECK_EQ(args.Length(), 1); Local requests = dependent->module_.Get(isolate)->GetModuleRequests(); Local modules = args[0].As(); CHECK_EQ(modules->Length(), static_cast(requests->Length())); std::vector> modules_buffer; if (FromV8Array(context, modules, &modules_buffer).IsNothing()) { return; } for (uint32_t i = 0; i < modules_buffer.size(); i++) { Local module_object = modules_buffer[i].Get(isolate).As(); CHECK( realm->isolate_data()->module_wrap_constructor_template()->HasInstance( module_object)); ModuleCacheKey module_cache_key = ModuleCacheKey::From( context, requests->Get(context, i).As()); dependent->resolve_cache_[module_cache_key].Reset(isolate, module_object); } } void ModuleWrap::Instantiate(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local context = obj->context(); Local module = obj->module_.Get(isolate); TryCatchScope try_catch(realm->env()); USE(module->InstantiateModule( context, ResolveModuleCallback, ResolveSourceCallback)); // clear resolve cache on instantiate obj->resolve_cache_.clear(); if (try_catch.HasCaught() && !try_catch.HasTerminated()) { CHECK(!try_catch.Message().IsEmpty()); CHECK(!try_catch.Exception().IsEmpty()); AppendExceptionLine(realm->env(), try_catch.Exception(), try_catch.Message(), ErrorHandlingMode::MODULE_ERROR); try_catch.ReThrow(); return; } } void ModuleWrap::Evaluate(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); Isolate* isolate = realm->isolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local context = obj->context(); Local module = obj->module_.Get(isolate); ContextifyContext* contextify_context = obj->contextify_context_; MicrotaskQueue* microtask_queue = nullptr; if (contextify_context != nullptr) microtask_queue = contextify_context->microtask_queue(); // module.evaluate(timeout, breakOnSigint) CHECK_EQ(args.Length(), 2); CHECK(args[0]->IsNumber()); int64_t timeout; if (!args[0]->IntegerValue(realm->context()).To(&timeout)) { return; } CHECK(args[1]->IsBoolean()); bool break_on_sigint = args[1]->IsTrue(); ShouldNotAbortOnUncaughtScope no_abort_scope(realm->env()); TryCatchScope try_catch(realm->env()); bool timed_out = false; bool received_signal = false; MaybeLocal result; auto run = [&]() { MaybeLocal result = module->Evaluate(context); if (!result.IsEmpty() && microtask_queue) microtask_queue->PerformCheckpoint(isolate); return result; }; if (break_on_sigint && timeout != -1) { Watchdog wd(isolate, timeout, &timed_out); SigintWatchdog swd(isolate, &received_signal); result = run(); } else if (break_on_sigint) { SigintWatchdog swd(isolate, &received_signal); result = run(); } else if (timeout != -1) { Watchdog wd(isolate, timeout, &timed_out); result = run(); } else { result = run(); } if (result.IsEmpty()) { CHECK(try_catch.HasCaught()); } // Convert the termination exception into a regular exception. if (timed_out || received_signal) { if (!realm->env()->is_main_thread() && realm->env()->is_stopping()) return; isolate->CancelTerminateExecution(); // It is possible that execution was terminated by another timeout in // which this timeout is nested, so check whether one of the watchdogs // from this invocation is responsible for termination. if (timed_out) { THROW_ERR_SCRIPT_EXECUTION_TIMEOUT(realm->env(), timeout); } else if (received_signal) { THROW_ERR_SCRIPT_EXECUTION_INTERRUPTED(realm->env()); } } if (try_catch.HasCaught()) { if (!try_catch.HasTerminated()) try_catch.ReThrow(); return; } Local res; if (result.ToLocal(&res)) { args.GetReturnValue().Set(res); } } void ModuleWrap::InstantiateSync(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local context = obj->context(); Local module = obj->module_.Get(isolate); Environment* env = realm->env(); { TryCatchScope try_catch(env); USE(module->InstantiateModule( context, ResolveModuleCallback, ResolveSourceCallback)); // clear resolve cache on instantiate obj->resolve_cache_.clear(); if (try_catch.HasCaught() && !try_catch.HasTerminated()) { CHECK(!try_catch.Message().IsEmpty()); CHECK(!try_catch.Exception().IsEmpty()); AppendExceptionLine(env, try_catch.Exception(), try_catch.Message(), ErrorHandlingMode::MODULE_ERROR); try_catch.ReThrow(); return; } } // TODO(joyeecheung): record Module::HasTopLevelAwait() in every ModuleWrap // and infer the asynchronicity from a module's children during linking. args.GetReturnValue().Set(module->IsGraphAsync()); } Maybe ThrowIfPromiseRejected(Realm* realm, Local promise) { Isolate* isolate = realm->isolate(); Local context = realm->context(); if (promise->State() != Promise::PromiseState::kRejected) { return JustVoid(); } // The rejected promise is created by V8, so we don't get a chance to mark // it as resolved before the rejection happens from evaluation. But we can // tell the promise rejection callback to treat it as a promise rejected // before handler was added which would remove it from the unhandled // rejection handling, since we are converting it into an error and throw // from here directly. Local type = Integer::New(isolate, static_cast( PromiseRejectEvent::kPromiseHandlerAddedAfterReject)); Local args[] = {type, promise, Undefined(isolate)}; if (realm->promise_reject_callback() ->Call(context, Undefined(isolate), arraysize(args), args) .IsEmpty()) { return Nothing(); } Local exception = promise->Result(); Local message = Exception::CreateMessage(isolate, exception); AppendExceptionLine( realm->env(), exception, message, ErrorHandlingMode::MODULE_ERROR); isolate->ThrowException(exception); return Nothing(); } void ThrowIfPromiseRejected(const FunctionCallbackInfo& args) { if (!args[0]->IsPromise()) { return; } ThrowIfPromiseRejected(Realm::GetCurrent(args), args[0].As()); } void ModuleWrap::EvaluateSync(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local context = obj->context(); Local module = obj->module_.Get(isolate); Environment* env = realm->env(); Local result; { TryCatchScope try_catch(env); if (!module->Evaluate(context).ToLocal(&result)) { if (try_catch.HasCaught()) { if (!try_catch.HasTerminated()) { try_catch.ReThrow(); } return; } } } CHECK(result->IsPromise()); Local promise = result.As(); if (ThrowIfPromiseRejected(realm, promise).IsNothing()) { return; } if (module->IsGraphAsync()) { CHECK(env->options()->print_required_tla); auto stalled_messages = std::get<1>(module->GetStalledTopLevelAwaitMessages(isolate)); if (stalled_messages.size() != 0) { for (auto& message : stalled_messages) { std::string reason = "Error: unexpected top-level await at "; std::string info = FormatErrorMessage(isolate, context, "", message, true); reason += info; FPrintF(stderr, "%s\n", reason); } } THROW_ERR_REQUIRE_ASYNC_MODULE(env, args[0], args[1]); return; } CHECK_EQ(promise->State(), Promise::PromiseState::kFulfilled); args.GetReturnValue().Set(module->GetModuleNamespace()); } void ModuleWrap::GetNamespaceSync(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local module = obj->module_.Get(isolate); switch (module->GetStatus()) { case Module::Status::kUninstantiated: case Module::Status::kInstantiating: return realm->env()->ThrowError( "Cannot get namespace, module has not been instantiated"); case Module::Status::kInstantiated: case Module::Status::kEvaluating: case Module::Status::kEvaluated: case Module::Status::kErrored: break; } if (module->IsGraphAsync()) { return THROW_ERR_REQUIRE_ASYNC_MODULE(realm->env(), args[0], args[1]); } Local result = module->GetModuleNamespace(); args.GetReturnValue().Set(result); } void ModuleWrap::GetNamespace(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local module = obj->module_.Get(isolate); switch (module->GetStatus()) { case Module::Status::kUninstantiated: case Module::Status::kInstantiating: return realm->env()->ThrowError( "cannot get namespace, module has not been instantiated"); case Module::Status::kInstantiated: case Module::Status::kEvaluating: case Module::Status::kEvaluated: case Module::Status::kErrored: break; default: UNREACHABLE(); } Local result = module->GetModuleNamespace(); args.GetReturnValue().Set(result); } void ModuleWrap::SetModuleSourceObject( const FunctionCallbackInfo& args) { ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsObject()); CHECK(obj->object() ->GetInternalField(kModuleSourceObjectSlot) .As() ->IsUndefined()); obj->object()->SetInternalField(kModuleSourceObjectSlot, args[0]); } void ModuleWrap::GetModuleSourceObject( const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); CHECK_EQ(args.Length(), 0); Local module_source_object = obj->object()->GetInternalField(kModuleSourceObjectSlot).As(); if (module_source_object->IsUndefined()) { Local url = obj->object()->GetInternalField(kURLSlot).As(); THROW_ERR_SOURCE_PHASE_NOT_DEFINED(isolate, url); return; } args.GetReturnValue().Set(module_source_object); } void ModuleWrap::GetStatus(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local module = obj->module_.Get(isolate); args.GetReturnValue().Set(module->GetStatus()); } void ModuleWrap::IsGraphAsync(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local module = obj->module_.Get(isolate); args.GetReturnValue().Set(module->IsGraphAsync()); } void ModuleWrap::HasTopLevelAwait(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local module = obj->module_.Get(isolate); // Check if module is valid if (module.IsEmpty()) { args.GetReturnValue().Set(false); return; } // For source text modules, check if the graph is async // For synthetic modules, it's always false bool has_top_level_await = module->IsSourceTextModule() && module->IsGraphAsync(); args.GetReturnValue().Set(has_top_level_await); } void ModuleWrap::GetError(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); Local module = obj->module_.Get(isolate); args.GetReturnValue().Set(module->GetException()); } MaybeLocal ModuleWrap::ResolveModuleCallback( Local context, Local specifier, Local import_attributes, Local referrer) { Isolate* isolate = context->GetIsolate(); Environment* env = Environment::GetCurrent(context); if (env == nullptr) { THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate); return MaybeLocal(); } ModuleCacheKey cache_key = ModuleCacheKey::From(context, specifier, import_attributes); ModuleWrap* dependent = GetFromModule(env, referrer); if (dependent == nullptr) { THROW_ERR_VM_MODULE_LINK_FAILURE( env, "request for '%s' is from invalid module", cache_key.specifier); return MaybeLocal(); } if (dependent->resolve_cache_.count(cache_key) != 1) { THROW_ERR_VM_MODULE_LINK_FAILURE( env, "request for '%s' is not in cache", cache_key.specifier); return MaybeLocal(); } Local module_object = dependent->resolve_cache_[cache_key].Get(isolate); if (module_object.IsEmpty() || !module_object->IsObject()) { THROW_ERR_VM_MODULE_LINK_FAILURE( env, "request for '%s' did not return an object", cache_key.specifier); return MaybeLocal(); } ModuleWrap* module; ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal()); return module->module_.Get(isolate); } MaybeLocal ModuleWrap::ResolveSourceCallback( Local context, Local specifier, Local import_attributes, Local referrer) { Isolate* isolate = context->GetIsolate(); Environment* env = Environment::GetCurrent(context); if (env == nullptr) { THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate); return MaybeLocal(); } ModuleCacheKey cache_key = ModuleCacheKey::From(context, specifier, import_attributes); ModuleWrap* dependent = GetFromModule(env, referrer); if (dependent == nullptr) { THROW_ERR_VM_MODULE_LINK_FAILURE( env, "request for '%s' is from invalid module", cache_key.specifier); return MaybeLocal(); } if (dependent->resolve_cache_.count(cache_key) != 1) { THROW_ERR_VM_MODULE_LINK_FAILURE( env, "request for '%s' is not in cache", cache_key.specifier); return MaybeLocal(); } Local module_object = dependent->resolve_cache_[cache_key].Get(isolate); if (module_object.IsEmpty() || !module_object->IsObject()) { THROW_ERR_VM_MODULE_LINK_FAILURE( env, "request for '%s' did not return an object", cache_key.specifier); return MaybeLocal(); } ModuleWrap* module; ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal()); Local module_source_object = module->object()->GetInternalField(kModuleSourceObjectSlot).As(); if (module_source_object->IsUndefined()) { Local url = module->object()->GetInternalField(kURLSlot).As(); THROW_ERR_SOURCE_PHASE_NOT_DEFINED(isolate, url); return MaybeLocal(); } CHECK(module_source_object->IsObject()); return module_source_object.As(); } static MaybeLocal ImportModuleDynamicallyWithPhase( Local context, Local host_defined_options, Local resource_name, Local specifier, ModuleImportPhase phase, Local import_attributes) { Isolate* isolate = context->GetIsolate(); Environment* env = Environment::GetCurrent(context); if (env == nullptr) { THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate); return MaybeLocal(); } Realm* realm = Realm::GetCurrent(context); if (realm == nullptr) { // Fallback to the principal realm if it's in a vm context. realm = env->principal_realm(); } EscapableHandleScope handle_scope(isolate); Local import_callback = realm->host_import_module_dynamically_callback(); Local id; Local options = host_defined_options.As(); // Get referrer id symbol from the host-defined options. // If the host-defined options are empty, get the referrer id symbol // from the realm global object. if (options->Length() == HostDefinedOptions::kLength) { id = options->Get(context, HostDefinedOptions::kID).As(); } else if (!context->Global() ->GetPrivate(context, env->host_defined_option_symbol()) .ToLocal(&id)) { return MaybeLocal(); } Local attributes = createImportAttributesContainer(realm, isolate, import_attributes, 2); Local import_args[] = { id, Local(specifier), Integer::New(isolate, to_phase_constant(phase)), attributes, resource_name, }; Local result; if (!import_callback ->Call( context, Undefined(isolate), arraysize(import_args), import_args) .ToLocal(&result)) { return {}; } // Wrap the returned value in a promise created in the referrer context to // avoid dynamic scopes. Local resolver; if (!Promise::Resolver::New(context).ToLocal(&resolver)) { return {}; } if (resolver->Resolve(context, result).IsNothing()) { return {}; } return handle_scope.Escape(resolver->GetPromise()); } static MaybeLocal ImportModuleDynamically( Local context, Local host_defined_options, Local resource_name, Local specifier, Local import_attributes) { return ImportModuleDynamicallyWithPhase(context, host_defined_options, resource_name, specifier, ModuleImportPhase::kEvaluation, import_attributes); } void ModuleWrap::SetImportModuleDynamicallyCallback( const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Realm* realm = Realm::GetCurrent(args); HandleScope handle_scope(isolate); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsFunction()); Local import_callback = args[0].As(); realm->set_host_import_module_dynamically_callback(import_callback); isolate->SetHostImportModuleDynamicallyCallback(ImportModuleDynamically); isolate->SetHostImportModuleWithPhaseDynamicallyCallback( ImportModuleDynamicallyWithPhase); } void ModuleWrap::HostInitializeImportMetaObjectCallback( Local context, Local module, Local meta) { Environment* env = Environment::GetCurrent(context); if (env == nullptr) return; ModuleWrap* module_wrap = GetFromModule(env, module); if (module_wrap == nullptr) { return; } Realm* realm = Realm::GetCurrent(context); if (realm == nullptr) { // Fallback to the principal realm if it's in a vm context. realm = env->principal_realm(); } Local wrap = module_wrap->object(); Local callback = realm->host_initialize_import_meta_object_callback(); Local id; if (!wrap->GetPrivate(context, env->host_defined_option_symbol()) .ToLocal(&id)) { return; } DCHECK(id->IsSymbol()); Local args[] = {id, meta, wrap}; TryCatchScope try_catch(env); USE(callback->Call( context, Undefined(realm->isolate()), arraysize(args), args)); if (try_catch.HasCaught() && !try_catch.HasTerminated()) { try_catch.ReThrow(); } } void ModuleWrap::SetInitializeImportMetaObjectCallback( const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); Isolate* isolate = realm->isolate(); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsFunction()); Local import_meta_callback = args[0].As(); realm->set_host_initialize_import_meta_object_callback(import_meta_callback); isolate->SetHostInitializeImportMetaObjectCallback( HostInitializeImportMetaObjectCallback); } MaybeLocal ModuleWrap::SyntheticModuleEvaluationStepsCallback( Local context, Local module) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); ModuleWrap* obj = GetFromModule(env, module); TryCatchScope try_catch(env); Local synthetic_evaluation_steps = obj->object() ->GetInternalField(kSyntheticEvaluationStepsSlot) .As() .As(); obj->object()->SetInternalField( kSyntheticEvaluationStepsSlot, Undefined(isolate)); MaybeLocal ret = synthetic_evaluation_steps->Call(context, obj->object(), 0, nullptr); if (ret.IsEmpty()) { CHECK(try_catch.HasCaught()); } if (try_catch.HasCaught() && !try_catch.HasTerminated()) { CHECK(!try_catch.Message().IsEmpty()); CHECK(!try_catch.Exception().IsEmpty()); try_catch.ReThrow(); return MaybeLocal(); } Local resolver; if (!Promise::Resolver::New(context).ToLocal(&resolver)) { return MaybeLocal(); } if (resolver->Resolve(context, Undefined(isolate)).IsNothing()) { return MaybeLocal(); } return resolver->GetPromise(); } void ModuleWrap::SetSyntheticExport(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local that = args.This(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, that); CHECK(obj->synthetic_); CHECK_EQ(args.Length(), 2); CHECK(args[0]->IsString()); Local export_name = args[0].As(); Local export_value = args[1]; Local module = obj->module_.Get(isolate); USE(module->SetSyntheticModuleExport(isolate, export_name, export_value)); } void ModuleWrap::CreateCachedData(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local that = args.This(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, that); CHECK(!obj->synthetic_); Local module = obj->module_.Get(isolate); CHECK_LT(module->GetStatus(), Module::Status::kEvaluating); Local unbound_module_script = module->GetUnboundModuleScript(); std::unique_ptr cached_data( ScriptCompiler::CreateCodeCache(unbound_module_script)); Environment* env = Environment::GetCurrent(args); Local result; if (!cached_data) { if (!Buffer::New(env, 0).ToLocal(&result)) { return; } } else if (!Buffer::Copy(env, reinterpret_cast(cached_data->data), cached_data->length) .ToLocal(&result)) { return; } args.GetReturnValue().Set(result); } // This v8::Module::ResolveModuleCallback simply links `import 'original'` // to the env->temporary_required_module_facade_original() which is stashed // right before this callback is called and will be restored as soon as // v8::Module::Instantiate() returns. MaybeLocal LinkRequireFacadeWithOriginal( Local context, Local specifier, Local import_attributes, Local referrer) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = context->GetIsolate(); CHECK(specifier->Equals(context, env->original_string()).ToChecked()); CHECK(!env->temporary_required_module_facade_original.IsEmpty()); return env->temporary_required_module_facade_original.Get(isolate); } // Wraps an existing source text module with a facade that adds // .__esModule = true to the exports. // See env->required_module_facade_source_string() for the source. void ModuleWrap::CreateRequiredModuleFacade( const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); CHECK(args[0]->IsObject()); // original module Local wrap = args[0].As(); ModuleWrap* original; ASSIGN_OR_RETURN_UNWRAP(&original, wrap); // Use the same facade source and URL to hit the compilation cache. ScriptOrigin origin(env->required_module_facade_url_string(), 0, // line offset 0, // column offset true, // is cross origin -1, // script id Local(), // source map URL false, // is opaque (?) false, // is WASM true); // is ES Module ScriptCompiler::Source source(env->required_module_facade_source_string(), origin); // The module facade instantiation simply links `import 'original'` in the // facade with the original module and should never fail. Local facade; if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&facade)) { return; } // Stash the original module in temporary_required_module_facade_original // for the LinkRequireFacadeWithOriginal() callback to pick it up. CHECK(env->temporary_required_module_facade_original.IsEmpty()); env->temporary_required_module_facade_original.Reset( isolate, original->module_.Get(isolate)); CHECK(facade->InstantiateModule(context, LinkRequireFacadeWithOriginal) .IsJust()); env->temporary_required_module_facade_original.Reset(); // The evaluation of the facade is synchronous. Local evaluated; if (!facade->Evaluate(context).ToLocal(&evaluated)) { return; } CHECK(evaluated->IsPromise()); CHECK_EQ(evaluated.As()->State(), Promise::PromiseState::kFulfilled); args.GetReturnValue().Set(facade->GetModuleNamespace()); } void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data, Local target) { Isolate* isolate = isolate_data->isolate(); Local tpl = NewFunctionTemplate(isolate, New); tpl->InstanceTemplate()->SetInternalFieldCount( ModuleWrap::kInternalFieldCount); SetProtoMethod(isolate, tpl, "link", Link); SetProtoMethod(isolate, tpl, "getModuleRequests", GetModuleRequests); SetProtoMethod(isolate, tpl, "instantiateSync", InstantiateSync); SetProtoMethod(isolate, tpl, "evaluateSync", EvaluateSync); SetProtoMethod(isolate, tpl, "getNamespaceSync", GetNamespaceSync); SetProtoMethod(isolate, tpl, "instantiate", Instantiate); SetProtoMethod(isolate, tpl, "evaluate", Evaluate); SetProtoMethod(isolate, tpl, "setExport", SetSyntheticExport); SetProtoMethod(isolate, tpl, "setModuleSourceObject", SetModuleSourceObject); SetProtoMethod(isolate, tpl, "getModuleSourceObject", GetModuleSourceObject); SetProtoMethodNoSideEffect( isolate, tpl, "createCachedData", CreateCachedData); SetProtoMethodNoSideEffect(isolate, tpl, "getNamespace", GetNamespace); SetProtoMethodNoSideEffect(isolate, tpl, "getStatus", GetStatus); SetProtoMethodNoSideEffect(isolate, tpl, "isGraphAsync", IsGraphAsync); SetProtoMethodNoSideEffect( isolate, tpl, "hasTopLevelAwait", HasTopLevelAwait); SetProtoMethodNoSideEffect(isolate, tpl, "getError", GetError); SetConstructorFunction(isolate, target, "ModuleWrap", tpl); isolate_data->set_module_wrap_constructor_template(tpl); SetMethod(isolate, target, "setImportModuleDynamicallyCallback", SetImportModuleDynamicallyCallback); SetMethod(isolate, target, "setInitializeImportMetaObjectCallback", SetInitializeImportMetaObjectCallback); SetMethod(isolate, target, "createRequiredModuleFacade", CreateRequiredModuleFacade); SetMethod(isolate, target, "throwIfPromiseRejected", ThrowIfPromiseRejected); } void ModuleWrap::CreatePerContextProperties(Local target, Local unused, Local context, void* priv) { Realm* realm = Realm::GetCurrent(context); Isolate* isolate = realm->isolate(); #define V(enum_type, name) \ target \ ->Set(context, \ FIXED_ONE_BYTE_STRING(isolate, #name), \ Integer::New(isolate, enum_type::name)) \ .FromJust() V(Module::Status, kUninstantiated); V(Module::Status, kInstantiating); V(Module::Status, kInstantiated); V(Module::Status, kEvaluating); V(Module::Status, kEvaluated); V(Module::Status, kErrored); V(ModulePhase, kEvaluationPhase); V(ModulePhase, kSourcePhase); #undef V } void ModuleWrap::RegisterExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(New); registry->Register(Link); registry->Register(GetModuleRequests); registry->Register(InstantiateSync); registry->Register(EvaluateSync); registry->Register(GetNamespaceSync); registry->Register(Instantiate); registry->Register(Evaluate); registry->Register(SetSyntheticExport); registry->Register(SetModuleSourceObject); registry->Register(GetModuleSourceObject); registry->Register(CreateCachedData); registry->Register(GetNamespace); registry->Register(GetStatus); registry->Register(GetError); registry->Register(IsGraphAsync); registry->Register(HasTopLevelAwait); registry->Register(CreateRequiredModuleFacade); registry->Register(SetImportModuleDynamicallyCallback); registry->Register(SetInitializeImportMetaObjectCallback); registry->Register(ThrowIfPromiseRejected); } } // namespace loader } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL( module_wrap, node::loader::ModuleWrap::CreatePerContextProperties) NODE_BINDING_PER_ISOLATE_INIT( module_wrap, node::loader::ModuleWrap::CreatePerIsolateProperties) NODE_BINDING_EXTERNAL_REFERENCE( module_wrap, node::loader::ModuleWrap::RegisterExternalReferences)