From 16f6d7e7ab5bf2f2cc9b3266b39baf214c186ec2 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 22 Feb 2023 20:30:59 +0100 Subject: [PATCH] vm: fix leak in vm.compileFunction when importModuleDynamically is used Previously in the implementation there was a cycle that V8 could not detect: Strong global reference to CompiledFnEntry (JS wrapper) -> strong reference to callback setting (through the callbackMap key-value pair) -> importModuleDynamically (wrapper in internalCompileFunction()) -> Strong reference to the compiled function (through closure in internalCompileFunction()) The CompiledFnEntry only gets GC'ed when the compiled function is GC'ed. Since the compiled function is always reachable as described above, there is a leak. We only needed the first strong global reference because we didn't want the function to outlive the CompiledFnEntry. In this case it can be solved by using a private symbol instead of going with the global reference + destruction in the weak callback, which V8's GC is not going to understand. PR-URL: https://github.com/nodejs/node/pull/46785 Fixes: https://github.com/nodejs/node/issues/42080 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Chengzhong Wu Reviewed-By: Benjamin Gruenbaum --- src/env_properties.h | 1 + src/node_contextify.cc | 17 +++++------------ src/node_contextify.h | 3 --- test/pummel/test-vm-compile-function-leak.js | 14 ++++++++++++++ 4 files changed, 20 insertions(+), 15 deletions(-) create mode 100644 test/pummel/test-vm-compile-function-leak.js diff --git a/src/env_properties.h b/src/env_properties.h index 623e3022856989..7553823a9be48a 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -20,6 +20,7 @@ #define PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V) \ V(arrow_message_private_symbol, "node:arrowMessage") \ V(contextify_context_private_symbol, "node:contextify:context") \ + V(compiled_function_entry, "node:compiled_function_entry") \ V(decorated_private_symbol, "node:decorated") \ V(napi_type_tag, "node:napi:type_tag") \ V(napi_wrapper, "node:napi:wrapper") \ diff --git a/src/node_contextify.cc b/src/node_contextify.cc index d618dafcb5f14d..2a4d4618dbd2a3 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -77,7 +77,6 @@ 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 @@ -1263,8 +1262,7 @@ void ContextifyContext::CompileFunction( context).ToLocal(&cache_key)) { return; } - CompiledFnEntry* entry = new CompiledFnEntry(env, cache_key, id, fn); - env->id_to_function_map.emplace(id, entry); + new CompiledFnEntry(env, cache_key, id, fn); Local result = Object::New(isolate); if (result->Set(parsing_context, env->function_string(), fn).IsNothing()) @@ -1296,23 +1294,18 @@ void ContextifyContext::CompileFunction( args.GetReturnValue().Set(result); } -void CompiledFnEntry::WeakCallback( - const WeakCallbackInfo& data) { - CompiledFnEntry* entry = data.GetParameter(); - delete entry; -} - CompiledFnEntry::CompiledFnEntry(Environment* env, Local object, uint32_t id, Local fn) - : BaseObject(env, object), id_(id), fn_(env->isolate(), fn) { - fn_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); + : BaseObject(env, object), id_(id) { + MakeWeak(); + fn->SetPrivate(env->context(), env->compiled_function_entry(), object); + env->id_to_function_map.emplace(id, this); } CompiledFnEntry::~CompiledFnEntry() { env()->id_to_function_map.erase(id_); - fn_.ClearWeak(); } static void StartSigintWatchdog(const FunctionCallbackInfo& args) { diff --git a/src/node_contextify.h b/src/node_contextify.h index 76c89318bb6cbf..a9b7741e61b2c2 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -194,9 +194,6 @@ class CompiledFnEntry final : public BaseObject { private: uint32_t id_; - v8::Global fn_; - - static void WeakCallback(const v8::WeakCallbackInfo& data); }; v8::Maybe StoreCodeCacheResult( diff --git a/test/pummel/test-vm-compile-function-leak.js b/test/pummel/test-vm-compile-function-leak.js new file mode 100644 index 00000000000000..465f300d4310d1 --- /dev/null +++ b/test/pummel/test-vm-compile-function-leak.js @@ -0,0 +1,14 @@ +'use strict'; + +// Flags: --max-old-space-size=10 + +require('../common'); +const vm = require('vm'); + +const code = `console.log("${'hello world '.repeat(1e5)}");`; + +for (let i = 0; i < 10000; i++) { + vm.compileFunction(code, [], { + importModuleDynamically: () => {}, + }); +}