Skip to content

Commit 84701ff

Browse files
legendecasaduh95
authored andcommitted
src: clear all linked module caches once instantiated
There are two phases in module linking: link, and instantiate. These two operations are required to be separated to allow cyclic dependencies. `v8::Module::InstantiateModule` is only required to be invoked on the root module. The global references created by `ModuleWrap::Link` are only cleared at `ModuleWrap::Instantiate`. So the global references created for depended modules are usually not cleared because `ModuleWrap::Instantiate` is not invoked for each of depended modules, and caused memory leak. The change references the linked modules in an object internal slot. This is not an issue for Node.js ESM support as these modules can not be off-loaded. However, this could be outstanding for `vm.Module`. PR-URL: #59117 Backport-PR-URL: #60152 Fixes: #50113 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent ef005d0 commit 84701ff

File tree

3 files changed

+115
-46
lines changed

3 files changed

+115
-46
lines changed

src/module_wrap.cc

Lines changed: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,23 @@ ModuleWrap::ModuleWrap(Realm* realm,
123123
object->SetInternalField(kSyntheticEvaluationStepsSlot,
124124
synthetic_evaluation_step);
125125
object->SetInternalField(kContextObjectSlot, context_object);
126+
object->SetInternalField(kLinkedRequestsSlot,
127+
v8::Undefined(realm->isolate()));
126128

127129
if (!synthetic_evaluation_step->IsUndefined()) {
128130
synthetic_ = true;
129131
}
130132
MakeWeak();
131133
module_.SetWeak();
134+
135+
HandleScope scope(realm->isolate());
136+
Local<Context> context = realm->context();
137+
Local<FixedArray> requests = module->GetModuleRequests();
138+
for (int i = 0; i < requests->Length(); i++) {
139+
ModuleCacheKey module_cache_key = ModuleCacheKey::From(
140+
context, requests->Get(context, i).As<ModuleRequest>());
141+
resolve_cache_[module_cache_key] = i;
142+
}
132143
}
133144

134145
ModuleWrap::~ModuleWrap() {
@@ -149,6 +160,30 @@ Local<Context> ModuleWrap::context() const {
149160
return obj.As<Object>()->GetCreationContextChecked();
150161
}
151162

163+
ModuleWrap* ModuleWrap::GetLinkedRequest(uint32_t index) {
164+
DCHECK(IsLinked());
165+
Isolate* isolate = env()->isolate();
166+
EscapableHandleScope scope(isolate);
167+
Local<Data> linked_requests_data =
168+
object()->GetInternalField(kLinkedRequestsSlot);
169+
DCHECK(linked_requests_data->IsValue() &&
170+
linked_requests_data.As<Value>()->IsArray());
171+
Local<Array> requests = linked_requests_data.As<Array>();
172+
173+
CHECK_LT(index, requests->Length());
174+
175+
Local<Value> module_value;
176+
if (!requests->Get(context(), index).ToLocal(&module_value)) {
177+
return nullptr;
178+
}
179+
CHECK(module_value->IsObject());
180+
Local<Object> module_object = module_value.As<Object>();
181+
182+
ModuleWrap* module_wrap;
183+
ASSIGN_OR_RETURN_UNWRAP(&module_wrap, module_object, nullptr);
184+
return module_wrap;
185+
}
186+
152187
ModuleWrap* ModuleWrap::GetFromModule(Environment* env,
153188
Local<Module> module) {
154189
auto range = env->hash_to_module_map.equal_range(module->GetIdentityHash());
@@ -542,34 +577,28 @@ void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo<Value>& args) {
542577
void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
543578
Realm* realm = Realm::GetCurrent(args);
544579
Isolate* isolate = args.GetIsolate();
545-
Local<Context> context = realm->context();
546580

547581
ModuleWrap* dependent;
548582
ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This());
549583

550584
CHECK_EQ(args.Length(), 1);
551585

586+
Local<Data> linked_requests =
587+
args.This()->GetInternalField(kLinkedRequestsSlot);
588+
if (linked_requests->IsValue() &&
589+
!linked_requests.As<Value>()->IsUndefined()) {
590+
// If the module is already linked, we should not link it again.
591+
THROW_ERR_VM_MODULE_LINK_FAILURE(realm->env(), "module is already linked");
592+
return;
593+
}
594+
552595
Local<FixedArray> requests =
553596
dependent->module_.Get(isolate)->GetModuleRequests();
554597
Local<Array> modules = args[0].As<Array>();
555598
CHECK_EQ(modules->Length(), static_cast<uint32_t>(requests->Length()));
556599

557-
std::vector<Global<Value>> modules_buffer;
558-
if (FromV8Array(context, modules, &modules_buffer).IsNothing()) {
559-
return;
560-
}
561-
562-
for (uint32_t i = 0; i < modules_buffer.size(); i++) {
563-
Local<Object> module_object = modules_buffer[i].Get(isolate).As<Object>();
564-
565-
CHECK(
566-
realm->isolate_data()->module_wrap_constructor_template()->HasInstance(
567-
module_object));
568-
569-
ModuleCacheKey module_cache_key = ModuleCacheKey::From(
570-
context, requests->Get(context, i).As<ModuleRequest>());
571-
dependent->resolve_cache_[module_cache_key].Reset(isolate, module_object);
572-
}
600+
args.This()->SetInternalField(kLinkedRequestsSlot, modules);
601+
dependent->linked_ = true;
573602
}
574603

575604
void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
@@ -582,9 +611,6 @@ void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
582611
TryCatchScope try_catch(realm->env());
583612
USE(module->InstantiateModule(context, ResolveModuleCallback));
584613

585-
// clear resolve cache on instantiate
586-
obj->resolve_cache_.clear();
587-
588614
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
589615
CHECK(!try_catch.Message().IsEmpty());
590616
CHECK(!try_catch.Exception().IsEmpty());
@@ -731,9 +757,6 @@ void ModuleWrap::InstantiateSync(const FunctionCallbackInfo<Value>& args) {
731757
TryCatchScope try_catch(env);
732758
USE(module->InstantiateModule(context, ResolveModuleCallback));
733759

734-
// clear resolve cache on instantiate
735-
obj->resolve_cache_.clear();
736-
737760
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
738761
CHECK(!try_catch.Message().IsEmpty());
739762
CHECK(!try_catch.Exception().IsEmpty());
@@ -940,45 +963,63 @@ void ModuleWrap::GetError(const FunctionCallbackInfo<Value>& args) {
940963
args.GetReturnValue().Set(module->GetException());
941964
}
942965

966+
// static
943967
MaybeLocal<Module> ModuleWrap::ResolveModuleCallback(
944968
Local<Context> context,
945969
Local<String> specifier,
946970
Local<FixedArray> import_attributes,
947971
Local<Module> referrer) {
972+
ModuleWrap* resolved_module;
973+
if (!ResolveModule(context, specifier, import_attributes, referrer)
974+
.To(&resolved_module)) {
975+
return {};
976+
}
977+
DCHECK_NOT_NULL(resolved_module);
978+
return resolved_module->module_.Get(context->GetIsolate());
979+
}
980+
981+
// static
982+
Maybe<ModuleWrap*> ModuleWrap::ResolveModule(
983+
Local<Context> context,
984+
Local<String> specifier,
985+
Local<FixedArray> import_attributes,
986+
Local<Module> referrer) {
948987
Isolate* isolate = context->GetIsolate();
949988
Environment* env = Environment::GetCurrent(context);
950989
if (env == nullptr) {
951990
THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate);
952-
return MaybeLocal<Module>();
991+
return Nothing<ModuleWrap*>();
953992
}
993+
// Check that the referrer is not yet been instantiated.
994+
DCHECK(referrer->GetStatus() <= Module::kInstantiated);
954995

955996
ModuleCacheKey cache_key =
956997
ModuleCacheKey::From(context, specifier, import_attributes);
957998

958-
ModuleWrap* dependent = GetFromModule(env, referrer);
999+
ModuleWrap* dependent = ModuleWrap::GetFromModule(env, referrer);
9591000
if (dependent == nullptr) {
9601001
THROW_ERR_VM_MODULE_LINK_FAILURE(
9611002
env, "request for '%s' is from invalid module", cache_key.specifier);
962-
return MaybeLocal<Module>();
1003+
return Nothing<ModuleWrap*>();
9631004
}
964-
965-
if (dependent->resolve_cache_.count(cache_key) != 1) {
1005+
if (!dependent->IsLinked()) {
9661006
THROW_ERR_VM_MODULE_LINK_FAILURE(
967-
env, "request for '%s' is not in cache", cache_key.specifier);
968-
return MaybeLocal<Module>();
1007+
env,
1008+
"request for '%s' is from a module not been linked",
1009+
cache_key.specifier);
1010+
return Nothing<ModuleWrap*>();
9691011
}
9701012

971-
Local<Object> module_object =
972-
dependent->resolve_cache_[cache_key].Get(isolate);
973-
if (module_object.IsEmpty() || !module_object->IsObject()) {
1013+
auto it = dependent->resolve_cache_.find(cache_key);
1014+
if (it == dependent->resolve_cache_.end()) {
9741015
THROW_ERR_VM_MODULE_LINK_FAILURE(
975-
env, "request for '%s' did not return an object", cache_key.specifier);
976-
return MaybeLocal<Module>();
1016+
env, "request for '%s' is not in cache", cache_key.specifier);
1017+
return Nothing<ModuleWrap*>();
9771018
}
9781019

979-
ModuleWrap* module;
980-
ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Module>());
981-
return module->module_.Get(isolate);
1020+
ModuleWrap* module_wrap = dependent->GetLinkedRequest(it->second);
1021+
CHECK_NOT_NULL(module_wrap);
1022+
return Just(module_wrap);
9821023
}
9831024

9841025
static MaybeLocal<Promise> ImportModuleDynamically(

src/module_wrap.h

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,16 @@ struct ModuleCacheKey : public MemoryRetainer {
8484
};
8585

8686
class ModuleWrap : public BaseObject {
87+
using ResolveCache =
88+
std::unordered_map<ModuleCacheKey, uint32_t, ModuleCacheKey::Hash>;
89+
8790
public:
8891
enum InternalFields {
8992
kModuleSlot = BaseObject::kInternalFieldCount,
9093
kURLSlot,
9194
kSyntheticEvaluationStepsSlot,
92-
kContextObjectSlot, // Object whose creation context is the target Context
95+
kContextObjectSlot, // Object whose creation context is the target Context
96+
kLinkedRequestsSlot, // Array of linked requests
9397
kInternalFieldCount
9498
};
9599

@@ -105,23 +109,25 @@ class ModuleWrap : public BaseObject {
105109
v8::Local<v8::Module> module,
106110
v8::Local<v8::Object> meta);
107111

108-
void MemoryInfo(MemoryTracker* tracker) const override {
109-
tracker->TrackField("resolve_cache", resolve_cache_);
110-
}
111112
static void HasTopLevelAwait(const v8::FunctionCallbackInfo<v8::Value>& args);
112113

113114
v8::Local<v8::Context> context() const;
114115
v8::Maybe<bool> CheckUnsettledTopLevelAwait();
115116

116117
SET_MEMORY_INFO_NAME(ModuleWrap)
117118
SET_SELF_SIZE(ModuleWrap)
119+
SET_NO_MEMORY_INFO()
118120

119121
bool IsNotIndicativeOfMemoryLeakAtExit() const override {
120122
// XXX: The garbage collection rules for ModuleWrap are *super* unclear.
121123
// Do these objects ever get GC'd? Are we just okay with leaking them?
122124
return true;
123125
}
124126

127+
bool IsLinked() const { return linked_; }
128+
129+
ModuleWrap* GetLinkedRequest(uint32_t index);
130+
125131
static v8::Local<v8::PrimitiveArray> GetHostDefinedOptions(
126132
v8::Isolate* isolate, v8::Local<v8::Symbol> symbol);
127133

@@ -183,13 +189,19 @@ class ModuleWrap : public BaseObject {
183189
v8::Local<v8::Module> referrer);
184190
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);
185191

192+
// This method may throw a JavaScript exception, so the return type is
193+
// wrapped in a Maybe.
194+
static v8::Maybe<ModuleWrap*> ResolveModule(
195+
v8::Local<v8::Context> context,
196+
v8::Local<v8::String> specifier,
197+
v8::Local<v8::FixedArray> import_attributes,
198+
v8::Local<v8::Module> referrer);
199+
186200
v8::Global<v8::Module> module_;
187-
std::unordered_map<ModuleCacheKey,
188-
v8::Global<v8::Object>,
189-
ModuleCacheKey::Hash>
190-
resolve_cache_;
201+
ResolveCache resolve_cache_;
191202
contextify::ContextifyContext* contextify_context_ = nullptr;
192203
bool synthetic_ = false;
204+
bool linked_ = false;
193205
int module_hash_;
194206
};
195207

test/parallel/test-internal-module-wrap.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ const assert = require('assert');
66
const { internalBinding } = require('internal/test/binding');
77
const { ModuleWrap } = internalBinding('module_wrap');
88

9+
const unlinked = new ModuleWrap('unlinked', undefined, 'export * from "bar";', 0, 0);
10+
assert.throws(() => {
11+
unlinked.instantiate();
12+
}, {
13+
code: 'ERR_VM_MODULE_LINK_FAILURE',
14+
});
15+
16+
const dependsOnUnlinked = new ModuleWrap('dependsOnUnlinked', undefined, 'export * from "unlinked";', 0, 0);
17+
dependsOnUnlinked.link([unlinked]);
18+
assert.throws(() => {
19+
dependsOnUnlinked.instantiate();
20+
}, {
21+
code: 'ERR_VM_MODULE_LINK_FAILURE',
22+
});
23+
924
const foo = new ModuleWrap('foo', undefined, 'export * from "bar";', 0, 0);
1025
const bar = new ModuleWrap('bar', undefined, 'export const five = 5', 0, 0);
1126

@@ -22,4 +37,5 @@ const bar = new ModuleWrap('bar', undefined, 'export const five = 5', 0, 0);
2237

2338
// Check that the module requests are the same after linking, instantiate, and evaluation.
2439
assert.deepStrictEqual(moduleRequests, foo.getModuleRequests());
40+
2541
})().then(common.mustCall());

0 commit comments

Comments
 (0)