authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-31 18:06:48-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 00:27:08-08:00
log9009ab2495a6f6c7eaa990d87986f32c85360667
tree6e446b2a9c52d89b6ee2e8971b0f0ae33198c739
parentde8c4cd64e0599abda0a0c5e1187391352020478

std.Io.Threaded: make environ init non-optional

and argv0 on systems that need it too. fixes surprising behavior for applications that forget to initialize the environment field.

7 files changed, 88 insertions(+), 47 deletions(-)

lib/compiler/build_runner.zig+4-1
......@@ -39,7 +39,10 @@ pub fn main(init: process.Init.Minimal) !void {
3939
4040 const args = try init.args.toSlice(arena);
4141
42 var threaded: std.Io.Threaded = .init(gpa, .{});
42 var threaded: std.Io.Threaded = .init(gpa, .{
43 .environ = init.environ,
44 .argv0 = .init(init.args),
45 });
4346 defer threaded.deinit();
4447 const io = threaded.io();
4548
lib/compiler/test_runner.zig+8-6
......@@ -65,13 +65,13 @@ pub fn main(init: std.process.Init.Minimal) void {
6565 }
6666
6767 if (listen) {
68 return mainServer(args) catch @panic("internal test runner failure");
68 return mainServer(init) catch @panic("internal test runner failure");
6969 } else {
70 return mainTerminal(args);
70 return mainTerminal(init);
7171 }
7272}
7373
74fn mainServer(args: []const [:0]const u8) !void {
74fn mainServer(init: std.process.Init.Minimal) !void {
7575 @disableInstrumentation();
7676 var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);
7777 var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);
......@@ -131,7 +131,8 @@ fn mainServer(args: []const [:0]const u8) !void {
131131 .run_test => {
132132 testing.allocator_instance = .{};
133133 testing.io_instance = .init(testing.allocator, .{
134 .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},
134 .argv0 = .init(init.args),
135 .environ = init.environ,
135136 });
136137 log_err_count = 0;
137138 const index = try server.receiveBody_u32();
......@@ -216,7 +217,7 @@ fn mainServer(args: []const [:0]const u8) !void {
216217 }
217218}
218219
219fn mainTerminal(args: []const [:0]const u8) void {
220fn mainTerminal(init: std.process.Init.Minimal) void {
220221 @disableInstrumentation();
221222 if (builtin.fuzz) @panic("fuzz test requires server");
222223
......@@ -235,7 +236,8 @@ fn mainTerminal(args: []const [:0]const u8) void {
235236 for (test_fn_list, 0..) |test_fn, i| {
236237 testing.allocator_instance = .{};
237238 testing.io_instance = .init(testing.allocator, .{
238 .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},
239 .argv0 = .init(init.args),
240 .environ = init.environ,
239241 });
240242 defer {
241243 testing.io_instance.deinit();
lib/std/Build.zig+4-12
......@@ -1881,19 +1881,11 @@ pub fn runAllowFail(
18811881/// inside step make() functions. If any errors occur, it fails the build with
18821882/// a helpful message.
18831883pub fn run(b: *Build, argv: []const []const u8) []u8 {
1884 if (!process.can_spawn) {
1885 std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{
1886 try Step.allocPrintCmd(b.allocator, null, null, argv),
1887 });
1888 process.exit(1);
1889 }
1890
18911884 var code: u8 = undefined;
1892 return b.runAllowFail(argv, &code, .inherit) catch |err| {
1893 const printed_cmd = Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM");
1894 std.debug.print("unable to spawn the following command: {t}\n{s}\n", .{ err, printed_cmd });
1895 process.exit(1);
1896 };
1885 return b.runAllowFail(argv, &code, .inherit) catch |err| process.fatal(
1886 "the following command failed with {t}:\n{s}",
1887 .{ err, Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM") },
1888 );
18971889}
18981890
18991891pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void {
lib/std/Io/Threaded.zig+20-5
......@@ -66,12 +66,25 @@ environ: Environ,
6666
6767pub const Argv0 = switch (native_os) {
6868 .openbsd, .haiku => struct {
69 value: ?[*:0]const u8 = null,
69 value: ?[*:0]const u8,
70
71 pub const empty: Argv0 = .{ .value = null };
72
73 pub fn init(args: process.Args) Argv0 {
74 return .{ .value = args.value[0] };
75 }
76 },
77 else => struct {
78 pub const empty: Argv0 = .{};
79
80 pub fn init(args: process.Args) Argv0 {
81 _ = args;
82 return .{};
83 }
7084 },
71 else => struct {},
7285};
7386
74pub const Environ = struct {
87const Environ = struct {
7588 /// Unmodified data directly from the OS.
7689 block: process.Environ.Block = &.{},
7790 /// Protected by `mutex`. Determines whether the other fields have been
......@@ -1141,7 +1154,8 @@ pub const InitOptions = struct {
11411154 /// Affects the following operations:
11421155 /// * `fileIsTty`
11431156 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").
1144 environ: Environ = .{},
1157 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
1158 environ: process.Environ,
11451159};
11461160
11471161/// Related:
......@@ -1171,8 +1185,9 @@ pub fn init(
11711185 .old_sig_pipe = undefined,
11721186 .have_signal_handler = false,
11731187 .argv0 = options.argv0,
1174 .environ = options.environ,
11751188 .worker_threads = .init(null),
1189 .environ = .{ .block = options.environ.block },
1190 .robust_cancel = options.robust_cancel,
11761191 };
11771192
11781193 if (posix.Sigaction != void) {
lib/std/Io/Threaded/test.zig+20-5
......@@ -13,7 +13,10 @@ test "concurrent vs main prevents deadlock via oversubscription" {
1313 return error.SkipZigTest;
1414 }
1515
16 var threaded: Io.Threaded = .init(std.testing.allocator, .{});
16 var threaded: Io.Threaded = .init(std.testing.allocator, .{
17 .argv0 = .empty,
18 .environ = .empty,
19 });
1720 defer threaded.deinit();
1821 const io = threaded.io();
1922
......@@ -46,7 +49,10 @@ test "concurrent vs concurrent prevents deadlock via oversubscription" {
4649 return error.SkipZigTest;
4750 }
4851
49 var threaded: Io.Threaded = .init(std.testing.allocator, .{});
52 var threaded: Io.Threaded = .init(std.testing.allocator, .{
53 .argv0 = .empty,
54 .environ = .empty,
55 });
5056 defer threaded.deinit();
5157 const io = threaded.io();
5258
......@@ -80,7 +86,10 @@ test "async/concurrent context and result alignment" {
8086 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;
8187 var fba: std.heap.FixedBufferAllocator = .init(&buffer);
8288
83 var threaded: std.Io.Threaded = .init(fba.allocator(), .{});
89 var threaded: std.Io.Threaded = .init(fba.allocator(), .{
90 .argv0 = .empty,
91 .environ = .empty,
92 });
8493 defer threaded.deinit();
8594 const io = threaded.io();
8695
......@@ -113,7 +122,10 @@ test "Group.async context alignment" {
113122 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;
114123 var fba: std.heap.FixedBufferAllocator = .init(&buffer);
115124
116 var threaded: std.Io.Threaded = .init(fba.allocator(), .{});
125 var threaded: std.Io.Threaded = .init(fba.allocator(), .{
126 .argv0 = .empty,
127 .environ = .empty,
128 });
117129 defer threaded.deinit();
118130 const io = threaded.io();
119131
......@@ -133,7 +145,10 @@ fn returnArray() [32]u8 {
133145}
134146
135147test "async with array return type" {
136 var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});
148 var threaded: std.Io.Threaded = .init(std.testing.allocator, .{
149 .argv0 = .empty,
150 .environ = .empty,
151 });
137152 defer threaded.deinit();
138153 const io = threaded.io();
139154
lib/std/process/Environ.zig+7
......@@ -19,6 +19,13 @@ const mem = std.mem;
1919/// queried and heap-allocated at runtime.
2020block: Block,
2121
22pub const empty: Environ = .{
23 .block = switch (@TypeOf(Block)) {
24 void => {},
25 else => &.{},
26 },
27};
28
2229pub const Block = switch (native_os) {
2330 .windows => [*:0]const u16,
2431 .wasi => switch (builtin.link_libc) {
src/main.zig+25-18
......@@ -186,36 +186,43 @@ pub fn main(init: std.process.Init.Minimal) anyerror!void {
186186
187187 if (args.len > 0) crash_report.zig_argv0 = args[0];
188188
189 var env_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err});
190
191 if (tracy.enable_allocation) {
192 var gpa_tracy = tracy.tracyAllocator(gpa);
193 return mainArgs(gpa_tracy.allocator(), arena, args, &env_map);
194 }
195
196 if (native_os == .wasi) {
197 wasi_preopens = try fs.wasi.preopensAlloc(arena);
198 }
199
200 return mainArgs(gpa, arena, args, &env_map);
201}
202
203fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_map: *process.Environ.Map) !void {
204 Compilation.setMainThread();
205
206189 if (args.len <= 1) {
207190 std.log.info("{s}", .{usage});
208191 fatal("expected command argument", .{});
209192 }
210193
194 var env_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err});
195
196 Compilation.setMainThread();
197
211198 var threaded: Io.Threaded = .init(gpa, .{
212 .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},
199 .argv0 = .init(init.args),
200 .environ = init.environ,
213201 });
214202 defer threaded.deinit();
215203 threaded_impl_ptr = &threaded;
216204 threaded.stack_size = thread_stack_size;
217205 const io = threaded.io();
218206
207 if (tracy.enable_allocation) {
208 var gpa_tracy = tracy.tracyAllocator(gpa);
209 return mainArgs(gpa_tracy.allocator(), arena, io, args, &env_map);
210 }
211
212 if (native_os == .wasi) {
213 wasi_preopens = try fs.wasi.preopensAlloc(arena);
214 }
215
216 return mainArgs(gpa, arena, io, args, &env_map);
217}
218
219fn mainArgs(
220 gpa: Allocator,
221 arena: Allocator,
222 io: Io,
223 args: []const [:0]const u8,
224 env_map: *process.Environ.Map,
225) !void {
219226 if (process.can_replace and EnvVar.ZIG_IS_DETECTING_LIBC_PATHS.isSet(env_map)) {
220227 dev.check(.cc_command);
221228 // In this case we have accidentally invoked ourselves as "the system C compiler"