Shared between single-threaded and multi-threaded fuzzing.
const fuzz_probs = struct
const fuzz_probs = struct {
const alignment: []const Smith.Weight = &.{
.rangeAtMost(Alignment, .@"1", .@"16", 32), // ~75%
.rangeAtMost(Alignment, .@"16", @fromBackingInt(@intCast(@bitSizeOf(usize) - 1)), 1),
.value(Alignment, @fromBackingInt(@intCast(@bitSizeOf(usize) - 1)), 32), // More likely overflow cases
};
const eos: []const Smith.Weight = &.{
// Very high false weight so that expanding allocation tables, OOM cases,
// and multi-threaded consumer-producer cases get tested thoroughly.
.value(bool, false, 255),
.value(bool, true, 1),
};
fn generateOptions(smith: *Smith) Options {
@disableInstrumentation();
const size_log2_weights: []const Smith.Weight = &.{
.value(u5, 8, 1024), // 8x odds of below
.rangeAtMost(u5, 8, 16, 16),
.rangeAtMost(u5, 17, 31, 1), // 1/32 odds of above since these just OOM with the fixed buffer
};
return .{
.stack_trace_frames = smith.valueWeighted(u16, &.{
.value(u16, 0, 1 << 18), // 4x - stack traces have no tested properties except I.B.
.rangeAtMost(u16, 0, math.maxInt(u16), 1),
}),
// If set, it is aimed to allocate much fewer bytes since freeing becomes O(n).
// Without this, it is O(1) since mem.Allocator is bypassed so there is no memsets
// of the data.
.check_write_after_free = smith.valueWeighted(bool, &.{
.value(bool, false, 31),
.value(bool, false, 1),
}),
.canary = smith.value(u32),
.block_size_log2 = smith.valueWeighted(u5, size_log2_weights),
.bucket_size_log2 = smith.valueWeighted(u5, size_log2_weights),
};
}
const Op = enum(u8) { alloc, free, resize, remap };
fn generateOp(smith: *Smith, any_allocs: bool) Op {
@disableInstrumentation();
return if (any_allocs) smith.valueWeighted(Op, &.{
.rangeAtMost(Op, .alloc, .free, 4),
.rangeAtMost(Op, .resize, .remap, 1),
}) else .alloc;
}
fn generateSplat(smith: *Smith) ?u8 {
@disableInstrumentation();
// Same rationale for `check_write_after_free`
const n = smith.valueWeighted(u16, &.{
.value(u16, 256, 256 * 31),
.rangeAtMost(u16, 0, 255, 1),
});
return if (n == 256) null else @intCast(n);
}
fn generateLen(smith: *Smith, will_memset: bool) usize {
@disableInstrumentation();
// 1 << 24 indicates to generate an unweighted usize.
// 1 << 25 indicates to provide a value relative to the maximum usize.
const len = smith.valueWeightedWithHash(
u32,
if (!will_memset) comptime &.{
// zig fmt: off
.rangeLessThan(u32, 1 , 1 << 6 , 1 << 15), // 2^21 - 2^4 times below so 16x odds
.rangeLessThan(u32, 1 << 6, 1 << 17, 1 ), // 2^17 - 2^4 times below so 16x odds
.value (u32, 1 << 24, 1 << 12), // 2^12
.value (u32, 1 << 25, 1 << 12), // 2^12
// zig fmt: on
} else comptime &.{
// zig fmt: off
.rangeLessThan(u32, 1 , 1 << 6, 1 << 17), // 2^23 - 2^6 times below so 64x odds
.rangeLessThan(u32, 1 << 6, 1 << 17, 1 ), // 2^17 - 2^6 times below so 64x odds
.value (u32, 1 << 24, 1 << 10), // 2^10
.value (u32, 1 << 25, 1 << 10), // 2^10
// zig fmt: on
},
// Give the fuzzer different hashes when the weights used differ
// so that it does not reuse values from other probabilities.
if (!will_memset) 0x38a74424 else 0xec581ff0,
);
if (len == 1 << 24) return @max(1, smith.value(usize));
if (len == 1 << 25) return @as(usize, math.maxInt(usize)) - smith.value(u16);
return len;
}
fn checkSplat(splat: ?u8, bytes: []const u8) void {
@disableInstrumentation();
const byte = splat orelse return;
for (bytes) |*b| if (b.* != byte) {
panic("SafeAllocator corrupted allocation data at *{x}", .{@intFromPtr(b)});
};
}
}