This code produces a segmentation fault depending on whether the target in calculate_hash_chain_root() is passed in place or indirectly to the @bitCast, and you can toggle the segfault on/off by changing const segfault = true:
zig test segfault.zig
const std = @import("std");
const assert = std.debug.assert;
const segfault = true;
pub const JournalHeader = packed struct {
hash_chain_root: u128 = undefined,
prev_hash_chain_root: u128,
checksum: u128 = undefined,
magic: u64,
command: u32,
size: u32,
pub fn calculate_checksum(self: *const JournalHeader, entry: []const u8) u128 {
assert(entry.len >= @sizeOf(JournalHeader));
assert(entry.len == self.size);
const checksum_offset = @byteOffsetOf(JournalHeader, "checksum");
const checksum_size = @sizeOf(@TypeOf(self.checksum));
assert(checksum_offset == 0 + 16 + 16);
assert(checksum_size == 16);
var target: [32]u8 = undefined;
std.crypto.hash.Blake3.hash(entry[checksum_offset + checksum_size..], target[0..], .{});
return @bitCast(u128, target[0..checksum_size].*);
}
pub fn calculate_hash_chain_root(self: *const JournalHeader) u128 {
const hash_chain_root_size = @sizeOf(@TypeOf(self.hash_chain_root));
assert(hash_chain_root_size == 16);
const prev_hash_chain_root_offset = @byteOffsetOf(JournalHeader, "prev_hash_chain_root");
const prev_hash_chain_root_size = @sizeOf(@TypeOf(self.prev_hash_chain_root));
assert(prev_hash_chain_root_offset == 0 + 16);
assert(prev_hash_chain_root_size == 16);
const checksum_offset = @byteOffsetOf(JournalHeader, "checksum");
const checksum_size = @sizeOf(@TypeOf(self.checksum));
assert(checksum_offset == 0 + 16 + 16);
assert(checksum_size == 16);
assert(prev_hash_chain_root_offset + prev_hash_chain_root_size == checksum_offset);
const header = @bitCast([@sizeOf(JournalHeader)]u8, self.*);
const source = header[prev_hash_chain_root_offset..checksum_offset + checksum_size];
assert(source.len == prev_hash_chain_root_size + checksum_size);
var target: [32]u8 = undefined;
std.crypto.hash.Blake3.hash(source, target[0..], .{});
if (segfault) {
return @bitCast(u128, target[0..hash_chain_root_size].*);
} else {
var array = target[0..hash_chain_root_size].*;
return @bitCast(u128, array);
}
}
pub fn set_checksum_and_hash_chain_root(self: *JournalHeader, entry: []const u8) void {
self.checksum = self.calculate_checksum(entry);
self.hash_chain_root = self.calculate_hash_chain_root();
}
};
test "" {
var buffer = [_]u8{0} ** 65536;
var entry = std.mem.bytesAsValue(JournalHeader, buffer[0..@sizeOf(JournalHeader)]);
entry.* = .{
.prev_hash_chain_root = 0,
.magic = 0,
.command = 0,
.size = 64 + 128
};
entry.set_checksum_and_hash_chain_root(buffer[0..entry.size]);
std.debug.print("{}\n", .{ entry });
}
Something interesting with this:
calculate_checksum() and calculate_hash_chain_root() use target in the same style, but only one of them causes the segfault.I can't reproduce the crash under any optimization mode, are you using a fresh version of the Zig compiler?
A segfault at runtime or is the compiler segfaulting? I'm also not able to repro. Also both the compiler and the binary run valgrind-clean for me.
Thanks @LemonBoy and @andrewrk, this was on 0.6.0+e20703183, from 2 October, and was a runtime segfault if the slice was inline, but not if referenced with a variable name.
On 0.6.0+91a1c20e7, from 24 October, I also can't reproduce it anymore.
Any idea what it could have been?
If the crash was happening inside a memcpy operation I'd guess #6698 solved this problem. The use of i128 with its 16 byte alignment seems to point towards that direction too.
I added your code as a regression test in e83334274f1d35690e4546938aff065397347943.
Awesome! Great to see a little TigerBeetle journalling protocol helping with regressions. 馃槃 Wish I could have reduced that test case further... it has so many asserts in there that if anything is changed it will blow up.
Most helpful comment
If the crash was happening inside a memcpy operation I'd guess #6698 solved this problem. The use of
i128with its 16 byte alignment seems to point towards that direction too.