From fa198b144102cfe2ae9bb703207e3bbb655af889 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Tue, 11 Nov 2025 03:29:11 +0000 Subject: [PATCH] Fix NAPI finalizer segfault when running rspack/rsbuild MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #24552 When NAPI modules (like rspack/rsbuild via SWC) are used in subprocesses, finalizers can be enqueued during garbage collection but may attempt to run after the NapiEnv has started tearing down, causing segmentation faults at address 0x0. The crash occurs because: 1. NAPI objects with finalizers are garbage collected 2. Finalizers are enqueued to run on the next tick 3. The NapiEnv begins cleanup (m_globalObject becomes null/invalid or VM terminates) 4. When the finalizer runs, it dereferences the invalid globalObject pointer The fix: - Add NapiEnv__canRunFinalizer() to check if env, globalObject are valid and VM is not terminating - Call this check in Finalizer.run() before accessing the env - Skip finalizer execution if the env is no longer valid This prevents the segfault while ensuring finalizers run when safe to do so. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/bun.js/bindings/napi.cpp | 6 ++++++ src/napi/napi.zig | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp index 5166bf1d55..7581b6730c 100644 --- a/src/bun.js/bindings/napi.cpp +++ b/src/bun.js/bindings/napi.cpp @@ -2970,4 +2970,10 @@ extern "C" void NapiEnv__deref(napi_env env) env->deref(); } +extern "C" bool NapiEnv__canRunFinalizer(napi_env env) +{ + // Check if the global object is still valid and VM is not terminating + return env && env->globalObject() && !env->isVMTerminating(); +} + } diff --git a/src/napi/napi.zig b/src/napi/napi.zig index d778cadd9b..69121e5e35 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -1366,6 +1366,7 @@ pub export fn napi_internal_suppress_crash_on_abort_if_desired() void { } extern fn napi_internal_remove_finalizer(env: napi_env, fun: napi_finalize, hint: ?*anyopaque, data: ?*anyopaque) callconv(.C) void; +extern fn NapiEnv__canRunFinalizer(*NapiEnv) bool; pub const Finalizer = struct { env: NapiEnv.Ref, @@ -1375,6 +1376,14 @@ pub const Finalizer = struct { pub fn run(this: *Finalizer) void { const env = this.env.get(); + + // Safety check: Ensure the env is still valid before running the finalizer. + // This prevents crashes when finalizers are enqueued but the NapiEnv is being + // torn down (e.g., when a subprocess using NAPI modules is terminating). + if (!NapiEnv__canRunFinalizer(env)) { + return; + } + const handle_scope = NapiHandleScope.open(env, false); defer if (handle_scope) |scope| scope.close(env);