Commit 551e009da7

Isaac Freund <mail@isaacfreund.com>
2025-08-16 11:32:10
Build.Step.Run: fix missing stdin buffer and flush
Writer.sendFileAll() asserts non-zero buffer capacity in the case that the fallback is hit. It also requires the caller to flush. The buffer may be bypassed as an optimization but this is not a guarantee. Also improve the Writer documentation and add an earlier assert on buffer capacity in sendFileAll().
1 parent 399bace
Changed files (2)
lib
std
Build
Step
Io
lib/std/Build/Step/Run.zig
@@ -1780,9 +1780,10 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !StdIoResult {
             };
             defer file.close();
             // TODO https://github.com/ziglang/zig/issues/23955
-            var buffer: [1024]u8 = undefined;
-            var file_reader = file.reader(&buffer);
-            var stdin_writer = child.stdin.?.writer(&.{});
+            var read_buffer: [1024]u8 = undefined;
+            var file_reader = file.reader(&read_buffer);
+            var write_buffer: [1024]u8 = undefined;
+            var stdin_writer = child.stdin.?.writer(&write_buffer);
             _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
                 error.ReadFailed => return run.step.fail("failed to read from {f}: {t}", .{
                     path, file_reader.err.?,
@@ -1791,6 +1792,11 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !StdIoResult {
                     stdin_writer.err.?,
                 }),
             };
+            stdin_writer.interface.flush() catch |err| switch (err) {
+                error.WriteFailed => return run.step.fail("failed to write to stdin: {t}", .{
+                    stdin_writer.err.?,
+                }),
+            };
             child.stdin.?.close();
             child.stdin = null;
         },
lib/std/Io/Writer.zig
@@ -882,6 +882,9 @@ pub fn writeSliceSwap(w: *Writer, Elem: type, slice: []const Elem) Error!void {
 /// Unlike `writeSplat` and `writeVec`, this function will call into `VTable`
 /// even if there is enough buffer capacity for the file contents.
 ///
+/// The caller is responsible for flushing. Although the buffer may be bypassed
+/// as an optimization, this is not a guarantee.
+///
 /// Although it would be possible to eliminate `error.Unimplemented` from the
 /// error set by reading directly into the buffer in such case, this is not
 /// done because it is more efficient to do it higher up the call stack so that
@@ -924,7 +927,16 @@ pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) File
 
 /// Number of bytes logically written is returned. This excludes bytes from
 /// `buffer` because they have already been logically written.
+///
+/// The caller is responsible for flushing. Although the buffer may be bypassed
+/// as an optimization, this is not a guarantee.
+///
+/// Asserts nonzero buffer capacity.
 pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
+    // The fallback sendFileReadingAll() path asserts non-zero buffer capacity.
+    // Explicitly assert it here as well to ensure the assert is hit even if
+    // the fallback path is not taken.
+    assert(w.buffer.len > 0);
     var remaining = @intFromEnum(limit);
     while (remaining > 0) {
         const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {