authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2025-10-17 16:38:03-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2025-11-21 22:34:55-08:00
log21f9f378f165a762059f12bc3d789c731037c8b6
treee29842646e88a4082df650cb10a4df8fa71803c5
parent2ea55d7153b9832e7f00c8a85ca4941ccde6b0d6

Reader.defaultDiscard: Fix for use with an indirect reader

If a Reader implementation implements `stream` by ignoring the Writer, writing directly to its internal buffer, and returning 0, then `defaultDiscard` would not update `seek` and also return 0, which is incorrect and can cause `discardShort` to violate the contract of `VTable.discard` by calling into `vtable.discard` with a non-empty buffer. This commit fixes the problem by advancing seek up to the limit after the stream call. This logic could likely be somewhat simplified in the future depending on how #25170 is resolved.

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

lib/std/Io/Reader.zig+19-1
......@@ -200,11 +200,17 @@ pub fn defaultDiscard(r: *Reader, limit: Limit) Error!usize {
200200 r.seek = 0;
201201 r.end = 0;
202202 var d: Writer.Discarding = .init(r.buffer);
203 const n = r.stream(&d.writer, limit) catch |err| switch (err) {
203 var n = r.stream(&d.writer, limit) catch |err| switch (err) {
204204 error.WriteFailed => unreachable,
205205 error.ReadFailed => return error.ReadFailed,
206206 error.EndOfStream => return error.EndOfStream,
207207 };
208 // If `stream` wrote to `r.buffer` without going through the writer,
209 // we need to discard as much of the buffered data as possible.
210 const remaining = @intFromEnum(limit) - n;
211 const buffered_n_to_discard = @min(remaining, r.end - r.seek);
212 n += buffered_n_to_discard;
213 r.seek += buffered_n_to_discard;
208214 assert(n <= @intFromEnum(limit));
209215 return n;
210216}
......@@ -1720,6 +1726,18 @@ fn failingDiscard(r: *Reader, limit: Limit) Error!usize {
17201726 return error.ReadFailed;
17211727}
17221728
1729test "discardAll that has to call discard multiple times on an indirect reader" {
1730 var fr: Reader = .fixed("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
1731 var indirect_buffer: [3]u8 = undefined;
1732 var tri: std.testing.ReaderIndirect = .init(&fr, &indirect_buffer);
1733 const r = &tri.interface;
1734
1735 try r.discardAll(10);
1736 var remaining_buf: [16]u8 = undefined;
1737 try r.readSliceAll(&remaining_buf);
1738 try std.testing.expectEqualStrings(fr.buffer[10..], remaining_buf[0..]);
1739}
1740
17231741test "readAlloc when the backing reader provides one byte at a time" {
17241742 const str = "This is a test";
17251743 var tiny_buffer: [1]u8 = undefined;