Skip to content

Commit a86a2e1

Browse files
joyeecheungrichardlau
authored andcommittedMar 15, 2024
module: use symbol in WeakMap to manage host defined options
Previously when managing the importModuleDynamically callback of vm.compileFunction(), we use an ID number as the host defined option and maintain a per-Environment ID -> CompiledFnEntry map to retain the top-level referrer function returned by vm.compileFunction() in order to pass it back to the callback, but it would leak because with how we used v8::Persistent to maintain this reference, V8 would not be able to understand the cycle and would just think that the CompiledFnEntry was supposed to live forever. We made an attempt to make that reference known to V8 by making the CompiledFnEntry weak and using a private symbol to make CompiledFnEntry strongly references the top-level referrer function in #46785, but that turned out to be unsound, because the there's no guarantee that the top-level function must be alive while import() can still be initiated from that function, since V8 could discard the top-level function and only keep inner functions alive, so relying on the top-level function to keep the CompiledFnEntry alive could result in use-after-free which caused a revert of that fix. With this patch we use a symbol in the host defined options instead of a number, because with the stage-3 symbol-as-weakmap-keys proposal we could directly use that symbol to keep the referrer alive using a WeakMap. As a bonus this also keeps the other kinds of referrers alive as long as import() can still be initiated from that Script/Module, so this also fixes the long-standing crash caused by vm.Script being GC'ed too early when its importModuleDynamically callback still needs it. PR-URL: #48510 Backport-PR-URL: #51004 Refs: #44211 Refs: #42080 Refs: #47096 Refs: #43205 Refs: #38695 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
1 parent 2c2892b commit a86a2e1

16 files changed

+185
-191
lines changed
 

‎lib/internal/modules/esm/create_dynamic_module.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ import.meta.done();
6969
if (imports.length) {
7070
reflect.imports = { __proto__: null };
7171
}
72-
const { setCallbackForWrap } = require('internal/modules/esm/utils');
73-
setCallbackForWrap(m, {
72+
const { registerModule } = require('internal/modules/esm/utils');
73+
registerModule(m, {
74+
__proto__: null,
7475
initializeImportMeta: (meta, wrap) => {
7576
meta.exports = reflect.exports;
7677
if (reflect.imports) {

‎lib/internal/modules/esm/loader.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,10 @@ class ModuleLoader {
210210
) {
211211
const evalInstance = (url) => {
212212
const { ModuleWrap } = internalBinding('module_wrap');
213-
const { setCallbackForWrap } = require('internal/modules/esm/utils');
213+
const { registerModule } = require('internal/modules/esm/utils');
214214
const module = new ModuleWrap(url, undefined, source, 0, 0);
215-
setCallbackForWrap(module, {
215+
registerModule(module, {
216+
__proto__: null,
216217
initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }),
217218
importModuleDynamically: (specifier, { url }, importAttributes) => {
218219
return this.import(specifier, url, importAttributes);

‎lib/internal/modules/esm/translators.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,9 @@ translators.set('module', async function moduleStrategy(url, source, isMain) {
150150
maybeCacheSourceMap(url, source);
151151
debug(`Translating StandardModule ${url}`);
152152
const module = new ModuleWrap(url, undefined, source, 0, 0);
153-
const { setCallbackForWrap } = require('internal/modules/esm/utils');
154-
setCallbackForWrap(module, {
153+
const { registerModule } = require('internal/modules/esm/utils');
154+
registerModule(module, {
155+
__proto__: null,
155156
initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }),
156157
importModuleDynamically,
157158
});

‎lib/internal/modules/esm/utils.js

+69-20
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ const {
77
ObjectFreeze,
88
} = primordials;
99

10+
const {
11+
privateSymbols: {
12+
host_defined_option_symbol,
13+
},
14+
} = internalBinding('util');
1015
const {
1116
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
1217
ERR_INVALID_ARG_VALUE,
@@ -21,16 +26,8 @@ const {
2126
setImportModuleDynamicallyCallback,
2227
setInitializeImportMetaObjectCallback,
2328
} = internalBinding('module_wrap');
24-
const {
25-
getModuleFromWrap,
26-
} = require('internal/vm/module');
2729
const assert = require('internal/assert');
2830

29-
const callbackMap = new SafeWeakMap();
30-
function setCallbackForWrap(wrap, data) {
31-
callbackMap.set(wrap, data);
32-
}
33-
3431
let defaultConditions;
3532
/**
3633
* Returns the default conditions for ES module loading.
@@ -83,34 +80,86 @@ function getConditionsSet(conditions) {
8380
return getDefaultConditionsSet();
8481
}
8582

83+
/**
84+
* @callback ImportModuleDynamicallyCallback
85+
* @param {string} specifier
86+
* @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer
87+
* @param {object} attributes
88+
* @returns { Promise<void> }
89+
*/
90+
91+
/**
92+
* @callback InitializeImportMetaCallback
93+
* @param {object} meta
94+
* @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer
95+
*/
96+
97+
/**
98+
* @typedef {{
99+
* callbackReferrer: ModuleWrap|ContextifyScript|Function|vm.Module
100+
* initializeImportMeta? : InitializeImportMetaCallback,
101+
* importModuleDynamically? : ImportModuleDynamicallyCallback
102+
* }} ModuleRegistry
103+
*/
104+
105+
/**
106+
* @type {WeakMap<symbol, ModuleRegistry>}
107+
*/
108+
const moduleRegistries = new SafeWeakMap();
109+
110+
/**
111+
* V8 would make sure that as long as import() can still be initiated from
112+
* the referrer, the symbol referenced by |host_defined_option_symbol| should
113+
* be alive, which in term would keep the settings object alive through the
114+
* WeakMap, and in turn that keeps the referrer object alive, which would be
115+
* passed into the callbacks.
116+
* The reference goes like this:
117+
* [v8::internal::Script] (via host defined options) ----1--> [idSymbol]
118+
* [callbackReferrer] (via host_defined_option_symbol) ------2------^ |
119+
* ^----------3---- (via WeakMap)------
120+
* 1+3 makes sure that as long as import() can still be initiated, the
121+
* referrer wrap is still around and can be passed into the callbacks.
122+
* 2 is only there so that we can get the id symbol to configure the
123+
* weak map.
124+
* @param {ModuleWrap|ContextifyScript|Function} referrer The referrer to
125+
* get the id symbol from. This is different from callbackReferrer which
126+
* could be set by the caller.
127+
* @param {ModuleRegistry} registry
128+
*/
129+
function registerModule(referrer, registry) {
130+
const idSymbol = referrer[host_defined_option_symbol];
131+
// To prevent it from being GC'ed.
132+
registry.callbackReferrer ??= referrer;
133+
moduleRegistries.set(idSymbol, registry);
134+
}
135+
86136
/**
87137
* Defines the `import.meta` object for a given module.
88-
* @param {object} wrap - Reference to the module.
138+
* @param {symbol} symbol - Reference to the module.
89139
* @param {Record<string, string | Function>} meta - The import.meta object to initialize.
90140
*/
91-
function initializeImportMetaObject(wrap, meta) {
92-
if (callbackMap.has(wrap)) {
93-
const { initializeImportMeta } = callbackMap.get(wrap);
141+
function initializeImportMetaObject(symbol, meta) {
142+
if (moduleRegistries.has(symbol)) {
143+
const { initializeImportMeta, callbackReferrer } = moduleRegistries.get(symbol);
94144
if (initializeImportMeta !== undefined) {
95-
meta = initializeImportMeta(meta, getModuleFromWrap(wrap) || wrap);
145+
meta = initializeImportMeta(meta, callbackReferrer);
96146
}
97147
}
98148
}
99149

100150
/**
101151
* Asynchronously imports a module dynamically using a callback function. The native callback.
102-
* @param {object} wrap - Reference to the module.
152+
* @param {symbol} symbol - Reference to the module.
103153
* @param {string} specifier - The module specifier string.
104154
* @param {Record<string, string>} attributes - The import attributes object.
105155
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
106156
* @throws {ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING} - If the callback function is missing.
107157
*/
108-
async function importModuleDynamicallyCallback(wrap, specifier, attributes) {
109-
if (callbackMap.has(wrap)) {
110-
const { importModuleDynamically } = callbackMap.get(wrap);
158+
async function importModuleDynamicallyCallback(symbol, specifier, attributes) {
159+
if (moduleRegistries.has(symbol)) {
160+
const { importModuleDynamically, callbackReferrer } = moduleRegistries.get(symbol);
111161
if (importModuleDynamically !== undefined) {
112-
return importModuleDynamically(
113-
specifier, getModuleFromWrap(wrap) || wrap, attributes);
162+
return importModuleDynamically(specifier, callbackReferrer, attributes);
114163
}
115164
}
116165
throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING();
@@ -176,7 +225,7 @@ async function initializeHooks() {
176225
}
177226

178227
module.exports = {
179-
setCallbackForWrap,
228+
registerModule,
180229
initializeESM,
181230
initializeHooks,
182231
getDefaultConditions,

‎lib/internal/vm.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,10 @@ function internalCompileFunction(code, params, options) {
100100
const { importModuleDynamicallyWrap } = require('internal/vm/module');
101101
const wrapped = importModuleDynamicallyWrap(importModuleDynamically);
102102
const func = result.function;
103-
const { setCallbackForWrap } = require('internal/modules/esm/utils');
104-
setCallbackForWrap(result.cacheKey, {
105-
importModuleDynamically: (s, _k, i) => wrapped(s, func, i),
103+
const { registerModule } = require('internal/modules/esm/utils');
104+
registerModule(func, {
105+
__proto__: null,
106+
importModuleDynamically: wrapped,
106107
});
107108
}
108109

‎lib/internal/vm/module.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ const {
1212
ObjectSetPrototypeOf,
1313
ReflectApply,
1414
SafePromiseAllReturnVoid,
15-
SafeWeakMap,
1615
Symbol,
1716
SymbolToStringTag,
1817
TypeError,
@@ -70,7 +69,6 @@ const STATUS_MAP = {
7069

7170
let globalModuleId = 0;
7271
const defaultModuleName = 'vm:module';
73-
const wrapToModuleMap = new SafeWeakMap();
7472

7573
const kWrap = Symbol('kWrap');
7674
const kContext = Symbol('kContext');
@@ -121,25 +119,30 @@ class Module {
121119
});
122120
}
123121

122+
let registry = { __proto__: null };
124123
if (sourceText !== undefined) {
125124
this[kWrap] = new ModuleWrap(identifier, context, sourceText,
126125
options.lineOffset, options.columnOffset,
127126
options.cachedData);
128-
const { setCallbackForWrap } = require('internal/modules/esm/utils');
129-
setCallbackForWrap(this[kWrap], {
127+
registry = {
128+
__proto__: null,
130129
initializeImportMeta: options.initializeImportMeta,
131130
importModuleDynamically: options.importModuleDynamically ?
132131
importModuleDynamicallyWrap(options.importModuleDynamically) :
133132
undefined,
134-
});
133+
};
135134
} else {
136135
assert(syntheticEvaluationSteps);
137136
this[kWrap] = new ModuleWrap(identifier, context,
138137
syntheticExportNames,
139138
syntheticEvaluationSteps);
140139
}
141140

142-
wrapToModuleMap.set(this[kWrap], this);
141+
// This will take precedence over the referrer as the object being
142+
// passed into the callbacks.
143+
registry.callbackReferrer = this;
144+
const { registerModule } = require('internal/modules/esm/utils');
145+
registerModule(this[kWrap], registry);
143146

144147
this[kContext] = context;
145148
}
@@ -446,5 +449,4 @@ module.exports = {
446449
SourceTextModule,
447450
SyntheticModule,
448451
importModuleDynamicallyWrap,
449-
getModuleFromWrap: (wrap) => wrapToModuleMap.get(wrap),
450452
};

‎lib/vm.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,9 @@ class Script extends ContextifyScript {
106106
validateFunction(importModuleDynamically,
107107
'options.importModuleDynamically');
108108
const { importModuleDynamicallyWrap } = require('internal/vm/module');
109-
const { setCallbackForWrap } = require('internal/modules/esm/utils');
110-
setCallbackForWrap(this, {
109+
const { registerModule } = require('internal/modules/esm/utils');
110+
registerModule(this, {
111+
__proto__: null,
111112
importModuleDynamically:
112113
importModuleDynamicallyWrap(importModuleDynamically),
113114
});

‎src/env-inl.h

-10
Original file line numberDiff line numberDiff line change
@@ -345,16 +345,6 @@ inline AliasedInt32Array& Environment::stream_base_state() {
345345
return stream_base_state_;
346346
}
347347

348-
inline uint32_t Environment::get_next_module_id() {
349-
return module_id_counter_++;
350-
}
351-
inline uint32_t Environment::get_next_script_id() {
352-
return script_id_counter_++;
353-
}
354-
inline uint32_t Environment::get_next_function_id() {
355-
return function_id_counter_++;
356-
}
357-
358348
ShouldNotAbortOnUncaughtScope::ShouldNotAbortOnUncaughtScope(
359349
Environment* env)
360350
: env_(env) {

‎src/env.h

-8
Original file line numberDiff line numberDiff line change
@@ -698,14 +698,6 @@ class Environment : public MemoryRetainer {
698698
builtins::BuiltinLoader* builtin_loader();
699699

700700
std::unordered_multimap<int, loader::ModuleWrap*> hash_to_module_map;
701-
std::unordered_map<uint32_t, loader::ModuleWrap*> id_to_module_map;
702-
std::unordered_map<uint32_t, contextify::ContextifyScript*>
703-
id_to_script_map;
704-
std::unordered_map<uint32_t, contextify::CompiledFnEntry*> id_to_function_map;
705-
706-
inline uint32_t get_next_module_id();
707-
inline uint32_t get_next_script_id();
708-
inline uint32_t get_next_function_id();
709701

710702
EnabledDebugList* enabled_debug_list() { return &enabled_debug_list_; }
711703

‎src/env_properties.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
V(arrow_message_private_symbol, "node:arrowMessage") \
2222
V(contextify_context_private_symbol, "node:contextify:context") \
2323
V(decorated_private_symbol, "node:decorated") \
24+
V(host_defined_option_symbol, "node:host_defined_option_symbol") \
2425
V(napi_type_tag, "node:napi:type_tag") \
2526
V(napi_wrapper, "node:napi:wrapper") \
2627
V(untransferable_object_private_symbol, "node:untransferableObject") \
@@ -337,7 +338,6 @@
337338
V(blocklist_constructor_template, v8::FunctionTemplate) \
338339
V(contextify_global_template, v8::ObjectTemplate) \
339340
V(contextify_wrapper_template, v8::ObjectTemplate) \
340-
V(compiled_fn_entry_template, v8::ObjectTemplate) \
341341
V(crypto_key_object_handle_constructor, v8::FunctionTemplate) \
342342
V(env_proxy_template, v8::ObjectTemplate) \
343343
V(env_proxy_ctor_template, v8::FunctionTemplate) \

‎src/module_wrap.cc

+21-45
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ using v8::MaybeLocal;
3838
using v8::MicrotaskQueue;
3939
using v8::Module;
4040
using v8::ModuleRequest;
41-
using v8::Number;
4241
using v8::Object;
4342
using v8::PrimitiveArray;
4443
using v8::Promise;
4544
using v8::ScriptCompiler;
4645
using v8::ScriptOrigin;
4746
using v8::String;
47+
using v8::Symbol;
4848
using v8::UnboundModuleScript;
4949
using v8::Undefined;
5050
using v8::Value;
@@ -55,11 +55,7 @@ ModuleWrap::ModuleWrap(Environment* env,
5555
Local<String> url,
5656
Local<Object> context_object,
5757
Local<Value> synthetic_evaluation_step)
58-
: BaseObject(env, object),
59-
module_(env->isolate(), module),
60-
id_(env->get_next_module_id()) {
61-
env->id_to_module_map.emplace(id_, this);
62-
58+
: BaseObject(env, object), module_(env->isolate(), module) {
6359
object->SetInternalField(kURLSlot, url);
6460
object->SetInternalField(kSyntheticEvaluationStepsSlot,
6561
synthetic_evaluation_step);
@@ -73,7 +69,6 @@ ModuleWrap::ModuleWrap(Environment* env,
7369
ModuleWrap::~ModuleWrap() {
7470
HandleScope scope(env()->isolate());
7571
Local<Module> module = module_.Get(env()->isolate());
76-
env()->id_to_module_map.erase(id_);
7772
auto range = env()->hash_to_module_map.equal_range(module->GetIdentityHash());
7873
for (auto it = range.first; it != range.second; ++it) {
7974
if (it->second == this) {
@@ -102,14 +97,6 @@ ModuleWrap* ModuleWrap::GetFromModule(Environment* env,
10297
return nullptr;
10398
}
10499

105-
ModuleWrap* ModuleWrap::GetFromID(Environment* env, uint32_t id) {
106-
auto module_wrap_it = env->id_to_module_map.find(id);
107-
if (module_wrap_it == env->id_to_module_map.end()) {
108-
return nullptr;
109-
}
110-
return module_wrap_it->second;
111-
}
112-
113100
// new ModuleWrap(url, context, source, lineOffset, columnOffset)
114101
// new ModuleWrap(url, context, exportNames, syntheticExecutionFunction)
115102
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
@@ -154,8 +141,8 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
154141

155142
Local<PrimitiveArray> host_defined_options =
156143
PrimitiveArray::New(isolate, HostDefinedOptions::kLength);
157-
host_defined_options->Set(isolate, HostDefinedOptions::kType,
158-
Number::New(isolate, ScriptType::kModule));
144+
Local<Symbol> id_symbol = Symbol::New(isolate, url);
145+
host_defined_options->Set(isolate, HostDefinedOptions::kID, id_symbol);
159146

160147
ShouldNotAbortOnUncaughtScope no_abort_scope(env);
161148
TryCatchScope try_catch(env);
@@ -235,6 +222,11 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
235222
return;
236223
}
237224

225+
if (that->SetPrivate(context, env->host_defined_option_symbol(), id_symbol)
226+
.IsNothing()) {
227+
return;
228+
}
229+
238230
// Use the extras object as an object whose GetCreationContext() will be the
239231
// original `context`, since the `Context` itself strictly speaking cannot
240232
// be stored in an internal field.
@@ -249,9 +241,6 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
249241

250242
env->hash_to_module_map.emplace(module->GetIdentityHash(), obj);
251243

252-
host_defined_options->Set(isolate, HostDefinedOptions::kID,
253-
Number::New(isolate, obj->id()));
254-
255244
that->SetIntegrityLevel(context, IntegrityLevel::kFrozen);
256245
args.GetReturnValue().Set(that);
257246
}
@@ -586,35 +575,16 @@ static MaybeLocal<Promise> ImportModuleDynamically(
586575

587576
Local<Value> object;
588577

589-
int type = options->Get(context, HostDefinedOptions::kType)
590-
.As<Number>()
591-
->Int32Value(context)
592-
.ToChecked();
593-
uint32_t id = options->Get(context, HostDefinedOptions::kID)
594-
.As<Number>()
595-
->Uint32Value(context)
596-
.ToChecked();
597-
if (type == ScriptType::kScript) {
598-
contextify::ContextifyScript* wrap = env->id_to_script_map.find(id)->second;
599-
object = wrap->object();
600-
} else if (type == ScriptType::kModule) {
601-
ModuleWrap* wrap = ModuleWrap::GetFromID(env, id);
602-
object = wrap->object();
603-
} else if (type == ScriptType::kFunction) {
604-
auto it = env->id_to_function_map.find(id);
605-
CHECK_NE(it, env->id_to_function_map.end());
606-
object = it->second->object();
607-
} else {
608-
UNREACHABLE();
609-
}
578+
Local<Symbol> id =
579+
options->Get(context, HostDefinedOptions::kID).As<Symbol>();
610580

611581
Local<Object> attributes =
612582
createImportAttributesContainer(env, isolate, import_attributes);
613583

614584
Local<Value> import_args[] = {
615-
object,
616-
Local<Value>(specifier),
617-
attributes,
585+
id,
586+
Local<Value>(specifier),
587+
attributes,
618588
};
619589

620590
Local<Value> result;
@@ -658,7 +628,13 @@ void ModuleWrap::HostInitializeImportMetaObjectCallback(
658628
Local<Object> wrap = module_wrap->object();
659629
Local<Function> callback =
660630
env->host_initialize_import_meta_object_callback();
661-
Local<Value> args[] = { wrap, meta };
631+
Local<Value> id;
632+
if (!wrap->GetPrivate(context, env->host_defined_option_symbol())
633+
.ToLocal(&id)) {
634+
return;
635+
}
636+
DCHECK(id->IsSymbol());
637+
Local<Value> args[] = {id, meta};
662638
TryCatchScope try_catch(env);
663639
USE(callback->Call(
664640
context, Undefined(env->isolate()), arraysize(args), args));

‎src/module_wrap.h

+2-6
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ enum ScriptType : int {
2626
};
2727

2828
enum HostDefinedOptions : int {
29-
kType = 8,
30-
kID = 9,
31-
kLength = 10,
29+
kID = 8,
30+
kLength = 9,
3231
};
3332

3433
class ModuleWrap : public BaseObject {
@@ -55,9 +54,7 @@ class ModuleWrap : public BaseObject {
5554
tracker->TrackField("resolve_cache", resolve_cache_);
5655
}
5756

58-
inline uint32_t id() { return id_; }
5957
v8::Local<v8::Context> context() const;
60-
static ModuleWrap* GetFromID(node::Environment*, uint32_t id);
6158

6259
SET_MEMORY_INFO_NAME(ModuleWrap)
6360
SET_SELF_SIZE(ModuleWrap)
@@ -109,7 +106,6 @@ class ModuleWrap : public BaseObject {
109106
contextify::ContextifyContext* contextify_context_ = nullptr;
110107
bool synthetic_ = false;
111108
bool linked_ = false;
112-
uint32_t id_;
113109
};
114110

115111
} // namespace loader

‎src/node_contextify.cc

+16-59
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ using v8::MicrotasksPolicy;
6060
using v8::Name;
6161
using v8::NamedPropertyHandlerConfiguration;
6262
using v8::Nothing;
63-
using v8::Number;
6463
using v8::Object;
6564
using v8::ObjectTemplate;
6665
using v8::PrimitiveArray;
@@ -73,11 +72,11 @@ using v8::Script;
7372
using v8::ScriptCompiler;
7473
using v8::ScriptOrigin;
7574
using v8::String;
75+
using v8::Symbol;
7676
using v8::Uint32;
7777
using v8::UnboundScript;
7878
using v8::Value;
7979
using v8::WeakCallbackInfo;
80-
using v8::WeakCallbackType;
8180

8281
// The vm module executes code in a sandboxed environment with a different
8382
// global object than the rest of the code. This is achieved by applying
@@ -833,10 +832,9 @@ void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) {
833832

834833
Local<PrimitiveArray> host_defined_options =
835834
PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength);
836-
host_defined_options->Set(isolate, loader::HostDefinedOptions::kType,
837-
Number::New(isolate, loader::ScriptType::kScript));
838-
host_defined_options->Set(isolate, loader::HostDefinedOptions::kID,
839-
Number::New(isolate, contextify_script->id()));
835+
Local<Symbol> id_symbol = Symbol::New(isolate, filename);
836+
host_defined_options->Set(
837+
isolate, loader::HostDefinedOptions::kID, id_symbol);
840838

841839
ScriptOrigin origin(isolate,
842840
filename,
@@ -880,6 +878,12 @@ void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) {
880878
new_cached_data.reset(ScriptCompiler::CreateCodeCache(v8_script));
881879
}
882880

881+
if (contextify_script->object()
882+
->SetPrivate(context, env->host_defined_option_symbol(), id_symbol)
883+
.IsNothing()) {
884+
return;
885+
}
886+
883887
if (StoreCodeCacheResult(env,
884888
args.This(),
885889
compile_options,
@@ -1117,17 +1121,11 @@ bool ContextifyScript::EvalMachine(Local<Context> context,
11171121

11181122

11191123
ContextifyScript::ContextifyScript(Environment* env, Local<Object> object)
1120-
: BaseObject(env, object),
1121-
id_(env->get_next_script_id()) {
1124+
: BaseObject(env, object) {
11221125
MakeWeak();
1123-
env->id_to_script_map.emplace(id_, this);
1124-
}
1125-
1126-
1127-
ContextifyScript::~ContextifyScript() {
1128-
env()->id_to_script_map.erase(id_);
11291126
}
11301127

1128+
ContextifyScript::~ContextifyScript() {}
11311129

11321130
void ContextifyContext::CompileFunction(
11331131
const FunctionCallbackInfo<Value>& args) {
@@ -1197,18 +1195,12 @@ void ContextifyContext::CompileFunction(
11971195
data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength());
11981196
}
11991197

1200-
// Get the function id
1201-
uint32_t id = env->get_next_function_id();
1202-
12031198
// Set host_defined_options
12041199
Local<PrimitiveArray> host_defined_options =
12051200
PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength);
1201+
Local<Symbol> id_symbol = Symbol::New(isolate, filename);
12061202
host_defined_options->Set(
1207-
isolate,
1208-
loader::HostDefinedOptions::kType,
1209-
Number::New(isolate, loader::ScriptType::kFunction));
1210-
host_defined_options->Set(
1211-
isolate, loader::HostDefinedOptions::kID, Number::New(isolate, id));
1203+
isolate, loader::HostDefinedOptions::kID, id_symbol);
12121204

12131205
ScriptOrigin origin(isolate,
12141206
filename,
@@ -1273,21 +1265,14 @@ void ContextifyContext::CompileFunction(
12731265
}
12741266
return;
12751267
}
1276-
1277-
Local<Object> cache_key;
1278-
if (!env->compiled_fn_entry_template()->NewInstance(
1279-
context).ToLocal(&cache_key)) {
1268+
if (fn->SetPrivate(context, env->host_defined_option_symbol(), id_symbol)
1269+
.IsNothing()) {
12801270
return;
12811271
}
1282-
CompiledFnEntry* entry = new CompiledFnEntry(env, cache_key, id, fn);
1283-
env->id_to_function_map.emplace(id, entry);
12841272

12851273
Local<Object> result = Object::New(isolate);
12861274
if (result->Set(parsing_context, env->function_string(), fn).IsNothing())
12871275
return;
1288-
if (result->Set(parsing_context, env->cache_key_string(), cache_key)
1289-
.IsNothing())
1290-
return;
12911276
if (result
12921277
->Set(parsing_context,
12931278
env->source_map_url_string(),
@@ -1312,25 +1297,6 @@ void ContextifyContext::CompileFunction(
13121297
args.GetReturnValue().Set(result);
13131298
}
13141299

1315-
void CompiledFnEntry::WeakCallback(
1316-
const WeakCallbackInfo<CompiledFnEntry>& data) {
1317-
CompiledFnEntry* entry = data.GetParameter();
1318-
delete entry;
1319-
}
1320-
1321-
CompiledFnEntry::CompiledFnEntry(Environment* env,
1322-
Local<Object> object,
1323-
uint32_t id,
1324-
Local<Function> fn)
1325-
: BaseObject(env, object), id_(id), fn_(env->isolate(), fn) {
1326-
fn_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
1327-
}
1328-
1329-
CompiledFnEntry::~CompiledFnEntry() {
1330-
env()->id_to_function_map.erase(id_);
1331-
fn_.ClearWeak();
1332-
}
1333-
13341300
static void StartSigintWatchdog(const FunctionCallbackInfo<Value>& args) {
13351301
int ret = SigintWatchdogHelper::GetInstance()->Start();
13361302
args.GetReturnValue().Set(ret == 0);
@@ -1418,15 +1384,6 @@ void Initialize(Local<Object> target,
14181384
SetMethodNoSideEffect(
14191385
context, target, "watchdogHasPendingSigint", WatchdogHasPendingSigint);
14201386

1421-
{
1422-
Local<FunctionTemplate> tpl = FunctionTemplate::New(env->isolate());
1423-
tpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "CompiledFnEntry"));
1424-
tpl->InstanceTemplate()->SetInternalFieldCount(
1425-
CompiledFnEntry::kInternalFieldCount);
1426-
1427-
env->set_compiled_fn_entry_template(tpl->InstanceTemplate());
1428-
}
1429-
14301387
Local<Object> constants = Object::New(env->isolate());
14311388
Local<Object> measure_memory = Object::New(env->isolate());
14321389
Local<Object> memory_execution = Object::New(env->isolate());

‎src/node_contextify.h

-24
Original file line numberDiff line numberDiff line change
@@ -171,32 +171,8 @@ class ContextifyScript : public BaseObject {
171171
std::shared_ptr<v8::MicrotaskQueue> microtask_queue,
172172
const v8::FunctionCallbackInfo<v8::Value>& args);
173173

174-
inline uint32_t id() { return id_; }
175-
176174
private:
177175
v8::Global<v8::UnboundScript> script_;
178-
uint32_t id_;
179-
};
180-
181-
class CompiledFnEntry final : public BaseObject {
182-
public:
183-
SET_NO_MEMORY_INFO()
184-
SET_MEMORY_INFO_NAME(CompiledFnEntry)
185-
SET_SELF_SIZE(CompiledFnEntry)
186-
187-
CompiledFnEntry(Environment* env,
188-
v8::Local<v8::Object> object,
189-
uint32_t id,
190-
v8::Local<v8::Function> fn);
191-
~CompiledFnEntry();
192-
193-
bool IsNotIndicativeOfMemoryLeakAtExit() const override { return true; }
194-
195-
private:
196-
uint32_t id_;
197-
v8::Global<v8::Function> fn_;
198-
199-
static void WeakCallback(const v8::WeakCallbackInfo<CompiledFnEntry>& data);
200176
};
201177

202178
v8::Maybe<bool> StoreCodeCacheResult(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Flags: --expose-gc --experimental-vm-modules
2+
3+
'use strict';
4+
5+
// This tests that vm.Script would not get GC'ed while the script can still
6+
// initiate dynamic import.
7+
// See https://github.com/nodejs/node/issues/43205.
8+
9+
require('../common');
10+
const vm = require('vm');
11+
12+
const code = `
13+
new Promise(resolve => {
14+
setTimeout(() => {
15+
gc(); // vm.Script should not be GC'ed while the script is alive.
16+
resolve();
17+
}, 1);
18+
}).then(() => import('foo'));`;
19+
20+
// vm.runInThisContext creates a vm.Script underneath, which should not be GC'ed
21+
// while import() can still be initiated.
22+
vm.runInThisContext(code, {
23+
async importModuleDynamically() {
24+
const m = new vm.SyntheticModule(['bar'], () => {
25+
m.setExport('bar', 1);
26+
});
27+
28+
await m.link(() => {});
29+
await m.evaluate();
30+
return m;
31+
}
32+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Flags: --max-old-space-size=16 --trace-gc
2+
'use strict';
3+
4+
// This tests that vm.compileFunction with dynamic import callback does not leak.
5+
// See https://github.com/nodejs/node/issues/44211
6+
require('../common');
7+
const vm = require('vm');
8+
let count = 0;
9+
10+
function main() {
11+
// Try to reach the maximum old space size.
12+
vm.compileFunction(`"${Math.random().toString().repeat(512)}"`, [], {
13+
async importModuleDynamically() {},
14+
});
15+
if (count++ < 2048) {
16+
setTimeout(main, 1);
17+
}
18+
}
19+
main();

0 commit comments

Comments
 (0)
Please sign in to comment.