Skip to content

Commit 6a47ff4

Browse files
legendecastargos
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 Fixes: #50113 Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 713c70c commit 6a47ff4

File tree

3 files changed

+132
-86
lines changed

3 files changed

+132
-86
lines changed

src/module_wrap.cc

Lines changed: 96 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,23 @@ ModuleWrap::ModuleWrap(Realm* realm,
133133
object->SetInternalField(kSyntheticEvaluationStepsSlot,
134134
synthetic_evaluation_step);
135135
object->SetInternalField(kContextObjectSlot, context_object);
136+
object->SetInternalField(kLinkedRequestsSlot,
137+
v8::Undefined(realm->isolate()));
136138

137139
if (!synthetic_evaluation_step->IsUndefined()) {
138140
synthetic_ = true;
139141
}
140142
MakeWeak();
141143
module_.SetWeak();
144+
145+
HandleScope scope(realm->isolate());
146+
Local<Context> context = realm->context();
147+
Local<FixedArray> requests = module->GetModuleRequests();
148+
for (int i = 0; i < requests->Length(); i++) {
149+
ModuleCacheKey module_cache_key = ModuleCacheKey::From(
150+
context, requests->Get(context, i).As<ModuleRequest>());
151+
resolve_cache_[module_cache_key] = i;
152+
}
142153
}
143154

144155
ModuleWrap::~ModuleWrap() {
@@ -159,6 +170,30 @@ Local<Context> ModuleWrap::context() const {
159170
return obj.As<Object>()->GetCreationContextChecked();
160171
}
161172

173+
ModuleWrap* ModuleWrap::GetLinkedRequest(uint32_t index) {
174+
DCHECK(IsLinked());
175+
Isolate* isolate = env()->isolate();
176+
EscapableHandleScope scope(isolate);
177+
Local<Data> linked_requests_data =
178+
object()->GetInternalField(kLinkedRequestsSlot);
179+
DCHECK(linked_requests_data->IsValue() &&
180+
linked_requests_data.As<Value>()->IsArray());
181+
Local<Array> requests = linked_requests_data.As<Array>();
182+
183+
CHECK_LT(index, requests->Length());
184+
185+
Local<Value> module_value;
186+
if (!requests->Get(context(), index).ToLocal(&module_value)) {
187+
return nullptr;
188+
}
189+
CHECK(module_value->IsObject());
190+
Local<Object> module_object = module_value.As<Object>();
191+
192+
ModuleWrap* module_wrap;
193+
ASSIGN_OR_RETURN_UNWRAP(&module_wrap, module_object, nullptr);
194+
return module_wrap;
195+
}
196+
162197
ModuleWrap* ModuleWrap::GetFromModule(Environment* env,
163198
Local<Module> module) {
164199
auto range = env->hash_to_module_map.equal_range(module->GetIdentityHash());
@@ -571,34 +606,28 @@ void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo<Value>& args) {
571606
void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
572607
Realm* realm = Realm::GetCurrent(args);
573608
Isolate* isolate = args.GetIsolate();
574-
Local<Context> context = realm->context();
575609

576610
ModuleWrap* dependent;
577611
ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This());
578612

579613
CHECK_EQ(args.Length(), 1);
580614

615+
Local<Data> linked_requests =
616+
args.This()->GetInternalField(kLinkedRequestsSlot);
617+
if (linked_requests->IsValue() &&
618+
!linked_requests.As<Value>()->IsUndefined()) {
619+
// If the module is already linked, we should not link it again.
620+
THROW_ERR_VM_MODULE_LINK_FAILURE(realm->env(), "module is already linked");
621+
return;
622+
}
623+
581624
Local<FixedArray> requests =
582625
dependent->module_.Get(isolate)->GetModuleRequests();
583626
Local<Array> modules = args[0].As<Array>();
584627
CHECK_EQ(modules->Length(), static_cast<uint32_t>(requests->Length()));
585628

586-
std::vector<Global<Value>> modules_buffer;
587-
if (FromV8Array(context, modules, &modules_buffer).IsNothing()) {
588-
return;
589-
}
590-
591-
for (uint32_t i = 0; i < modules_buffer.size(); i++) {
592-
Local<Object> module_object = modules_buffer[i].Get(isolate).As<Object>();
593-
594-
CHECK(
595-
realm->isolate_data()->module_wrap_constructor_template()->HasInstance(
596-
module_object));
597-
598-
ModuleCacheKey module_cache_key = ModuleCacheKey::From(
599-
context, requests->Get(context, i).As<ModuleRequest>());
600-
dependent->resolve_cache_[module_cache_key].Reset(isolate, module_object);
601-
}
629+
args.This()->SetInternalField(kLinkedRequestsSlot, modules);
630+
dependent->linked_ = true;
602631
}
603632

604633
void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
@@ -612,9 +641,6 @@ void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
612641
USE(module->InstantiateModule(
613642
context, ResolveModuleCallback, ResolveSourceCallback));
614643

615-
// clear resolve cache on instantiate
616-
obj->resolve_cache_.clear();
617-
618644
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
619645
CHECK(!try_catch.Message().IsEmpty());
620646
CHECK(!try_catch.Exception().IsEmpty());
@@ -722,9 +748,6 @@ void ModuleWrap::InstantiateSync(const FunctionCallbackInfo<Value>& args) {
722748
USE(module->InstantiateModule(
723749
context, ResolveModuleCallback, ResolveSourceCallback));
724750

725-
// clear resolve cache on instantiate
726-
obj->resolve_cache_.clear();
727-
728751
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
729752
CHECK(!try_catch.Message().IsEmpty());
730753
CHECK(!try_catch.Exception().IsEmpty());
@@ -965,48 +988,51 @@ void ModuleWrap::GetError(const FunctionCallbackInfo<Value>& args) {
965988
args.GetReturnValue().Set(module->GetException());
966989
}
967990

991+
// static
968992
MaybeLocal<Module> ModuleWrap::ResolveModuleCallback(
969993
Local<Context> context,
970994
Local<String> specifier,
971995
Local<FixedArray> import_attributes,
972996
Local<Module> referrer) {
973-
Isolate* isolate = context->GetIsolate();
974-
Environment* env = Environment::GetCurrent(context);
975-
if (env == nullptr) {
976-
THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate);
977-
return MaybeLocal<Module>();
978-
}
979-
980-
ModuleCacheKey cache_key =
981-
ModuleCacheKey::From(context, specifier, import_attributes);
982-
983-
ModuleWrap* dependent = GetFromModule(env, referrer);
984-
if (dependent == nullptr) {
985-
THROW_ERR_VM_MODULE_LINK_FAILURE(
986-
env, "request for '%s' is from invalid module", cache_key.specifier);
987-
return MaybeLocal<Module>();
997+
ModuleWrap* resolved_module;
998+
if (!ResolveModule(context, specifier, import_attributes, referrer)
999+
.To(&resolved_module)) {
1000+
return {};
9881001
}
1002+
DCHECK_NOT_NULL(resolved_module);
1003+
return resolved_module->module_.Get(context->GetIsolate());
1004+
}
9891005

990-
if (dependent->resolve_cache_.count(cache_key) != 1) {
991-
THROW_ERR_VM_MODULE_LINK_FAILURE(
992-
env, "request for '%s' is not in cache", cache_key.specifier);
993-
return MaybeLocal<Module>();
1006+
// static
1007+
MaybeLocal<Object> ModuleWrap::ResolveSourceCallback(
1008+
Local<Context> context,
1009+
Local<String> specifier,
1010+
Local<FixedArray> import_attributes,
1011+
Local<Module> referrer) {
1012+
ModuleWrap* resolved_module;
1013+
if (!ResolveModule(context, specifier, import_attributes, referrer)
1014+
.To(&resolved_module)) {
1015+
return {};
9941016
}
1017+
DCHECK_NOT_NULL(resolved_module);
9951018

996-
Local<Object> module_object =
997-
dependent->resolve_cache_[cache_key].Get(isolate);
998-
if (module_object.IsEmpty() || !module_object->IsObject()) {
999-
THROW_ERR_VM_MODULE_LINK_FAILURE(
1000-
env, "request for '%s' did not return an object", cache_key.specifier);
1001-
return MaybeLocal<Module>();
1019+
Local<Value> module_source_object =
1020+
resolved_module->object()
1021+
->GetInternalField(ModuleWrap::kModuleSourceObjectSlot)
1022+
.As<Value>();
1023+
if (module_source_object->IsUndefined()) {
1024+
Local<String> url = resolved_module->object()
1025+
->GetInternalField(ModuleWrap::kURLSlot)
1026+
.As<String>();
1027+
THROW_ERR_SOURCE_PHASE_NOT_DEFINED(context->GetIsolate(), url);
1028+
return {};
10021029
}
1003-
1004-
ModuleWrap* module;
1005-
ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Module>());
1006-
return module->module_.Get(isolate);
1030+
CHECK(module_source_object->IsObject());
1031+
return module_source_object.As<Object>();
10071032
}
10081033

1009-
MaybeLocal<Object> ModuleWrap::ResolveSourceCallback(
1034+
// static
1035+
Maybe<ModuleWrap*> ModuleWrap::ResolveModule(
10101036
Local<Context> context,
10111037
Local<String> specifier,
10121038
Local<FixedArray> import_attributes,
@@ -1015,46 +1041,38 @@ MaybeLocal<Object> ModuleWrap::ResolveSourceCallback(
10151041
Environment* env = Environment::GetCurrent(context);
10161042
if (env == nullptr) {
10171043
THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate);
1018-
return MaybeLocal<Object>();
1044+
return Nothing<ModuleWrap*>();
10191045
}
1046+
// Check that the referrer is not yet been instantiated.
1047+
DCHECK(referrer->GetStatus() <= Module::kInstantiated);
10201048

10211049
ModuleCacheKey cache_key =
10221050
ModuleCacheKey::From(context, specifier, import_attributes);
10231051

1024-
ModuleWrap* dependent = GetFromModule(env, referrer);
1052+
ModuleWrap* dependent = ModuleWrap::GetFromModule(env, referrer);
10251053
if (dependent == nullptr) {
10261054
THROW_ERR_VM_MODULE_LINK_FAILURE(
10271055
env, "request for '%s' is from invalid module", cache_key.specifier);
1028-
return MaybeLocal<Object>();
1056+
return Nothing<ModuleWrap*>();
10291057
}
1030-
1031-
if (dependent->resolve_cache_.count(cache_key) != 1) {
1058+
if (!dependent->IsLinked()) {
10321059
THROW_ERR_VM_MODULE_LINK_FAILURE(
1033-
env, "request for '%s' is not in cache", cache_key.specifier);
1034-
return MaybeLocal<Object>();
1060+
env,
1061+
"request for '%s' is from a module not been linked",
1062+
cache_key.specifier);
1063+
return Nothing<ModuleWrap*>();
10351064
}
10361065

1037-
Local<Object> module_object =
1038-
dependent->resolve_cache_[cache_key].Get(isolate);
1039-
if (module_object.IsEmpty() || !module_object->IsObject()) {
1066+
auto it = dependent->resolve_cache_.find(cache_key);
1067+
if (it == dependent->resolve_cache_.end()) {
10401068
THROW_ERR_VM_MODULE_LINK_FAILURE(
1041-
env, "request for '%s' did not return an object", cache_key.specifier);
1042-
return MaybeLocal<Object>();
1069+
env, "request for '%s' is not in cache", cache_key.specifier);
1070+
return Nothing<ModuleWrap*>();
10431071
}
10441072

1045-
ModuleWrap* module;
1046-
ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Object>());
1047-
1048-
Local<Value> module_source_object =
1049-
module->object()->GetInternalField(kModuleSourceObjectSlot).As<Value>();
1050-
if (module_source_object->IsUndefined()) {
1051-
Local<String> url =
1052-
module->object()->GetInternalField(kURLSlot).As<String>();
1053-
THROW_ERR_SOURCE_PHASE_NOT_DEFINED(isolate, url);
1054-
return MaybeLocal<Object>();
1055-
}
1056-
CHECK(module_source_object->IsObject());
1057-
return module_source_object.As<Object>();
1073+
ModuleWrap* module_wrap = dependent->GetLinkedRequest(it->second);
1074+
CHECK_NOT_NULL(module_wrap);
1075+
return Just(module_wrap);
10581076
}
10591077

10601078
static MaybeLocal<Promise> ImportModuleDynamicallyWithPhase(

src/module_wrap.h

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,17 @@ struct ModuleCacheKey : public MemoryRetainer {
9090
};
9191

9292
class ModuleWrap : public BaseObject {
93+
using ResolveCache =
94+
std::unordered_map<ModuleCacheKey, uint32_t, ModuleCacheKey::Hash>;
95+
9396
public:
9497
enum InternalFields {
9598
kModuleSlot = BaseObject::kInternalFieldCount,
9699
kURLSlot,
97100
kModuleSourceObjectSlot,
98101
kSyntheticEvaluationStepsSlot,
99-
kContextObjectSlot, // Object whose creation context is the target Context
102+
kContextObjectSlot, // Object whose creation context is the target Context
103+
kLinkedRequestsSlot, // Array of linked requests
100104
kInternalFieldCount
101105
};
102106

@@ -112,23 +116,25 @@ class ModuleWrap : public BaseObject {
112116
v8::Local<v8::Module> module,
113117
v8::Local<v8::Object> meta);
114118

115-
void MemoryInfo(MemoryTracker* tracker) const override {
116-
tracker->TrackField("resolve_cache", resolve_cache_);
117-
}
118119
static void HasTopLevelAwait(const v8::FunctionCallbackInfo<v8::Value>& args);
119120

120121
v8::Local<v8::Context> context() const;
121122
v8::Maybe<bool> CheckUnsettledTopLevelAwait();
122123

123124
SET_MEMORY_INFO_NAME(ModuleWrap)
124125
SET_SELF_SIZE(ModuleWrap)
126+
SET_NO_MEMORY_INFO()
125127

126128
bool IsNotIndicativeOfMemoryLeakAtExit() const override {
127129
// XXX: The garbage collection rules for ModuleWrap are *super* unclear.
128130
// Do these objects ever get GC'd? Are we just okay with leaking them?
129131
return true;
130132
}
131133

134+
bool IsLinked() const { return linked_; }
135+
136+
ModuleWrap* GetLinkedRequest(uint32_t index);
137+
132138
static v8::Local<v8::PrimitiveArray> GetHostDefinedOptions(
133139
v8::Isolate* isolate, v8::Local<v8::Symbol> symbol);
134140

@@ -199,13 +205,19 @@ class ModuleWrap : public BaseObject {
199205
v8::Local<v8::Module> referrer);
200206
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);
201207

208+
// This method may throw a JavaScript exception, so the return type is
209+
// wrapped in a Maybe.
210+
static v8::Maybe<ModuleWrap*> ResolveModule(
211+
v8::Local<v8::Context> context,
212+
v8::Local<v8::String> specifier,
213+
v8::Local<v8::FixedArray> import_attributes,
214+
v8::Local<v8::Module> referrer);
215+
202216
v8::Global<v8::Module> module_;
203-
std::unordered_map<ModuleCacheKey,
204-
v8::Global<v8::Object>,
205-
ModuleCacheKey::Hash>
206-
resolve_cache_;
217+
ResolveCache resolve_cache_;
207218
contextify::ContextifyContext* contextify_context_ = nullptr;
208219
bool synthetic_ = false;
220+
bool linked_ = false;
209221
int module_hash_;
210222
};
211223

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)