| #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 <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::Context; |
| using v8::EscapableHandleScope; |
| using v8::FixedArray; |
| using v8::Function; |
| using v8::FunctionCallbackInfo; |
| using v8::FunctionTemplate; |
| using v8::HandleScope; |
| using v8::Int32; |
| using v8::Integer; |
| using v8::IntegrityLevel; |
| using v8::Isolate; |
| using v8::Local; |
| using v8::MaybeLocal; |
| using v8::MemorySpan; |
| using v8::MicrotaskQueue; |
| using v8::Module; |
| using v8::ModuleRequest; |
| using v8::Object; |
| using v8::ObjectTemplate; |
| using v8::PrimitiveArray; |
| using v8::Promise; |
| using v8::ScriptCompiler; |
| using v8::ScriptOrigin; |
| using v8::String; |
| using v8::Symbol; |
| using v8::UnboundModuleScript; |
| using v8::Undefined; |
| using v8::Value; |
| |
| 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), |
| 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(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<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; |
| } |
| |
| v8::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 v8::Just(true); |
| } |
| |
| if (!module->IsGraphAsync()) { // There is no TLA, no need to check. |
| return v8::Just(true); |
| } |
| |
| auto stalled_messages = |
| std::get<1>(module->GetStalledTopLevelAwaitMessages(isolate)); |
| if (stalled_messages.size() == 0) { |
| return v8::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 v8::Just(false); |
| } |
| |
| 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, columOffset, |
| // 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, columOffset[, |
| // cachedData]); |
| // new ModuleWrap(url, context, source, lineOffset, columOffset, |
| // 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(); |
| std::vector<Local<String>> export_names(len); |
| for (uint32_t i = 0; i < len; i++) { |
| Local<Value> export_name_val = |
| export_names_arr->Get(context, i).ToLocalChecked(); |
| 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<v8::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()->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<Object> context_object = context->GetExtrasBindingObject(); |
| Local<Value> synthetic_evaluation_step = |
| synthetic ? args[3] : Undefined(realm->isolate()).As<v8::Value>(); |
| |
| ModuleWrap* obj = new ModuleWrap( |
| realm, that, module, url, context_object, synthetic_evaluation_step); |
| |
| obj->contextify_context_ = contextify_context; |
| |
| that->SetIntegrityLevel(context, IntegrityLevel::kFrozen); |
| 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); |
| } |
| |
| 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; |
| std::vector<Local<v8::Name>> names(num_attributes); |
| std::vector<Local<v8::Value>> values(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<v8::Name>(); |
| values[idx] = raw_attributes->Get(realm->context(), i + 1).As<Value>(); |
| } |
| |
| return Object::New( |
| isolate, v8::Null(isolate), names.data(), values.data(), num_attributes); |
| } |
| |
| static Local<Array> createModuleRequestsContainer( |
| Realm* realm, Isolate* isolate, Local<FixedArray> raw_requests) { |
| std::vector<Local<Value>> requests(raw_requests->Length()); |
| |
| for (int i = 0; i < raw_requests->Length(); i++) { |
| Local<ModuleRequest> module_request = |
| raw_requests->Get(realm->context(), 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); |
| |
| Local<v8::Name> names[] = { |
| realm->isolate_data()->specifier_string(), |
| realm->isolate_data()->attributes_string(), |
| }; |
| Local<Value> values[] = { |
| specifier, |
| attributes, |
| }; |
| DCHECK_EQ(arraysize(names), arraysize(values)); |
| |
| Local<Object> request = Object::New( |
| isolate, v8::Null(isolate), names, values, arraysize(names)); |
| |
| requests[i] = request; |
| } |
| |
| return 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(specifiers, moduleWraps) |
| void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) { |
| Realm* realm = Realm::GetCurrent(args); |
| Isolate* isolate = args.GetIsolate(); |
| Local<Context> context = realm->context(); |
| |
| ModuleWrap* dependent; |
| ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This()); |
| |
| CHECK_EQ(args.Length(), 2); |
| |
| Local<Array> specifiers = args[0].As<Array>(); |
| Local<Array> modules = args[1].As<Array>(); |
| CHECK_EQ(specifiers->Length(), modules->Length()); |
| |
| std::vector<v8::Global<Value>> specifiers_buffer; |
| if (FromV8Array(context, specifiers, &specifiers_buffer).IsNothing()) { |
| return; |
| } |
| std::vector<v8::Global<Value>> modules_buffer; |
| if (FromV8Array(context, modules, &modules_buffer).IsNothing()) { |
| return; |
| } |
| |
| for (uint32_t i = 0; i < specifiers->Length(); i++) { |
| Local<String> specifier_str = |
| specifiers_buffer[i].Get(isolate).As<String>(); |
| Local<Object> module_object = modules_buffer[i].Get(isolate).As<Object>(); |
| |
| CHECK( |
| realm->isolate_data()->module_wrap_constructor_template()->HasInstance( |
| module_object)); |
| |
| Utf8Value specifier(isolate, specifier_str); |
| dependent->resolve_cache_[specifier.ToString()].Reset(isolate, |
| module_object); |
| } |
| } |
| |
| 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); |
| TryCatchScope try_catch(realm->env()); |
| USE(module->InstantiateModule(context, ResolveModuleCallback)); |
| |
| // 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<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 = args[0]->IntegerValue(realm->context()).FromJust(); |
| |
| 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 run = [&]() { |
| MaybeLocal<Value> 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; |
| } |
| |
| args.GetReturnValue().Set(result.ToLocalChecked()); |
| } |
| |
| void ModuleWrap::InstantiateSync(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(); |
| |
| { |
| TryCatchScope try_catch(env); |
| USE(module->InstantiateModule(context, ResolveModuleCallback)); |
| |
| // 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; |
| } |
| } |
| |
| // If --experimental-print-required-tla is true, proceeds to evaluation even |
| // if it's async because we want to search for the TLA and help users locate |
| // them. |
| if (module->IsGraphAsync() && !env->options()->print_required_tla) { |
| THROW_ERR_REQUIRE_ASYNC_MODULE(env); |
| return; |
| } |
| } |
| |
| 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 (promise->State() == Promise::PromiseState::kRejected) { |
| Local<Value> exception = promise->Result(); |
| Local<v8::Message> message = |
| v8::Exception::CreateMessage(isolate, exception); |
| AppendExceptionLine( |
| env, exception, message, ErrorHandlingMode::MODULE_ERROR); |
| isolate->ThrowException(exception); |
| 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); |
| return; |
| } |
| |
| CHECK_EQ(promise->State(), Promise::PromiseState::kFulfilled); |
| |
| args.GetReturnValue().Set(module->GetModuleNamespace()); |
| } |
| |
| void ModuleWrap::GetNamespaceSync(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); |
| |
| switch (module->GetStatus()) { |
| case v8::Module::Status::kUninstantiated: |
| case v8::Module::Status::kInstantiating: |
| return realm->env()->ThrowError( |
| "Cannot get namespace, module has not been instantiated"); |
| case v8::Module::Status::kInstantiated: |
| case v8::Module::Status::kEvaluated: |
| case v8::Module::Status::kErrored: |
| break; |
| case v8::Module::Status::kEvaluating: |
| UNREACHABLE(); |
| } |
| |
| if (module->IsGraphAsync()) { |
| return THROW_ERR_REQUIRE_ASYNC_MODULE(realm->env()); |
| } |
| Local<Value> result = module->GetModuleNamespace(); |
| args.GetReturnValue().Set(result); |
| } |
| |
| 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); |
| |
| switch (module->GetStatus()) { |
| case v8::Module::Status::kUninstantiated: |
| case v8::Module::Status::kInstantiating: |
| return realm->env()->ThrowError( |
| "cannot get namespace, module has not been instantiated"); |
| case v8::Module::Status::kInstantiated: |
| case v8::Module::Status::kEvaluating: |
| case v8::Module::Status::kEvaluated: |
| case v8::Module::Status::kErrored: |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| |
| Local<Value> result = module->GetModuleNamespace(); |
| args.GetReturnValue().Set(result); |
| } |
| |
| 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()); |
| } |
| |
| MaybeLocal<Module> ModuleWrap::ResolveModuleCallback( |
| Local<Context> context, |
| Local<String> specifier, |
| Local<FixedArray> import_attributes, |
| Local<Module> referrer) { |
| Isolate* isolate = context->GetIsolate(); |
| Environment* env = Environment::GetCurrent(context); |
| if (env == nullptr) { |
| THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate); |
| return MaybeLocal<Module>(); |
| } |
| |
| Utf8Value specifier_utf8(isolate, specifier); |
| std::string specifier_std(*specifier_utf8, specifier_utf8.length()); |
| |
| ModuleWrap* dependent = GetFromModule(env, referrer); |
| if (dependent == nullptr) { |
| THROW_ERR_VM_MODULE_LINK_FAILURE( |
| env, "request for '%s' is from invalid module", specifier_std); |
| return MaybeLocal<Module>(); |
| } |
| |
| if (dependent->resolve_cache_.count(specifier_std) != 1) { |
| THROW_ERR_VM_MODULE_LINK_FAILURE( |
| env, "request for '%s' is not in cache", specifier_std); |
| return MaybeLocal<Module>(); |
| } |
| |
| Local<Object> module_object = |
| dependent->resolve_cache_[specifier_std].Get(isolate); |
| if (module_object.IsEmpty() || !module_object->IsObject()) { |
| THROW_ERR_VM_MODULE_LINK_FAILURE( |
| env, "request for '%s' did not return an object", specifier_std); |
| return MaybeLocal<Module>(); |
| } |
| |
| ModuleWrap* module; |
| ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Module>()); |
| return module->module_.Get(isolate); |
| } |
| |
| static MaybeLocal<Promise> ImportModuleDynamically( |
| Local<Context> context, |
| Local<v8::Data> host_defined_options, |
| Local<Value> resource_name, |
| Local<String> specifier, |
| Local<FixedArray> import_attributes) { |
| Isolate* isolate = context->GetIsolate(); |
| 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(context, HostDefinedOptions::kID).As<Symbol>(); |
| } else { |
| id = context->Global() |
| ->GetPrivate(context, env->host_defined_option_symbol()) |
| .ToLocalChecked(); |
| } |
| |
| Local<Object> attributes = |
| createImportAttributesContainer(realm, isolate, import_attributes, 2); |
| |
| Local<Value> import_args[] = { |
| id, |
| Local<Value>(specifier), |
| attributes, |
| resource_name, |
| }; |
| |
| Local<Value> result; |
| if (import_callback->Call( |
| context, |
| Undefined(isolate), |
| arraysize(import_args), |
| import_args).ToLocal(&result)) { |
| CHECK(result->IsPromise()); |
| return handle_scope.Escape(result.As<Promise>()); |
| } |
| |
| return MaybeLocal<Promise>(); |
| } |
| |
| 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); |
| } |
| |
| 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()); |
| 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>(); |
| } |
| |
| resolver->Resolve(context, Undefined(isolate)).ToChecked(); |
| 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(), v8::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); |
| if (!cached_data) { |
| args.GetReturnValue().Set(Buffer::New(env, 0).ToLocalChecked()); |
| } else { |
| MaybeLocal<Object> buf = |
| Buffer::Copy(env, |
| reinterpret_cast<const char*>(cached_data->data), |
| cached_data->length); |
| args.GetReturnValue().Set(buf.ToLocalChecked()); |
| } |
| } |
| |
| 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, "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); |
| 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); |
| isolate_data->set_module_wrap_constructor_template(tpl); |
| |
| SetMethod(isolate, |
| target, |
| "setImportModuleDynamicallyCallback", |
| SetImportModuleDynamicallyCallback); |
| SetMethod(isolate, |
| target, |
| "setInitializeImportMetaObjectCallback", |
| SetInitializeImportMetaObjectCallback); |
| } |
| |
| 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(name) \ |
| target \ |
| ->Set(context, \ |
| FIXED_ONE_BYTE_STRING(isolate, #name), \ |
| Integer::New(isolate, Module::Status::name)) \ |
| .FromJust() |
| V(kUninstantiated); |
| V(kInstantiating); |
| V(kInstantiated); |
| V(kEvaluating); |
| V(kEvaluated); |
| V(kErrored); |
| #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(CreateCachedData); |
| registry->Register(GetNamespace); |
| registry->Register(GetStatus); |
| registry->Register(GetError); |
| |
| registry->Register(SetImportModuleDynamicallyCallback); |
| registry->Register(SetInitializeImportMetaObjectCallback); |
| } |
| } // 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) |