From 3fbb88c4bd146ca7bd9e7ab5da9c4b05298f3b34 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 19 Sep 2025 22:10:53 -0700 Subject: [PATCH] Reader.defaultReadVec: Workaround bad `r.end += r.vtable.stream()` behavior If `r.end` is updated in the `stream` implementation, then it's possible that `r.end += ...` will behave unexpectedly. What seems to happen is that it reverts back to its value before the function call and then the increment happens. Here's a reproduction: ```zig test "fill when stream modifies `end` and returns 0" { var buf: [3]u8 = undefined; var zero_reader = infiniteZeroes(&buf); _ = try zero_reader.fill(1); try std.testing.expectEqual(buf.len, zero_reader.end); } pub fn infiniteZeroes(buf: []u8) std.Io.Reader { return .{ .vtable = &.{ .stream = stream, }, .buffer = buf, .end = 0, .seek = 0, }; } fn stream(r: *std.Io.Reader, _: *std.Io.Writer, _: std.Io.Limit) std.Io.Reader.StreamError!usize { @memset(r.buffer[r.seek..], 0); r.end = r.buffer.len; return 0; } ``` When `fill` is called, it will call into `vtable.readVec` which in this case is `defaultReadVec`. In `defaultReadVec`: - Before the `r.end += r.vtable.stream` line, `r.end` will be 0 - In `r.vtable.stream`, `r.end` is modified to 3 and it returns 0 - After the `r.end += r.vtable.stream` line, `r.end` will be 0 instead of the expected 3 Separating the `r.end += stream();` into two lines fixes the problem (and this separation is done elsewhere in `Reader` so it seems possible that this class of bug has been encountered before). Potentially related issues: - https://github.com/ziglang/zig/issues/4021 - https://github.com/ziglang/zig/issues/12064 --- lib/std/Io/Reader.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index e0bc8e5c4c..6ed0230317 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -432,10 +432,11 @@ pub fn defaultReadVec(r: *Reader, data: [][]u8) Error!usize { .vtable = &.{ .drain = Writer.fixedDrain }, }; const limit: Limit = .limited(writer.buffer.len - writer.end); - r.end += r.vtable.stream(r, &writer, limit) catch |err| switch (err) { + const n = r.vtable.stream(r, &writer, limit) catch |err| switch (err) { error.WriteFailed => unreachable, else => |e| return e, }; + r.end += n; return 0; }