feature. See also
. The project being documented here (as the example) is the Zig library itself.
SafeAllocator.fuzzSingleThreaded
fn fuzzSingleThreaded(ctx: FuzzSingleThreadedContext, smith: *Smith) !void
File
Code
fn fuzzSingleThreaded(ctx: FuzzSingleThreadedContext, smith: *Smith) !void {
@disableInstrumentation();
var gpa_instance: std.heap.FixedBufferAllocator = .init(ctx.testing_buf);
const gpa = gpa_instance.allocator();
var backing_gpa_instance: FuzzSingleThreadedAllocator = .{
.gpa = gpa,
.smith = smith,
.buf = ctx.backing_buf,
.fill = 0,
.allocs = .empty,
};
const backing_gpa = backing_gpa_instance.allocator();
const options = fuzz_probs.generateOptions(smith);
var s: SafeAllocator = .init(backing_gpa, options);
const no_ra: usize = 0;
var allocs: std.MultiArrayList(struct {
memory: []u8,
alignment: Alignment,
splat: ?u8,
}) = .empty;
var used_memory: std.ArrayList(struct {
start: usize,
end: usize,
}) = .empty;
while (!smith.eosWeighted(fuzz_probs.eos)) {
const op = fuzz_probs.generateOp(smith, allocs.len != 0);
const new_mem: []const u8, const old_mem: ?[]const u8 = new_alloc: switch (op) {
.alloc => {
used_memory.ensureUnusedCapacity(gpa, 1) catch break;
allocs.ensureUnusedCapacity(gpa, 1) catch break;
const splat = fuzz_probs.generateSplat(smith);
const will_memset = options.check_write_after_free or splat != null;
const len = fuzz_probs.generateLen(smith, will_memset);
const alignment = smith.valueWeighted(Alignment, fuzz_probs.alignment);
const ptr = alloc(&s, len, alignment, no_ra) orelse continue;
if (!alignment.check(@intFromPtr(ptr))) @panic("bad returned alignment");
const memory = ptr[0..len];
if (splat) |b| @memset(memory, b);
allocs.appendAssumeCapacity(.{
.memory = memory,
.alignment = alignment,
.splat = splat,
});
break :new_alloc .{ memory, null };
},
.free => {
const i = smith.valueRangeLessThan(u32, 0, @intCast(allocs.len));
const alloc_info = allocs.get(i);
allocs.swapRemove(i);
fuzz_probs.checkSplat(alloc_info.splat, alloc_info.memory);
free(&s, alloc_info.memory, alloc_info.alignment, no_ra);
continue;
},
.resize => {
used_memory.ensureUnusedCapacity(gpa, 1) catch break;
const i = smith.valueRangeLessThan(u32, 0, @intCast(allocs.len));
const allocs_slice = allocs.slice();
const prev_alloc = allocs_slice.get(i);
const old_len = prev_alloc.memory.len;
const alloc_memory = &allocs_slice.items(.memory)[i];
const splat = prev_alloc.splat;
const will_memset = options.check_write_after_free or splat != null;
const new_len = fuzz_probs.generateLen(smith, will_memset);
if (!resize(&s, prev_alloc.memory, prev_alloc.alignment, new_len, no_ra)) {
fuzz_probs.checkSplat(prev_alloc.splat, prev_alloc.memory);
continue;
}
alloc_memory.len = new_len;
fuzz_probs.checkSplat(prev_alloc.splat, alloc_memory.*[0..@min(old_len, new_len)]);
if (splat) |b| @memset(alloc_memory.*[@min(old_len, new_len)..], b);
break :new_alloc .{ alloc_memory.*, prev_alloc.memory };
},
.remap => {
used_memory.ensureUnusedCapacity(gpa, 1) catch break;
const i = smith.valueRangeLessThan(u32, 0, @intCast(allocs.len));
const allocs_slice = allocs.slice();
const prev_alloc = allocs_slice.get(i);
const old_len = prev_alloc.memory.len;
const alloc_memory = &allocs_slice.items(.memory)[i];
const alignment = prev_alloc.alignment;
const splat = prev_alloc.splat;
const will_memset = options.check_write_after_free or splat != null;
const new_len = fuzz_probs.generateLen(smith, will_memset);
const new_ptr = remap(
&s,
prev_alloc.memory,
prev_alloc.alignment,
new_len,
no_ra,
) orelse {
fuzz_probs.checkSplat(prev_alloc.splat, prev_alloc.memory);
continue;
};
alloc_memory.* = new_ptr[0..new_len];
if (!alignment.check(@intFromPtr(new_ptr))) @panic("bad returned alignment");
fuzz_probs.checkSplat(prev_alloc.splat, alloc_memory.*[0..@min(old_len, new_len)]);
if (splat) |b| @memset(alloc_memory.*[@min(old_len, new_len)..], b);
break :new_alloc .{ alloc_memory.*, prev_alloc.memory };
},
};
const new_start = @intFromPtr(new_mem.ptr);
const new_end = new_start + new_mem.len;
const old_start = if (old_mem) |old| @intFromPtr(old.ptr) else 0;
const old_end = new_start + if (old_mem) |old| old.len else 0;
for (used_memory.items) |used| {
if (old_start <= used.end and used.start <= old_end) {
continue;
}
if (new_start <= used.end and used.start <= new_end) {
panic(
"memory reuse between [addr: {x}, len: {}] and new [addr: {x}, len: {}]",
.{ used.start, used.end, new_start, new_end },
);
}
}
used_memory.appendAssumeCapacity(.{ .start = new_start, .end = new_end });
}
try std.testing.expectEqual(allocs.len, s.deinitLog(false));
const leaks_slice = backing_gpa_instance.allocs.slice();
for (0..leaks_slice.len) |i| {
const leak = leaks_slice.get(i);
std.log.err("SafeAllocator leaked {f}", .{FormatMemory{
.memory = leak.ptr[0..leak.len],
.alignment = leak.alignment,
}});
}
try std.testing.expectEqual(0, leaks_slice.len);
}