From 6da1889e21b1de7a803a245aacd18f3933f41488 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:25:59 +0200 Subject: [PATCH 1/2] fix SafeMultiList CompactWriter test failure --- src/collections/safe_list.zig | 48 +++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/collections/safe_list.zig b/src/collections/safe_list.zig index d55532667e..e66dba3c84 100644 --- a/src/collections/safe_list.zig +++ b/src/collections/safe_list.zig @@ -597,6 +597,32 @@ pub fn SafeMultiList(comptime T: type) type { len: u64, capacity: u64, + // We copy capacity-sized regions below; clear per-field slack so the writer never + // observes uninitialized bytes. Leaving that memory undefined is UB and makes the + // output non-deterministic. The one-time zeroing cost is negligible next to writing + // the same memory to disk. + fn zeroUnusedCapacity(list: *SafeMultiList(T)) void { + const list_len = list.items.len; + const total_capacity = list.items.capacity; + if (total_capacity == 0 or total_capacity <= list_len) return; + + const slice = list.items.slice(); + inline for (std.meta.fields(T), 0..) |field_info, field_index| { + const field_size = @sizeOf(field_info.type); + if (field_size == 0) continue; + + const capacity_bytes = field_size * total_capacity; + const initialized_bytes = field_size * list_len; + + if (initialized_bytes < capacity_bytes) { + const field_ptr = slice.ptrs[field_index]; + const tail_ptr = field_ptr + initialized_bytes; + const tail_len = capacity_bytes - initialized_bytes; + @memset(tail_ptr[0..tail_len], 0); + } + } + } + /// Serialize a SafeMultiList into this Serialized struct, appending data to the writer pub fn serialize( self: *Serialized, @@ -606,6 +632,8 @@ pub fn SafeMultiList(comptime T: type) type { ) Allocator.Error!void { // MultiArrayList reorders fields by alignment internally. // We need to copy the raw bytes exactly as they are laid out. + zeroUnusedCapacity(@constCast(safe_multi_list)); + const data_offset = if (safe_multi_list.items.len > 0) blk: { const MultiArrayListType = std.MultiArrayList(T); // We need to write all the bytes up to where the actual data is stored @@ -1945,36 +1973,36 @@ test "SafeMultiList CompactWriter verify exact memory layout" { const field_items = original.field(.a); const field_bytes = std.mem.sliceAsBytes(field_items); @memcpy(field_dest[0..field_bytes.len], field_bytes); - // Fill remaining capacity with pattern (0xAA) to match uninitialized memory + // Fill remaining capacity with zeros to match serialization sanitization if (field_bytes.len < field_capacity_bytes) { - @memset(field_dest[field_bytes.len..], 0xAA); + @memset(field_dest[field_bytes.len..], 0); } }, 1 => { // field b const field_items = original.field(.b); const field_bytes = std.mem.sliceAsBytes(field_items); @memcpy(field_dest[0..field_bytes.len], field_bytes); - // Fill remaining capacity with pattern (0xAA) to match uninitialized memory + // Fill remaining capacity with zeros to match serialization sanitization if (field_bytes.len < field_capacity_bytes) { - @memset(field_dest[field_bytes.len..], 0xAA); + @memset(field_dest[field_bytes.len..], 0); } }, 2 => { // field c const field_items = original.field(.c); const field_bytes = std.mem.sliceAsBytes(field_items); @memcpy(field_dest[0..field_bytes.len], field_bytes); - // Fill remaining capacity with pattern (0xAA) to match uninitialized memory + // Fill remaining capacity with zeros to match serialization sanitization if (field_bytes.len < field_capacity_bytes) { - @memset(field_dest[field_bytes.len..], 0xAA); + @memset(field_dest[field_bytes.len..], 0); } }, 3 => { // field d const field_items = original.field(.d); const field_bytes = std.mem.sliceAsBytes(field_items); @memcpy(field_dest[0..field_bytes.len], field_bytes); - // Fill remaining capacity with pattern (0xAA) to match uninitialized memory + // Fill remaining capacity with zeros to match serialization sanitization if (field_bytes.len < field_capacity_bytes) { - @memset(field_dest[field_bytes.len..], 0xAA); + @memset(field_dest[field_bytes.len..], 0); } }, else => unreachable, @@ -1983,9 +2011,9 @@ test "SafeMultiList CompactWriter verify exact memory layout" { offset += field_info.size * original.items.capacity; } - // Fill remaining space with pattern + // Fill remaining space with zeros to match serialization sanitization if (offset < expected_bytes.len) { - @memset(expected_bytes[offset..], 0xAA); + @memset(expected_bytes[offset..], 0); } // Now serialize using our implementation From 20ff5ffd82448a3b3a62a5b68bbc227076c9a5f5 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:32:48 +0200 Subject: [PATCH 2/2] CI tests with ReleaseFast too --- .github/workflows/ci_zig.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_zig.yml b/.github/workflows/ci_zig.yml index 24eb914347..6415b3beda 100644 --- a/.github/workflows/ci_zig.yml +++ b/.github/workflows/ci_zig.yml @@ -144,13 +144,20 @@ jobs: - name: zig snapshot tests run: zig build snapshot -- --debug - - name: build tests and repro executables + - name: build and execute tests, build repro executables uses: ./.github/actions/flaky-retry with: command: 'zig build test -Dfuzz -Dsystem-afl=false' error_string_contains: 'double roundtrip bundle' retry_count: 3 + - name: Build and execute tests, build repro executables. All in release mode. + uses: ./.github/actions/flaky-retry + with: + command: 'zig build test -Doptimize=ReleaseFast -Dfuzz -Dsystem-afl=false' + error_string_contains: 'double roundtrip bundle' + retry_count: 3 + - name: Check for snapshot changes run: | git diff --exit-code test/snapshots || {