| #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_url.h" |
| #include "node_watchdog.h" |
| #include "util-inl.h" |
| |
| #include <sys/stat.h> // S_IFDIR |
| |
| #include <algorithm> |
| |
| namespace node { |
| namespace loader { |
| |
| using errors::TryCatchScope; |
| |
| using node::contextify::ContextifyContext; |
| using v8::Array; |
| using v8::ArrayBufferView; |
| using v8::Boolean; |
| 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::PropertyAttribute; |
| using v8::PropertyCallbackInfo; |
| using v8::ScriptCompiler; |
| using v8::ScriptOrigin; |
| using v8::String; |
| using v8::Symbol; |
| using v8::UnboundModuleScript; |
| using v8::Undefined; |
| using v8::Value; |
| |
| inline bool DataIsString(Local<Data> data) { |
| return data->IsValue() && data.As<Value>()->IsString(); |
| } |
| |
| void ModuleCacheKey::MemoryInfo(MemoryTracker* tracker) const { |
| tracker->TrackField("specifier", specifier); |
| tracker->TrackField("import_attributes", import_attributes); |
| } |
| |
| std::string ModuleCacheKey::ToString() const { |
| std::string result = "ModuleCacheKey(\"" + specifier + "\""; |
| if (!import_attributes.empty()) { |
| result += ", {"; |
| bool first = true; |
| for (const auto& attr : import_attributes) { |
| if (first) { |
| first = false; |
| } else { |
| result += ", "; |
| } |
| result += attr.first + ": " + attr.second; |
| } |
| result += "}"; |
| } |
| result += ")"; |
| return result; |
| } |
| |
| template <int elements_per_attribute> |
| ModuleCacheKey ModuleCacheKey::From(Local<Context> context, |
| Local<String> specifier, |
| Local<FixedArray> import_attributes) { |
| CHECK_EQ(import_attributes->Length() % elements_per_attribute, 0); |
| Isolate* isolate = Isolate::GetCurrent(); |
| 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) { |
| DCHECK(DataIsString(import_attributes->Get(context, i))); |
| DCHECK(DataIsString(import_attributes->Get(context, i + 1))); |
| |
| Local<String> v8_key = import_attributes->Get(i).As<String>(); |
| Local<String> v8_value = import_attributes->Get(i + 1).As<String>(); |
| 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> context, |
| Local<ModuleRequest> v8_request) { |
| return From( |
| context, v8_request->GetSpecifier(), v8_request->GetImportAttributes()); |
| } |
| |
| ModuleWrap::ModuleWrap(Realm* realm, |
| Local<Object> object, |
| Local<Module> module, |
| Local<String> url, |
| Local<Object> context_object, |
| Local<Value> synthetic_evaluation_step) |
| : BaseObject(realm, object), |
| url_(Utf8Value(realm->isolate(), url).ToString()), |
| module_(realm->isolate(), module), |
| module_hash_(module->GetIdentityHash()) { |
| realm->env()->hash_to_module_map.emplace(module_hash_, this); |
| |
| object->SetInternalField(kModuleSlot, module); |
| object->SetInternalField(kModuleSourceObjectSlot, |
| v8::Undefined(realm->isolate())); |
| object->SetInternalField(kSyntheticEvaluationStepsSlot, |
| synthetic_evaluation_step); |
| object->SetInternalField(kContextObjectSlot, context_object); |
| object->SetInternalField(kLinkedRequestsSlot, |
| v8::Undefined(realm->isolate())); |
| |
| if (!synthetic_evaluation_step->IsUndefined()) { |
| synthetic_ = true; |
| // Synthetic modules have no dependencies. |
| linked_ = 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<Context> ModuleWrap::context() const { |
| Local<Value> obj = object()->GetInternalField(kContextObjectSlot).As<Value>(); |
| // 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<Object>()->GetCreationContextChecked(); |
| } |
| |
| ModuleWrap* ModuleWrap::GetFromModule(Environment* env, |
| Local<Module> 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<bool> ModuleWrap::CheckUnsettledTopLevelAwait() { |
| Isolate* isolate = env()->isolate(); |
| Local<Context> 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 = module_.Get(isolate); |
| // It's a synthetic module, likely a facade wrapping CJS. |
| if (!module->IsSourceTextModule()) { |
| return Just(true); |
| } |
| |
| if (!HasAsyncGraph()) { // 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); |
| } |
| |
| bool ModuleWrap::HasAsyncGraph() { |
| if (!has_async_graph_.has_value()) { |
| Isolate* isolate = env()->isolate(); |
| HandleScope scope(isolate); |
| |
| has_async_graph_ = module_.Get(isolate)->IsGraphAsync(); |
| } |
| return has_async_graph_.value(); |
| } |
| |
| Local<PrimitiveArray> ModuleWrap::GetHostDefinedOptions( |
| Isolate* isolate, Local<Symbol> id_symbol) { |
| Local<PrimitiveArray> 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<Value>& args) { |
| CHECK(args.IsConstructCall()); |
| CHECK_GE(args.Length(), 3); |
| |
| Realm* realm = Realm::GetCurrent(args); |
| Isolate* isolate = realm->isolate(); |
| |
| Local<Object> that = args.This(); |
| |
| CHECK(args[0]->IsString()); |
| Local<String> url = args[0].As<String>(); |
| |
| Local<Context> 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<Object>()); |
| 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<PrimitiveArray> host_defined_options = |
| PrimitiveArray::New(isolate, HostDefinedOptions::kLength); |
| Local<Symbol> 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<Int32>()->Value(); |
| CHECK(args[4]->IsNumber()); |
| column_offset = args[4].As<Int32>()->Value(); |
| if (args[5]->IsSymbol()) { |
| id_symbol = args[5].As<Symbol>(); |
| 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> module; |
| |
| { |
| Context::Scope context_scope(context); |
| if (synthetic) { |
| CHECK(args[2]->IsArray()); |
| Local<Array> export_names_arr = args[2].As<Array>(); |
| |
| uint32_t len = export_names_arr->Length(); |
| LocalVector<String> export_names(realm->isolate(), len); |
| for (uint32_t i = 0; i < len; i++) { |
| Local<Value> 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<String>(); |
| } |
| |
| const MemorySpan<const Local<String>> 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<ScriptCompiler::CachedData*> 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<ArrayBufferView> cached_data_buf = args[5].As<ArrayBufferView>(); |
| uint8_t* data = |
| static_cast<uint8_t*>(cached_data_buf->Buffer()->Data()); |
| user_cached_data = |
| new ScriptCompiler::CachedData(data + cached_data_buf->ByteOffset(), |
| cached_data_buf->ByteLength()); |
| } |
| Local<String> source_text = args[2].As<String>(); |
| |
| 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()->has_top_level_await_string(), |
| Boolean::New(isolate, module->HasTopLevelAwait())) |
| .IsNothing()) { |
| 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()->synthetic_string(), |
| Boolean::New(isolate, synthetic)) |
| .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<Object> context_object = context->GetExtrasBindingObject(); |
| Local<Value> synthetic_evaluation_step = |
| synthetic ? args[3] : Undefined(realm->isolate()).As<Value>(); |
| |
| ModuleWrap* obj = new ModuleWrap( |
| realm, that, module, url, context_object, synthetic_evaluation_step); |
| |
| obj->contextify_context_ = contextify_context; |
| |
| args.GetReturnValue().Set(that); |
| } |
| |
| MaybeLocal<Module> ModuleWrap::CompileSourceTextModule( |
| Realm* realm, |
| Local<String> source_text, |
| Local<String> url, |
| int line_offset, |
| int column_offset, |
| Local<PrimitiveArray> host_defined_options, |
| std::optional<ScriptCompiler::CachedData*> 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<Value>(), // 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> module; |
| if (!ScriptCompiler::CompileModule(isolate, &source, options) |
| .ToLocal(&module)) { |
| return scope.EscapeMaybe(MaybeLocal<Module>()); |
| } |
| |
| 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; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| static Local<Object> createImportAttributesContainer( |
| Realm* realm, |
| Isolate* isolate, |
| Local<FixedArray> 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<Name> names(isolate, num_attributes); |
| LocalVector<Value> values(isolate, num_attributes); |
| |
| for (int i = 0; i < raw_attributes->Length(); i += elements_per_attribute) { |
| Local<Data> key = raw_attributes->Get(i); |
| Local<Data> value = raw_attributes->Get(i + 1); |
| DCHECK(DataIsString(key)); |
| DCHECK(DataIsString(value)); |
| |
| int idx = i / elements_per_attribute; |
| names[idx] = key.As<String>(); |
| values[idx] = value.As<String>(); |
| } |
| |
| Local<Object> attributes = Object::New( |
| isolate, Null(isolate), names.data(), values.data(), num_attributes); |
| attributes->SetIntegrityLevel(realm->context(), v8::IntegrityLevel::kFrozen) |
| .Check(); |
| return attributes; |
| } |
| |
| static Local<Array> createModuleRequestsContainer( |
| Realm* realm, Isolate* isolate, Local<FixedArray> raw_requests) { |
| EscapableHandleScope scope(isolate); |
| Local<Context> context = realm->context(); |
| LocalVector<Value> requests(isolate, raw_requests->Length()); |
| |
| for (int i = 0; i < raw_requests->Length(); i++) { |
| DCHECK(raw_requests->Get(context, i)->IsModuleRequest()); |
| Local<ModuleRequest> module_request = |
| raw_requests->Get(i).As<ModuleRequest>(); |
| |
| Local<String> specifier = module_request->GetSpecifier(); |
| |
| // Contains the import attributes for this request in the form: |
| // [key1, value1, source_offset1, key2, value2, source_offset2, ...]. |
| Local<FixedArray> raw_attributes = module_request->GetImportAttributes(); |
| Local<Object> attributes = |
| createImportAttributesContainer(realm, isolate, raw_attributes, 3); |
| ModuleImportPhase phase = module_request->GetPhase(); |
| |
| Local<Name> names[] = { |
| realm->isolate_data()->specifier_string(), |
| realm->isolate_data()->attributes_string(), |
| realm->isolate_data()->phase_string(), |
| }; |
| Local<Value> values[] = { |
| specifier, |
| attributes, |
| Integer::New(isolate, to_phase_constant(phase)), |
| }; |
| DCHECK_EQ(arraysize(names), arraysize(values)); |
| |
| Local<Object> 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<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| Isolate* isolate = args.GetIsolate(); |
| Local<Object> that = args.This(); |
| |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, that); |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| args.GetReturnValue().Set(createModuleRequestsContainer( |
| realm, isolate, module->GetModuleRequests())); |
| } |
| |
| // moduleWrap.link(moduleWraps) |
| void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| HandleScope handle_scope(isolate); |
| Realm* realm = Realm::GetCurrent(args); |
| Local<Context> context = realm->context(); |
| |
| ModuleWrap* dependent; |
| ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This()); |
| |
| CHECK_EQ(args.Length(), 1); |
| |
| Local<FixedArray> requests = |
| dependent->module_.Get(isolate)->GetModuleRequests(); |
| Local<Array> modules = args[0].As<Array>(); |
| std::vector<Global<Value>> modules_vector; |
| if (FromV8Array(context, modules, &modules_vector).IsEmpty()) { |
| return; |
| } |
| size_t request_count = static_cast<size_t>(requests->Length()); |
| CHECK_EQ(modules_vector.size(), request_count); |
| std::vector<ModuleWrap*> linked_module_wraps(request_count); |
| |
| // Track the duplicated module requests. For example if a modulelooks like |
| // this: |
| // |
| // import { foo } from 'mod' with { type: 'json' }; |
| // import source ModSource from 'mod' with { type: 'json' }; |
| // import { baz } from 'mod2'; |
| // |
| // The first two module requests are identical. The map would look like |
| // { mod_key: 0, mod2_key: 2 } in this case, so that module request 0 and |
| // module request 1 would be mapped to mod_key and both should resolve to the |
| // module identified by module request 0 (the first one with this identity), |
| // and module request 2 should resolve the module identified by index 2. |
| std::unordered_map<ModuleCacheKey, size_t, ModuleCacheKey::Hash> |
| module_request_map; |
| |
| for (size_t i = 0; i < request_count; i++) { |
| // TODO(joyeecheung): merge this with the serializeKey() in module_map.js. |
| // This currently doesn't sort the import attributes. |
| Local<Value> module_value = modules_vector[i].Get(isolate); |
| ModuleCacheKey module_cache_key = |
| ModuleCacheKey::From(context, requests->Get(i).As<ModuleRequest>()); |
| auto it = module_request_map.find(module_cache_key); |
| if (it == module_request_map.end()) { |
| // This is the first request with this identity, record it - any mismatch |
| // for this would only be found in subsequent requests, so no need to |
| // check here. |
| module_request_map[module_cache_key] = i; |
| } else { // This identity has been seen before, check for mismatch. |
| size_t first_seen_index = it->second; |
| // Check that the module is the same as the one resolved by the first |
| // request with this identity. |
| Local<Value> first_seen_value = |
| modules_vector[first_seen_index].Get(isolate); |
| if (!module_value->StrictEquals(first_seen_value)) { |
| // If the module is different from the one of the same request, throw an |
| // error. |
| THROW_ERR_MODULE_LINK_MISMATCH( |
| realm->env(), |
| "Module request '%s' at index %d must be linked " |
| "to the same module requested at index %d", |
| module_cache_key.ToString(), |
| i, |
| first_seen_index); |
| return; |
| } |
| } |
| |
| CHECK(module_value->IsObject()); // Guaranteed by link methods in JS land. |
| ModuleWrap* resolved = |
| BaseObject::Unwrap<ModuleWrap>(module_value.As<Object>()); |
| CHECK_NOT_NULL(resolved); // Guaranteed by link methods in JS land. |
| linked_module_wraps[i] = resolved; |
| } |
| |
| args.This()->SetInternalField(kLinkedRequestsSlot, modules); |
| std::swap(dependent->linked_module_wraps_, linked_module_wraps); |
| dependent->linked_ = true; |
| } |
| |
| void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| Isolate* isolate = args.GetIsolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| Local<Context> context = obj->context(); |
| Local<Module> module = obj->module_.Get(isolate); |
| Environment* env = realm->env(); |
| |
| if (!obj->IsLinked()) { |
| THROW_ERR_VM_MODULE_LINK_FAILURE(env, "module is not linked"); |
| return; |
| } |
| |
| { |
| TryCatchScope try_catch(env); |
| USE(module->InstantiateModule( |
| context, ResolveModuleCallback, ResolveSourceCallback)); |
| |
| 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; |
| } |
| } |
| } |
| |
| void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| Isolate* isolate = realm->isolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| Local<Context> context = obj->context(); |
| Local<Module> 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<Value> result; |
| { |
| auto wd = timeout != -1 |
| ? std::make_optional<Watchdog>(isolate, timeout, &timed_out) |
| : std::nullopt; |
| auto swd = break_on_sigint ? std::make_optional<SigintWatchdog>( |
| isolate, &received_signal) |
| : std::nullopt; |
| |
| result = module->Evaluate(context); |
| |
| Local<Value> res; |
| if (result.ToLocal(&res) && microtask_queue) { |
| DCHECK(res->IsPromise()); |
| |
| // To address https://github.com/nodejs/node/issues/59541 when the |
| // module has its own separate microtask queue in microtaskMode |
| // "afterEvaluate", we avoid returning a promise built inside the |
| // module's own context. |
| // |
| // Instead, we build a promise in the outer context, which we resolve |
| // with {result}, then we checkpoint the module's own queue, and finally |
| // we return the outer-context promise. |
| // |
| // If we simply returned the inner promise {result} directly, per |
| // https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob, the outer |
| // context, when resolving a promise coming from a different context, |
| // would need to enqueue a task (known as a thenable job task) onto the |
| // queue of that different context (the module's context). But this queue |
| // will normally not be checkpointed after evaluate() returns. |
| // |
| // This means that the execution flow in the outer context would |
| // silently fall through at the statement (in lib/internal/vm/module.js): |
| // await this[kWrap].evaluate(timeout, breakOnSigint) |
| // |
| // This is true for any promises created inside the module's context |
| // and made available to the outer context, as the node:vm doc explains. |
| // |
| // We must handle this particular return value differently to make it |
| // possible to await on the result of evaluate(). |
| Local<Context> outer_context = isolate->GetCurrentContext(); |
| Local<Promise::Resolver> resolver; |
| if (!Promise::Resolver::New(outer_context).ToLocal(&resolver)) { |
| result = {}; |
| } |
| if (resolver->Resolve(outer_context, res).IsNothing()) { |
| result = {}; |
| } |
| result = resolver->GetPromise(); |
| |
| microtask_queue->PerformCheckpoint(isolate); |
| } |
| } |
| |
| 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<Value> res; |
| if (result.ToLocal(&res)) { |
| args.GetReturnValue().Set(res); |
| } |
| } |
| |
| Maybe<void> ThrowIfPromiseRejected(Realm* realm, Local<Promise> promise) { |
| Isolate* isolate = realm->isolate(); |
| Local<Context> 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<Value> type = |
| Integer::New(isolate, |
| static_cast<int32_t>( |
| PromiseRejectEvent::kPromiseHandlerAddedAfterReject)); |
| Local<Value> args[] = {type, promise, Undefined(isolate)}; |
| if (realm->promise_reject_callback() |
| ->Call(context, Undefined(isolate), arraysize(args), args) |
| .IsEmpty()) { |
| return Nothing<void>(); |
| } |
| Local<Value> exception = promise->Result(); |
| Local<Message> message = Exception::CreateMessage(isolate, exception); |
| AppendExceptionLine( |
| realm->env(), exception, message, ErrorHandlingMode::MODULE_ERROR); |
| isolate->ThrowException(exception); |
| return Nothing<void>(); |
| } |
| |
| void ThrowIfPromiseRejected(const FunctionCallbackInfo<Value>& args) { |
| if (!args[0]->IsPromise()) { |
| return; |
| } |
| ThrowIfPromiseRejected(Realm::GetCurrent(args), args[0].As<Promise>()); |
| } |
| |
| void ModuleWrap::EvaluateSync(const FunctionCallbackInfo<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| Isolate* isolate = args.GetIsolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| Local<Context> context = obj->context(); |
| Local<Module> module = obj->module_.Get(isolate); |
| Environment* env = realm->env(); |
| |
| Local<Value> 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> promise = result.As<Promise>(); |
| if (ThrowIfPromiseRejected(realm, promise).IsNothing()) { |
| return; |
| } |
| |
| if (obj->HasAsyncGraph()) { |
| 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::GetNamespace(const FunctionCallbackInfo<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| Isolate* isolate = args.GetIsolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| if (module->GetStatus() < Module::kInstantiated) { |
| return THROW_ERR_MODULE_NOT_INSTANTIATED(realm->env()); |
| } |
| |
| Local<Value> result = module->GetModuleNamespace(); |
| args.GetReturnValue().Set(result); |
| } |
| |
| void ModuleWrap::SetModuleSourceObject( |
| const FunctionCallbackInfo<Value>& 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<Value>() |
| ->IsUndefined()); |
| |
| obj->object()->SetInternalField(kModuleSourceObjectSlot, args[0]); |
| } |
| |
| void ModuleWrap::GetModuleSourceObject( |
| const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| |
| CHECK_EQ(args.Length(), 0); |
| Local<Value> module_source_object = |
| obj->object()->GetInternalField(kModuleSourceObjectSlot).As<Value>(); |
| |
| if (module_source_object->IsUndefined()) { |
| THROW_ERR_SOURCE_PHASE_NOT_DEFINED(isolate, obj->url_); |
| return; |
| } |
| |
| args.GetReturnValue().Set(module_source_object); |
| } |
| |
| void ModuleWrap::GetStatus(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| |
| args.GetReturnValue().Set(module->GetStatus()); |
| } |
| |
| void ModuleWrap::GetError(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| args.GetReturnValue().Set(module->GetException()); |
| } |
| |
| void ModuleWrap::HasAsyncGraph(Local<Name> property, |
| const PropertyCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Environment* env = Environment::GetCurrent(isolate); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.HolderV2()); |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| if (module->GetStatus() < Module::kInstantiated) { |
| return THROW_ERR_MODULE_NOT_INSTANTIATED(env); |
| } |
| |
| args.GetReturnValue().Set(obj->HasAsyncGraph()); |
| } |
| |
| // static |
| MaybeLocal<Module> ModuleWrap::ResolveModuleCallback( |
| Local<Context> context, |
| size_t module_request_index, |
| Local<Module> referrer) { |
| ModuleWrap* resolved_module; |
| if (!ResolveModule(context, module_request_index, referrer) |
| .To(&resolved_module)) { |
| return {}; |
| } |
| DCHECK_NOT_NULL(resolved_module); |
| return resolved_module->module_.Get(Isolate::GetCurrent()); |
| } |
| |
| // static |
| MaybeLocal<Object> ModuleWrap::ResolveSourceCallback( |
| Local<Context> context, |
| size_t module_request_index, |
| Local<Module> referrer) { |
| ModuleWrap* resolved_module; |
| if (!ResolveModule(context, module_request_index, referrer) |
| .To(&resolved_module)) { |
| return {}; |
| } |
| DCHECK_NOT_NULL(resolved_module); |
| |
| Local<Value> module_source_object = |
| resolved_module->object() |
| ->GetInternalField(ModuleWrap::kModuleSourceObjectSlot) |
| .As<Value>(); |
| if (module_source_object->IsUndefined()) { |
| THROW_ERR_SOURCE_PHASE_NOT_DEFINED(Isolate::GetCurrent(), |
| resolved_module->url_); |
| return {}; |
| } |
| CHECK(module_source_object->IsObject()); |
| return module_source_object.As<Object>(); |
| } |
| |
| static std::string GetSpecifierFromModuleRequest(Local<Module> referrer, |
| size_t module_request_index) { |
| Local<ModuleRequest> raw_request = |
| referrer->GetModuleRequests() |
| ->Get(static_cast<int>(module_request_index)) |
| .As<ModuleRequest>(); |
| Local<String> specifier = raw_request->GetSpecifier(); |
| Utf8Value specifier_utf8(Isolate::GetCurrent(), specifier); |
| return specifier_utf8.ToString(); |
| } |
| |
| // static |
| Maybe<ModuleWrap*> ModuleWrap::ResolveModule(Local<Context> context, |
| size_t module_request_index, |
| Local<Module> referrer) { |
| Isolate* isolate = Isolate::GetCurrent(); |
| Environment* env = Environment::GetCurrent(context); |
| if (env == nullptr) { |
| THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate); |
| return Nothing<ModuleWrap*>(); |
| } |
| // Check that the referrer is not yet been instantiated. |
| DCHECK(referrer->GetStatus() <= Module::kInstantiated); |
| |
| ModuleWrap* dependent = ModuleWrap::GetFromModule(env, referrer); |
| if (dependent == nullptr) { |
| std::string specifier = |
| GetSpecifierFromModuleRequest(referrer, module_request_index); |
| THROW_ERR_VM_MODULE_LINK_FAILURE( |
| env, "request for '%s' is from invalid module", specifier); |
| return Nothing<ModuleWrap*>(); |
| } |
| if (!dependent->IsLinked()) { |
| std::string specifier = |
| GetSpecifierFromModuleRequest(referrer, module_request_index); |
| THROW_ERR_VM_MODULE_LINK_FAILURE(env, |
| "request for '%s' can not be resolved on " |
| "module '%s' that is not linked", |
| specifier, |
| dependent->url_); |
| return Nothing<ModuleWrap*>(); |
| } |
| |
| size_t linked_module_count = dependent->linked_module_wraps_.size(); |
| if (linked_module_count > 0) { |
| CHECK_LT(module_request_index, linked_module_count); |
| } else { |
| UNREACHABLE("Module resolution callback invoked for a module" |
| " without linked requests"); |
| } |
| |
| return Just(dependent->linked_module_wraps_[module_request_index]); |
| } |
| |
| static MaybeLocal<Promise> ImportModuleDynamicallyWithPhase( |
| Local<Context> context, |
| Local<Data> host_defined_options, |
| Local<Value> resource_name, |
| Local<String> specifier, |
| ModuleImportPhase phase, |
| Local<FixedArray> import_attributes) { |
| Isolate* isolate = Isolate::GetCurrent(); |
| Environment* env = Environment::GetCurrent(context); |
| if (env == nullptr) { |
| THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate); |
| return MaybeLocal<Promise>(); |
| } |
| 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<Function> import_callback = |
| realm->host_import_module_dynamically_callback(); |
| Local<Value> id; |
| |
| Local<FixedArray> options = host_defined_options.As<FixedArray>(); |
| // 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(HostDefinedOptions::kID).As<Symbol>(); |
| } else if (!context->Global() |
| ->GetPrivate(context, env->host_defined_option_symbol()) |
| .ToLocal(&id)) { |
| return MaybeLocal<Promise>(); |
| } |
| |
| Local<Object> attributes = |
| createImportAttributesContainer(realm, isolate, import_attributes, 2); |
| |
| Local<Value> import_args[] = { |
| id, |
| Local<Value>(specifier), |
| Integer::New(isolate, to_phase_constant(phase)), |
| attributes, |
| resource_name, |
| }; |
| |
| Local<Value> 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<Promise::Resolver> resolver; |
| if (!Promise::Resolver::New(context).ToLocal(&resolver)) { |
| return {}; |
| } |
| if (resolver->Resolve(context, result).IsNothing()) { |
| return {}; |
| } |
| return handle_scope.Escape(resolver->GetPromise()); |
| } |
| |
| static MaybeLocal<Promise> ImportModuleDynamically( |
| Local<Context> context, |
| Local<Data> host_defined_options, |
| Local<Value> resource_name, |
| Local<String> specifier, |
| Local<FixedArray> import_attributes) { |
| return ImportModuleDynamicallyWithPhase(context, |
| host_defined_options, |
| resource_name, |
| specifier, |
| ModuleImportPhase::kEvaluation, |
| import_attributes); |
| } |
| |
| void ModuleWrap::SetImportModuleDynamicallyCallback( |
| const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Realm* realm = Realm::GetCurrent(args); |
| HandleScope handle_scope(isolate); |
| |
| CHECK_EQ(args.Length(), 1); |
| CHECK(args[0]->IsFunction()); |
| Local<Function> import_callback = args[0].As<Function>(); |
| realm->set_host_import_module_dynamically_callback(import_callback); |
| |
| isolate->SetHostImportModuleDynamicallyCallback(ImportModuleDynamically); |
| isolate->SetHostImportModuleWithPhaseDynamicallyCallback( |
| ImportModuleDynamicallyWithPhase); |
| } |
| |
| void ModuleWrap::SetImportMetaResolveInitializer( |
| const v8::FunctionCallbackInfo<v8::Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Realm* realm = Realm::GetCurrent(args); |
| HandleScope handle_scope(isolate); |
| |
| CHECK_EQ(args.Length(), 1); |
| CHECK(args[0]->IsFunction()); |
| Local<Function> initializer = args[0].As<Function>(); |
| realm->set_host_import_meta_resolve_initializer(initializer); |
| } |
| |
| static void ImportMetaResolveLazyGetter( |
| Local<v8::Name> name, const PropertyCallbackInfo<Value>& info) { |
| Isolate* isolate = info.GetIsolate(); |
| Local<Value> receiver_val = info.HolderV2(); |
| if (!receiver_val->IsObject()) { |
| THROW_ERR_INVALID_INVOCATION(isolate); |
| return; |
| } |
| Local<Object> receiver = receiver_val.As<Object>(); |
| Local<Context> context; |
| if (!receiver->GetCreationContext().ToLocal(&context)) { |
| THROW_ERR_INVALID_INVOCATION(isolate); |
| return; |
| } |
| Realm* realm = Realm::GetCurrent(context); |
| if (realm == nullptr) { |
| THROW_ERR_INVALID_INVOCATION(isolate); |
| } |
| Local<Function> initializer = realm->host_import_meta_resolve_initializer(); |
| if (initializer.IsEmpty()) { |
| THROW_ERR_INVALID_INVOCATION(isolate); |
| return; |
| } |
| |
| // This should be createImportMetaResolve(). The loader argument is already |
| // bound at initialization time. |
| Local<Value> args[] = {info.Data()}; |
| Local<Value> ret; |
| if (!initializer |
| ->Call(context, Undefined(realm->isolate()), arraysize(args), args) |
| .ToLocal(&ret)) { |
| return; |
| } |
| info.GetReturnValue().Set(ret); |
| } |
| |
| static void PathHelpersLazyGetter(Local<v8::Name> name, |
| const PropertyCallbackInfo<Value>& info) { |
| Isolate* isolate = info.GetIsolate(); |
| // This getter has no JavaScript function representation and is not |
| // invoked in the creation context. |
| // When this getter is invoked in a vm context, the `Realm::GetCurrent(info)` |
| // returns a nullptr and retrieve the creation context via `this` object and |
| // get the creation Realm. |
| Local<Value> receiver_val = info.HolderV2(); |
| if (!receiver_val->IsObject()) { |
| THROW_ERR_INVALID_INVOCATION(isolate); |
| return; |
| } |
| Local<Object> receiver = receiver_val.As<Object>(); |
| Local<Context> context; |
| if (!receiver->GetCreationContext().ToLocal(&context)) { |
| THROW_ERR_INVALID_INVOCATION(isolate); |
| return; |
| } |
| Environment* env = Environment::GetCurrent(context); |
| |
| node::Utf8Value url(isolate, info.Data()); |
| auto file_url = ada::parse(url.ToStringView()); |
| CHECK(file_url); |
| auto file_path = url::FileURLToPath(env, *file_url); |
| CHECK(file_path.has_value()); |
| std::string_view ret_view = file_path.value(); |
| |
| node::Utf8Value utf8name(isolate, name); |
| auto plain_name = utf8name.ToStringView(); |
| if (plain_name == "dirname") { |
| #ifdef _WIN32 |
| #define PATH_SEPARATOR '\\' |
| #else |
| #define PATH_SEPARATOR '/' |
| #endif |
| auto index = ret_view.rfind(PATH_SEPARATOR); |
| CHECK(index != std::string_view::npos); |
| ret_view.remove_suffix(ret_view.size() - index); |
| #undef PATH_SEPARATOR |
| } |
| Local<Value> ret; |
| if (!ToV8Value(context, ret_view, isolate).ToLocal(&ret)) { |
| return; |
| } |
| info.GetReturnValue().Set(ret); |
| } |
| |
| static Maybe<void> DefaultImportMetaObjectInitializer(Realm* realm, |
| Local<Object> wrap, |
| Local<Object> meta) { |
| Local<Context> context = realm->context(); |
| Isolate* isolate = realm->isolate(); |
| Environment* env = realm->env(); |
| |
| Local<Value> url; |
| if (!wrap->Get(context, env->url_string()).ToLocal(&url)) { |
| return Nothing<void>(); |
| } |
| |
| // N.B.: Order is important to keep keys in alphabetical order. |
| |
| Utf8Value url_utf8(isolate, url); |
| if (url_utf8.ToStringView().starts_with("file:")) { |
| // Set a lazy getter of import.meta.dirname |
| if (meta->SetLazyDataProperty( |
| context, env->dirname_string(), PathHelpersLazyGetter, url) |
| .IsNothing()) { |
| return Nothing<void>(); |
| } |
| |
| // Set a lazy getter of import.meta.filename |
| if (meta->SetLazyDataProperty( |
| context, env->filename_string(), PathHelpersLazyGetter, url) |
| .IsNothing()) { |
| return Nothing<void>(); |
| } |
| } |
| |
| // Set import.meta.main = moduleWrap.isMain |
| Local<Value> is_main; |
| if (!wrap->Get(context, FIXED_ONE_BYTE_STRING(isolate, "isMain")) |
| .ToLocal(&is_main)) { |
| return Nothing<void>(); |
| } |
| if (meta->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "main"), |
| Boolean::New(isolate, is_main->IsTrue())) |
| .IsEmpty()) { |
| return Nothing<void>(); |
| } |
| |
| // Set a lazy getter of import.meta.resolve - only if the initializer is set, |
| // which is only the case when run on a non-loader-hook thread. |
| Local<Function> import_meta_resolve_initializer = |
| realm->host_import_meta_resolve_initializer(); |
| if (!import_meta_resolve_initializer.IsEmpty() && |
| meta->SetLazyDataProperty(context, |
| FIXED_ONE_BYTE_STRING(isolate, "resolve"), |
| ImportMetaResolveLazyGetter, |
| url) |
| .IsNothing()) { |
| return Nothing<void>(); |
| } |
| |
| // Set import.meta.url = moduleWrap.url |
| if (meta->Set(context, env->url_string(), url).IsEmpty()) { |
| return Nothing<void>(); |
| } |
| |
| return JustVoid(); |
| } |
| |
| void ModuleWrap::HostInitializeImportMetaObjectCallback( |
| Local<Context> context, Local<Module> module, Local<Object> 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<Object> wrap = module_wrap->object(); |
| Local<Function> callback = |
| realm->host_initialize_import_meta_object_callback(); |
| Local<Value> id; |
| if (!wrap->GetPrivate(context, env->host_defined_option_symbol()) |
| .ToLocal(&id)) { |
| return; |
| } |
| DCHECK(id->IsSymbol()); |
| |
| // Use the default initializer for source text modules without custom |
| // callbacks. |
| if (id == env->source_text_module_default_hdo()) { |
| USE(DefaultImportMetaObjectInitializer(realm, wrap, meta)); |
| return; |
| } |
| |
| // For modules that have custom callbacks, call into JS land |
| // to look up the callback from the registry. |
| Local<Value> 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<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| Isolate* isolate = realm->isolate(); |
| |
| CHECK_EQ(args.Length(), 1); |
| CHECK(args[0]->IsFunction()); |
| Local<Function> import_meta_callback = args[0].As<Function>(); |
| realm->set_host_initialize_import_meta_object_callback(import_meta_callback); |
| |
| isolate->SetHostInitializeImportMetaObjectCallback( |
| HostInitializeImportMetaObjectCallback); |
| } |
| |
| MaybeLocal<Value> ModuleWrap::SyntheticModuleEvaluationStepsCallback( |
| Local<Context> context, Local<Module> module) { |
| Environment* env = Environment::GetCurrent(context); |
| Isolate* isolate = env->isolate(); |
| |
| ModuleWrap* obj = GetFromModule(env, module); |
| |
| TryCatchScope try_catch(env); |
| Local<Function> synthetic_evaluation_steps = |
| obj->object() |
| ->GetInternalField(kSyntheticEvaluationStepsSlot) |
| .As<Value>() |
| .As<Function>(); |
| obj->object()->SetInternalField( |
| kSyntheticEvaluationStepsSlot, Undefined(isolate)); |
| MaybeLocal<Value> 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<Value>(); |
| } |
| |
| Local<Promise::Resolver> resolver; |
| if (!Promise::Resolver::New(context).ToLocal(&resolver)) { |
| return MaybeLocal<Value>(); |
| } |
| |
| if (resolver->Resolve(context, Undefined(isolate)).IsNothing()) { |
| return MaybeLocal<Value>(); |
| } |
| return resolver->GetPromise(); |
| } |
| |
| void ModuleWrap::SetSyntheticExport(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Local<Object> that = args.This(); |
| |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, that); |
| |
| CHECK(obj->synthetic_); |
| |
| CHECK_EQ(args.Length(), 2); |
| |
| CHECK(args[0]->IsString()); |
| Local<String> export_name = args[0].As<String>(); |
| |
| Local<Value> export_value = args[1]; |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| USE(module->SetSyntheticModuleExport(isolate, export_name, export_value)); |
| } |
| |
| void ModuleWrap::CreateCachedData(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Local<Object> that = args.This(); |
| |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, that); |
| |
| CHECK(!obj->synthetic_); |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| |
| CHECK_LT(module->GetStatus(), Module::Status::kEvaluating); |
| |
| Local<UnboundModuleScript> unbound_module_script = |
| module->GetUnboundModuleScript(); |
| std::unique_ptr<ScriptCompiler::CachedData> cached_data( |
| ScriptCompiler::CreateCodeCache(unbound_module_script)); |
| Environment* env = Environment::GetCurrent(args); |
| Local<Object> result; |
| if (!cached_data) { |
| if (!Buffer::New(env, 0).ToLocal(&result)) { |
| return; |
| } |
| } else if (!Buffer::Copy(env, |
| reinterpret_cast<const char*>(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<Module> LinkRequireFacadeWithOriginal( |
| Local<Context> context, |
| Local<String> specifier, |
| Local<FixedArray> import_attributes, |
| Local<Module> referrer) { |
| Environment* env = Environment::GetCurrent(context); |
| Isolate* isolate = Isolate::GetCurrent(); |
| 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<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Local<Context> context = isolate->GetCurrentContext(); |
| Environment* env = Environment::GetCurrent(context); |
| CHECK(args[0]->IsObject()); // original module |
| Local<Object> wrap = args[0].As<Object>(); |
| 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<Value>(), // 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<Module> 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<Value> evaluated; |
| if (!facade->Evaluate(context).ToLocal(&evaluated)) { |
| return; |
| } |
| CHECK(evaluated->IsPromise()); |
| CHECK_EQ(evaluated.As<Promise>()->State(), Promise::PromiseState::kFulfilled); |
| |
| args.GetReturnValue().Set(facade->GetModuleNamespace()); |
| } |
| |
| void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data, |
| Local<ObjectTemplate> target) { |
| Isolate* isolate = isolate_data->isolate(); |
| |
| Local<FunctionTemplate> tpl = NewFunctionTemplate(isolate, New); |
| tpl->InstanceTemplate()->SetInternalFieldCount( |
| ModuleWrap::kInternalFieldCount); |
| |
| SetProtoMethod(isolate, tpl, "link", Link); |
| SetProtoMethod(isolate, tpl, "getModuleRequests", GetModuleRequests); |
| SetProtoMethod(isolate, tpl, "instantiate", Instantiate); |
| SetProtoMethod(isolate, tpl, "evaluateSync", EvaluateSync); |
| 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, "getError", GetError); |
| SetConstructorFunction(isolate, target, "ModuleWrap", tpl); |
| |
| tpl->InstanceTemplate()->SetLazyDataProperty( |
| FIXED_ONE_BYTE_STRING(isolate, "hasAsyncGraph"), |
| HasAsyncGraph, |
| Local<Value>(), |
| PropertyAttribute::DontEnum); |
| |
| isolate_data->set_module_wrap_constructor_template(tpl); |
| |
| SetMethod(isolate, |
| target, |
| "setImportModuleDynamicallyCallback", |
| SetImportModuleDynamicallyCallback); |
| SetMethod(isolate, |
| target, |
| "setInitializeImportMetaObjectCallback", |
| SetInitializeImportMetaObjectCallback); |
| SetMethod(isolate, |
| target, |
| "setImportMetaResolveInitializer", |
| SetImportMetaResolveInitializer); |
| SetMethod(isolate, |
| target, |
| "createRequiredModuleFacade", |
| CreateRequiredModuleFacade); |
| SetMethod(isolate, target, "throwIfPromiseRejected", ThrowIfPromiseRejected); |
| } |
| |
| void ModuleWrap::CreatePerContextProperties(Local<Object> target, |
| Local<Value> unused, |
| Local<Context> 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(EvaluateSync); |
| 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(HasAsyncGraph); |
| |
| registry->Register(CreateRequiredModuleFacade); |
| |
| registry->Register(SetImportModuleDynamicallyCallback); |
| registry->Register(SetInitializeImportMetaObjectCallback); |
| registry->Register(SetImportMetaResolveInitializer); |
| 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) |