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(
245245
246246 if (remaining == 0) break :remaining_zero;
247247 const pattern = data[data.len - 1];
248 for (0..splat) |_| {
248 for (0..splat) |i| {
249249 const copy_len = @min(pattern.len, remaining);
250250 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);
252253 return @backingInt(limit) - remaining + n;
253254 }
254255 @memcpy(w.buffer[w.end..][0..copy_len], pattern[0..copy_len]);
......@@ -300,7 +301,10 @@ fn writeSplatHeaderLimitFinish(
300301 return w.vtable.drain(w, (&vecs)[0..i], 1);
301302}
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.
304308 buf_len: usize = 100,
305309 header: []const u8,
306310 data: []const []const u8,
......@@ -310,9 +314,18 @@ const FixedSplatHeaderTestCase = struct {
310314 expected_buf_content: []const u8,
311315};
312316
313fn testFixedWriteSplatHeaderLimit(comptime test_case: FixedSplatHeaderTestCase) !void {
317fn testWriteSplatHeaderLimit(comptime test_case: SplatHeaderTestCase) !void {
314318 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 };
316329 const n_or_error = w.writeSplatHeaderLimit(test_case.header, test_case.data, test_case.splat, .limited(test_case.limit));
317330 switch (test_case.expected_res) {
318331 .written => |expected_len| {
......@@ -327,20 +340,23 @@ fn testFixedWriteSplatHeaderLimit(comptime test_case: FixedSplatHeaderTestCase)
327340}
328341
329342test "fixed writer writeSplatHeaderLimit" {
330 // buffer is large
331 try testFixedWriteSplatHeaderLimit(.{ .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" });
333 try testFixedWriteSplatHeaderLimit(.{ .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" });
335 try testFixedWriteSplatHeaderLimit(.{ .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" });
337 try testFixedWriteSplatHeaderLimit(.{ .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" });
339
340 // buffer is small
341 try testFixedWriteSplatHeaderLimit(.{ .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 });
343 try testFixedWriteSplatHeaderLimit(.{ .header = "head", .data = &.{ "123", "ab" }, .splat = 2, .limit = 100, .expected_res = .write_failed, .expected_buf_content = "head123aba", .buf_len = 10 });
343 // fixed writer with buffer larger than the full data size
344 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "header is longer", .data = &.{""}, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "header" });
345 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{"123456"}, .splat = 1, .limit = 5, .expected_res = .{ .written = 5 }, .expected_buf_content = "head1" });
346 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{"123"}, .splat = 1, .limit = 10, .expected_res = .{ .written = 7 }, .expected_buf_content = "head123" });
347 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "1", "abcdefg" }, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head1a" });
348 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head12" });
349 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 11, .expected_res = .{ .written = 11 }, .expected_buf_content = "head123abca" });
350 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "a" }, .splat = 2, .limit = 10, .expected_res = .{ .written = 9 }, .expected_buf_content = "head123aa" });
351 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 100, .expected_res = .{ .written = 13 }, .expected_buf_content = "head123abcabc" });
352
353 // fixed writer with buffer smaller than the full data size
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 });
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 });
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" });
344360}
345361
346362test "writeSplatHeader splatting avoids buffer aliasing temptation" {