authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-08-25 12:14:58-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-08-26 10:50:20+02:00
log78e3b1c7383bf7126876b6367192aef17d3d92b9
tree7cce031f9c3f903cd31f96dfad3e4e72915a4661
parente955d137e7d23eacd6aeb0eeb406d9fd9cfa9526

Writer: Fix writeSplatHeaderLimit calling writeSplatHeaderLimitFinish mid-splat

Fixes https://codeberg.org/ziglang/zig/issues/36634

1 files changed, 35 insertions(+), 19 deletions(-)

lib/std/Io/Writer.zig+35-19
...@@ -245,10 +245,11 @@ pub fn writeSplatHeaderLimit(...@@ -245,10 +245,11 @@ pub fn writeSplatHeaderLimit(
245245
246 if (remaining == 0) break :remaining_zero;246 if (remaining == 0) break :remaining_zero;
247 const pattern = data[data.len - 1];247 const pattern = data[data.len - 1];
248 for (0..splat) |_| {248 for (0..splat) |i| {
249 const copy_len = @min(pattern.len, remaining);249 const copy_len = @min(pattern.len, remaining);
250 if (w.buffer.len - w.end < copy_len) {250 if (w.buffer.len - w.end < copy_len) {
251 const n = try writeSplatHeaderLimitFinish(w, &.{}, data[data.len - 1 ..][0..1], splat, remaining);251 const remaining_splat = splat - i;
252 const n = try writeSplatHeaderLimitFinish(w, &.{}, data[data.len - 1 ..][0..1], remaining_splat, remaining);
252 return @backingInt(limit) - remaining + n;253 return @backingInt(limit) - remaining + n;
253 }254 }
254 @memcpy(w.buffer[w.end..][0..copy_len], pattern[0..copy_len]);255 @memcpy(w.buffer[w.end..][0..copy_len], pattern[0..copy_len]);
...@@ -300,7 +301,10 @@ fn writeSplatHeaderLimitFinish(...@@ -300,7 +301,10 @@ fn writeSplatHeaderLimitFinish(
300 return w.vtable.drain(w, (&vecs)[0..i], 1);301 return w.vtable.drain(w, (&vecs)[0..i], 1);
301}302}
302303
303const FixedSplatHeaderTestCase = struct {304const SplatHeaderTestCase = struct {
305 writer_type: enum { fixed, allocating },
306 /// When writer_type is .fixed, determines the buffer size.
307 /// When writer_type is .allocating, determines the initial capacity.
304 buf_len: usize = 100,308 buf_len: usize = 100,
305 header: []const u8,309 header: []const u8,
306 data: []const []const u8,310 data: []const []const u8,
...@@ -310,9 +314,18 @@ const FixedSplatHeaderTestCase = struct {...@@ -310,9 +314,18 @@ const FixedSplatHeaderTestCase = struct {
310 expected_buf_content: []const u8,314 expected_buf_content: []const u8,
311};315};
312316
313fn testFixedWriteSplatHeaderLimit(comptime test_case: FixedSplatHeaderTestCase) !void {317fn testWriteSplatHeaderLimit(comptime test_case: SplatHeaderTestCase) !void {
314 var buf: [test_case.buf_len]u8 = @splat(0);318 var buf: [test_case.buf_len]u8 = @splat(0);
315 var w: std.Io.Writer = .fixed(&buf);319 var aw: Allocating = if (test_case.writer_type == .allocating)
320 try Allocating.initCapacity(testing.allocator, test_case.buf_len)
321 else
322 undefined;
323 defer if (test_case.writer_type == .allocating) aw.deinit();
324 var fw: Writer = if (test_case.writer_type == .fixed) .fixed(&buf) else undefined;
325 var w: *Writer = switch (test_case.writer_type) {
326 .allocating => &aw.writer,
327 .fixed => &fw,
328 };
316 const n_or_error = w.writeSplatHeaderLimit(test_case.header, test_case.data, test_case.splat, .limited(test_case.limit));329 const n_or_error = w.writeSplatHeaderLimit(test_case.header, test_case.data, test_case.splat, .limited(test_case.limit));
317 switch (test_case.expected_res) {330 switch (test_case.expected_res) {
318 .written => |expected_len| {331 .written => |expected_len| {
...@@ -327,20 +340,23 @@ fn testFixedWriteSplatHeaderLimit(comptime test_case: FixedSplatHeaderTestCase)...@@ -327,20 +340,23 @@ fn testFixedWriteSplatHeaderLimit(comptime test_case: FixedSplatHeaderTestCase)
327}340}
328341
329test "fixed writer writeSplatHeaderLimit" {342test "fixed writer writeSplatHeaderLimit" {
330 // buffer is large343 // fixed writer with buffer larger than the full data size
331 try testFixedWriteSplatHeaderLimit(.{ .header = "header is longer", .data = &.{""}, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "header" });344 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "header is longer", .data = &.{""}, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "header" });
332 try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{"123456"}, .splat = 1, .limit = 5, .expected_res = .{ .written = 5 }, .expected_buf_content = "head1" });345 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{"123456"}, .splat = 1, .limit = 5, .expected_res = .{ .written = 5 }, .expected_buf_content = "head1" });
333 try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{"123"}, .splat = 1, .limit = 10, .expected_res = .{ .written = 7 }, .expected_buf_content = "head123" });346 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{"123"}, .splat = 1, .limit = 10, .expected_res = .{ .written = 7 }, .expected_buf_content = "head123" });
334 try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "1", "abcdefg" }, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head1a" });347 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "1", "abcdefg" }, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head1a" });
335 try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head12" });348 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head12" });
336 try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 11, .expected_res = .{ .written = 11 }, .expected_buf_content = "head123abca" });349 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 11, .expected_res = .{ .written = 11 }, .expected_buf_content = "head123abca" });
337 try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "123", "a" }, .splat = 2, .limit = 10, .expected_res = .{ .written = 9 }, .expected_buf_content = "head123aa" });350 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "a" }, .splat = 2, .limit = 10, .expected_res = .{ .written = 9 }, .expected_buf_content = "head123aa" });
338 try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 100, .expected_res = .{ .written = 13 }, .expected_buf_content = "head123abcabc" });351 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 100, .expected_res = .{ .written = 13 }, .expected_buf_content = "head123abcabc" });
339352
340 // buffer is small353 // fixed writer with buffer smaller than the full data size
341 try testFixedWriteSplatHeaderLimit(.{ .header = "header is longer", .data = &.{""}, .splat = 1, .limit = 6, .expected_res = .write_failed, .expected_buf_content = "head", .buf_len = 4 });354 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "header is longer", .data = &.{""}, .splat = 1, .limit = 6, .expected_res = .write_failed, .expected_buf_content = "head", .buf_len = 4 });
342 try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{"123456"}, .splat = 1, .limit = 8, .expected_res = .write_failed, .expected_buf_content = "head1", .buf_len = 5 });355 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{"123456"}, .splat = 1, .limit = 8, .expected_res = .write_failed, .expected_buf_content = "head1", .buf_len = 5 });
343 try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "123", "ab" }, .splat = 2, .limit = 100, .expected_res = .write_failed, .expected_buf_content = "head123aba", .buf_len = 10 });356 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "ab" }, .splat = 2, .limit = 100, .expected_res = .write_failed, .expected_buf_content = "head123aba", .buf_len = 10 });
357
358 // allocating writer that needs to expand capacity during splat
359 try testWriteSplatHeaderLimit(.{ .writer_type = .allocating, .buf_len = 8, .header = "hhhh", .data = &.{"PP"}, .splat = 3, .limit = 100, .expected_res = .{ .written = 10 }, .expected_buf_content = "hhhhPPPPPP" });
344}360}
345361
346test "writeSplatHeader splatting avoids buffer aliasing temptation" {362test "writeSplatHeader splatting avoids buffer aliasing temptation" {