Reusable and recoverable input.
Has a 32-bit limit on the input length. This has the nice side effect that u32
can be used in most placed in fuzzer with the last @sizeOf(abi.MmapInputHeader)
values reserved.
const MemoryMappedInput = struct
const MemoryMappedInput = struct {
const Header = abi.MmapInputHeader;
len: u32,
/// Directly accessing `memory` is unsafe, use either `inputSlice` or `writeSlice`.
mmap: Io.File.MemoryMap,
in_i: u32,
/// `file` becomes owned by the returned `MemoryMappedInput`
pub fn init(file: Io.File, instance_id: u32, in_i: u32) MemoryMappedInput {
var size = file.length(io) catch |e|
panic("failed to get length of 'in{x}': {t}", .{ in_i, e });
if (size < std.heap.page_size_max) {
size = std.heap.page_size_max;
file.setLength(io, size) catch |e|
panic("failed to resize 'in{x}': {t}", .{ in_i, e });
}
const map = file.createMemoryMap(io, .{ .len = size }) catch |e|
panic("failed to memmap input file 'in{x}': {t}", .{ in_i, e });
@as(*volatile Header, @ptrCast(map.memory)).* = .{
.pc_digest = mem.nativeToLittle(u64, exec.pc_digest),
.instance_id = mem.nativeToLittle(u32, instance_id),
.test_i = 0,
.len = 0,
};
return .{
.len = 0,
.mmap = map,
.in_i = in_i,
};
}
pub fn deinit(l: *MemoryMappedInput) void {
const f = l.mmap.file;
l.mmap.write(io) catch |e| panic("failed to write memory map of 'in{x}': {t}", .{ l.in_i, e });
l.mmap.destroy(io);
f.close(io);
l.* = undefined;
}
/// Modify the array so that it can hold at least `additional_count` **more** items.
///
/// Invalidates element pointers if additional memory is needed.
pub fn ensureUnusedCapacity(l: *MemoryMappedInput, additional_count: usize) void {
return l.ensureSize(@sizeOf(Header) + l.len + additional_count);
}
fn ensureSize(l: *MemoryMappedInput, min_capacity: usize) void {
if (l.mmap.memory.len < min_capacity) {
@branchHint(.unlikely);
const max_capacity = 1 << 32; // The size of the header is not added
// in order to keep the capacity page aligned and to allow those values to
// reserved for other places.
if (min_capacity > max_capacity) @panic("too much smith data requested");
const new_capacity = @min(growCapacity(min_capacity), max_capacity);
l.mmap.file.setLength(io, new_capacity) catch |e|
panic("failed to resize 'in{x}': {t}", .{ l.in_i, e });
l.mmap.setLength(io, new_capacity) catch |se| switch (se) {
error.OperationUnsupported => {
const f = l.mmap.file;
l.mmap.destroy(io);
l.mmap = f.createMemoryMap(io, .{ .len = new_capacity }) catch |e|
panic("failed to memory map 'in{x}': {t}", .{ l.in_i, e });
},
else => panic("failed to resize memory map of 'in{x}': {t}", .{ l.in_i, se }),
};
}
}
// Only writing has side effects, so volatile is not needed
pub fn inputSlice(l: *MemoryMappedInput) []const u8 {
return l.mmap.memory[@sizeOf(Header)..][0..l.len];
}
// Writing has side effectsd, so volatile is necessary
pub fn writeSlice(l: *MemoryMappedInput) []volatile u8 {
return l.mmap.memory;
}
fn writeLen(l: *MemoryMappedInput) void {
l.writeSlice()[@offsetOf(Header, "len")..][0..4].* =
@bitCast(mem.nativeToLittle(u32, l.len));
}
pub fn setTest(l: *MemoryMappedInput, i: u32) void {
l.writeSlice()[@offsetOf(Header, "test_i")..][0..4].* =
@bitCast(mem.nativeToLittle(u32, i));
}
/// Invalidates all element pointers.
pub fn clearRetainingCapacity(l: *MemoryMappedInput) void {
l.len = 0;
l.writeLen();
}
/// Append the slice of items to the list.
///
/// Invalidates item pointers if more space is required.
pub fn appendSlice(l: *MemoryMappedInput, items: []const u8) void {
l.ensureUnusedCapacity(items.len);
@memcpy(l.writeSlice()[@sizeOf(Header) + l.len ..][0..items.len], items);
l.len += @as(u32, @intCast(items.len));
l.writeLen();
}
/// Append the little-endian integer to the list.
///
/// Invalidates item pointers if more space is required.
pub fn appendLittleInt(l: *MemoryMappedInput, T: type, x: T) void {
l.ensureUnusedCapacity(@sizeOf(T));
l.writeSlice()[@sizeOf(Header) + l.len ..][0..@sizeOf(T)].* =
@bitCast(mem.nativeToLittle(T, x));
l.len += @sizeOf(T);
l.writeLen();
}
/// Called when memory growth is necessary. Returns a capacity larger than
/// minimum that grows super-linearly.
fn growCapacity(minimum: usize) usize {
return mem.alignForward(
usize,
minimum +| (minimum / 2 + std.heap.page_size_max),
std.heap.page_size_max,
);
}
}