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
https://github.com/nodejs/node/pull/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: https://github.com/nodejs/node/pull/48510
Refs: https://github.com/nodejs/node/issues/44211
Refs: https://github.com/nodejs/node/issues/42080
Refs: https://github.com/nodejs/node/issues/47096
Refs: https://github.com/nodejs/node/issues/43205
Refs: https://github.com/nodejs/node/issues/38695
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
This commit is contained in:
Joyee Cheung 2023-06-21 16:01:50 +02:00 committed by Node.js GitHub Bot
parent b289c6df11
commit bc9a875c99
16 changed files with 185 additions and 188 deletions

View File

@ -45,8 +45,9 @@ import.meta.done();
if (imports.length)
reflect.imports = { __proto__: null };
const { setCallbackForWrap } = require('internal/modules/esm/utils');
setCallbackForWrap(m, {
const { registerModule } = require('internal/modules/esm/utils');
registerModule(m, {
__proto__: null,
initializeImportMeta: (meta, wrap) => {
meta.exports = reflect.exports;
if (reflect.imports)

View File

@ -180,9 +180,10 @@ class ModuleLoader {
) {
const evalInstance = (url) => {
const { ModuleWrap } = internalBinding('module_wrap');
const { setCallbackForWrap } = require('internal/modules/esm/utils');
const { registerModule } = require('internal/modules/esm/utils');
const module = new ModuleWrap(url, undefined, source, 0, 0);
setCallbackForWrap(module, {
registerModule(module, {
__proto__: null,
initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }),
importModuleDynamically: (specifier, { url }, importAssertions) => {
return this.import(specifier, url, importAssertions);

View File

@ -116,8 +116,9 @@ translators.set('module', async function moduleStrategy(url, source, isMain) {
maybeCacheSourceMap(url, source);
debug(`Translating StandardModule ${url}`);
const module = new ModuleWrap(url, undefined, source, 0, 0);
const { setCallbackForWrap } = require('internal/modules/esm/utils');
setCallbackForWrap(module, {
const { registerModule } = require('internal/modules/esm/utils');
registerModule(module, {
__proto__: null,
initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }),
importModuleDynamically,
});

View File

@ -7,6 +7,11 @@ const {
ObjectFreeze,
} = primordials;
const {
privateSymbols: {
host_defined_option_symbol,
},
} = internalBinding('util');
const {
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
ERR_INVALID_ARG_VALUE,
@ -21,16 +26,8 @@ const {
setImportModuleDynamicallyCallback,
setInitializeImportMetaObjectCallback,
} = internalBinding('module_wrap');
const {
getModuleFromWrap,
} = require('internal/vm/module');
const assert = require('internal/assert');
const callbackMap = new SafeWeakMap();
function setCallbackForWrap(wrap, data) {
callbackMap.set(wrap, data);
}
let defaultConditions;
function getDefaultConditions() {
assert(defaultConditions !== undefined);
@ -73,21 +70,75 @@ function getConditionsSet(conditions) {
return getDefaultConditionsSet();
}
function initializeImportMetaObject(wrap, meta) {
if (callbackMap.has(wrap)) {
const { initializeImportMeta } = callbackMap.get(wrap);
/**
* @callback ImportModuleDynamicallyCallback
* @param {string} specifier
* @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer
* @param {object} assertions
* @returns { Promise<void> }
*/
/**
* @callback InitializeImportMetaCallback
* @param {object} meta
* @param {ModuleWrap|ContextifyScript|Function|vm.Module} callbackReferrer
*/
/**
* @typedef {{
* callbackReferrer: ModuleWrap|ContextifyScript|Function|vm.Module
* initializeImportMeta? : InitializeImportMetaCallback,
* importModuleDynamically? : ImportModuleDynamicallyCallback
* }} ModuleRegistry
*/
/**
* @type {WeakMap<symbol, ModuleRegistry>}
*/
const moduleRegistries = new SafeWeakMap();
/**
* V8 would make sure that as long as import() can still be initiated from
* the referrer, the symbol referenced by |host_defined_option_symbol| should
* be alive, which in term would keep the settings object alive through the
* WeakMap, and in turn that keeps the referrer object alive, which would be
* passed into the callbacks.
* The reference goes like this:
* [v8::internal::Script] (via host defined options) ----1--> [idSymbol]
* [callbackReferrer] (via host_defined_option_symbol) ------2------^ |
* ^----------3---- (via WeakMap)------
* 1+3 makes sure that as long as import() can still be initiated, the
* referrer wrap is still around and can be passed into the callbacks.
* 2 is only there so that we can get the id symbol to configure the
* weak map.
* @param {ModuleWrap|ContextifyScript|Function} referrer The referrer to
* get the id symbol from. This is different from callbackReferrer which
* could be set by the caller.
* @param {ModuleRegistry} registry
*/
function registerModule(referrer, registry) {
const idSymbol = referrer[host_defined_option_symbol];
// To prevent it from being GC'ed.
registry.callbackReferrer ??= referrer;
moduleRegistries.set(idSymbol, registry);
}
// The native callback
function initializeImportMetaObject(symbol, meta) {
if (moduleRegistries.has(symbol)) {
const { initializeImportMeta, callbackReferrer } = moduleRegistries.get(symbol);
if (initializeImportMeta !== undefined) {
meta = initializeImportMeta(meta, getModuleFromWrap(wrap) || wrap);
meta = initializeImportMeta(meta, callbackReferrer);
}
}
}
async function importModuleDynamicallyCallback(wrap, specifier, assertions) {
if (callbackMap.has(wrap)) {
const { importModuleDynamically } = callbackMap.get(wrap);
// The native callback
async function importModuleDynamicallyCallback(symbol, specifier, assertions) {
if (moduleRegistries.has(symbol)) {
const { importModuleDynamically, callbackReferrer } = moduleRegistries.get(symbol);
if (importModuleDynamically !== undefined) {
return importModuleDynamically(
specifier, getModuleFromWrap(wrap) || wrap, assertions);
return importModuleDynamically(specifier, callbackReferrer, assertions);
}
}
throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING();
@ -149,7 +200,7 @@ async function initializeHooks() {
}
module.exports = {
setCallbackForWrap,
registerModule,
initializeESM,
initializeHooks,
getDefaultConditions,

View File

@ -100,9 +100,10 @@ function internalCompileFunction(code, params, options) {
const { importModuleDynamicallyWrap } = require('internal/vm/module');
const wrapped = importModuleDynamicallyWrap(importModuleDynamically);
const func = result.function;
const { setCallbackForWrap } = require('internal/modules/esm/utils');
setCallbackForWrap(result.cacheKey, {
importModuleDynamically: (s, _k, i) => wrapped(s, func, i),
const { registerModule } = require('internal/modules/esm/utils');
registerModule(func, {
__proto__: null,
importModuleDynamically: wrapped,
});
}

View File

@ -11,7 +11,6 @@ const {
ObjectSetPrototypeOf,
ReflectApply,
SafePromiseAllReturnVoid,
SafeWeakMap,
Symbol,
SymbolToStringTag,
TypeError,
@ -69,7 +68,6 @@ const STATUS_MAP = {
let globalModuleId = 0;
const defaultModuleName = 'vm:module';
const wrapToModuleMap = new SafeWeakMap();
const kWrap = Symbol('kWrap');
const kContext = Symbol('kContext');
@ -120,17 +118,18 @@ class Module {
});
}
let registry = { __proto__: null };
if (sourceText !== undefined) {
this[kWrap] = new ModuleWrap(identifier, context, sourceText,
options.lineOffset, options.columnOffset,
options.cachedData);
const { setCallbackForWrap } = require('internal/modules/esm/utils');
setCallbackForWrap(this[kWrap], {
registry = {
__proto__: null,
initializeImportMeta: options.initializeImportMeta,
importModuleDynamically: options.importModuleDynamically ?
importModuleDynamicallyWrap(options.importModuleDynamically) :
undefined,
});
};
} else {
assert(syntheticEvaluationSteps);
this[kWrap] = new ModuleWrap(identifier, context,
@ -138,7 +137,11 @@ class Module {
syntheticEvaluationSteps);
}
wrapToModuleMap.set(this[kWrap], this);
// This will take precedence over the referrer as the object being
// passed into the callbacks.
registry.callbackReferrer = this;
const { registerModule } = require('internal/modules/esm/utils');
registerModule(this[kWrap], registry);
this[kContext] = context;
}
@ -445,5 +448,4 @@ module.exports = {
SourceTextModule,
SyntheticModule,
importModuleDynamicallyWrap,
getModuleFromWrap: (wrap) => wrapToModuleMap.get(wrap),
};

View File

@ -105,8 +105,9 @@ class Script extends ContextifyScript {
validateFunction(importModuleDynamically,
'options.importModuleDynamically');
const { importModuleDynamicallyWrap } = require('internal/vm/module');
const { setCallbackForWrap } = require('internal/modules/esm/utils');
setCallbackForWrap(this, {
const { registerModule } = require('internal/modules/esm/utils');
registerModule(this, {
__proto__: null,
importModuleDynamically:
importModuleDynamicallyWrap(importModuleDynamically),
});

View File

@ -393,16 +393,6 @@ inline AliasedInt32Array& Environment::stream_base_state() {
return stream_base_state_;
}
inline uint32_t Environment::get_next_module_id() {
return module_id_counter_++;
}
inline uint32_t Environment::get_next_script_id() {
return script_id_counter_++;
}
inline uint32_t Environment::get_next_function_id() {
return function_id_counter_++;
}
ShouldNotAbortOnUncaughtScope::ShouldNotAbortOnUncaughtScope(
Environment* env)
: env_(env) {

View File

@ -746,14 +746,6 @@ class Environment : public MemoryRetainer {
builtins::BuiltinLoader* builtin_loader();
std::unordered_multimap<int, loader::ModuleWrap*> hash_to_module_map;
std::unordered_map<uint32_t, loader::ModuleWrap*> id_to_module_map;
std::unordered_map<uint32_t, contextify::ContextifyScript*>
id_to_script_map;
std::unordered_map<uint32_t, contextify::CompiledFnEntry*> id_to_function_map;
inline uint32_t get_next_module_id();
inline uint32_t get_next_script_id();
inline uint32_t get_next_function_id();
EnabledDebugList* enabled_debug_list() { return &enabled_debug_list_; }

View File

@ -22,6 +22,7 @@
V(contextify_context_private_symbol, "node:contextify:context") \
V(decorated_private_symbol, "node:decorated") \
V(transfer_mode_private_symbol, "node:transfer_mode") \
V(host_defined_option_symbol, "node:host_defined_option_symbol") \
V(js_transferable_wrapper_private_symbol, "node:js_transferable_wrapper") \
V(napi_type_tag, "node:napi:type_tag") \
V(napi_wrapper, "node:napi:wrapper") \
@ -342,7 +343,6 @@
V(blocklist_constructor_template, v8::FunctionTemplate) \
V(contextify_global_template, v8::ObjectTemplate) \
V(contextify_wrapper_template, v8::ObjectTemplate) \
V(compiled_fn_entry_template, v8::ObjectTemplate) \
V(crypto_key_object_handle_constructor, v8::FunctionTemplate) \
V(env_proxy_template, v8::ObjectTemplate) \
V(env_proxy_ctor_template, v8::FunctionTemplate) \

View File

@ -38,13 +38,13 @@ using v8::MaybeLocal;
using v8::MicrotaskQueue;
using v8::Module;
using v8::ModuleRequest;
using v8::Number;
using v8::Object;
using v8::PrimitiveArray;
using v8::Promise;
using v8::ScriptCompiler;
using v8::ScriptOrigin;
using v8::String;
using v8::Symbol;
using v8::UnboundModuleScript;
using v8::Undefined;
using v8::Value;
@ -55,11 +55,7 @@ ModuleWrap::ModuleWrap(Environment* env,
Local<String> url,
Local<Object> context_object,
Local<Value> synthetic_evaluation_step)
: BaseObject(env, object),
module_(env->isolate(), module),
id_(env->get_next_module_id()) {
env->id_to_module_map.emplace(id_, this);
: BaseObject(env, object), module_(env->isolate(), module) {
object->SetInternalField(kURLSlot, url);
object->SetInternalField(kSyntheticEvaluationStepsSlot,
synthetic_evaluation_step);
@ -73,7 +69,6 @@ ModuleWrap::ModuleWrap(Environment* env,
ModuleWrap::~ModuleWrap() {
HandleScope scope(env()->isolate());
Local<Module> module = module_.Get(env()->isolate());
env()->id_to_module_map.erase(id_);
auto range = env()->hash_to_module_map.equal_range(module->GetIdentityHash());
for (auto it = range.first; it != range.second; ++it) {
if (it->second == this) {
@ -102,14 +97,6 @@ ModuleWrap* ModuleWrap::GetFromModule(Environment* env,
return nullptr;
}
ModuleWrap* ModuleWrap::GetFromID(Environment* env, uint32_t id) {
auto module_wrap_it = env->id_to_module_map.find(id);
if (module_wrap_it == env->id_to_module_map.end()) {
return nullptr;
}
return module_wrap_it->second;
}
// new ModuleWrap(url, context, source, lineOffset, columnOffset)
// new ModuleWrap(url, context, exportNames, syntheticExecutionFunction)
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
@ -154,8 +141,8 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
Local<PrimitiveArray> host_defined_options =
PrimitiveArray::New(isolate, HostDefinedOptions::kLength);
host_defined_options->Set(isolate, HostDefinedOptions::kType,
Number::New(isolate, ScriptType::kModule));
Local<Symbol> id_symbol = Symbol::New(isolate, url);
host_defined_options->Set(isolate, HostDefinedOptions::kID, id_symbol);
ShouldNotAbortOnUncaughtScope no_abort_scope(env);
TryCatchScope try_catch(env);
@ -235,6 +222,11 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
return;
}
if (that->SetPrivate(context, env->host_defined_option_symbol(), id_symbol)
.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.
@ -249,9 +241,6 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
env->hash_to_module_map.emplace(module->GetIdentityHash(), obj);
host_defined_options->Set(isolate, HostDefinedOptions::kID,
Number::New(isolate, obj->id()));
that->SetIntegrityLevel(context, IntegrityLevel::kFrozen);
args.GetReturnValue().Set(that);
}
@ -586,35 +575,16 @@ static MaybeLocal<Promise> ImportModuleDynamically(
Local<Value> object;
int type = options->Get(context, HostDefinedOptions::kType)
.As<Number>()
->Int32Value(context)
.ToChecked();
uint32_t id = options->Get(context, HostDefinedOptions::kID)
.As<Number>()
->Uint32Value(context)
.ToChecked();
if (type == ScriptType::kScript) {
contextify::ContextifyScript* wrap = env->id_to_script_map.find(id)->second;
object = wrap->object();
} else if (type == ScriptType::kModule) {
ModuleWrap* wrap = ModuleWrap::GetFromID(env, id);
object = wrap->object();
} else if (type == ScriptType::kFunction) {
auto it = env->id_to_function_map.find(id);
CHECK_NE(it, env->id_to_function_map.end());
object = it->second->object();
} else {
UNREACHABLE();
}
Local<Symbol> id =
options->Get(context, HostDefinedOptions::kID).As<Symbol>();
Local<Object> assertions =
createImportAssertionContainer(env, isolate, import_assertions);
Local<Value> import_args[] = {
object,
Local<Value>(specifier),
assertions,
id,
Local<Value>(specifier),
assertions,
};
Local<Value> result;
@ -658,7 +628,13 @@ void ModuleWrap::HostInitializeImportMetaObjectCallback(
Local<Object> wrap = module_wrap->object();
Local<Function> callback =
env->host_initialize_import_meta_object_callback();
Local<Value> args[] = { wrap, meta };
Local<Value> id;
if (!wrap->GetPrivate(context, env->host_defined_option_symbol())
.ToLocal(&id)) {
return;
}
DCHECK(id->IsSymbol());
Local<Value> args[] = {id, meta};
TryCatchScope try_catch(env);
USE(callback->Call(
context, Undefined(env->isolate()), arraysize(args), args));

View File

@ -26,9 +26,8 @@ enum ScriptType : int {
};
enum HostDefinedOptions : int {
kType = 8,
kID = 9,
kLength = 10,
kID = 8,
kLength = 9,
};
class ModuleWrap : public BaseObject {
@ -55,9 +54,7 @@ class ModuleWrap : public BaseObject {
tracker->TrackField("resolve_cache", resolve_cache_);
}
inline uint32_t id() { return id_; }
v8::Local<v8::Context> context() const;
static ModuleWrap* GetFromID(node::Environment*, uint32_t id);
SET_MEMORY_INFO_NAME(ModuleWrap)
SET_SELF_SIZE(ModuleWrap)
@ -109,7 +106,6 @@ class ModuleWrap : public BaseObject {
contextify::ContextifyContext* contextify_context_ = nullptr;
bool synthetic_ = false;
bool linked_ = false;
uint32_t id_;
};
} // namespace loader

View File

@ -60,7 +60,6 @@ using v8::MicrotasksPolicy;
using v8::Name;
using v8::NamedPropertyHandlerConfiguration;
using v8::Nothing;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::PrimitiveArray;
@ -73,11 +72,11 @@ using v8::Script;
using v8::ScriptCompiler;
using v8::ScriptOrigin;
using v8::String;
using v8::Symbol;
using v8::Uint32;
using v8::UnboundScript;
using v8::Value;
using v8::WeakCallbackInfo;
using v8::WeakCallbackType;
// The vm module executes code in a sandboxed environment with a different
// global object than the rest of the code. This is achieved by applying
@ -817,10 +816,9 @@ void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) {
Local<PrimitiveArray> host_defined_options =
PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength);
host_defined_options->Set(isolate, loader::HostDefinedOptions::kType,
Number::New(isolate, loader::ScriptType::kScript));
host_defined_options->Set(isolate, loader::HostDefinedOptions::kID,
Number::New(isolate, contextify_script->id()));
Local<Symbol> id_symbol = Symbol::New(isolate, filename);
host_defined_options->Set(
isolate, loader::HostDefinedOptions::kID, id_symbol);
ScriptOrigin origin(isolate,
filename,
@ -864,6 +862,12 @@ void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) {
new_cached_data.reset(ScriptCompiler::CreateCodeCache(v8_script));
}
if (contextify_script->object()
->SetPrivate(context, env->host_defined_option_symbol(), id_symbol)
.IsNothing()) {
return;
}
if (StoreCodeCacheResult(env,
args.This(),
compile_options,
@ -1116,17 +1120,11 @@ bool ContextifyScript::EvalMachine(Local<Context> context,
}
ContextifyScript::ContextifyScript(Environment* env, Local<Object> object)
: BaseObject(env, object),
id_(env->get_next_script_id()) {
: BaseObject(env, object) {
MakeWeak();
env->id_to_script_map.emplace(id_, this);
}
ContextifyScript::~ContextifyScript() {
env()->id_to_script_map.erase(id_);
}
ContextifyScript::~ContextifyScript() {}
void ContextifyContext::CompileFunction(
const FunctionCallbackInfo<Value>& args) {
@ -1196,18 +1194,12 @@ void ContextifyContext::CompileFunction(
data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength());
}
// Get the function id
uint32_t id = env->get_next_function_id();
// Set host_defined_options
Local<PrimitiveArray> host_defined_options =
PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength);
Local<Symbol> id_symbol = Symbol::New(isolate, filename);
host_defined_options->Set(
isolate,
loader::HostDefinedOptions::kType,
Number::New(isolate, loader::ScriptType::kFunction));
host_defined_options->Set(
isolate, loader::HostDefinedOptions::kID, Number::New(isolate, id));
isolate, loader::HostDefinedOptions::kID, id_symbol);
ScriptOrigin origin(isolate,
filename,
@ -1272,21 +1264,14 @@ void ContextifyContext::CompileFunction(
}
return;
}
Local<Object> cache_key;
if (!env->compiled_fn_entry_template()->NewInstance(
context).ToLocal(&cache_key)) {
if (fn->SetPrivate(context, env->host_defined_option_symbol(), id_symbol)
.IsNothing()) {
return;
}
CompiledFnEntry* entry = new CompiledFnEntry(env, cache_key, id, fn);
env->id_to_function_map.emplace(id, entry);
Local<Object> result = Object::New(isolate);
if (result->Set(parsing_context, env->function_string(), fn).IsNothing())
return;
if (result->Set(parsing_context, env->cache_key_string(), cache_key)
.IsNothing())
return;
if (result
->Set(parsing_context,
env->source_map_url_string(),
@ -1311,25 +1296,6 @@ void ContextifyContext::CompileFunction(
args.GetReturnValue().Set(result);
}
void CompiledFnEntry::WeakCallback(
const WeakCallbackInfo<CompiledFnEntry>& data) {
CompiledFnEntry* entry = data.GetParameter();
delete entry;
}
CompiledFnEntry::CompiledFnEntry(Environment* env,
Local<Object> object,
uint32_t id,
Local<Function> fn)
: BaseObject(env, object), id_(id), fn_(env->isolate(), fn) {
fn_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
}
CompiledFnEntry::~CompiledFnEntry() {
env()->id_to_function_map.erase(id_);
fn_.ClearWeak();
}
static void StartSigintWatchdog(const FunctionCallbackInfo<Value>& args) {
int ret = SigintWatchdogHelper::GetInstance()->Start();
args.GetReturnValue().Set(ret == 0);
@ -1381,14 +1347,6 @@ void CreatePerIsolateProperties(IsolateData* isolate_data,
SetMethodNoSideEffect(
isolate, target, "watchdogHasPendingSigint", WatchdogHasPendingSigint);
{
Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate);
tpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "CompiledFnEntry"));
tpl->InstanceTemplate()->SetInternalFieldCount(
CompiledFnEntry::kInternalFieldCount);
isolate_data->set_compiled_fn_entry_template(tpl->InstanceTemplate());
}
SetMethod(isolate, target, "measureMemory", MeasureMemory);
}

View File

@ -151,32 +151,8 @@ class ContextifyScript : public BaseObject {
v8::MicrotaskQueue* microtask_queue,
const v8::FunctionCallbackInfo<v8::Value>& args);
inline uint32_t id() { return id_; }
private:
v8::Global<v8::UnboundScript> script_;
uint32_t id_;
};
class CompiledFnEntry final : public BaseObject {
public:
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(CompiledFnEntry)
SET_SELF_SIZE(CompiledFnEntry)
CompiledFnEntry(Environment* env,
v8::Local<v8::Object> object,
uint32_t id,
v8::Local<v8::Function> fn);
~CompiledFnEntry();
bool IsNotIndicativeOfMemoryLeakAtExit() const override { return true; }
private:
uint32_t id_;
v8::Global<v8::Function> fn_;
static void WeakCallback(const v8::WeakCallbackInfo<CompiledFnEntry>& data);
};
v8::Maybe<bool> StoreCodeCacheResult(

View File

@ -0,0 +1,32 @@
// Flags: --expose-gc --experimental-vm-modules
'use strict';
// This tests that vm.Script would not get GC'ed while the script can still
// initiate dynamic import.
// See https://github.com/nodejs/node/issues/43205.
require('../common');
const vm = require('vm');
const code = `
new Promise(resolve => {
setTimeout(() => {
gc(); // vm.Script should not be GC'ed while the script is alive.
resolve();
}, 1);
}).then(() => import('foo'));`;
// vm.runInThisContext creates a vm.Script underneath, which should not be GC'ed
// while import() can still be initiated.
vm.runInThisContext(code, {
async importModuleDynamically() {
const m = new vm.SyntheticModule(['bar'], () => {
m.setExport('bar', 1);
});
await m.link(() => {});
await m.evaluate();
return m;
}
});

View File

@ -0,0 +1,19 @@
// Flags: --max-old-space-size=16 --trace-gc
'use strict';
// This tests that vm.compileFunction with dynamic import callback does not leak.
// See https://github.com/nodejs/node/issues/44211
require('../common');
const vm = require('vm');
let count = 0;
function main() {
// Try to reach the maximum old space size.
vm.compileFunction(`"${Math.random().toString().repeat(512)}"`, [], {
async importModuleDynamically() {},
});
if (count++ < 2048) {
setTimeout(main, 1);
}
}
main();