Commit 21f9f378f1

Ryan Liptak <squeek502@hotmail.com>
2025-10-18 01:38:03
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 parent 2ea55d7
Changed files (1)
lib
std
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;