zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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:
Mlib/std/Io/Reader.zig | 53++++++++++++++++++++++++++++++++++++++++++++++++-----
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; }