authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2025-09-19 22:10:53-07:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-09-21 21:08:51+02:00
log8d5b651c3a37a432957aefd633e755c56c07b59d
tree800006854ebb188dc4e33332f9a7ed21ae5f5b61
parentc15f8b9fc97e141bb59f7d11c3bf4ad76a5036ed
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

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

1 files changed, 2 insertions(+), 1 deletions(-)

lib/std/Io/Reader.zig+2-1
......@@ -400,10 +400,11 @@ pub fn defaultReadVec(r: *Reader, data: [][]u8) Error!usize {
400400 .vtable = &.{ .drain = Writer.fixedDrain },
401401 };
402402 const limit: Limit = .limited(writer.buffer.len - writer.end);
403 r.end += r.vtable.stream(r, &writer, limit) catch |err| switch (err) {
403 const n = r.vtable.stream(r, &writer, limit) catch |err| switch (err) {
404404 error.WriteFailed => unreachable,
405405 else => |e| return e,
406406 };
407 r.end += n;
407408 return 0;
408409}
409410