fix(shell): fix use-after-free in shell interpreter error path on Windows

When `runFromJS()` encounters an IO setup failure, it was calling
`#deinitFromExec()` which directly frees the interpreter via
`allocator.destroy(this)`. However, in the JS code path the interpreter
is a GC-managed object — the GC finalizer (`deinitFromFinalizer`) will
later attempt to access and free the already-freed memory, causing a
use-after-free / segfault.

Replace `#deinitFromExec()` with `#derefRootShellAndIOIfNeeded(true)`
which properly cleans up only runtime resources (IO, shell env) and
marks `cleanup_state = .runtime_cleaned`, allowing the GC finalizer
to safely handle the final destruction of the interpreter object.

Closes #26836

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-02-09 20:54:46 +00:00
parent b7475d8768
commit aeddfca1f0

View File

@@ -1154,7 +1154,11 @@ pub const Interpreter = struct {
_ = callframe; // autofix
if (this.setupIOBeforeRun().asErr()) |e| {
defer this.#deinitFromExec();
// Clean up runtime resources but NOT the interpreter itself —
// this is a GC-managed object, so let the finalizer handle destruction.
// Using #deinitFromExec() here would call allocator.destroy(this),
// causing a use-after-free when the GC later runs deinitFromFinalizer().
defer this.#derefRootShellAndIOIfNeeded(true);
const shellerr = bun.shell.ShellErr.newSys(e);
return try throwShellErr(&shellerr, .{ .js = globalThis.bunVM().event_loop });
}