Files
node/src/module_wrap.cc
T
Joyee Cheung 85c31dfae1 esm: print required top-level await locations without evaluating
Previously in order to collect the locations of the TLA, we wait
until right before evalutation to ensure instantiation is
completed so that we can use
v8::Module::GetStalledTopLevelAwaitMessages(). Now we try to
add an additioanl shortcut to the source code in the module wraps
instead during compilation for modules that contain TLAs and
use acron to locate the TLAs when we need to throw
ERR_REQUIRE_AYNSC_MODULE, so we can do this as early as before
instantiation and do not need to run the module again to collect
the locations.

In addition, we now collect the require stack for
ERR_REQUIRE_ASYNC_MODULE too for better metadata in the errors.

Signed-off-by: Joyee Cheung <joyeec9h3@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64154
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
2026-07-02 09:42:32 +00:00

1712 lines
59 KiB
C++

#include "module_wrap.h"
#include "debug_utils-inl.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_sea.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<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(i)));
DCHECK(DataIsString(import_attributes->Get(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<ModuleRequest> v8_request) {
return From(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());
}
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
// For embedder ESM in a SEA, use the bundled code cache if available.
if (id_symbol == realm->isolate_data()->embedder_module_hdo() &&
sea::IsSingleExecutable()) {
sea::SeaResource sea = sea::FindSingleExecutableResource();
if (sea.use_code_cache()) {
std::string_view data = sea.code_cache.value();
user_cached_data = new ScriptCompiler::CachedData(
reinterpret_cast<const uint8_t*>(data.data()),
static_cast<int>(data.size()),
ScriptCompiler::CachedData::BufferNotOwned);
}
}
#endif // !DISABLE_SINGLE_EXECUTABLE_APPLICATION
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) {
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
if (id_symbol == realm->isolate_data()->embedder_module_hdo() &&
sea::IsSingleExecutable()) {
if (cache_rejected) {
per_process::Debug(DebugCategory::SEA,
"SEA module code cache rejected\n");
ProcessEmitWarningSync(realm->env(), "Code cache data rejected.");
} else {
per_process::Debug(DebugCategory::SEA,
"SEA module code cache accepted\n");
}
} else // NOLINT(readability/braces)
#endif // !DISABLE_SINGLE_EXECUTABLE_APPLICATION
if (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::kDefer:
return kDeferPhase;
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(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(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;
}
// Graphs with top-level await are rejected by the caller before evaluation
// starts, so the promise must have been settled synchronously.
CHECK(!obj->HasAsyncGraph());
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() ||
id == env->embedder_module_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, kDeferPhase);
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)