commit 18b3c78a9e2f2b89efcca63501fffb9edf67e88c (tree)
parent 53c66e652764d176f1ef3f277b6c02138841b29b
Author: Ryan Liptak <squeek502@hotmail.com>
Date: Tue, 26 May 2026 15:37:17 -0700
Reader: Fix streamExactPreserve losing data in certain cases
In cases where the now removed `preserve < w.end` branch was taken, some of the data from the reader would never make it to the writer's logical sink. For example, in one of the newly added test cases where a reader streams 9 bytes to a writer with a buffer of length 10, and then `streamExactPreserve` is called with a preserve of 5 and an `n` of 2, the 5 "preserved" bytes would get memmoved to the front of the writer's buffer in the `preserve < w.end` branch, clobbering the first 4 bytes (without any chance of them making it to the logical sink).
After this commit, `rebase` is called to allow the writer to do the preservation (and therefore sending any relevant bytes to the logical sink in the process).
Addresses part of https://github.com/ziglang/zig/issues/24767
Supersedes https://github.com/ziglang/zig/pull/24787
Diffstat:
1 file changed, 48 insertions(+), 5 deletions(-)
diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig
@@ -242,11 +242,10 @@ pub fn streamExactPreserve(r: *Reader, w: *Writer, preserve_len: usize, n: usize
remaining -= try r.stream(w, .limited(remaining - preserve_len));
if (w.end + remaining <= w.buffer.len) return streamExact(r, w, remaining);
}
- // All the next bytes received must be preserved.
- if (preserve_len < w.end) {
- @memmove(w.buffer[0..preserve_len], w.buffer[w.end - preserve_len ..][0..preserve_len]);
- w.end = preserve_len;
- }
+ // Offset the amount preserved by the amount we have left to stream
+ // since the remaining bytes are always going to be part of that
+ // preservation.
+ try w.rebase(preserve_len -| remaining, remaining);
return streamExact(r, w, remaining);
}
@@ -2300,6 +2299,50 @@ fn testLeb128(comptime T: type, encoded: []const u8) !T {
return result;
}
+test streamExactPreserve {
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .stream_len = 5 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 9, .preserve = 5, .stream_len = 2 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .stream_len = 6 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .stream_len = 6 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .stream_len = 10 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .stream_len = 10 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .stream_len = 11 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .stream_len = 80 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .stream_len = 85 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .stream_len = 6 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .stream_len = 11 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .stream_len = 80 });
+ try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .stream_len = 85 });
+}
+
+fn testStreamExactPreserve(options: struct { buf_len: u4, fill_len: u4, preserve: u4, stream_len: u8 }) !void {
+ assert(options.fill_len <= options.buf_len);
+ assert(options.preserve <= options.buf_len);
+
+ var input: [256]u8 = undefined;
+ for (&input, 0..) |*val, i| {
+ val.* = @as(u8, @intCast(i % 26)) + 'a';
+ }
+ const expected_out = input[0 .. options.fill_len + options.stream_len];
+ const expected_preserved = expected_out[expected_out.len -| options.preserve..];
+
+ var r: Reader = .fixed(&input);
+ var out_buf: [256]u8 = undefined;
+ var fw: Writer = .fixed(&out_buf);
+ var indirect_buffer: [16]u8 = undefined;
+ var twi: std.testing.WriterIndirect = .init(&fw, indirect_buffer[0..options.buf_len]);
+ const w = &twi.interface;
+
+ try r.streamExact(w, options.fill_len);
+ try r.streamExactPreserve(w, options.preserve, options.stream_len);
+
+ try std.testing.expectEqualStrings(expected_preserved, w.buffer[w.end -| options.preserve..w.end]);
+
+ try w.flush();
+
+ try std.testing.expectEqualStrings(expected_out, fw.buffered());
+}
+
test {
_ = Limited;
}