authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2025-09-19 22:10:53-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-20 18:31:38-07:00
log3fbb88c4bd146ca7bd9e7ab5da9c4b05298f3b34
tree8a07a0a5999991b99e31062acd6f87da1a1307d3
parent0e47bd16dac9dcdfc12168c07104030af8eaaa8b

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
......@@ -432,10 +432,11 @@ pub fn defaultReadVec(r: *Reader, data: [][]u8) Error!usize {
432432 .vtable = &.{ .drain = Writer.fixedDrain },
433433 };
434434 const limit: Limit = .limited(writer.buffer.len - writer.end);
435 r.end += r.vtable.stream(r, &writer, limit) catch |err| switch (err) {
435 const n = r.vtable.stream(r, &writer, limit) catch |err| switch (err) {
436436 error.WriteFailed => unreachable,
437437 else => |e| return e,
438438 };
439 r.end += n;
439440 return 0;
440441}
441442