This code causes node to segfault on exit.
function run() {
let vm = require('vm');
let ctx = vm.createContext();
let scr = new vm.Script('({})');
require('weak-napi')(ctx, () => console.log('freed'));
scr.runInContext(ctx);
}
run();
ketchup% npm i weak-napi && node alpha.js
zsh: segmentation fault (core dumped) node alpha.js
Sometimes(:tm:) you can fix this by adding global.gc() before the script exits, but that's defeatable, e.g. with this, which segfaults again:
let vm = require('vm');
let ctx = vm.createContext();
let scr = new vm.Script('({"bar":function(require){require("https").get = () => {};}})');
scr.runInContext(ctx).bar(require);
require('weak-napi')(ctx, () => console.log('freed'))
vm = null; ctx = null; scr = null;
global.gc();
The backtrace is meaningless to me; it's trying to clean up the environment and it's trying a double-free. Ooh, I wonder if ASAN will catch this? Note that the backtrace is way worse on older nodes.
ketchup% gdb --args ~/clone/node/out/Debug/node alpha.js
...
Thread 1 "node" received signal SIGSEGV, Segmentation fault.
0x000055837b4de4b2 in v8impl::(anonymous namespace)::RefBase::Delete (reference=0x558381065540) at ../src/js_native_api_v8.cc:248
248 delete reference;
(gdb) bt
#0 0x000055837b4de4b2 in v8impl::(anonymous namespace)::RefBase::Delete (reference=0x558381065540) at ../src/js_native_api_v8.cc:248
#1 v8impl::(anonymous namespace)::RefBase::Finalize (this=0x558381065540, is_env_teardown=<optimised out>)
at ../src/js_native_api_v8.cc:281
#2 0x000055837b4feb4c in v8impl::RefTracker::FinalizeAll (list=0x558381109d08) at ../src/js_native_api_v8.h:43
#3 napi_env__::~napi_env__ (this=0x558381109cd0, __in_chrg=<optimised out>) at ../src/js_native_api_v8.h:66
#4 node_napi_env__::~node_napi_env__ (this=0x558381109cd0, __in_chrg=<optimised out>) at ../src/node_api.cc:17
#5 node_napi_env__::~node_napi_env__ (this=0x558381109cd0, __in_chrg=<optimised out>) at ../src/node_api.cc:17
#6 0x000055837b4fa0c2 in napi_env__::Unref (this=<optimised out>) at ../src/js_native_api_v8.h:77
#7 operator() (arg=<optimised out>, __closure=0x0) at ../src/node_api.cc:103
#8 _FUN () at ../src/node_api.cc:104
#9 0x000055837b4d18ec in node::Environment::RunCleanup (this=this@entry=0x558380ff2830) at ../src/env.cc:661
#10 0x000055837b4797ef in node::FreeEnvironment (env=0x558380ff2830) at ../src/api/environment.cc:371
#11 0x000055837b57deb1 in node::FunctionDeleter<node::Environment, &node::FreeEnvironment>::operator() (pointer=<optimised out>,
this=0x7ffed4b26e30) at ../src/util.h:636
#12 std::unique_ptr<node::Environment, node::FunctionDeleter<node::Environment, &node::FreeEnvironment> >::~unique_ptr (
this=0x7ffed4b26e30, __in_chrg=<optimised out>) at /usr/include/c++/10/bits/unique_ptr.h:361
#13 node::NodeMainInstance::Run (this=this@entry=0x7ffed4b26f80, env_info=env_info@entry=0x55837fa57780 <node::env_info>)
at ../src/node_main_instance.cc:135
#14 0x000055837b4f7908 in node::Start (argc=<optimised out>, argv=<optimised out>) at ../src/node.cc:1078
#15 0x000055837cb7cb63 in main (argc=2, argv=0x7ffed4b271b8) at ../src/node_main.cc:127
ketchup% node --version
v12.20.0
Debug node built is from HEAD today (d90fa196c5540109bf9c5063f8c51673340ad9e3). Ubuntu 20.10, amd64.
I found this trying to diagnose jestjs/jest#10289 ; this createContext / runInContext dance is how Jest works. However, I assume Jest works for most people most of the time, so it can't always segfault. As far as I can see, Jest always use Script to load user code, with most core modules require'd like in the second bit of code.
This code causes node to segfault on exit.
Sometimes(:tm:) you can fix this by adding
global.gc()before the script exits, but that's defeatable, e.g. with this, which segfaults again:The backtrace is meaningless to me; it's trying to clean up the environment and it's trying a double-free. Ooh, I wonder if ASAN will catch this? Note that the backtrace is way worse on older nodes.
Debug node built is from HEAD today (d90fa196c5540109bf9c5063f8c51673340ad9e3). Ubuntu 20.10, amd64.
I found this trying to diagnose jestjs/jest#10289 ; this
createContext/runInContextdance is how Jest works. However, I assume Jest works for most people most of the time, so it can't always segfault. As far as I can see, Jest always useScriptto load user code, with most core modulesrequire'd like in the second bit of code.