mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
When the source text module is compiled without custom callbacks, instead of calling into JS land from the per-isolate import.meta initializer and then back to C++ land to set up lazy data properties, just do the initialization all in C++ land. Only import.meta.resolve initialization will call back into JS land to generate a closure that call the cascaded loader for resolution. In addition, simplify the loader structure by merging allowImportMetaResolve into isForAsyncLoaderHookWorker - the two are essentially equivalent, as import.meta.resolve is only allowed in a non-loader-hook worker thread's loader. PR-URL: https://github.com/nodejs/node/pull/60603 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
1697 lines
58 KiB
C++
1697 lines
58 KiB
C++
#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(context, i).As<String>();
|
|
Local<String> v8_value =
|
|
import_attributes->Get(context, 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(realm->context(), i);
|
|
Local<Data> value = raw_attributes->Get(realm->context(), 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(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);
|
|
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(context, 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.This());
|
|
|
|
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<Context> context,
|
|
Local<Module> referrer,
|
|
size_t module_request_index) {
|
|
Local<ModuleRequest> raw_request =
|
|
referrer->GetModuleRequests()
|
|
->Get(context, 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(context, 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(context, 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(context, 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.This();
|
|
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.This();
|
|
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)
|