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.
This commit is contained in:
Andrew Kelley
2025-12-31 18:06:48 -08:00
parent de8c4cd64e
commit 9009ab2495
7 changed files with 88 additions and 47 deletions
+4 -1
View File
@@ -39,7 +39,10 @@ pub fn main(init: process.Init.Minimal) !void {
const args = try init.args.toSlice(arena);
var threaded: std.Io.Threaded = .init(gpa, .{});
var threaded: std.Io.Threaded = .init(gpa, .{
.environ = init.environ,
.argv0 = .init(init.args),
});
defer threaded.deinit();
const io = threaded.io();
+8 -6
View File
@@ -65,13 +65,13 @@ pub fn main(init: std.process.Init.Minimal) void {
}
if (listen) {
return mainServer(args) catch @panic("internal test runner failure");
return mainServer(init) catch @panic("internal test runner failure");
} else {
return mainTerminal(args);
return mainTerminal(init);
}
}
fn mainServer(args: []const [:0]const u8) !void {
fn mainServer(init: std.process.Init.Minimal) !void {
@disableInstrumentation();
var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);
var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);
@@ -131,7 +131,8 @@ fn mainServer(args: []const [:0]const u8) !void {
.run_test => {
testing.allocator_instance = .{};
testing.io_instance = .init(testing.allocator, .{
.argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},
.argv0 = .init(init.args),
.environ = init.environ,
});
log_err_count = 0;
const index = try server.receiveBody_u32();
@@ -216,7 +217,7 @@ fn mainServer(args: []const [:0]const u8) !void {
}
}
fn mainTerminal(args: []const [:0]const u8) void {
fn mainTerminal(init: std.process.Init.Minimal) void {
@disableInstrumentation();
if (builtin.fuzz) @panic("fuzz test requires server");
@@ -235,7 +236,8 @@ fn mainTerminal(args: []const [:0]const u8) void {
for (test_fn_list, 0..) |test_fn, i| {
testing.allocator_instance = .{};
testing.io_instance = .init(testing.allocator, .{
.argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},
.argv0 = .init(init.args),
.environ = init.environ,
});
defer {
testing.io_instance.deinit();
+4 -12
View File
@@ -1881,19 +1881,11 @@ pub fn runAllowFail(
/// inside step make() functions. If any errors occur, it fails the build with
/// a helpful message.
pub fn run(b: *Build, argv: []const []const u8) []u8 {
if (!process.can_spawn) {
std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{
try Step.allocPrintCmd(b.allocator, null, null, argv),
});
process.exit(1);
}
var code: u8 = undefined;
return b.runAllowFail(argv, &code, .inherit) catch |err| {
const printed_cmd = Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM");
std.debug.print("unable to spawn the following command: {t}\n{s}\n", .{ err, printed_cmd });
process.exit(1);
};
return b.runAllowFail(argv, &code, .inherit) catch |err| process.fatal(
"the following command failed with {t}:\n{s}",
.{ err, Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM") },
);
}
pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void {
+20 -5
View File
@@ -66,12 +66,25 @@ environ: Environ,
pub const Argv0 = switch (native_os) {
.openbsd, .haiku => struct {
value: ?[*:0]const u8 = null,
value: ?[*:0]const u8,
pub const empty: Argv0 = .{ .value = null };
pub fn init(args: process.Args) Argv0 {
return .{ .value = args.value[0] };
}
},
else => struct {
pub const empty: Argv0 = .{};
pub fn init(args: process.Args) Argv0 {
_ = args;
return .{};
}
},
else => struct {},
};
pub const Environ = struct {
const Environ = struct {
/// Unmodified data directly from the OS.
block: process.Environ.Block = &.{},
/// Protected by `mutex`. Determines whether the other fields have been
@@ -1141,7 +1154,8 @@ pub const InitOptions = struct {
/// Affects the following operations:
/// * `fileIsTty`
/// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").
environ: Environ = .{},
/// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
environ: process.Environ,
};
/// Related:
@@ -1171,8 +1185,9 @@ pub fn init(
.old_sig_pipe = undefined,
.have_signal_handler = false,
.argv0 = options.argv0,
.environ = options.environ,
.worker_threads = .init(null),
.environ = .{ .block = options.environ.block },
.robust_cancel = options.robust_cancel,
};
if (posix.Sigaction != void) {
+20 -5
View File
@@ -13,7 +13,10 @@ test "concurrent vs main prevents deadlock via oversubscription" {
return error.SkipZigTest;
}
var threaded: Io.Threaded = .init(std.testing.allocator, .{});
var threaded: Io.Threaded = .init(std.testing.allocator, .{
.argv0 = .empty,
.environ = .empty,
});
defer threaded.deinit();
const io = threaded.io();
@@ -46,7 +49,10 @@ test "concurrent vs concurrent prevents deadlock via oversubscription" {
return error.SkipZigTest;
}
var threaded: Io.Threaded = .init(std.testing.allocator, .{});
var threaded: Io.Threaded = .init(std.testing.allocator, .{
.argv0 = .empty,
.environ = .empty,
});
defer threaded.deinit();
const io = threaded.io();
@@ -80,7 +86,10 @@ test "async/concurrent context and result alignment" {
var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;
var fba: std.heap.FixedBufferAllocator = .init(&buffer);
var threaded: std.Io.Threaded = .init(fba.allocator(), .{});
var threaded: std.Io.Threaded = .init(fba.allocator(), .{
.argv0 = .empty,
.environ = .empty,
});
defer threaded.deinit();
const io = threaded.io();
@@ -113,7 +122,10 @@ test "Group.async context alignment" {
var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;
var fba: std.heap.FixedBufferAllocator = .init(&buffer);
var threaded: std.Io.Threaded = .init(fba.allocator(), .{});
var threaded: std.Io.Threaded = .init(fba.allocator(), .{
.argv0 = .empty,
.environ = .empty,
});
defer threaded.deinit();
const io = threaded.io();
@@ -133,7 +145,10 @@ fn returnArray() [32]u8 {
}
test "async with array return type" {
var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});
var threaded: std.Io.Threaded = .init(std.testing.allocator, .{
.argv0 = .empty,
.environ = .empty,
});
defer threaded.deinit();
const io = threaded.io();
+7
View File
@@ -19,6 +19,13 @@ const mem = std.mem;
/// queried and heap-allocated at runtime.
block: Block,
pub const empty: Environ = .{
.block = switch (@TypeOf(Block)) {
void => {},
else => &.{},
},
};
pub const Block = switch (native_os) {
.windows => [*:0]const u16,
.wasi => switch (builtin.link_libc) {
+25 -18
View File
@@ -186,36 +186,43 @@ pub fn main(init: std.process.Init.Minimal) anyerror!void {
if (args.len > 0) crash_report.zig_argv0 = args[0];
var env_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err});
if (tracy.enable_allocation) {
var gpa_tracy = tracy.tracyAllocator(gpa);
return mainArgs(gpa_tracy.allocator(), arena, args, &env_map);
}
if (native_os == .wasi) {
wasi_preopens = try fs.wasi.preopensAlloc(arena);
}
return mainArgs(gpa, arena, args, &env_map);
}
fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_map: *process.Environ.Map) !void {
Compilation.setMainThread();
if (args.len <= 1) {
std.log.info("{s}", .{usage});
fatal("expected command argument", .{});
}
var env_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err});
Compilation.setMainThread();
var threaded: Io.Threaded = .init(gpa, .{
.argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},
.argv0 = .init(init.args),
.environ = init.environ,
});
defer threaded.deinit();
threaded_impl_ptr = &threaded;
threaded.stack_size = thread_stack_size;
const io = threaded.io();
if (tracy.enable_allocation) {
var gpa_tracy = tracy.tracyAllocator(gpa);
return mainArgs(gpa_tracy.allocator(), arena, io, args, &env_map);
}
if (native_os == .wasi) {
wasi_preopens = try fs.wasi.preopensAlloc(arena);
}
return mainArgs(gpa, arena, io, args, &env_map);
}
fn mainArgs(
gpa: Allocator,
arena: Allocator,
io: Io,
args: []const [:0]const u8,
env_map: *process.Environ.Map,
) !void {
if (process.can_replace and EnvVar.ZIG_IS_DETECTING_LIBC_PATHS.isSet(env_map)) {
dev.check(.cc_command);
// In this case we have accidentally invoked ourselves as "the system C compiler"