mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
## Summary Fixes a critical segmentation fault crash occurring during NAPI finalizer cleanup when finalizers trigger GC operations. This crash was reported with `node-sqlite3` and other NAPI modules during process exit. ## Root Cause The crash was caused by **iterator invalidation** in `napi_env__::cleanup()`: 1. Range-based for loop iterates over `m_finalizers` (std::unordered_set) 2. `boundFinalizer.call(this)` executes finalizer callbacks 3. Finalizers can trigger JavaScript execution and GC operations 4. GC can add/remove entries from `m_finalizers` during iteration 5. **Iterator invalidation** → undefined behavior → segfault ## Solution Added `JSC::DeferGCForAWhile deferGC(m_vm)` scope during entire finalizer cleanup to prevent any GC operations from occurring during iteration. ### Why This Approach? - **Addresses root cause**: Prevents GC entirely during critical section - **Simple & safe**: One-line RAII fix using existing JSC patterns - **Minimal impact**: Only affects process cleanup (not runtime performance) - **Proven pattern**: Already used elsewhere in Bun's codebase - **Better than alternatives**: Cleaner than Node.js manual iterator approach ## Testing Added comprehensive test (`test_finalizer_iterator_invalidation.c`) that reproduces the crash by: - Creating finalizers that trigger GC operations - Forcing JavaScript execution during finalization - Allocating objects that can trigger more GC - Calling process exit to trigger finalizer cleanup **Before fix**: Segmentation fault **After fix**: Clean exit ✅ ## Files Changed - `src/bun.js/bindings/napi.h`: Core fix + include - `test/napi/napi-app/test_finalizer_iterator_invalidation.c`: Test addon - `test/napi/napi-app/binding.gyp`: Build config for test addon - `test/regression/issue/napi-finalizer-crash.test.ts`: Integration test ## Test Plan - [x] Reproduces original crash without fix - [x] Passes cleanly with fix applied - [x] Existing NAPI tests continue to pass - [x] Manual testing with node-sqlite3 scenarios 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Kai Tamkun <kai@tamkun.io>
214 lines
8.8 KiB
Python
214 lines
8.8 KiB
Python
{
|
|
"targets": [
|
|
{
|
|
"target_name": "napitests",
|
|
"cflags!": ["-fno-exceptions"],
|
|
"cflags_cc!": ["-fno-exceptions"],
|
|
"msvs_settings": {
|
|
"VCCLCompilerTool": {
|
|
"ExceptionHandling": "0",
|
|
"AdditionalOptions": ["/std:c++20"],
|
|
},
|
|
},
|
|
# leak tests are unused as of #14501
|
|
"sources": ["main.cpp", "async_tests.cpp", "class_test.cpp", "conversion_tests.cpp", "js_test_helpers.cpp", "standalone_tests.cpp", "wrap_tests.cpp", "get_string_tests.cpp"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "second_addon",
|
|
"sources": ["second_addon.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "nullptr_addon",
|
|
"sources": ["null_addon.cpp"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
"MODULE_INIT_RETURN_NULLPTR=1"
|
|
],
|
|
},
|
|
{
|
|
"target_name": "null_addon",
|
|
"sources": ["null_addon.cpp"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
"MODULE_INIT_RETURN_NULL=1"
|
|
],
|
|
},
|
|
{
|
|
"target_name": "undefined_addon",
|
|
"sources": ["null_addon.cpp"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
"MODULE_INIT_RETURN_UNDEFINED=1"
|
|
],
|
|
},
|
|
{
|
|
"target_name": "throw_addon",
|
|
"sources": ["null_addon.cpp"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
"MODULE_INIT_THROW=1"
|
|
],
|
|
},
|
|
{
|
|
"target_name": "async_finalize_addon",
|
|
"sources": ["async_finalize_addon.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "ffi_addon_1",
|
|
"sources": ["ffi_addon_1.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "ffi_addon_2",
|
|
"sources": ["ffi_addon_2.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "constructor_order_addon",
|
|
"sources": ["constructor_order_addon.cpp"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "test_cleanup_hook_order",
|
|
"sources": ["test_cleanup_hook_order.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "test_cleanup_hook_remove_nonexistent",
|
|
"sources": ["test_cleanup_hook_remove_nonexistent.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "test_async_cleanup_hook_remove_nonexistent",
|
|
"sources": ["test_async_cleanup_hook_remove_nonexistent.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "test_cleanup_hook_duplicates",
|
|
"sources": ["test_cleanup_hook_duplicates.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "test_cleanup_hook_duplicates_release",
|
|
"sources": ["test_cleanup_hook_duplicates_release.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "test_cleanup_hook_mixed_order",
|
|
"sources": ["test_cleanup_hook_mixed_order.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "test_cleanup_hook_modification_during_iteration",
|
|
"sources": ["test_cleanup_hook_modification_during_iteration.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
{
|
|
"target_name": "test_finalizer_iterator_invalidation",
|
|
"sources": ["test_finalizer_iterator_invalidation.c"],
|
|
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
|
|
"libraries": [],
|
|
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
|
|
"defines": [
|
|
"NAPI_DISABLE_CPP_EXCEPTIONS",
|
|
"NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT=1",
|
|
],
|
|
},
|
|
]
|
|
}
|