std.io.Writer.writeSplatHeader: fix splat alias bug

pointer comparison cannot be used since the buffers may alias
This commit is contained in:
Andrew Kelley 2025-07-09 18:28:46 -07:00
parent 09cdcf67e1
commit 9e52febeeb

View File

@ -211,16 +211,37 @@ pub fn writeSplatHeader(
var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size. var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size.
var i: usize = 1; var i: usize = 1;
vecs[0] = header; vecs[0] = header;
for (data) |buf| { for (data[0 .. data.len - 1]) |buf| {
if (buf.len == 0) continue; if (buf.len == 0) continue;
vecs[i] = buf; vecs[i] = buf;
i += 1; i += 1;
if (vecs.len - i == 0) break; if (vecs.len - i == 0) break;
} }
const new_splat = if (vecs[i - 1].ptr == data[data.len - 1].ptr) splat else 1; const pattern = data[data.len - 1];
const new_splat = s: {
if (pattern.len == 0 or vecs.len - i == 0) break :s 1;
vecs[i] = pattern;
i += 1;
break :s splat;
};
return w.vtable.drain(w, vecs[0..i], new_splat); return w.vtable.drain(w, vecs[0..i], new_splat);
} }
test "writeSplatHeader splatting avoids buffer aliasing temptation" {
const initial_buf = try testing.allocator.alloc(u8, 8);
var aw: std.io.Writer.Allocating = .initOwnedSlice(testing.allocator, initial_buf);
defer aw.deinit();
// This test assumes 8 vector buffer in this function.
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.expectEqualStrings(
"header which is longer than buf 123456foo",
aw.writer.buffered(),
);
}
/// Equivalent to `writeSplatHeader` but writes at most `limit` bytes. /// Equivalent to `writeSplatHeader` but writes at most `limit` bytes.
pub fn writeSplatHeaderLimit( pub fn writeSplatHeaderLimit(
w: *Writer, w: *Writer,
@ -2037,8 +2058,8 @@ test "printFloat with comptime_float" {
var buf: [20]u8 = undefined; var buf: [20]u8 = undefined;
var w: Writer = .fixed(&buf); var w: Writer = .fixed(&buf);
try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower)); try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower));
try std.testing.expectEqualStrings(w.buffered(), "1e0"); try testing.expectEqualStrings(w.buffered(), "1e0");
try std.testing.expectFmt("1", "{}", .{1.0}); try testing.expectFmt("1", "{}", .{1.0});
} }
fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void { fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void {
@ -2065,12 +2086,12 @@ test printByteSize {
test "bytes.hex" { test "bytes.hex" {
const some_bytes = "\xCA\xFE\xBA\xBE"; const some_bytes = "\xCA\xFE\xBA\xBE";
try std.testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes}); try testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes});
try std.testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes}); try testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes});
try std.testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]}); try testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]});
try std.testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]}); try testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]});
const bytes_with_zeros = "\x00\x0E\xBA\xBE"; const bytes_with_zeros = "\x00\x0E\xBA\xBE";
try std.testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros});
} }
test fixed { test fixed {
@ -2479,7 +2500,7 @@ pub const Allocating = struct {
} }
test Allocating { test Allocating {
var a: Allocating = .init(std.testing.allocator); var a: Allocating = .init(testing.allocator);
defer a.deinit(); defer a.deinit();
const w = &a.writer; const w = &a.writer;