authorgravatar for aspatelakbar@gmail.comakbarhusain <aspatelakbar@gmail.com> 2026-08-31 01:43:02+02:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-08-31 01:43:02+02:00
logfbdf84138ee8320c3edcd396f229e152307baebf
tree2ee3de833daf8ddd75e0c7f50fdad3c3bef12c50
parent8a6f4ae1827a5b6b28b07f57852008ea001bf02e

Fix data truncation in writeSplatHeaderLimitFinish (#36672)

fixes #36637 writeSplatHeaderLimitFinish batches up to 8 slices in a fixed `vecs: [8][]const u8` buffer before draining. When more than 8 slices (header plus data elements) were present, the loop did `break :v` once the buffer filled, silently dropping the remaining data and the splat pattern. Now the full batch is drained so processing continues for the remaining slices, with `total` accumulating the bytes written across batches. Also updated test `writeSplatHeader splatting avoids buffer aliasing temptation` which had incorrect expectations. Co-authored-by: Ryan Liptak <squeek502@hotmail.com> Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36672 Reviewed-by: Ryan Liptak <squeek502@noreply.codeberg.org>

1 files changed, 34 insertions(+), 25 deletions(-)

lib/std/Io/Writer.zig+34-25
......@@ -269,36 +269,43 @@ fn writeSplatHeaderLimitFinish(
269269 limit: usize,
270270) Error!usize {
271271 var remaining = limit;
272 var total: usize = 0;
272273 var vecs: [8][]const u8 = undefined;
273274 var i: usize = 0;
274 v: {
275 if (header.len != 0) {
276 const copy_len = @min(header.len, remaining);
277 vecs[i] = header[0..copy_len];
278 i += 1;
279 remaining -= copy_len;
280 if (remaining == 0) break :v;
275 if (header.len != 0) {
276 const copy_len = @min(header.len, remaining);
277 vecs[i] = header[0..copy_len];
278 i += 1;
279 remaining -= copy_len;
280 if (remaining == 0) {
281 return w.vtable.drain(w, (&vecs)[0..i], 1);
281282 }
282 for (data[0 .. data.len - 1]) |buf| {
283 if (buf.len == 0) continue;
284 const copy_len = @min(buf.len, remaining);
285 vecs[i] = buf[0..copy_len];
286 i += 1;
287 remaining -= copy_len;
288 if (remaining == 0) break :v;
289 if (vecs.len - i == 0) break :v;
283 }
284 for (data[0 .. data.len - 1]) |buf| {
285 if (buf.len == 0) continue;
286 const copy_len = @min(buf.len, remaining);
287 vecs[i] = buf[0..copy_len];
288 i += 1;
289 remaining -= copy_len;
290 if (remaining == 0) {
291 return w.vtable.drain(w, (&vecs)[0..i], 1);
290292 }
291 const pattern = data[data.len - 1];
292 if (splat == 1 or remaining < pattern.len) {
293 vecs[i] = pattern[0..@min(remaining, pattern.len)];
294 i += 1;
295 break :v;
293 if (i == vecs.len) {
294 total += try w.vtable.drain(w, &vecs, 1);
295 i = 0;
296296 }
297 vecs[i] = pattern;
297 }
298 const pattern = data[data.len - 1];
299 if (splat == 1 or remaining < pattern.len) {
300 vecs[i] = pattern[0..@min(remaining, pattern.len)];
298301 i += 1;
299 return w.vtable.drain(w, (&vecs)[0..i], @min(remaining / pattern.len, splat));
302 total += try w.vtable.drain(w, (&vecs)[0..i], 1);
303 return total;
300304 }
301 return w.vtable.drain(w, (&vecs)[0..i], 1);
305 vecs[i] = pattern;
306 i += 1;
307 total += try w.vtable.drain(w, (&vecs)[0..i], @min(remaining / pattern.len, splat));
308 return total;
302309}
303310
304311const SplatHeaderTestCase = struct {
......@@ -357,6 +364,8 @@ test "fixed writer writeSplatHeaderLimit" {
357364
358365 // allocating writer that needs to expand capacity during splat
359366 try testWriteSplatHeaderLimit(.{ .writer_type = .allocating, .buf_len = 8, .header = "hhhh", .data = &.{"PP"}, .splat = 3, .limit = 100, .expected_res = .{ .written = 10 }, .expected_buf_content = "hhhhPPPPPP" });
367 try testWriteSplatHeaderLimit(.{ .writer_type = .allocating, .buf_len = 2, .header = "", .data = &.{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "X", "Y", "ZZ" }, .splat = 2, .limit = 100, .expected_res = .{ .written = 16 }, .expected_buf_content = "0123456789XYZZZZ" });
368 try testWriteSplatHeaderLimit(.{ .writer_type = .allocating, .buf_len = 2, .header = "", .data = &.{ "0", "1", "2", "", "", "3", "4" }, .splat = 2, .limit = 4, .expected_res = .{ .written = 4 }, .expected_buf_content = "0123" });
360369}
361370
362371test "writeSplatHeader splatting avoids buffer aliasing temptation" {
......@@ -367,9 +376,9 @@ test "writeSplatHeader splatting avoids buffer aliasing temptation" {
367376 const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{
368377 "1", "2", "3", "4", "5", "6", "foo", "bar", "foo",
369378 }, 3);
370 try testing.expectEqual(41, n);
379 try testing.expectEqual(53, n);
371380 try testing.expectEqualStrings(
372 "header which is longer than buf 123456foo",
381 "header which is longer than buf 123456foobarfoofoofoo",
373382 aw.writer.buffered(),
374383 );
375384}