From 21f9f378f165a762059f12bc3d789c731037c8b6 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 17 Oct 2025 16:38:03 -0700 Subject: [PATCH] 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. --- lib/std/Io/Reader.zig | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index 167e1cb91f..56a70f0781 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -200,11 +200,17 @@ pub fn defaultDiscard(r: *Reader, limit: Limit) Error!usize { r.seek = 0; r.end = 0; var d: Writer.Discarding = .init(r.buffer); - const n = r.stream(&d.writer, limit) catch |err| switch (err) { + var n = r.stream(&d.writer, limit) catch |err| switch (err) { error.WriteFailed => unreachable, error.ReadFailed => return error.ReadFailed, error.EndOfStream => return error.EndOfStream, }; + // If `stream` wrote to `r.buffer` without going through the writer, + // we need to discard as much of the buffered data as possible. + const remaining = @intFromEnum(limit) - n; + const buffered_n_to_discard = @min(remaining, r.end - r.seek); + n += buffered_n_to_discard; + r.seek += buffered_n_to_discard; assert(n <= @intFromEnum(limit)); return n; } @@ -1720,6 +1726,18 @@ fn failingDiscard(r: *Reader, limit: Limit) Error!usize { return error.ReadFailed; } +test "discardAll that has to call discard multiple times on an indirect reader" { + var fr: Reader = .fixed("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + var indirect_buffer: [3]u8 = undefined; + var tri: std.testing.ReaderIndirect = .init(&fr, &indirect_buffer); + const r = &tri.interface; + + try r.discardAll(10); + var remaining_buf: [16]u8 = undefined; + try r.readSliceAll(&remaining_buf); + try std.testing.expectEqualStrings(fr.buffer[10..], remaining_buf[0..]); +} + test "readAlloc when the backing reader provides one byte at a time" { const str = "This is a test"; var tiny_buffer: [1]u8 = undefined;