From 367eeb308ed756625656392de1ca80298f62b51a Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 9 Jan 2026 15:14:00 -0800 Subject: [PATCH] Fixes #23177 (#25936) ### What does this PR do? Fixes #23177 ### How did you verify your code works? --- src/shell/interpreter.zig | 61 +++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/shell/interpreter.zig b/src/shell/interpreter.zig index d4a1d6aa50..f556889893 100644 --- a/src/shell/interpreter.zig +++ b/src/shell/interpreter.zig @@ -444,10 +444,10 @@ pub const Interpreter = struct { if (comptime free_buffered_io) { if (this._buffered_stdout == .owned) { - this._buffered_stdout.owned.deinit(bun.default_allocator); + this._buffered_stdout.owned.clearAndFree(bun.default_allocator); } if (this._buffered_stderr == .owned) { - this._buffered_stderr.owned.deinit(bun.default_allocator); + this._buffered_stderr.owned.clearAndFree(bun.default_allocator); } } @@ -994,7 +994,7 @@ pub const Interpreter = struct { interp.exit_code = exit_code; switch (try interp.run()) { .err => |e| { - interp.deinitEverything(); + interp.#deinitFromExec(); bun.Output.err(e, "Failed to run script {s}", .{std.fs.path.basename(path)}); bun.Global.exit(1); return 1; @@ -1003,7 +1003,7 @@ pub const Interpreter = struct { } mini.tick(&is_done, @as(fn (*anyopaque) bool, IsDone.isDone)); const code = interp.exit_code.?; - interp.deinitEverything(); + interp.#deinitFromExec(); return code; } @@ -1061,7 +1061,7 @@ pub const Interpreter = struct { interp.exit_code = exit_code; switch (try interp.run()) { .err => |e| { - interp.deinitEverything(); + interp.#deinitFromExec(); bun.Output.err(e, "Failed to run script {s}", .{path_for_errors}); bun.Global.exit(1); return 1; @@ -1070,7 +1070,7 @@ pub const Interpreter = struct { } mini.tick(&is_done, @as(fn (*anyopaque) bool, IsDone.isDone)); const code = interp.exit_code.?; - interp.deinitEverything(); + interp.#deinitFromExec(); return code; } @@ -1142,7 +1142,7 @@ pub const Interpreter = struct { _ = callframe; // autofix if (this.setupIOBeforeRun().asErr()) |e| { - defer this.deinitEverything(); + defer this.#deinitFromExec(); const shellerr = bun.shell.ShellErr.newSys(e); return try throwShellErr(&shellerr, .{ .js = globalThis.bunVM().event_loop }); } @@ -1191,20 +1191,21 @@ pub const Interpreter = struct { defer decrPendingActivityFlag(&this.has_pending_activity); if (this.event_loop == .js) { - defer this.deinitAfterJSRun(); this.exit_code = exit_code; const this_jsvalue = this.this_jsvalue; if (this_jsvalue != .zero) { if (jsc.Codegen.JSShellInterpreter.resolveGetCached(this_jsvalue)) |resolve| { const loop = this.event_loop.js; const globalThis = this.globalThis; - this.this_jsvalue = .zero; + const buffered_stdout = this.getBufferedStdout(globalThis); + const buffered_stderr = this.getBufferedStderr(globalThis); this.keep_alive.disable(); + this.#derefRootShellAndIOIfNeeded(true); loop.enter(); _ = resolve.call(globalThis, .js_undefined, &.{ JSValue.jsNumberFromU16(exit_code), - this.getBufferedStdout(globalThis), - this.getBufferedStderr(globalThis), + buffered_stdout, + buffered_stderr, }) catch |err| globalThis.reportActiveExceptionAsUnhandled(err); jsc.Codegen.JSShellInterpreter.resolveSetCached(this_jsvalue, globalThis, .js_undefined); jsc.Codegen.JSShellInterpreter.rejectSetCached(this_jsvalue, globalThis, .js_undefined); @@ -1219,35 +1220,45 @@ pub const Interpreter = struct { return .done; } - fn deinitAfterJSRun(this: *ThisInterpreter) void { - log("Interpreter(0x{x}) deinitAfterJSRun", .{@intFromPtr(this)}); - this.root_io.deref(); - this.keep_alive.disable(); - this.root_shell.deinitImpl(false, false); + fn #derefRootShellAndIOIfNeeded(this: *ThisInterpreter, free_buffered_io: bool) void { + if (free_buffered_io) { + // Can safely be called multiple times. + if (this.root_shell._buffered_stderr == .owned) { + this.root_shell._buffered_stderr.owned.clearAndFree(bun.default_allocator); + } + if (this.root_shell._buffered_stdout == .owned) { + this.root_shell._buffered_stdout.owned.clearAndFree(bun.default_allocator); + } + } + + // Has this already been finalized? + if (this.this_jsvalue != .zero) { + // Cannot be safely called multiple times. + this.root_io.deref(); + this.root_shell.deinitImpl(false, false); + } + this.this_jsvalue = .zero; } fn deinitFromFinalizer(this: *ThisInterpreter) void { - if (this.root_shell._buffered_stderr == .owned) { - this.root_shell._buffered_stderr.owned.deinit(bun.default_allocator); - } - if (this.root_shell._buffered_stdout == .owned) { - this.root_shell._buffered_stdout.owned.deinit(bun.default_allocator); - } - this.this_jsvalue = .zero; + this.#derefRootShellAndIOIfNeeded(true); + this.keep_alive.disable(); this.args.deinit(); this.allocator.destroy(this); } - fn deinitEverything(this: *ThisInterpreter) void { + fn #deinitFromExec(this: *ThisInterpreter) void { log("deinit interpreter", .{}); + + this.this_jsvalue = .zero; this.root_io.deref(); this.root_shell.deinitImpl(false, true); + for (this.vm_args_utf8.items[0..]) |str| { str.deinit(); } this.vm_args_utf8.deinit(); - this.this_jsvalue = .zero; this.allocator.destroy(this); }