authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-05-26 15:37:17-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-05-29 21:22:51+02:00
log18b3c78a9e2f2b89efcca63501fffb9edf67e88c
tree32cd8728611f26fe865ee157977282e07bdf1907
parent53c66e652764d176f1ef3f277b6c02138841b29b

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

1 files changed, 48 insertions(+), 5 deletions(-)

lib/std/Io/Reader.zig+48-5
...@@ -242,11 +242,10 @@ pub fn streamExactPreserve(r: *Reader, w: *Writer, preserve_len: usize, n: usize...@@ -242,11 +242,10 @@ pub fn streamExactPreserve(r: *Reader, w: *Writer, preserve_len: usize, n: usize
242 remaining -= try r.stream(w, .limited(remaining - preserve_len));242 remaining -= try r.stream(w, .limited(remaining - preserve_len));
243 if (w.end + remaining <= w.buffer.len) return streamExact(r, w, remaining);243 if (w.end + remaining <= w.buffer.len) return streamExact(r, w, remaining);
244 }244 }
245 // All the next bytes received must be preserved.245 // Offset the amount preserved by the amount we have left to stream
246 if (preserve_len < w.end) {246 // since the remaining bytes are always going to be part of that
247 @memmove(w.buffer[0..preserve_len], w.buffer[w.end - preserve_len ..][0..preserve_len]);247 // preservation.
248 w.end = preserve_len;248 try w.rebase(preserve_len -| remaining, remaining);
249 }
250 return streamExact(r, w, remaining);249 return streamExact(r, w, remaining);
251}250}
252251
...@@ -2300,6 +2299,50 @@ fn testLeb128(comptime T: type, encoded: []const u8) !T {...@@ -2300,6 +2299,50 @@ fn testLeb128(comptime T: type, encoded: []const u8) !T {
2300 return result;2299 return result;
2301}2300}
23022301
2302test streamExactPreserve {
2303 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .stream_len = 5 });
2304 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 9, .preserve = 5, .stream_len = 2 });
2305 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .stream_len = 6 });
2306 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .stream_len = 6 });
2307 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .stream_len = 10 });
2308 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .stream_len = 10 });
2309 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .stream_len = 11 });
2310 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .stream_len = 80 });
2311 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .stream_len = 85 });
2312 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .stream_len = 6 });
2313 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .stream_len = 11 });
2314 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .stream_len = 80 });
2315 try testStreamExactPreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .stream_len = 85 });
2316}
2317
2318fn testStreamExactPreserve(options: struct { buf_len: u4, fill_len: u4, preserve: u4, stream_len: u8 }) !void {
2319 assert(options.fill_len <= options.buf_len);
2320 assert(options.preserve <= options.buf_len);
2321
2322 var input: [256]u8 = undefined;
2323 for (&input, 0..) |*val, i| {
2324 val.* = @as(u8, @intCast(i % 26)) + 'a';
2325 }
2326 const expected_out = input[0 .. options.fill_len + options.stream_len];
2327 const expected_preserved = expected_out[expected_out.len -| options.preserve..];
2328
2329 var r: Reader = .fixed(&input);
2330 var out_buf: [256]u8 = undefined;
2331 var fw: Writer = .fixed(&out_buf);
2332 var indirect_buffer: [16]u8 = undefined;
2333 var twi: std.testing.WriterIndirect = .init(&fw, indirect_buffer[0..options.buf_len]);
2334 const w = &twi.interface;
2335
2336 try r.streamExact(w, options.fill_len);
2337 try r.streamExactPreserve(w, options.preserve, options.stream_len);
2338
2339 try std.testing.expectEqualStrings(expected_preserved, w.buffer[w.end -| options.preserve..w.end]);
2340
2341 try w.flush();
2342
2343 try std.testing.expectEqualStrings(expected_out, fw.buffered());
2344}
2345
2303test {2346test {
2304 _ = Limited;2347 _ = Limited;
2305}2348}