Skip to content

Commit 6bafe26

Browse files
Jarred-Sumnerzackradisicautofix-ci[bot]
authored
Fix Windows shell crash with && operator and external commands (#22651)
## What does this PR do? Fixes #22650 Fixes #22615 Fixes #22603 Fixes #22602 Fixes a crash that occurred when running shell commands through `bun run` (package.json scripts) on Windows that use the `&&` operator followed by an external command. ### The Problem The minimal reproduction was: ```bash bun exec 'echo && node --version' ``` This would crash with: `panic(main thread): attempt to use null value` ### Root Causes Two issues were causing the crash: 1. **Missing top_level_dir**: When `runPackageScriptForeground` creates a MiniEventLoop for running package scripts, it wasn't setting the `top_level_dir` field. This caused a null pointer dereference when the shell tried to access it. 2. **MovableIfWindowsFd handling**: After PR #21800 introduced `MovableIfWindowsFd` to handle file descriptor ownership on Windows, the `IOWriter.fd` could be moved to libuv, leaving it null. When the shell tried to spawn an external command after a `&&` operator, it would crash trying to access this null fd. ### The Fix 1. Set `mini.top_level_dir = cwd` after initializing the MiniEventLoop in `run_command.zig` 2. In `IO.zig`, when the fd has been moved to libuv (is null), use `.inherit` for stdio instead of trying to pass the null fd ### How did you verify your code works? - Added a regression test that reproduces the issue - Verified the test fails without the fix and passes with it - Tested the minimal reproduction command directly - The fix correctly allows both commands in the `&&` chain to execute ```bash # Before fix: crashes > bun exec 'echo test && node --version' panic(main thread): attempt to use null value # After fix: works correctly > bun exec 'echo test && node --version' test v22.4.1 ``` <sub> also probably fixes #22615 and fixes #22603 and fixes #22602 </sub> --------- Co-authored-by: Zack Radisic <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 9411c62 commit 6bafe26

File tree

14 files changed

+67
-30
lines changed

14 files changed

+67
-30
lines changed

src/bun.js.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ pub const Run = struct {
135135
null,
136136
);
137137
try bundle.runEnvLoader(false);
138-
const mini = jsc.MiniEventLoop.initGlobal(bundle.env);
138+
const mini = jsc.MiniEventLoop.initGlobal(bundle.env, null);
139139
mini.top_level_dir = ctx.args.absolute_working_dir orelse "";
140140
return bun.shell.Interpreter.initAndRunFromFile(ctx, mini, entry_path);
141141
}

src/bun.js/event_loop/MiniEventLoop.zig

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub threadlocal var global: *MiniEventLoop = undefined;
4040

4141
pub const ConcurrentTaskQueue = UnboundedQueue(AnyTaskWithExtraContext, .next);
4242

43-
pub fn initGlobal(env: ?*bun.DotEnv.Loader) *MiniEventLoop {
43+
pub fn initGlobal(env: ?*bun.DotEnv.Loader, cwd: ?[]const u8) *MiniEventLoop {
4444
if (globalInitialized) return global;
4545
const loop = MiniEventLoop.init(bun.default_allocator);
4646
global = bun.handleOom(bun.default_allocator.create(MiniEventLoop));
@@ -54,6 +54,22 @@ pub fn initGlobal(env: ?*bun.DotEnv.Loader) *MiniEventLoop {
5454
loader.* = bun.DotEnv.Loader.init(map, bun.default_allocator);
5555
break :env_loader loader;
5656
};
57+
58+
// Set top_level_dir from provided cwd or get current working directory
59+
if (cwd) |dir| {
60+
global.top_level_dir = dir;
61+
} else if (global.top_level_dir.len == 0) {
62+
var buf: bun.PathBuffer = undefined;
63+
switch (bun.sys.getcwd(&buf)) {
64+
.result => |p| {
65+
global.top_level_dir = bun.default_allocator.dupe(u8, p) catch "";
66+
},
67+
.err => {
68+
global.top_level_dir = "";
69+
},
70+
}
71+
}
72+
5773
globalInitialized = true;
5874
return global;
5975
}

src/cli/bunx_command.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,7 @@ pub const BunxCommand = struct {
756756
.stdin = .inherit,
757757

758758
.windows = if (Environment.isWindows) .{
759-
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(this_transpiler.env)),
759+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(this_transpiler.env, null)),
760760
},
761761
}) catch |err| {
762762
Output.prettyErrorln("<r><red>error<r>: bunx failed to install <b>{s}<r> due to error <b>{s}<r>", .{ install_param, @errorName(err) });

src/cli/create_command.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ fn execTask(allocator: std.mem.Allocator, task_: string, cwd: string, _: string,
109109
.stdin = .inherit,
110110

111111
.windows = if (Environment.isWindows) .{
112-
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)),
112+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)),
113113
},
114114
}) catch return;
115115
}
@@ -1487,7 +1487,7 @@ pub const CreateCommand = struct {
14871487
.stdin = .inherit,
14881488

14891489
.windows = if (Environment.isWindows) .{
1490-
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)),
1490+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)),
14911491
},
14921492
});
14931493
_ = try process.unwrap();

src/cli/exec_command.zig

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,15 @@ pub const ExecCommand = struct {
99
null,
1010
);
1111
try bundle.runEnvLoader(false);
12-
const mini = bun.jsc.MiniEventLoop.initGlobal(bundle.env);
1312
var buf: bun.PathBuffer = undefined;
14-
1513
const cwd = switch (bun.sys.getcwd(&buf)) {
1614
.result => |p| p,
1715
.err => |e| {
1816
Output.err(e, "failed to run script <b>{s}<r>", .{script});
1917
Global.exit(1);
2018
},
2119
};
20+
const mini = bun.jsc.MiniEventLoop.initGlobal(bundle.env, cwd);
2221
const parts: []const []const u8 = &[_][]const u8{
2322
cwd,
2423
"[eval]",

src/cli/filter_run.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ pub fn runScriptsWithFilter(ctx: Command.Context) !noreturn {
532532
Global.exit(1);
533533
}
534534

535-
const event_loop = bun.jsc.MiniEventLoop.initGlobal(this_transpiler.env);
535+
const event_loop = bun.jsc.MiniEventLoop.initGlobal(this_transpiler.env, null);
536536
const shell_bin: [:0]const u8 = if (Environment.isPosix)
537537
RunCommand.findShell(this_transpiler.env.get("PATH") orelse "", fsinstance.top_level_dir) orelse return error.MissingShell
538538
else

src/cli/pm_version_command.zig

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ pub const PmVersionCommand = struct {
461461
.cwd = cwd,
462462
.envp = null,
463463
.windows = if (Environment.isWindows) .{
464-
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)),
464+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)),
465465
},
466466
}) catch |err| {
467467
Output.errGeneric("Failed to spawn git process: {s}", .{@errorName(err)});
@@ -494,7 +494,7 @@ pub const PmVersionCommand = struct {
494494
.cwd = cwd,
495495
.envp = null,
496496
.windows = if (Environment.isWindows) .{
497-
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)),
497+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)),
498498
},
499499
}) catch |err| {
500500
Output.err(err, "Failed to spawn git process", .{});
@@ -541,7 +541,7 @@ pub const PmVersionCommand = struct {
541541
.stdin = .ignore,
542542
.envp = null,
543543
.windows = if (Environment.isWindows) .{
544-
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)),
544+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)),
545545
},
546546
}) catch |err| {
547547
Output.errGeneric("Git add failed: {s}", .{@errorName(err)});
@@ -575,7 +575,7 @@ pub const PmVersionCommand = struct {
575575
.stdin = .ignore,
576576
.envp = null,
577577
.windows = if (Environment.isWindows) .{
578-
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)),
578+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)),
579579
},
580580
}) catch |err| {
581581
Output.errGeneric("Git commit failed: {s}", .{@errorName(err)});
@@ -606,7 +606,7 @@ pub const PmVersionCommand = struct {
606606
.stdin = .ignore,
607607
.envp = null,
608608
.windows = if (Environment.isWindows) .{
609-
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)),
609+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)),
610610
},
611611
}) catch |err| {
612612
Output.errGeneric("Git tag failed: {s}", .{@errorName(err)});

src/cli/run_command.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ pub const RunCommand = struct {
246246
}
247247

248248
if (!use_system_shell) {
249-
const mini = bun.jsc.MiniEventLoop.initGlobal(env);
249+
const mini = bun.jsc.MiniEventLoop.initGlobal(env, cwd);
250250
const code = bun.shell.Interpreter.initAndRunFromSource(ctx, mini, name, copy_script.items, cwd) catch |err| {
251251
if (!silent) {
252252
Output.prettyErrorln("<r><red>error<r>: Failed to run script <b>{s}<r> due to error <b>{s}<r>", .{ name, @errorName(err) });
@@ -294,7 +294,7 @@ pub const RunCommand = struct {
294294
.ipc = ipc_fd,
295295

296296
.windows = if (Environment.isWindows) .{
297-
.loop = jsc.EventLoopHandle.init(jsc.MiniEventLoop.initGlobal(env)),
297+
.loop = jsc.EventLoopHandle.init(jsc.MiniEventLoop.initGlobal(env, null)),
298298
},
299299
}) catch |err| {
300300
if (!silent) {
@@ -467,7 +467,7 @@ pub const RunCommand = struct {
467467
.use_execve_on_macos = silent,
468468

469469
.windows = if (Environment.isWindows) .{
470-
.loop = jsc.EventLoopHandle.init(jsc.MiniEventLoop.initGlobal(env)),
470+
.loop = jsc.EventLoopHandle.init(jsc.MiniEventLoop.initGlobal(env, null)),
471471
},
472472
}) catch |err| {
473473
bun.handleErrorReturnTrace(err, @errorReturnTrace());

src/cli/upgrade_command.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ pub const UpgradeCommand = struct {
601601
.stdin = .inherit,
602602

603603
.windows = if (Environment.isWindows) .{
604-
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)),
604+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)),
605605
},
606606
}) catch |err| {
607607
Output.prettyErrorln("<r><red>error:<r> Failed to spawn Expand-Archive on {s} due to error {s}", .{ tmpname, @errorName(err) });

src/create/SourceFileProjectGenerator.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ pub fn generateFiles(allocator: std.mem.Allocator, entry_point: string, dependen
248248
.stdin = .inherit,
249249

250250
.windows = if (Environment.isWindows) .{
251-
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)),
251+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)),
252252
},
253253
}) catch |err| {
254254
Output.err(err, "failed to install dependencies", .{});
@@ -361,7 +361,7 @@ pub fn generateFiles(allocator: std.mem.Allocator, entry_point: string, dependen
361361
.stdin = .inherit,
362362

363363
.windows = if (Environment.isWindows) .{
364-
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null)),
364+
.loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(null, null)),
365365
},
366366
}) catch |err| {
367367
Output.err(err, "failed to start app", .{});

0 commit comments

Comments
 (0)