From fbdf84138ee8320c3edcd396f229e152307baebf Mon Sep 17 00:00:00 2001 From: akbarhusain Date: Mon, 31 Aug 2026 01:43:02 +0200 Subject: [PATCH] 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 Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36672 Reviewed-by: Ryan Liptak --- lib/std/Io/Writer.zig | 59 +++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 17cff637912fbab283e38ac9e4022c9b1c84e918..492a6a03bea5a1b181fa71e5a656a06ca63488f6 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -269,36 +269,43 @@ fn writeSplatHeaderLimitFinish( limit: usize, ) Error!usize { var remaining = limit; + var total: usize = 0; var vecs: [8][]const u8 = undefined; var i: usize = 0; - v: { - if (header.len != 0) { - const copy_len = @min(header.len, remaining); - vecs[i] = header[0..copy_len]; - i += 1; - remaining -= copy_len; - if (remaining == 0) break :v; + if (header.len != 0) { + const copy_len = @min(header.len, remaining); + vecs[i] = header[0..copy_len]; + i += 1; + remaining -= copy_len; + if (remaining == 0) { + return w.vtable.drain(w, (&vecs)[0..i], 1); } - for (data[0 .. data.len - 1]) |buf| { - if (buf.len == 0) continue; - const copy_len = @min(buf.len, remaining); - vecs[i] = buf[0..copy_len]; - i += 1; - remaining -= copy_len; - if (remaining == 0) break :v; - if (vecs.len - i == 0) break :v; + } + for (data[0 .. data.len - 1]) |buf| { + if (buf.len == 0) continue; + const copy_len = @min(buf.len, remaining); + vecs[i] = buf[0..copy_len]; + i += 1; + remaining -= copy_len; + if (remaining == 0) { + return w.vtable.drain(w, (&vecs)[0..i], 1); } - const pattern = data[data.len - 1]; - if (splat == 1 or remaining < pattern.len) { - vecs[i] = pattern[0..@min(remaining, pattern.len)]; - i += 1; - break :v; + if (i == vecs.len) { + total += try w.vtable.drain(w, &vecs, 1); + i = 0; } - vecs[i] = pattern; + } + const pattern = data[data.len - 1]; + if (splat == 1 or remaining < pattern.len) { + vecs[i] = pattern[0..@min(remaining, pattern.len)]; i += 1; - return w.vtable.drain(w, (&vecs)[0..i], @min(remaining / pattern.len, splat)); + total += try w.vtable.drain(w, (&vecs)[0..i], 1); + return total; } - return w.vtable.drain(w, (&vecs)[0..i], 1); + vecs[i] = pattern; + i += 1; + total += try w.vtable.drain(w, (&vecs)[0..i], @min(remaining / pattern.len, splat)); + return total; } const SplatHeaderTestCase = struct { @@ -357,6 +364,8 @@ test "fixed writer writeSplatHeaderLimit" { // allocating writer that needs to expand capacity during splat try testWriteSplatHeaderLimit(.{ .writer_type = .allocating, .buf_len = 8, .header = "hhhh", .data = &.{"PP"}, .splat = 3, .limit = 100, .expected_res = .{ .written = 10 }, .expected_buf_content = "hhhhPPPPPP" }); + 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" }); + 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" }); } test "writeSplatHeader splatting avoids buffer aliasing temptation" { @@ -367,9 +376,9 @@ test "writeSplatHeader splatting avoids buffer aliasing temptation" { const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{ "1", "2", "3", "4", "5", "6", "foo", "bar", "foo", }, 3); - try testing.expectEqual(41, n); + try testing.expectEqual(53, n); try testing.expectEqualStrings( - "header which is longer than buf 123456foo", + "header which is longer than buf 123456foobarfoofoofoo", aw.writer.buffered(), ); } -- 2.54.0