1const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
5const mem = std.mem;
6const math = std.math;
7const assert = std.debug.assert;
8const panic = std.debug.panic;
9const abi = std.Build.abi.fuzz;
10const Uid = abi.Uid;
11
12pub const std_options = std.Options{
13 .logFn = logOverride,
14};
15
16const io = Io.Threaded.global_single_threaded.io();
17
18fn logOverride(
19 comptime level: std.log.Level,
20 comptime scope: @EnumLiteral(),
21 comptime format: []const u8,
22 args: anytype,
23) void {
24 const f = log_f orelse panic("log before initialization, message:\n" ++ format, args);
25 f.lock(io, .exclusive) catch |e| panic("failed to lock logging file: {t}", .{e});
26 defer f.unlock(io);
27
28 var buf: [256]u8 = undefined;
29 var fw = f.writer(io, &buf);
30 const end = f.length(io) catch |e| panic("failed to get fuzzer log file end: {t}", .{e});
31 fw.seekTo(end) catch |e| panic("failed to seek to fuzzer log file end: {t}", .{e});
32
33 const prefix1 = comptime level.asText();
34 const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
35 fw.interface.print(
36 "[{s}] " ++ prefix1 ++ prefix2 ++ format ++ "\n",
37 .{current_test_name orelse "setup"} ++ args,
38 ) catch panic("failed to write to fuzzer log: {t}", .{fw.err.?});
39 fw.interface.flush() catch panic("failed to write to fuzzer log: {t}", .{fw.err.?});
40}
41
42var safe_allocator: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{});
43const gpa = switch (builtin.mode) {
44 .debug, .safe => safe_allocator.allocator(),
45 .fast, .small => std.heap.smp_allocator,
46};
47
48// Seperate from `exec` to allow initialization before `exec` is.
49var log_f: ?Io.File = null;
50var exec: Executable = undefined;
51var fuzzer: Fuzzer = undefined;
52var current_test_name: ?[]const u8 = null;
53
54fn bitsetUsizes(elems: usize) usize {
55 return @divCeil(elems, @bitSizeOf(usize));
56}
57
58const Executable = struct {
59 /// Tracks the hit count for each pc as updated by the test's instrumentation.
60 pc_counters: []u8,
61
62 cache_f: Io.Dir,
63 /// Shared copy of all pcs that have been hit stored in a memory-mapped file that can viewed
64 /// while the fuzzer is running.
65 shared_seen_pcs: []align(std.heap.page_size_min) volatile u8,
66 /// Hash of pcs used to uniquely identify the shared coverage file
67 pc_digest: u64,
68
69 fn getCoverageMap(
70 cache_dir: Io.Dir,
71 pcs: []const usize,
72 pc_digest: u64,
73 ) []align(std.heap.page_size_min) volatile u8 {
74 const file_name = std.fmt.hex(pc_digest);
75
76 var v = cache_dir.createDirPathOpen(io, "v", .{}) catch |e|
77 panic("failed to create directory 'v': {t}", .{e});
78 defer v.close(io);
79
80 // Since acquiring locks in createFile is not gauraunteed to be atomic, it is not possible
81 // to ensure if we create the file we obtain an exclusive lock to populate it since another
82 // process may acquire a shared lock between the file being created and the lock request.
83 //
84 // Instead, the length will be used to determine if the file needs populated, and no
85 // process will acquire a shared lock before the coverage file is known to have been
86 // exclusively locked (i.e. is already locked). This means another process than the
87 // one which created the file could populate it, which is fine.
88 const coverage_file = v.createFile(io, &file_name, .{
89 .read = true,
90 .truncate = false,
91 }) catch |e| panic("failed to open coverage file '{s}': {t}", .{ &file_name, e });
92
93 const maybe_populate = coverage_file.tryLock(io, .exclusive) catch |e| panic(
94 "failed to acquire exclusive lock coverage file '{s}': {t}",
95 .{ &file_name, e },
96 );
97 if (!maybe_populate) {
98 coverage_file.lock(io, .shared) catch |e|
99 panic("failed to acquire share lock coverage file '{s}': {t}", .{ &file_name, e });
100 }
101
102 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
103 comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);
104 const pc_bitset_usizes = bitsetUsizes(pcs.len);
105 const coverage_file_len = @sizeOf(abi.SeenPcsHeader) +
106 pc_bitset_usizes * @sizeOf(usize) +
107 pcs.len * @sizeOf(usize);
108
109 var populate: bool = false;
110 const size = coverage_file.length(io) catch |e|
111 panic("failed to stat coverage file '{s}': {t}", .{ &file_name, e });
112 if (size == 0 and maybe_populate) {
113 coverage_file.setLength(io, coverage_file_len) catch |e|
114 panic("failed to resize new coverage file '{s}': {t}", .{ &file_name, e });
115 populate = true;
116 } else if (size != coverage_file_len) {
117 panic(
118 "incompatible existing coverage file '{s}' (differing lengths: {} != {})",
119 .{ &file_name, size, coverage_file_len },
120 );
121 } else if (maybe_populate) {
122 coverage_file.lock(io, .shared) catch |e|
123 panic("failed to demote lock for coverage file '{s}': {t}", .{ &file_name, e });
124 }
125
126 var io_map = coverage_file.createMemoryMap(io, .{ .len = coverage_file_len }) catch |e|
127 panic("failed to memmap coverage file '{s}': {t}", .{ &file_name, e });
128 const map = io_map.memory;
129
130 const header: *abi.SeenPcsHeader = @ptrCast(map[0..@sizeOf(abi.SeenPcsHeader)]);
131 const trailing = map[@sizeOf(abi.SeenPcsHeader)..];
132 const trailing_bitset_end = pc_bitset_usizes * @sizeOf(usize);
133 const trailing_bitset: []usize = @ptrCast(@alignCast(trailing[0..trailing_bitset_end]));
134 const trailing_addresses: []usize = @ptrCast(@alignCast(trailing[trailing_bitset_end..]));
135
136 if (populate) {
137 header.* = .{
138 .n_runs = 0,
139 .unique_runs = 0,
140 .pcs_len = pcs.len,
141 };
142 @memset(trailing_bitset, 0);
143 for (trailing_addresses, pcs) |*cov_pc, slided_pc| {
144 cov_pc.* = fuzzer_unslide_address(slided_pc);
145 }
146 io_map.write(io) catch |e|
147 panic("failed to write memory map of '{s}': {t}", .{ &file_name, e });
148
149 coverage_file.lock(io, .shared) catch |e| panic(
150 "failed to demote lock for coverage file '{s}': {t}",
151 .{ &file_name, e },
152 );
153 } else { // Check expected contents
154 if (header.pcs_len != pcs.len) panic(
155 "incompatible existing coverage file '{s}' (differing pcs length: {} != {})",
156 .{ &file_name, header.pcs_len, pcs.len },
157 );
158 for (0.., header.pcAddrs(), pcs) |i, cov_pc, slided_pc| {
159 const pc = fuzzer_unslide_address(slided_pc);
160 if (cov_pc != pc) panic(
161 "incompatible existing coverage file '{s}' (differing pc at index {d}: {x} != {x})",
162 .{ &file_name, i, cov_pc, pc },
163 );
164 }
165 }
166 return map;
167 }
168
169 pub fn init(cache_dir_path: []const u8) Executable {
170 var self: Executable = undefined;
171
172 const cache_dir = Io.Dir.cwd().createDirPathOpen(io, cache_dir_path, .{}) catch |e|
173 panic("failed to open directory '{s}': {t}", .{ cache_dir_path, e });
174 cache_dir.createDirPath(io, "tmp") catch |e|
175 panic("failed to create directory 'tmp': {t}", .{e});
176 log_f = cache_dir.createFile(io, "tmp/libfuzzer.log", .{ .truncate = false }) catch |e|
177 panic("failed to create file 'tmp/libfuzzer.log': {t}", .{e});
178 self.cache_f = cache_dir.createDirPathOpen(io, "f", .{}) catch |e|
179 panic("failed to open directory 'f': {t}", .{e});
180
181 // Linkers are expected to automatically add symbols prefixed with these for the start and
182 // end of sections whose names are valid C identifiers.
183 const ofmt = builtin.object_format;
184 const section_start_prefix, const section_end_prefix = switch (ofmt) {
185 .elf => .{ "__start_", "__stop_" },
186 .macho => .{ "\x01section$start$__DATA$", "\x01section$end$__DATA$" },
187 else => @compileError("unsupported fuzzing object format '" ++ @tagName(ofmt) ++ "'"),
188 };
189
190 self.pc_counters = blk: {
191 const pc_counters_start_name = section_start_prefix ++ "__sancov_cntrs";
192 const pc_counters_start = @extern([*]u8, .{
193 .name = pc_counters_start_name,
194 .linkage = .weak,
195 }) orelse panic("missing {s} symbol", .{pc_counters_start_name});
196
197 const pc_counters_end_name = section_end_prefix ++ "__sancov_cntrs";
198 const pc_counters_end = @extern([*]u8, .{
199 .name = pc_counters_end_name,
200 .linkage = .weak,
201 }) orelse panic("missing {s} symbol", .{pc_counters_end_name});
202
203 break :blk pc_counters_start[0 .. pc_counters_end - pc_counters_start];
204 };
205
206 const pcs = blk: {
207 const pcs_start_name = section_start_prefix ++ "__sancov_pcs1";
208 const pcs_start = @extern([*]usize, .{
209 .name = pcs_start_name,
210 .linkage = .weak,
211 }) orelse panic("missing {s} symbol", .{pcs_start_name});
212
213 const pcs_end_name = section_end_prefix ++ "__sancov_pcs1";
214 const pcs_end = @extern([*]usize, .{
215 .name = pcs_end_name,
216 .linkage = .weak,
217 }) orelse panic("missing {s} symbol", .{pcs_end_name});
218
219 break :blk pcs_start[0 .. pcs_end - pcs_start];
220 };
221
222 if (self.pc_counters.len != pcs.len) panic(
223 "pc counters length and pcs length do not match ({} != {})",
224 .{ self.pc_counters.len, pcs.len },
225 );
226
227 self.pc_digest = digest: {
228 // Relocations have been applied to `pcs` so it contains runtime addresses (with slide
229 // applied). We need to translate these to the virtual addresses as on disk.
230 var h: std.hash.Wyhash = .init(0);
231 for (pcs) |pc| {
232 const pc_vaddr = fuzzer_unslide_address(pc);
233 h.update(@ptrCast(&pc_vaddr));
234 }
235 break :digest h.final();
236 };
237 self.shared_seen_pcs = getCoverageMap(cache_dir, pcs, self.pc_digest);
238
239 return self;
240 }
241
242 /// Asserts `buf[0..2]` is "in"
243 fn inputFileName(buf: *[10]u8, i: u32) []u8 {
244 assert(buf[0..2].* == "in".*);
245 const hex = std.mem.print(buf[2..], "{x}", .{i}) catch unreachable;
246 return buf[0 .. 2 + hex.len];
247 }
248
249 pub fn pcBitsetIterator(self: Executable) PcBitsetIterator {
250 return .{ .pc_counters = self.pc_counters };
251 }
252
253 /// Iterates over pc_counters returning a bitset for if each of them have been hit
254 pub const PcBitsetIterator = struct {
255 index: usize = 0,
256 pc_counters: []u8,
257
258 pub fn next(i: *PcBitsetIterator) usize {
259 const rest = i.pc_counters[i.index..];
260 if (rest.len >= @bitSizeOf(usize)) {
261 defer i.index += @bitSizeOf(usize);
262 const V = @Vector(@bitSizeOf(usize), u8);
263 return @as(usize, @bitCast(@as(V, @splat(0)) != rest[0..@bitSizeOf(usize)].*));
264 } else if (rest.len != 0) {
265 defer i.index += rest.len;
266 var res: usize = 0;
267 for (0.., rest) |bit_index, byte| {
268 res |= @shlExact(@as(usize, @intFromBool(byte != 0)), @intCast(bit_index));
269 }
270 return res;
271 } else unreachable;
272 }
273 };
274
275 pub fn seenPcsHeader(e: Executable) *align(std.heap.page_size_min) volatile abi.SeenPcsHeader {
276 return mem.bytesAsValue(
277 abi.SeenPcsHeader,
278 e.shared_seen_pcs[0..@sizeOf(abi.SeenPcsHeader)],
279 );
280 }
281};
282
283const Fuzzer = struct {
284 tests: []Test,
285 test_i: u32,
286 test_one: abi.TestOne,
287
288 // The default PRNG is not used here since going through `Random` can be very expensive
289 // since LLVM often fails to devirtualize and inline `fill`. Additionally, optimization
290 // is simpler since integers are not serialized then deserialized in the random stream.
291 //
292 // This acounts for a 30% performance improvement with LLVM 21.
293 xoshiro: std.Random.Xoshiro256,
294 bytes_input: std.testing.Smith,
295 input_builder: Input.Builder,
296 /// Number of data calls the current run has made.
297 req_values: u32,
298 /// Number of bytes provided to the current run.
299 req_bytes: u32,
300 /// Index into the uid slices the current run is at.
301 /// `uid_data_i[i]` corresponds to `corpus[corpus_pos].data.uid_slices.values()[i]`.
302 uid_data_i: std.ArrayList(u32),
303 mut_data: struct {
304 /// Untyped indexes of `corpus[corpus_pos].data` that should be mutated.
305 ///
306 /// If an index appears multiple times, the first should be prioritized.
307 i: [4]u32,
308 /// For mutations which are a sequential mutation, the state is stored here.
309 seq: [4]struct {
310 kind: packed struct {
311 class: enum(u1) { replace, insert },
312 copy: bool,
313 /// If set then `.copy = true` and `.class = .replace`
314 ordered_mutate: bool,
315 /// If set then all other bits are undefined
316 none: bool,
317 },
318 len: u32,
319 copy: SeqCopy,
320 },
321 },
322
323 /// As values are provided to the Smith, they are appended to this. If the test
324 /// crashes, this can be recovered and used to obtain the crashing values. It is
325 /// also used to rerun fresh inputs.
326 mmap_input: MemoryMappedInput,
327 /// The instance is responsible for updating the filesystem corpus.
328 ///
329 /// Since different fuzzer instances can be out of sync due to finding inputs before recieving
330 /// others and nondeterministic tests, the filesystem is only based off the first instance.
331 main_instance: bool,
332
333 const Test = struct {
334 const NameHash = u64;
335 const dirname_len = @sizeOf(NameHash) * 2;
336
337 seen_pcs: []usize,
338 bests: struct {
339 len: u32,
340 quality_buf: []Input.Best,
341 input_buf: []Input.Best.Map,
342 },
343 seen_uids: std.array_hash_map.Custom(Uid, struct {
344 slices: union {
345 ints: std.ArrayList([]u64),
346 bytes: std.ArrayList(Input.Data.Bytes),
347 },
348 }, Uid.hashmap_ctx, false),
349
350 /// Past inputs leading to new pc or uid hits.
351 /// These are randomly mutated in round-robin fashion.
352 corpus: std.MultiArrayList(Input),
353 corpus_pos: Input.Index,
354 /// If this is `math.maxInt(u32)` (reserved), it means the corpus has not been loaded from
355 /// the filesystem.
356 ///
357 /// If `main_instance` is set, the values in `corpus` after this are mirrored to the
358 /// filesystem.
359 start_mut_corpus: u32,
360 dirname: [dirname_len]u8,
361 /// Ensures only one fuzzer writes to the corpus.
362 ///
363 /// Undefined if this is not the main instance.
364 lock_file: Io.File,
365 received: Received,
366
367 limit: ?u64,
368 /// A batch is the amount of cycles approximently for one second of runtime.
369 ///
370 /// This value is set to the previous batch's runs per second or run limit.
371 batch_cycles: u32,
372 batches: u64,
373 batches_since_find: u64,
374 seen_pc_count: u32,
375 };
376
377 const Received = struct {
378 state: State,
379 /// Stream of inputs with each prefixed with a u32 length
380 inputs: std.ArrayList(u8),
381
382 pub const empty: Received = .{
383 .state = .{
384 .pending = false,
385 .read_lock = false,
386 .write_lock = false,
387 },
388 .inputs = .empty,
389 };
390
391 pub const State = packed struct(u32) {
392 pending: bool,
393 read_lock: bool,
394 /// If set in conjucation with `read_lock`, then there is a waiter on state.
395 write_lock: bool,
396 _: u29 = 0,
397
398 pub fn hasPending(s: *State) bool {
399 return @atomicLoad(State, s, .monotonic).pending;
400 }
401
402 pub fn startReadIfPending(s: *State) bool {
403 return @cmpxchgWeak(
404 State,
405 s,
406 .{ .pending = true, .read_lock = false, .write_lock = false },
407 .{ .pending = true, .read_lock = true, .write_lock = false },
408 .acquire,
409 .monotonic,
410 ) == null;
411 }
412
413 pub fn finishRead(s: *State) void {
414 const prev = @atomicRmw(State, s, .And, .{
415 .pending = false,
416 .read_lock = false,
417 .write_lock = true,
418 }, .release);
419 assert(prev.read_lock);
420 if (prev.write_lock) {
421 abi.runner_futex_wake(@ptrCast(s), 1);
422 }
423 }
424
425 /// Returns if cancelation is requested.
426 pub fn startWrite(s: *State) bool {
427 var prev = @atomicRmw(State, s, .Or, .{
428 .pending = false,
429 .read_lock = false,
430 .write_lock = true,
431 }, .acquire);
432 assert(!prev.write_lock);
433 while (prev.read_lock) {
434 if (abi.runner_futex_wait(@ptrCast(s), @bitCast(prev))) {
435 s.* = undefined; // fuzzer is exiting
436 return true;
437 }
438 // Still need `.acquire` ordering so @atomicRmw is necessary
439 prev = @atomicRmw(State, s, .Or, .{
440 .pending = false,
441 .read_lock = false,
442 .write_lock = false,
443 }, .acquire);
444 assert(prev.write_lock);
445 }
446 return false;
447 }
448
449 pub fn finishWrite(s: *State) void {
450 @atomicStore(State, s, .{
451 .pending = true,
452 .read_lock = false,
453 .write_lock = false,
454 }, .release);
455 }
456 };
457 };
458
459 const SeqCopy = union {
460 order_i: u32,
461 ints: []u64,
462 bytes: Input.Data.Bytes,
463 };
464
465 const Input = struct {
466 /// Untyped indexes into this are formed as follows: If the index is less than `ints.len`
467 /// it indexes into `ints`, otherwise it indexes into `bytes` subtracted by `ints.len`.
468 /// `math.maxInt(u32)` is reserved and impossible normally.
469 data: Data,
470 /// Corresponds with `data.uid_slices`.
471 /// Values are the indexes of `seen_uids` with the same uid.
472 seen_uid_i: []u32,
473 /// Used to select a random uid to mutate from.
474 ///
475 /// The number of times a uid is present in this array is logarithmic
476 /// to its data length in order to avoid long inputs from only being
477 /// selected while still having some bias towards longer ones.
478 weighted_uid_slice_i: []u32,
479
480 ref: struct {
481 /// Values are indexes of `Fuzzer.bests`.
482 best_i_buf: []u32,
483 best_i_len: u32,
484 },
485
486 pub const Data = struct {
487 uid_slices: Data.UidSlices,
488 ints: []u64,
489 bytes: Bytes,
490 /// Contains untyped indexes in the order they were requested.
491 order: []u32,
492
493 pub const Bytes = struct {
494 entries: []Entry,
495 table: []u8,
496
497 pub const Entry = struct {
498 off: u32,
499 len: u32,
500 };
501
502 pub fn deinit(b: Bytes) void {
503 gpa.free(b.entries);
504 gpa.free(b.table);
505 }
506 };
507
508 pub const UidSlices = std.array_hash_map.Custom(Uid, struct {
509 base: u32,
510 len: u32,
511 }, Uid.hashmap_ctx, false);
512 };
513
514 pub fn deinit(i: *Input) void {
515 i.data.uid_slices.deinit(gpa);
516 gpa.free(i.data.ints);
517 i.data.bytes.deinit();
518 gpa.free(i.data.order);
519 gpa.free(i.seen_uid_i);
520 gpa.free(i.weighted_uid_slice_i);
521 gpa.free(i.ref.best_i_buf);
522 i.* = undefined;
523 }
524
525 pub const none: Input = .{
526 .data = .{
527 .uid_slices = .empty,
528 .ints = &.{},
529 .bytes = .{
530 .entries = &.{},
531 .table = undefined,
532 },
533 .order = &.{},
534 },
535 .seen_uid_i = &.{},
536 .weighted_uid_slice_i = &.{},
537
538 // Empty input is not referenced by `Fuzzer`
539 .ref = undefined,
540 };
541
542 pub const Index = enum(u32) {
543 pub const reserved_start: Index = .bytes_dry;
544 /// Only touches `Fuzzer.smith`.
545 bytes_dry = math.maxInt(u32) - 1,
546 /// Only touches `Fuzzer.smith` and `Fuzzer.input_builder`.
547 bytes_fresh = math.maxInt(u32),
548 _,
549 };
550
551 pub const Best = struct {
552 pc: u32,
553 min: Quality,
554 max: Quality,
555
556 /// Order of significance:
557 /// * n_pcs
558 /// * req.values
559 /// * req.bytes
560 pub const Quality = struct {
561 n_pcs: u32,
562 req: packed struct(u64) {
563 bytes: u32,
564 values: u32,
565
566 pub fn int(r: @This()) u64 {
567 return @bitCast(r);
568 }
569 },
570
571 pub fn betterLess(a: Quality, b: Quality) bool {
572 return (a.n_pcs < b.n_pcs) | ((a.n_pcs == b.n_pcs) & (a.req.int() < b.req.int()));
573 }
574
575 pub fn betterMore(a: Quality, b: Quality) bool {
576 return (a.n_pcs > b.n_pcs) | ((a.n_pcs == b.n_pcs) & (a.req.int() < b.req.int()));
577 }
578 };
579
580 pub const Map = struct {
581 min: Input.Index,
582 max: Input.Index,
583 };
584 };
585
586 pub const Builder = struct {
587 uid_slices: std.array_hash_map.Custom(Uid, union {
588 ints: std.MultiArrayList(struct {
589 value: u64,
590 order_i: u32,
591 }),
592 bytes: std.MultiArrayList(struct {
593 value: Data.Bytes.Entry,
594 order_i: u32,
595 }),
596 }, Uid.hashmap_ctx, false),
597 bytes_table: std.ArrayList(u8),
598 // These will not overflow due to the 32-bit constraint on `MemoryMappedInput`
599 total_ints: u32,
600 total_bytes: u32,
601 weighted_len: u32,
602 /// Used to ensure that the 32-bit constraint in
603 /// `MemoryMappedInput` applies to this run.
604 smithed_len: u32,
605
606 pub const init: Builder = .{
607 .uid_slices = .empty,
608 .bytes_table = .empty,
609 .total_ints = 0,
610 .total_bytes = 0,
611 .weighted_len = 0,
612 // The - 1 is because we check that `smithed_len` does not overflow a u32;
613 // however, `MemoryMappedInput` allows up to `1 << 32`.
614 .smithed_len = @sizeOf(abi.MmapInputHeader) - 1,
615 };
616
617 pub fn addInt(b: *Builder, uid: Uid, int: u64) void {
618 const u = &b.uid_slices;
619 const gop = u.getOrPutValue(gpa, uid, .{ .ints = .empty }) catch @panic("OOM");
620 gop.value_ptr.ints.append(gpa, .{
621 .value = int,
622 .order_i = b.total_ints + b.total_bytes,
623 }) catch @panic("OOM");
624 b.total_ints += 1;
625 b.weighted_len += @intFromBool(math.isPowerOfTwo(gop.value_ptr.ints.len));
626 }
627
628 pub fn addBytes(b: *Builder, uid: Uid, bytes: []const u8) void {
629 const u = &b.uid_slices;
630 const gop = u.getOrPutValue(gpa, uid, .{ .bytes = .empty }) catch @panic("OOM");
631 gop.value_ptr.bytes.append(gpa, .{
632 .value = .{
633 .off = @intCast(b.bytes_table.items.len),
634 .len = @intCast(bytes.len),
635 },
636 .order_i = b.total_ints + b.total_bytes,
637 }) catch @panic("OOM");
638 b.bytes_table.appendSlice(gpa, bytes) catch @panic("OOM");
639 b.total_bytes += 1;
640 b.weighted_len += @intFromBool(math.isPowerOfTwo(gop.value_ptr.bytes.len));
641 }
642
643 pub fn checkSmithedLen(b: *Builder, n: usize) void {
644 const n32 = @min(n, math.maxInt(u32)); // second will overflow
645 b.smithed_len, const ov = @addWithOverflow(b.smithed_len, n32);
646 if (ov == 1) @panic("too much smith data requested (non-deterministic)");
647 }
648
649 /// Additionally resets the state of this structure.
650 ///
651 /// The callee must populate
652 /// * `.seen_uid_i`
653 /// * `.ref`
654 pub fn build(b: *Builder) Input {
655 const uid_slices = b.uid_slices.entries.slice();
656 var input: Input = .{
657 .data = .{
658 .uid_slices = Data.UidSlices.init(gpa, uid_slices.items(.key), &.{}) catch
659 @panic("OOM"),
660 .ints = gpa.alloc(u64, b.total_ints) catch @panic("OOM"),
661 .bytes = .{
662 .entries = gpa.alloc(Data.Bytes.Entry, b.total_bytes) catch @panic("OOM"),
663 .table = b.bytes_table.toOwnedSlice(gpa) catch @panic("OOM"),
664 },
665 .order = gpa.alloc(u32, b.total_ints + b.total_bytes) catch @panic("OOM"),
666 },
667 .seen_uid_i = gpa.alloc(u32, uid_slices.len) catch @panic("OOM"),
668 .weighted_uid_slice_i = gpa.alloc(u32, b.weighted_len) catch @panic("OOM"),
669 .ref = undefined,
670 };
671 var ints_pos: u32 = 0;
672 var bytes_pos: u32 = 0;
673 var weighted_pos: u32 = 0;
674
675 assert(mem.eql(Uid, uid_slices.items(.key), input.data.uid_slices.keys()));
676 for (
677 0..,
678 uid_slices.items(.key),
679 uid_slices.items(.value),
680 input.data.uid_slices.values(),
681 ) |uid_i, uid, *uid_data, *slice| {
682 const weighted_len = 1 + math.log2_int(u32, len: switch (uid.kind) {
683 .int => {
684 const ints = uid_data.ints.slice();
685 @memcpy(input.data.ints[ints_pos..][0..ints.len], ints.items(.value));
686 for (ints.items(.order_i), ints_pos..) |order_i, data_i| {
687 input.data.order[order_i] = @intCast(data_i);
688 }
689 uid_data.ints.deinit(gpa);
690 slice.* = .{ .base = ints_pos, .len = @intCast(ints.len) };
691 ints_pos += @intCast(ints.len);
692 break :len @intCast(ints.len);
693 },
694 .bytes => {
695 const bytes = uid_data.bytes.slice();
696 @memcpy(
697 input.data.bytes.entries[bytes_pos..][0..bytes.len],
698 bytes.items(.value),
699 );
700 for (
701 bytes.items(.order_i),
702 b.total_ints + bytes_pos..,
703 ) |order_i, data_i| {
704 input.data.order[order_i] = @intCast(data_i);
705 }
706 uid_data.bytes.deinit(gpa);
707 slice.* = .{ .base = bytes_pos, .len = @intCast(bytes.len) };
708 bytes_pos += @intCast(bytes.len);
709 break :len @intCast(bytes.len);
710 },
711 });
712 const weighted = input.weighted_uid_slice_i[weighted_pos..][0..weighted_len];
713 @memset(weighted, @intCast(uid_i));
714 weighted_pos += weighted_len;
715 }
716
717 assert(ints_pos == b.total_ints);
718 assert(bytes_pos == b.total_bytes);
719 assert(weighted_pos == b.weighted_len);
720
721 b.uid_slices.clearRetainingCapacity();
722 b.total_ints = 0;
723 b.total_bytes = 0;
724 b.weighted_len = 0;
725 b.smithed_len = Builder.init.smithed_len;
726 return input;
727 }
728
729 pub fn reset(b: *Builder) void {
730 const uid_slices = b.uid_slices.entries.slice();
731 for (uid_slices.items(.key), uid_slices.items(.value)) |uid, *uid_data| {
732 switch (uid.kind) {
733 .int => uid_data.ints.deinit(gpa),
734 .bytes => uid_data.bytes.deinit(gpa),
735 }
736 }
737 b.uid_slices.clearRetainingCapacity();
738 b.bytes_table.clearRetainingCapacity();
739 b.total_ints = 0;
740 b.total_bytes = 0;
741 b.weighted_len = 0;
742 b.smithed_len = Builder.init.smithed_len;
743 }
744
745 /// Asserts the structure is reset
746 pub fn deinit(b: *Builder) void {
747 assert(b.uid_slices.entries.len == 0);
748 b.uid_slices.deinit(gpa);
749 b.bytes_table.deinit(gpa);
750 b.* = undefined;
751 }
752 };
753 };
754
755 pub fn init(n_tests: u32, seed: u64, instance_id: u32, limit: ?u64) Fuzzer {
756 const pcs = exec.pc_counters.len;
757 if (pcs > math.maxInt(u32)) @panic("too many pcs");
758
759 const mmap_input = map: {
760 // Find a free input file. `instance_id` should give one that is not in use;
761 // however, this may not be the case if there are multiple libfuzzers running.
762 var input_i = instance_id;
763 const input_f = while (true) {
764 var name_buf: [10]u8 = undefined;
765 name_buf[0..2].* = "in".*;
766 const hex = std.mem.print(name_buf[2..], "{x}", .{input_i}) catch unreachable;
767 const name = name_buf[0 .. 2 + hex.len];
768
769 if (exec.cache_f.createFile(io, name, .{
770 .read = true,
771 .truncate = false,
772 .lock = .exclusive,
773 .lock_nonblocking = true,
774 })) |f| {
775 break f;
776 } else |e| switch (e) {
777 // To ensure no input file is unused to avoid the number of input files
778 // growing indefinitely across runs, they are linearly searched through.
779 //
780 // This could be avoided by creating a shared file holding the current number
781 // of input files in use; however, using multiple libfuzzers is uncommon and
782 // there should not be that many input files to search through anyways.
783 error.WouldBlock => input_i += 1,
784 else => panic("failed to create file '{s}': {t}", .{ name, e }),
785 }
786 };
787 break :map MemoryMappedInput.init(input_f, instance_id, input_i);
788 };
789
790 const tests = gpa.alloc(Test, n_tests) catch @panic("OOM");
791 const seen_pcs_len = bitsetUsizes(pcs);
792 var seen_pcs_bufs = gpa.alloc(usize, seen_pcs_len * n_tests) catch @panic("OOM");
793 var best_quality_bufs = gpa.alloc(Input.Best, pcs * n_tests) catch @panic("OOM");
794 var best_input_bufs = gpa.alloc(Input.Best.Map, pcs * n_tests) catch @panic("OOM");
795 @memset(seen_pcs_bufs, 0);
796 for (0.., tests) |i, *t| {
797 const name = abi.runner_test_name(@intCast(i)).toSlice();
798 // A hash is used as the dirname instead of the actual test name since the test name
799 // may be not allowed by the filesystem or have a special meaning (e.g. absolute /
800 // relative paths).
801 const dirname = std.fmt.hex(std.hash.Wyhash.hash(0, name));
802
803 const lock_file = file: {
804 if (instance_id != 0) break :file undefined;
805
806 exec.cache_f.createDir(io, &dirname, .default_dir) catch |e| switch (e) {
807 error.PathAlreadyExists => {},
808 else => panic("failed to create directory '{s}': {t}", .{ &dirname, e }),
809 };
810
811 var cname: CorpusFileName = .fromTest(dirname);
812 const lock_name = cname.syncLockName();
813 break :file exec.cache_f.createFile(io, lock_name, .{
814 .truncate = false,
815 .lock = .exclusive,
816 .lock_nonblocking = true,
817 }) catch |e| switch (e) {
818 error.WouldBlock => panic("corpus of '{s}' is in use by another fuzzer", .{name}),
819 else => panic("failed to create file '{s}': {t}", .{ lock_name, e }),
820 };
821 };
822
823 t.* = .{
824 .seen_pcs = seen_pcs_bufs[0..seen_pcs_len],
825 .bests = .{
826 .len = 0,
827 .quality_buf = best_quality_bufs[0..pcs],
828 .input_buf = best_input_bufs[0..pcs],
829 },
830 .seen_uids = .empty,
831
832 .corpus = .empty,
833 .corpus_pos = @fromBackingInt(@intCast(0)),
834 .start_mut_corpus = math.maxInt(u32),
835 .dirname = dirname,
836 .lock_file = lock_file,
837 .received = .empty,
838
839 .limit = limit,
840 .batch_cycles = 1,
841 .batches = 0,
842 .batches_since_find = 0,
843 .seen_pc_count = 0,
844 };
845 t.corpus.append(gpa, .none) catch @panic("OOM"); // Also ensures the corpus is not empty
846 seen_pcs_bufs = seen_pcs_bufs[seen_pcs_len..];
847 best_quality_bufs = best_quality_bufs[pcs..];
848 best_input_bufs = best_input_bufs[pcs..];
849 }
850 assert(seen_pcs_bufs.len == 0);
851 assert(best_quality_bufs.len == 0);
852 assert(best_input_bufs.len == 0);
853
854 return .{
855 .tests = tests,
856 .test_i = undefined,
857 .test_one = undefined,
858
859 .xoshiro = .init(seed),
860 .bytes_input = undefined,
861 .input_builder = .init,
862 .req_values = undefined,
863 .req_bytes = undefined,
864 .uid_data_i = .empty,
865 .mut_data = undefined,
866
867 .mmap_input = mmap_input,
868 .main_instance = instance_id == 0,
869 };
870 }
871
872 pub fn deinit(f: *Fuzzer) void {
873 const pcs = exec.pc_counters.len;
874 const n_tests = f.tests.len;
875 gpa.free(f.tests[0].seen_pcs.ptr[0 .. bitsetUsizes(pcs) * n_tests]);
876 gpa.free(f.tests[0].bests.quality_buf.ptr[0 .. pcs * n_tests]);
877 gpa.free(f.tests[0].bests.input_buf.ptr[0 .. pcs * n_tests]);
878 for (f.tests) |*t| {
879 const seen_uids = t.seen_uids.entries.slice();
880 for (seen_uids.items(.key), seen_uids.items(.value)) |uid, *data| {
881 switch (uid.kind) {
882 .int => data.slices.ints.deinit(gpa),
883 .bytes => data.slices.bytes.deinit(gpa),
884 }
885 }
886 t.seen_uids.deinit(gpa);
887 const corpus = t.corpus.slice();
888 // The first input is `Input.none` and so is skipped as `deinit` is illegal.
889 for (1..corpus.len) |i| {
890 var in = corpus.get(i);
891 in.deinit();
892 }
893 if (f.main_instance) {
894 t.lock_file.close(io);
895 }
896 t.received.inputs.deinit(gpa);
897 }
898 gpa.free(f.tests);
899 f.input_builder.deinit();
900 f.mmap_input.deinit();
901 f.* = undefined;
902 }
903
904 pub fn ensureCorpusLoaded(f: *Fuzzer) void {
905 const t = &f.tests[f.test_i];
906 if (t.start_mut_corpus != math.maxInt(u32)) return;
907
908 const start_mut: u32 = @intCast(t.corpus.len);
909 if (!f.main_instance) {
910 // Inputs can be culled as added since filesystem synchronacy is not required
911 t.start_mut_corpus = start_mut;
912 }
913
914 read_corpus: {
915 var cname: CorpusFileName = .fromTest(t.dirname);
916
917 const readlock_name = cname.readLockName();
918 const readlock_file = exec.cache_f.createFile(io, readlock_name, .{
919 .truncate = false,
920 .lock = .shared,
921 }) catch |e| switch (e) {
922 // FileNotFound means the corpus directory does not exist, which means it is empty
923 error.FileNotFound => break :read_corpus,
924 else => panic("failed to open '{s}': {t}", .{ readlock_name, e }),
925 };
926 defer readlock_file.close(io);
927
928 var input_buf: std.ArrayList(u8) = .empty;
929 defer input_buf.deinit(gpa);
930 var i: u32 = 0;
931 while (true) {
932 const name = cname.inputName(i);
933 const input_file = exec.cache_f.openFile(io, name, .{}) catch |e| switch (e) {
934 error.FileNotFound => break,
935 else => panic("failed to open input file '{s}': {t}", .{ name, e }),
936 };
937
938 const len = input_file.length(io) catch |e|
939 panic("failed to get length of '{s}': {t}", .{ name, e });
940 const ulen = math.cast(usize, len) orelse @panic("OOM");
941 input_buf.resize(gpa, ulen) catch @panic("OOM");
942
943 var r = input_file.readerStreaming(io, &.{});
944 r.interface.readSliceAll(input_buf.items) catch |e| switch (e) {
945 error.ReadFailed => panic(
946 "failed to read from input file '{s}': {t}",
947 .{ name, r.err.? },
948 ),
949 error.EndOfStream => panic(
950 "input file '{s}' ended before its reported length",
951 .{name},
952 ),
953 };
954 f.newInputExternal(input_buf.items);
955
956 i += 1; // Cannot overflow due to corpus 32-bit size limit
957 }
958 }
959
960 if (f.main_instance) {
961 t.start_mut_corpus = start_mut;
962
963 // Cull old inputs
964 const ref = t.corpus.items(.ref);
965 var i: usize = t.start_mut_corpus;
966 while (i < t.corpus.len) {
967 if (ref[i].best_i_len == 0) {
968 f.removeInput(@fromBackingInt(@intCast(i)));
969 } else {
970 i += 1;
971 }
972 }
973 }
974
975 t.corpus_pos = @fromBackingInt(@intCast(0));
976 }
977
978 const CorpusFileName = struct {
979 buf: [Test.dirname_len + 9]u8,
980
981 pub fn fromTest(dirname: [Test.dirname_len]u8) CorpusFileName {
982 var n: CorpusFileName = undefined;
983 n.buf[0..dirname.len].* = dirname;
984 n.buf[dirname.len] = Io.Dir.path.sep;
985 return n;
986 }
987
988 pub fn readLockName(n: *CorpusFileName) []u8 {
989 const basename = "readlock";
990 n.buf[Test.dirname_len + 1 ..][0..basename.len].* = basename.*;
991 return n.buf[0 .. Test.dirname_len + 1 + basename.len];
992 }
993
994 pub fn syncLockName(n: *CorpusFileName) []u8 {
995 const basename = "synclock";
996 n.buf[Test.dirname_len + 1 ..][0..basename.len].* = basename.*;
997 return n.buf[0 .. Test.dirname_len + 1 + basename.len];
998 }
999
1000 pub fn inputName(n: *CorpusFileName, i: u32) []u8 {
1001 const hex = std.mem.print(n.buf[Test.dirname_len + 1 ..][0..8], "{x}", .{i}) catch unreachable;
1002 return n.buf[0 .. Test.dirname_len + 1 + hex.len];
1003 }
1004 };
1005
1006 fn rngInt(f: *Fuzzer, T: type) T {
1007 comptime assert(@bitSizeOf(T) <= 64);
1008 const Unsigned = @Int(.unsigned, @bitSizeOf(T));
1009 return @bitCast(@as(Unsigned, @truncate(f.xoshiro.next())));
1010 }
1011
1012 fn rngLessThan(f: *Fuzzer, T: type, limit: T) T {
1013 return std.Random.limitRangeBiased(T, f.rngInt(T), limit);
1014 }
1015
1016 /// Used for generating small values rather than making many calls into the prng.
1017 const SmallEntronopy = struct {
1018 bits: u64,
1019
1020 pub fn take(e: *SmallEntronopy, T: type) T {
1021 defer e.bits >>= @bitSizeOf(T);
1022 return @truncate(e.bits);
1023 }
1024 };
1025
1026 fn isFresh(f: *Fuzzer) bool {
1027 const t = &f.tests[f.test_i];
1028 // Store as a bool instead of returning immediately to aid optimizations
1029 // by reducing branching since a fresh input is the unlikely case.
1030 var fresh: bool = false;
1031
1032 var n_pcs: u32 = 0;
1033 var hit_pcs = exec.pcBitsetIterator();
1034 for (t.seen_pcs) |seen| {
1035 const hits = hit_pcs.next();
1036 fresh |= hits & ~seen != 0;
1037 n_pcs += @popCount(hits);
1038 }
1039
1040 const quality: Input.Best.Quality = .{
1041 .n_pcs = n_pcs,
1042 .req = .{
1043 .values = f.req_values,
1044 .bytes = f.req_bytes,
1045 },
1046 };
1047 for (t.bests.quality_buf[0..t.bests.len]) |best| {
1048 if (exec.pc_counters[best.pc] == 0) continue;
1049 fresh |= quality.betterLess(best.min) | quality.betterMore(best.max);
1050 }
1051
1052 return fresh;
1053 }
1054
1055 /// It is the callee's responsibility to reset the corpus pos
1056 ///
1057 /// Returns if `error.SkipZigTest` was indicated
1058 fn runBytes(f: *Fuzzer, bytes: []const u8, mode: Input.Index) bool {
1059 assert(mode == .bytes_dry or mode == .bytes_fresh);
1060
1061 f.bytes_input = .{ .in = bytes };
1062 f.tests[f.test_i].corpus_pos = mode;
1063 defer f.tests[f.test_i].corpus_pos = undefined;
1064 return f.run(0); // 0 since `f.uid_data` is unused
1065 }
1066
1067 fn updateSeenPcs(f: *Fuzzer) void {
1068 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
1069 const shared_seen_pcs: [*]volatile usize = @ptrCast(
1070 exec.shared_seen_pcs[@sizeOf(abi.SeenPcsHeader)..].ptr,
1071 );
1072
1073 const t = &f.tests[f.test_i];
1074 var hit_pcs = exec.pcBitsetIterator();
1075 for (t.seen_pcs, shared_seen_pcs) |*seen, *shared_seen| {
1076 const new = hit_pcs.next() & ~seen.*;
1077 if (new != 0) {
1078 seen.* |= new;
1079 _ = @atomicRmw(usize, shared_seen, .Or, new, .monotonic);
1080 t.seen_pc_count += @popCount(new);
1081 }
1082 }
1083 }
1084
1085 fn removeBest(f: *Fuzzer, i: Input.Index, best_i: u32) void {
1086 const t = &f.tests[f.test_i];
1087 const ref = &t.corpus.items(.ref)[@backingInt(i)];
1088 const list_i = mem.findScalar(u32, ref.best_i_buf[0..ref.best_i_len], best_i).?;
1089 ref.best_i_len -= 1;
1090 ref.best_i_buf[list_i] = ref.best_i_buf[ref.best_i_len];
1091
1092 if (ref.best_i_len == 0 and @backingInt(i) >= t.start_mut_corpus) {
1093 // The input is no longer valuable, so remove it.
1094 f.removeInput(i);
1095 }
1096 }
1097
1098 fn removeInput(f: *Fuzzer, i: Input.Index) void {
1099 const t = &f.tests[f.test_i];
1100 const ref = &t.corpus.items(.ref)[@backingInt(i)];
1101 assert(ref.best_i_len == 0 and @backingInt(i) >= t.start_mut_corpus);
1102
1103 var removed_input = t.corpus.get(@backingInt(i));
1104 for (
1105 removed_input.data.uid_slices.keys(),
1106 removed_input.data.uid_slices.values(),
1107 removed_input.seen_uid_i,
1108 ) |uid, slice, seen_uid_i| {
1109 switch (uid.kind) {
1110 .int => {
1111 const seen_ints = &t.seen_uids.values()[seen_uid_i].slices.ints;
1112 const removed_ints = removed_input.data.ints[slice.base..][0..slice.len];
1113 _ = seen_ints.swapRemove(for (0.., seen_ints.items) |idx, ints| {
1114 if (removed_ints.ptr == ints.ptr) {
1115 assert(removed_ints.len == ints.len);
1116 break idx;
1117 }
1118 } else unreachable);
1119 },
1120 .bytes => {
1121 const seen_bytes = &t.seen_uids.values()[seen_uid_i].slices.bytes;
1122 const removed_bytes: Input.Data.Bytes = .{
1123 .entries = removed_input.data.bytes.entries[slice.base..][0..slice.len],
1124 .table = removed_input.data.bytes.table,
1125 };
1126 _ = seen_bytes.swapRemove(for (0.., seen_bytes.items) |idx, bytes| {
1127 if (removed_bytes.entries.ptr == bytes.entries.ptr) {
1128 assert(removed_bytes.entries.len == bytes.entries.len);
1129 assert(removed_bytes.table.ptr == bytes.table.ptr);
1130 assert(removed_bytes.table.len == bytes.table.len);
1131 break idx;
1132 }
1133 } else unreachable);
1134 },
1135 }
1136 }
1137 removed_input.deinit();
1138 t.corpus.swapRemove(@backingInt(i));
1139
1140 if (@backingInt(i) != t.corpus.len) {
1141 // The last item was moved so its refs need updated.
1142 // `ref` can be reused since it was a swap remove.
1143 for (ref.best_i_buf[0..ref.best_i_len]) |update_pc_i| {
1144 const best = &t.bests.input_buf[update_pc_i];
1145 assert(@backingInt(best.min) == t.corpus.len or
1146 @backingInt(best.max) == t.corpus.len);
1147
1148 if (@backingInt(best.min) == t.corpus.len) best.min = i;
1149 if (@backingInt(best.max) == t.corpus.len) best.max = i;
1150 }
1151 }
1152
1153 if (!f.main_instance) return;
1154
1155 var removed_cname: CorpusFileName = .fromTest(t.dirname);
1156 // Temporarily use removed_name to construct the path to the lock
1157 const readlock_name = removed_cname.readLockName();
1158 const readlock_file = exec.cache_f.createFile(io, readlock_name, .{
1159 .truncate = false,
1160 .lock = .exclusive,
1161 }) catch |e| panic("failed to open '{s}': {t}", .{ readlock_name, e });
1162 defer readlock_file.close(io);
1163
1164 const removed_name = removed_cname.inputName(@backingInt(i) - t.start_mut_corpus);
1165 if (@backingInt(i) == t.corpus.len) {
1166 exec.cache_f.deleteFile(io, removed_name) catch |e| panic(
1167 "failed to remove corpus file '{s}': {t}",
1168 .{ removed_name, e },
1169 );
1170 } else {
1171 var swapped_cname: CorpusFileName = .fromTest(t.dirname);
1172 const swapped_i: u32 = @intCast(t.corpus.len);
1173 const swapped_name = swapped_cname.inputName(swapped_i - t.start_mut_corpus);
1174
1175 exec.cache_f.rename(swapped_name, exec.cache_f, removed_name, io) catch |e| panic(
1176 "failed to rename corpus file '{s}' to '{s}': {t}",
1177 .{ swapped_name, removed_name, e },
1178 );
1179 }
1180 }
1181
1182 pub fn newInputExternal(f: *Fuzzer, bytes: []const u8) void {
1183 // All inputs including the corpus are required to go through the memory
1184 // mapped input in case they cause a crash so they can be identified.
1185 f.mmap_input.appendSlice(bytes);
1186 f.newInput();
1187 f.mmap_input.clearRetainingCapacity();
1188 }
1189
1190 fn newInput(f: *Fuzzer) void {
1191 const t = &f.tests[f.test_i];
1192 const new_is_mut = t.start_mut_corpus != math.maxInt(u32);
1193 assert(new_is_mut == (t.corpus.len >= t.start_mut_corpus));
1194 const bytes = f.mmap_input.inputSlice();
1195 // `error.SkipZigTest` here can be from one of these causes:
1196 // * A previous corpus input after the test has changed
1197 // * An input provided by the test
1198 // * The test is non-deterministic
1199 if (f.runBytes(bytes, .bytes_fresh) and
1200 new_is_mut // The corpus must be mutable at this point for the input to be
1201 // omitted (i.e. test corpus inputs and filesystem inputs cannot be dropped)
1202 ) {
1203 f.input_builder.reset();
1204 t.corpus_pos = @fromBackingInt(@intCast(0));
1205 return;
1206 }
1207
1208 f.req_values = f.input_builder.total_ints + f.input_builder.total_bytes;
1209 f.req_bytes = @intCast(f.input_builder.bytes_table.items.len);
1210 const quality: Input.Best.Quality = .{
1211 .n_pcs = n_pcs: {
1212 @setRuntimeSafety(builtin.mode == .debug); // Necessary for vectorization
1213 var n: u32 = 0;
1214 for (exec.pc_counters) |c| {
1215 n += @intFromBool(c != 0);
1216 }
1217 break :n_pcs n;
1218 },
1219 .req = .{
1220 .values = f.req_values,
1221 .bytes = f.req_bytes,
1222 },
1223 };
1224
1225 var best_i_list: std.ArrayList(u32) = .empty;
1226 for (0.., t.bests.quality_buf[0..t.bests.len]) |best_i, best| {
1227 if (exec.pc_counters[best.pc] == 0) continue;
1228
1229 const better_min = quality.betterLess(best.min);
1230 const better_max = quality.betterMore(best.max);
1231 if (!better_min and !better_max) {
1232 @branchHint(.likely);
1233 continue;
1234 }
1235 best_i_list.append(gpa, @intCast(best_i)) catch @panic("OOM");
1236
1237 const map = &t.bests.input_buf[best_i];
1238 if (map.min != map.max) {
1239 if (better_min) {
1240 f.removeBest(map.min, @intCast(best_i));
1241 }
1242 if (better_max) {
1243 f.removeBest(map.max, @intCast(best_i));
1244 }
1245 } else {
1246 if (better_min and better_max) {
1247 f.removeBest(map.min, @intCast(best_i));
1248 }
1249 }
1250 }
1251
1252 // Must come after the above since some inputs may be removed
1253 const input_i: Input.Index = @fromBackingInt(@intCast(t.corpus.len));
1254 if (input_i == Input.Index.reserved_start) {
1255 @panic("corpus size limit exceeded");
1256 }
1257
1258 for (best_i_list.items) |i| {
1259 const best_qual = &t.bests.quality_buf[i];
1260 const best_map = &t.bests.input_buf[i];
1261
1262 if (quality.betterLess(best_qual.min)) {
1263 best_qual.min = quality;
1264 best_map.min = input_i;
1265 }
1266 if (quality.betterMore(best_qual.max)) {
1267 best_qual.max = quality;
1268 best_map.max = input_i;
1269 }
1270 }
1271
1272 for (0.., exec.pc_counters) |i, hits| {
1273 if (hits == 0) {
1274 @branchHint(.likely);
1275 continue;
1276 }
1277
1278 if ((t.seen_pcs[i / @bitSizeOf(usize)] >> @intCast(i % @bitSizeOf(usize))) & 1 == 0) {
1279 @branchHint(.unlikely);
1280 best_i_list.append(gpa, t.bests.len) catch @panic("OOM");
1281 t.bests.quality_buf[t.bests.len] = .{
1282 .pc = @intCast(i),
1283 .min = quality,
1284 .max = quality,
1285 };
1286 t.bests.input_buf[t.bests.len] = .{ .min = input_i, .max = input_i };
1287 t.bests.len += 1;
1288 }
1289 }
1290
1291 // Having no best qualities could be from one of these causes:
1292 // * A previous corpus input after the test has changed
1293 // * An input provided by the test
1294 // * The test is non-deterministic
1295 if (best_i_list.items.len == 0 and new_is_mut) {
1296 assert(best_i_list.capacity == 0);
1297 f.input_builder.reset();
1298 t.corpus_pos = @fromBackingInt(@intCast(0));
1299 return;
1300 }
1301
1302 var input = f.input_builder.build();
1303 f.uid_data_i.ensureTotalCapacity(gpa, input.data.uid_slices.entries.len) catch @panic("OOM");
1304 for (
1305 input.seen_uid_i,
1306 input.data.uid_slices.keys(),
1307 input.data.uid_slices.values(),
1308 ) |*i, uid, slice| {
1309 const gop = t.seen_uids.getOrPutValue(gpa, uid, switch (uid.kind) {
1310 .int => .{ .slices = .{ .ints = .empty } },
1311 .bytes => .{ .slices = .{ .bytes = .empty } },
1312 }) catch @panic("OOM");
1313 switch (uid.kind) {
1314 .int => t.seen_uids.values()[gop.index].slices.ints.append(
1315 gpa,
1316 input.data.ints[slice.base..][0..slice.len],
1317 ) catch @panic("OOM"),
1318 .bytes => t.seen_uids.values()[gop.index].slices.bytes.append(gpa, .{
1319 .entries = input.data.bytes.entries[slice.base..][0..slice.len],
1320 .table = input.data.bytes.table,
1321 }) catch @panic("OOM"),
1322 }
1323 i.* = @intCast(gop.index);
1324 }
1325
1326 input.ref.best_i_buf = best_i_list.toOwnedSlice(gpa) catch @panic("OOM");
1327 input.ref.best_i_len = @intCast(input.ref.best_i_buf.len);
1328 t.corpus.append(gpa, input) catch @panic("OOM");
1329 t.corpus_pos = input_i;
1330
1331 // Must come after the above since `seen_pcs` is used
1332 f.updateSeenPcs();
1333
1334 t.batches_since_find = 0;
1335 if (f.main_instance and new_is_mut) {
1336 // Only the main instance increments the number of unique runs since it is likely
1337 // multiple instances find the same new input at the same time.
1338 _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);
1339 // Write new input to the cache
1340 var cname: CorpusFileName = .fromTest(t.dirname);
1341 const name = cname.inputName(@backingInt(input_i) - t.start_mut_corpus);
1342 exec.cache_f.writeFile(io, .{ .sub_path = name, .data = bytes, .flags = .{
1343 .exclusive = true,
1344 } }) catch |e| panic("failed to write corpus file '{s}': {t}", .{ name, e });
1345 }
1346 }
1347
1348 /// Returns if `error.SkipZigTest` was indicated
1349 fn run(f: *Fuzzer, input_uids: usize) bool {
1350 @memset(exec.pc_counters, 0);
1351 f.uid_data_i.items.len = input_uids;
1352 @memset(f.uid_data_i.items, 0);
1353 f.req_values = 0;
1354 f.req_bytes = 0;
1355
1356 const skip = f.test_one();
1357 _ = @atomicRmw(usize, &exec.seenPcsHeader().n_runs, .Add, 1, .monotonic);
1358 return skip;
1359 }
1360
1361 /// Returns a number of mutations to perform from 1-4
1362 /// with smaller values exponentially more likely.
1363 pub fn mutCount(rng: u16) u8 {
1364 // The below provides the following distribution
1365 // @clz(@clz( range mapped percentage ratio
1366 // 0 -> 0 -> 4 1 = 93.750% (15 / 16 )
1367 // 1 -> 1 - 255 -> 3 2 = 5.859% (15 / 256 )
1368 // 2 -> 256 - 4095 -> 2 3 = .391% (<1 / 256 )
1369 // 3 -> 4096 - 16383 -> 1 4 = .002% ( 1 / 65536)
1370 // 4 -> 16384 - 32767 -> 1
1371 // 5 -> 32768 - 65535 -> 1
1372 return @as(u8, 4) - @min(@clz(@clz(rng)), 3);
1373 }
1374
1375 pub fn cycle(f: *Fuzzer) void {
1376 assert(f.mmap_input.len == 0);
1377
1378 const t = &f.tests[f.test_i];
1379 const corpus = t.corpus.slice();
1380 const corpus_i = @backingInt(t.corpus_pos);
1381
1382 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
1383 var n_mutate = mutCount(small_entronopy.take(u16));
1384 const data = &corpus.items(.data)[corpus_i];
1385 const weighted_uid_slice_i = corpus.items(.weighted_uid_slice_i)[corpus_i];
1386 n_mutate *= @intFromBool(weighted_uid_slice_i.len != 0); // No static mutations on empty
1387
1388 f.mut_data = .{
1389 .i = @splat(math.maxInt(u32)),
1390 .seq = @splat(.{
1391 .kind = .{
1392 .class = undefined,
1393 .copy = undefined,
1394 .ordered_mutate = undefined,
1395 .none = true,
1396 },
1397 .len = undefined,
1398 .copy = undefined,
1399 }),
1400 };
1401
1402 const uid_slices = data.uid_slices.entries.slice();
1403 for (
1404 f.mut_data.i[0..n_mutate],
1405 f.mut_data.seq[0..n_mutate],
1406 ) |*i, *s| if ((data.order.len < 2) | (small_entronopy.take(u3) != 0)) {
1407 // Mutation on uid
1408 const uid_slice_wi = f.rngLessThan(u32, @intCast(weighted_uid_slice_i.len));
1409 const uid_slice_i = weighted_uid_slice_i[uid_slice_wi];
1410
1411 const is_bytes = uid_slices.items(.key)[uid_slice_i].kind == .bytes;
1412 const data_slice = uid_slices.items(.value)[uid_slice_i];
1413 i.* = @as(u32, @intCast(data.ints.len)) * @intFromBool(is_bytes) +
1414 data_slice.base + f.rngLessThan(u32, data_slice.len);
1415 } else {
1416 // Sequence mutation on order
1417 const order_len: u32 = @intCast(data.order.len);
1418 const order_i = f.rngLessThan(u32, order_len - 1);
1419 s.* = .{
1420 .kind = .{
1421 .class = .replace,
1422 .copy = true,
1423 .ordered_mutate = true,
1424 .none = false,
1425 },
1426 .len = @min(@clz(f.rngInt(u16)) + 1, order_len - order_i),
1427 .copy = .{ .order_i = order_i },
1428 };
1429 i.* = data.order[order_i];
1430 };
1431
1432 const skip = f.run(data.uid_slices.entries.len);
1433 if (!skip and f.isFresh()) {
1434 @branchHint(.unlikely);
1435
1436 abi.runner_broadcast_input(f.test_i, .fromSlice(f.mmap_input.inputSlice()));
1437 f.newInput();
1438 } else {
1439 assert(@backingInt(t.corpus_pos) < t.corpus.len);
1440 t.corpus_pos = @fromBackingInt(@intCast((@backingInt(t.corpus_pos) + 1) % t.corpus.len));
1441 }
1442 f.mmap_input.clearRetainingCapacity();
1443 }
1444
1445 fn takeReceived(f: *Fuzzer) void {
1446 const t = &f.tests[f.test_i];
1447 if (t.received.state.startReadIfPending()) {
1448 defer t.received.state.finishRead();
1449 const inputs = &t.received.inputs;
1450 var rem = inputs.items;
1451
1452 while (true) {
1453 const len: u32 = @bitCast(rem[0..4].*);
1454 rem = rem[4..];
1455 const bytes = rem[0..len];
1456 rem = rem[len..];
1457
1458 f.mmap_input.appendSlice(bytes);
1459 f.newInput();
1460 f.mmap_input.clearRetainingCapacity();
1461
1462 if (rem.len == 0) break;
1463 }
1464
1465 inputs.clearRetainingCapacity();
1466 }
1467 }
1468
1469 pub fn batch(f: *Fuzzer) void {
1470 const t = &f.tests[f.test_i];
1471 assert(t.limit != 0);
1472 t.batches += 1;
1473 t.batches_since_find += 1;
1474 if (f.tests.len != 1) {
1475 // Use cpu_process since some fuzz tests may spawn
1476 // other threads and give all the work to them.
1477 const start: Io.Timestamp = .now(io, .cpu_process);
1478 var completed_cycles: u32 = 0;
1479 var total_cycles: u32 = t.batch_cycles;
1480
1481 while (true) {
1482 assert(completed_cycles != total_cycles);
1483 while (completed_cycles < total_cycles) {
1484 f.takeReceived();
1485 f.cycle();
1486 completed_cycles += 1;
1487 }
1488
1489 const duration = start.untilNow(io, .cpu_process);
1490 const ns = @min(@max(1, duration.nanoseconds), math.maxInt(u64));
1491 const speed = @as(u64, t.batch_cycles) * std.time.ns_per_s / ns;
1492 // @min avoids large increases in batch_cycles due to just a few cycles running
1493 // fast. For example, if batch_cycles is only 2, and both run very fast due to
1494 // unlucky rng, this avoids a large runtime on the next batch. This also avoids
1495 // timer inprecision giving large values.
1496 t.batch_cycles = @max(1, @min(speed, t.batch_cycles *| 2));
1497
1498 if (ns < std.time.ns_per_s * 7 / 8) {
1499 // Keep running the test to get closer to a second. This will almost always
1500 // be the case for the first batch as the default batch_cycles is 1.
1501 if (t.limit == total_cycles) break;
1502
1503 const rem_ns: u64 = @as(u32, std.time.ns_per_s) - ns;
1504 const extra: u32 = @intCast(rem_ns * t.batch_cycles / std.time.ns_per_s);
1505 if (extra == 0) break; // No better approximation of a second possible
1506 total_cycles += extra;
1507 if (t.limit) |limit| total_cycles = @min(total_cycles, limit);
1508 continue;
1509 }
1510
1511 break;
1512 }
1513
1514 assert(completed_cycles == total_cycles);
1515 if (t.limit) |prev| {
1516 t.limit = prev - total_cycles;
1517 t.batch_cycles = @min(t.batch_cycles, t.limit.?);
1518 }
1519 } else {
1520 while (true) {
1521 if (t.limit) |limit| {
1522 if (limit == 0) break;
1523 t.limit = limit - 1;
1524 }
1525 f.takeReceived();
1526 f.cycle();
1527 }
1528 }
1529 }
1530
1531 pub fn select(f: *Fuzzer) ?u32 {
1532 assert(f.tests.len > 1); // More efficiently handled by the callee
1533
1534 // The algorithm for selecting tests is such that:
1535 // - 1/4 are from the number of pcs as they give an indication of test complexity.
1536 // - 3/4 are from the recency of the last find as it gives an indication of the
1537 // effectiveness of fuzzing for the test.
1538 // - Tests finding fresh inputs are run 8x other tests.
1539 // - Since new tests are considered to have just found a fresh input, this means they
1540 // are also prioritized which allows their characteristics to be learnt.
1541 // When a test has a new input pending, it is treated as if it had just found a fresh
1542 // input instead of immediately being run. This avoids a test which is finding many new
1543 // inputs from being exclusively run.
1544 const new_batches = 16;
1545
1546 var n_with_new: u32 = 0;
1547 var n_seen_pcs: u64 = 0;
1548 var n_latest_find: u64 = 0;
1549
1550 for (f.tests) |*t| {
1551 const has_pending = t.received.state.hasPending();
1552 if (has_pending) {
1553 assert(t.limit == null); // If multiprocess limited fuzzing was to be added, then
1554 // `t.received.inputs.clearRetainingCapacity()` would need to be added after
1555 // `t.received.state.startReadIfPending()` when the limit has been reached.
1556 }
1557 if (t.limit == 0) continue;
1558
1559 const latest_find = t.batches - t.batches_since_find;
1560 n_with_new += @intFromBool(t.batches_since_find < new_batches or has_pending);
1561 n_seen_pcs += @max(t.seen_pc_count, 1);
1562 n_latest_find += @max(latest_find, 1);
1563 }
1564
1565 if (n_seen_pcs == 0) {
1566 assert(n_with_new == 0);
1567 assert(n_latest_find == 0);
1568 return null; // All fuzz tests have used up their limit
1569 }
1570
1571 const rng: packed struct(u64) {
1572 idx_rng: u32,
1573 from_new: u3,
1574 from_latest_find: u2,
1575 _: u27,
1576 } = @bitCast(f.rngInt(u64));
1577
1578 if (n_with_new != 0 and rng.from_new != 0) {
1579 var n = std.Random.limitRangeBiased(u32, rng.idx_rng, n_with_new);
1580 for (0.., f.tests) |i, *t| {
1581 if (t.limit == 0) continue;
1582 if (t.batches_since_find < new_batches or t.received.state.hasPending()) {
1583 if (n == 0) return @intCast(i);
1584 n -= 1;
1585 }
1586 }
1587 unreachable;
1588 }
1589
1590 if (rng.from_latest_find != 0) {
1591 const total_weight = n_latest_find;
1592 var n = f.rngLessThan(u64, total_weight);
1593 for (0.., f.tests) |i, *t| {
1594 if (t.limit == 0) continue;
1595 const latest_find = @max(t.batches - t.batches_since_find, 1);
1596 if (n < latest_find) return @intCast(i);
1597 n -= latest_find;
1598 }
1599 unreachable;
1600 } else {
1601 const total_weight = n_seen_pcs;
1602 var n = f.rngLessThan(u64, total_weight);
1603 for (0.., f.tests) |i, *t| {
1604 if (t.limit == 0) continue;
1605 const seen_pc_count = @max(t.seen_pc_count, 1);
1606 if (n < seen_pc_count) return @intCast(i);
1607 n -= seen_pc_count;
1608 }
1609 unreachable;
1610 }
1611 }
1612
1613 fn weightsContain(int: u64, weights: []const abi.Weight) bool {
1614 var contains: bool = false;
1615 for (weights) |w| {
1616 contains |= w.min <= int and int <= w.max;
1617 }
1618 return contains;
1619 }
1620
1621 fn weightsContainBytes(bytes: []const u8, weights: []const abi.Weight) bool {
1622 if (weights[0].min == 0 and weights[0].max == 0xff) {
1623 // Fast path: all bytes are valid
1624 return true;
1625 }
1626
1627 var contains: bool = true;
1628 for (bytes) |b| {
1629 contains &= weightsContain(b, weights);
1630 }
1631 return contains;
1632 }
1633
1634 fn sumWeightsInclusive(weights: []const abi.Weight) u64 {
1635 var sum: u64 = math.maxInt(u64);
1636 for (weights) |w| {
1637 sum +%= (w.max - w.min +% 1) *% w.weight;
1638 }
1639 return sum;
1640 }
1641
1642 fn weightedValue(f: *Fuzzer, weights: []const abi.Weight, incl_sum: u64) u64 {
1643 var incl_n: u64 = f.rngInt(u64);
1644 const limit = incl_sum +% 1;
1645 if (limit != 0) incl_n = std.Random.limitRangeBiased(u64, incl_n, limit);
1646
1647 for (weights) |w| {
1648 // (w.max - w.min + 1) * w.weight - 1
1649 const incl_vals = (w.max - w.min) * w.weight + (w.weight - 1);
1650 if (incl_n > incl_vals) {
1651 incl_n -= incl_vals + 1;
1652 } else {
1653 const val = w.min + incl_n / w.weight;
1654 assert(val <= w.max);
1655 return val;
1656 }
1657 } else unreachable;
1658 }
1659
1660 const Untyped = union {
1661 int: u64,
1662 bytes: []u8,
1663 };
1664
1665 fn nextUntyped(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) union(enum) {
1666 copy: Untyped,
1667 mutate: Untyped,
1668 fresh: void,
1669 } {
1670 const t = &f.tests[f.test_i];
1671 const corpus = t.corpus.slice();
1672 const corpus_i = @backingInt(t.corpus_pos);
1673 const data = &corpus.items(.data)[corpus_i];
1674 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
1675
1676 const uid_i = data.uid_slices.getIndex(uid) orelse {
1677 @branchHint(.unlikely);
1678 return .fresh;
1679 };
1680 const data_slice = data.uid_slices.values()[uid_i];
1681 var slice_i = f.uid_data_i.items[uid_i];
1682 var data_i = data_slice.base + slice_i;
1683
1684 new_data: while (true) {
1685 assert(slice_i == f.uid_data_i.items[uid_i] and data_i == data_slice.base + slice_i);
1686 if (slice_i == data_slice.len) break :new_data;
1687 assert(slice_i < data_slice.len);
1688
1689 f.uid_data_i.items[uid_i] += 1;
1690 const mut_i = std.simd.firstIndexOfValue(
1691 @as(@Vector(4, u32), f.mut_data.i),
1692 data_i + @as(u32, @intCast(data.ints.len)) * @backingInt(uid.kind),
1693 ) orelse {
1694 @branchHint(.likely);
1695 switch (uid.kind) {
1696 .int => {
1697 const int = data.ints[data_i];
1698 if (weightsContain(int, weights)) {
1699 @branchHint(.likely);
1700 return .{ .copy = .{ .int = int } };
1701 }
1702 },
1703 .bytes => {
1704 const entry = data.bytes.entries[data_i];
1705 const bytes = data.bytes.table[entry.off..][0..entry.len];
1706 if (weightsContainBytes(bytes, weights)) {
1707 @branchHint(.likely);
1708 return .{ .copy = .{ .bytes = bytes } };
1709 }
1710 },
1711 }
1712 break :new_data;
1713 };
1714
1715 const seq = &f.mut_data.seq[mut_i];
1716 new_seq: {
1717 if (!seq.kind.none) break :new_seq;
1718
1719 var opts: packed struct(u6) {
1720 // Matches layout as `mut_data.seq.kind`
1721 insert: bool,
1722 copy: bool,
1723
1724 seq: u2,
1725 delete: bool,
1726 splice: bool,
1727 } = @bitCast(small_entronopy.take(u6));
1728 if (opts.seq != 0) break :new_data;
1729
1730 const max_consume = data_slice.len - slice_i; // inclusive
1731 if (opts.delete) {
1732 f.uid_data_i.items[uid_i] += f.rngLessThan(u32, max_consume);
1733 slice_i = f.uid_data_i.items[uid_i];
1734 data_i = data_slice.base + slice_i;
1735 continue;
1736 }
1737 opts.insert |= max_consume == 0;
1738 seq.kind = .{
1739 .class = if (opts.insert) .replace else .insert,
1740 .copy = opts.copy,
1741 .ordered_mutate = false,
1742 .none = false,
1743 };
1744
1745 if (!seq.kind.copy) {
1746 seq.len = switch (seq.kind.class) {
1747 .replace => f.rngLessThan(u32, max_consume) + 1,
1748 .insert => @clz(f.rngInt(u16)) + 1,
1749 };
1750 seq.copy = undefined;
1751 } else {
1752 const src: SeqCopy, const src_len: u32 = if (!opts.splice) .{
1753 switch (uid.kind) {
1754 .int => .{ .ints = data.ints[data_slice.base..][0..data_slice.len] },
1755 .bytes => .{ .bytes = .{
1756 .entries = data.bytes.entries[data_slice.base..][0..data_slice.len],
1757 .table = data.bytes.table,
1758 } },
1759 },
1760 data_slice.len,
1761 } else src: {
1762 const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];
1763 const untyped_slices = t.seen_uids.values()[seen_uid_i].slices;
1764 switch (uid.kind) {
1765 .int => {
1766 const slices = untyped_slices.ints.items;
1767 const i = f.rngLessThan(u32, @intCast(slices.len));
1768 break :src .{
1769 .{ .ints = slices[i] },
1770 @intCast(slices[i].len),
1771 };
1772 },
1773 .bytes => {
1774 const slices = untyped_slices.bytes.items;
1775 const i = f.rngLessThan(u32, @intCast(slices.len));
1776 break :src .{
1777 .{ .bytes = slices[i] },
1778 @intCast(slices[i].entries.len),
1779 };
1780 },
1781 }
1782 };
1783
1784 const off = f.rngLessThan(u32, src_len);
1785 seq.len = f.rngLessThan(u32, src_len - off) + 1;
1786 if (seq.kind.class == .replace) seq.len = @min(seq.len, max_consume);
1787 seq.copy = switch (uid.kind) {
1788 .int => .{ .ints = src.ints[off..][0..seq.len] },
1789 .bytes => .{ .bytes = .{
1790 .entries = src.bytes.entries[off..][0..seq.len],
1791 .table = src.bytes.table,
1792 } },
1793 };
1794 }
1795 }
1796
1797 assert(!seq.kind.none);
1798 f.uid_data_i.items[uid_i] -= @intFromBool(seq.kind.class == .insert);
1799 seq.len -= 1;
1800 seq.kind.none |= seq.len == 0;
1801 f.mut_data.i[mut_i] += @intFromBool(seq.kind.class == .replace and seq.len != 0);
1802
1803 if (!seq.kind.copy) {
1804 assert(!seq.kind.ordered_mutate);
1805 break :new_data;
1806 }
1807 if (seq.kind.ordered_mutate) {
1808 assert(seq.kind.class == .replace);
1809 seq.copy.order_i += @intFromBool(seq.len != 0);
1810 f.mut_data.i[mut_i] = data.order[seq.copy.order_i];
1811 break :new_data;
1812 }
1813 switch (uid.kind) {
1814 .int => {
1815 const int = seq.copy.ints[0];
1816 seq.copy.ints = seq.copy.ints[1..];
1817 if (weightsContain(int, weights)) {
1818 @branchHint(.likely);
1819 return .{ .copy = .{ .int = int } };
1820 }
1821 },
1822 .bytes => {
1823 const entry = seq.copy.bytes.entries[0];
1824 const bytes = seq.copy.bytes.table[entry.off..][0..entry.len];
1825 seq.copy.bytes.entries = seq.copy.bytes.entries[1..];
1826 if (weightsContainBytes(bytes, weights)) {
1827 @branchHint(.likely);
1828 return .{ .copy = .{ .bytes = bytes } };
1829 }
1830 },
1831 }
1832 break;
1833 }
1834
1835 const opts: packed struct(u10) {
1836 copy: u2,
1837 fresh: u2,
1838 splice: bool,
1839 local_far: bool,
1840 local_off: i4,
1841 } = @bitCast(small_entronopy.take(u10));
1842
1843 if (opts.copy != 0) {
1844 if (opts.fresh == 0 or slice_i == data_slice.len) return .fresh;
1845 switch (uid.kind) {
1846 .int => {
1847 const int = data.ints[data_i];
1848 if (weightsContain(int, weights)) {
1849 @branchHint(.likely);
1850 return .{ .mutate = .{ .int = int } };
1851 }
1852 },
1853 .bytes => {
1854 const entry = data.bytes.entries[data_i];
1855 const bytes = data.bytes.table[entry.off..][0..entry.len];
1856 if (weightsContainBytes(bytes, weights)) {
1857 @branchHint(.likely);
1858 return .{ .mutate = .{ .bytes = bytes } };
1859 }
1860 },
1861 }
1862 }
1863
1864 if (!opts.splice) {
1865 const src_data_i = data_slice.base + if (!opts.local_far) i: {
1866 const off = opts.local_off;
1867 break :i if (off >= 0) @min(
1868 f.uid_data_i.items[uid_i] +| @as(u4, @intCast(off)),
1869 data_slice.len - 1,
1870 ) else f.uid_data_i.items[uid_i] -| @abs(off);
1871 } else f.rngLessThan(u32, data_slice.len);
1872 switch (uid.kind) {
1873 .int => {
1874 const int = data.ints[src_data_i];
1875 if (weightsContain(int, weights)) {
1876 @branchHint(.likely);
1877 return .{ .copy = .{ .int = int } };
1878 }
1879 },
1880 .bytes => {
1881 const entry = data.bytes.entries[src_data_i];
1882 const bytes = data.bytes.table[entry.off..][0..entry.len];
1883 if (weightsContainBytes(bytes, weights)) {
1884 @branchHint(.likely);
1885 return .{ .copy = .{ .bytes = bytes } };
1886 }
1887 },
1888 }
1889 } else {
1890 const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];
1891 const untyped_slices = t.seen_uids.values()[seen_uid_i].slices;
1892 switch (uid.kind) {
1893 .int => {
1894 const slices = untyped_slices.ints.items;
1895 const from = slices[f.rngLessThan(u32, @intCast(slices.len))];
1896 const int = from[f.rngLessThan(u32, @intCast(from.len))];
1897 if (weightsContain(int, weights)) {
1898 @branchHint(.likely);
1899 return .{ .copy = .{ .int = int } };
1900 }
1901 },
1902 .bytes => {
1903 const slices = untyped_slices.bytes.items;
1904 const from = slices[f.rngLessThan(u32, @intCast(slices.len))];
1905 const entry_i = f.rngLessThan(u32, @intCast(from.entries.len));
1906 const entry = from.entries[entry_i];
1907 const bytes = from.table[entry.off..][0..entry.len];
1908 if (weightsContainBytes(bytes, weights)) {
1909 @branchHint(.likely);
1910 return .{ .copy = .{ .bytes = bytes } };
1911 }
1912 },
1913 }
1914 }
1915 return .fresh;
1916 }
1917
1918 pub fn nextInt(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) u64 {
1919 const t = &f.tests[f.test_i];
1920 f.req_values += 1;
1921 if (@backingInt(t.corpus_pos) >= @backingInt(Input.Index.reserved_start)) {
1922 @branchHint(.unlikely);
1923 const int = f.bytes_input.valueWeightedWithHash(u64, weights, undefined);
1924 if (t.corpus_pos == .bytes_fresh) {
1925 f.input_builder.checkSmithedLen(8);
1926 f.input_builder.addInt(uid, int);
1927 }
1928 return int;
1929 }
1930 const int = f.nextIntInner(uid, weights);
1931 f.mmap_input.appendLittleInt(u64, int);
1932 return int;
1933 }
1934
1935 fn nextIntInner(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) u64 {
1936 return switch (f.nextUntyped(uid, weights)) {
1937 .copy => |u| u.int,
1938 .mutate, .fresh => f.weightedValue(weights, sumWeightsInclusive(weights)),
1939 };
1940 }
1941
1942 pub fn nextEos(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) bool {
1943 const t = &f.tests[f.test_i];
1944 f.req_values += 1;
1945 if (@backingInt(t.corpus_pos) >= @backingInt(Input.Index.reserved_start)) {
1946 @branchHint(.unlikely);
1947 const eos = f.bytes_input.eosWeightedWithHash(weights, undefined);
1948 if (t.corpus_pos == .bytes_fresh) {
1949 f.input_builder.checkSmithedLen(1);
1950 f.input_builder.addInt(uid, @intFromBool(eos));
1951 }
1952 return eos;
1953 }
1954 // `nextIntInner` is already gauraunteed to eventually return `1`
1955 const eos = @as(u1, @intCast(f.nextIntInner(uid, weights))) != 0;
1956 f.mmap_input.appendLittleInt(u8, @intFromBool(eos));
1957 return eos;
1958 }
1959
1960 fn mutateBytes(f: *Fuzzer, in: []u8, out: []u8, weights: []const abi.Weight) void {
1961 assert(in.len != 0);
1962 const weights_incl_sum = sumWeightsInclusive(weights);
1963
1964 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
1965 var muts = mutCount(small_entronopy.take(u16));
1966 var rem_out = out;
1967 var rem_copy = in;
1968 while (rem_out.len != 0 and muts != 0) {
1969 muts -= 1;
1970 const opts: packed struct(u4) {
1971 kind: enum(u2) {
1972 random,
1973 stream_copy,
1974 stream_discard,
1975 absolute_copy,
1976 },
1977 small: u2,
1978
1979 pub fn limitSmall(o: @This(), n: usize) u32 {
1980 return @min(
1981 @as(u32, @intCast(n)),
1982 @as(u32, if (o.small != 0) 8 else math.maxInt(u32)),
1983 );
1984 }
1985 } = @bitCast(small_entronopy.take(u4));
1986 s: switch (opts.kind) {
1987 .random => {
1988 const n = f.rngLessThan(u32, opts.limitSmall(rem_out.len)) + 1;
1989 for (rem_out[0..n]) |*o| {
1990 o.* = @intCast(f.weightedValue(weights, weights_incl_sum));
1991 }
1992 rem_out = rem_out[n..];
1993 },
1994 .stream_copy => {
1995 if (rem_copy.len == 0) continue :s .random;
1996 const n = @min(
1997 f.rngLessThan(u32, opts.limitSmall(rem_copy.len)) + 1,
1998 rem_out.len,
1999 );
2000 @memcpy(rem_out[0..n], rem_copy[0..n]);
2001 rem_out = rem_out[n..];
2002 rem_copy = rem_copy[n..];
2003 },
2004 .stream_discard => {
2005 if (rem_copy.len == 0) continue :s .random;
2006 const n = f.rngLessThan(u32, opts.limitSmall(rem_copy.len)) + 1;
2007 rem_copy = rem_copy[n..];
2008 },
2009 .absolute_copy => {
2010 const in_len: u32 = @intCast(in.len);
2011 const off = f.rngLessThan(u32, in_len);
2012 const len = @min(
2013 f.rngLessThan(u32, in_len - off) + 1,
2014 opts.limitSmall(rem_out.len),
2015 );
2016 @memcpy(rem_out[0..len], in[off..][0..len]);
2017 rem_out = rem_out[len..];
2018 },
2019 }
2020 }
2021
2022 const copy = @min(rem_out.len, rem_copy.len);
2023 @memcpy(rem_out[0..copy], rem_copy[0..copy]);
2024 for (rem_out[copy..]) |*o| {
2025 o.* = @intCast(f.weightedValue(weights, weights_incl_sum));
2026 }
2027 }
2028
2029 fn nextBytesInner(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void {
2030 so: switch (f.nextUntyped(uid, weights)) {
2031 .copy => |u| {
2032 if (u.bytes.len >= out.len) {
2033 @branchHint(.likely);
2034 @memcpy(out, u.bytes[0..out.len]);
2035 return;
2036 }
2037
2038 @memcpy(out[0..u.bytes.len], u.bytes);
2039 const weights_incl_sum = sumWeightsInclusive(weights);
2040 for (out[u.bytes.len..]) |*o| {
2041 o.* = @intCast(f.weightedValue(weights, weights_incl_sum));
2042 }
2043 },
2044 .mutate => |u| {
2045 if (u.bytes.len == 0) continue :so .fresh;
2046 f.mutateBytes(u.bytes, out, weights);
2047 },
2048 .fresh => {
2049 const weights_incl_sum = sumWeightsInclusive(weights);
2050 for (out) |*o| {
2051 o.* = @intCast(f.weightedValue(weights, weights_incl_sum));
2052 }
2053 },
2054 }
2055 }
2056
2057 pub fn nextBytes(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void {
2058 const t = &f.tests[f.test_i];
2059 f.req_values += 1;
2060 f.req_bytes +%= @truncate(out.len); // This function should panic since the 32-bit
2061 // data limit is exceeded, so wrapping is fine.
2062 if (@backingInt(t.corpus_pos) >= @backingInt(Input.Index.reserved_start)) {
2063 @branchHint(.unlikely);
2064 f.bytes_input.bytesWeightedWithHash(out, weights, undefined);
2065 if (t.corpus_pos == .bytes_fresh) {
2066 f.input_builder.checkSmithedLen(out.len);
2067 f.input_builder.addBytes(uid, out);
2068 }
2069 return;
2070 }
2071
2072 f.nextBytesInner(uid, out, weights);
2073 f.mmap_input.appendSlice(out);
2074 }
2075
2076 fn nextSliceInner(
2077 f: *Fuzzer,
2078 uid: Uid,
2079 buf: []u8,
2080 len_weights: []const abi.Weight,
2081 byte_weights: []const abi.Weight,
2082 ) u32 {
2083 so: switch (f.nextUntyped(uid, byte_weights)) {
2084 .copy => |u| {
2085 var len: u32 = @intCast(u.bytes.len);
2086 if (!weightsContain(len, len_weights)) {
2087 @branchHint(.unlikely);
2088 len = @intCast(f.weightedValue(len_weights, sumWeightsInclusive(len_weights)));
2089 }
2090
2091 if (u.bytes.len >= len) {
2092 @branchHint(.likely);
2093 @memcpy(buf[0..len], u.bytes[0..len]);
2094 return len;
2095 }
2096
2097 @memcpy(buf[0..u.bytes.len], u.bytes);
2098 const weights_incl_sum = sumWeightsInclusive(byte_weights);
2099 for (buf[u.bytes.len..len]) |*o| {
2100 o.* = @intCast(f.weightedValue(byte_weights, weights_incl_sum));
2101 }
2102 return len;
2103 },
2104 .mutate => |u| {
2105 if (u.bytes.len == 0) continue :so .fresh;
2106 const len: u32 = len: {
2107 const offseted: packed struct {
2108 is: u3,
2109 sub: bool,
2110 by: u3,
2111 } = @bitCast(f.rngInt(u7));
2112 if (offseted.is != 0) {
2113 const len = if (offseted.sub)
2114 @as(u32, @intCast(u.bytes.len)) -| offseted.by
2115 else
2116 @min(u.bytes.len + offseted.by, @as(u32, @intCast(buf.len)));
2117 if (weightsContain(len, len_weights)) {
2118 break :len len;
2119 }
2120 }
2121 break :len @intCast(f.weightedValue(
2122 len_weights,
2123 sumWeightsInclusive(len_weights),
2124 ));
2125 };
2126 f.mutateBytes(u.bytes, buf[0..len], byte_weights);
2127 return len;
2128 },
2129 .fresh => {
2130 const len: u32 = @intCast(f.weightedValue(
2131 len_weights,
2132 sumWeightsInclusive(len_weights),
2133 ));
2134 const weights_incl_sum = sumWeightsInclusive(byte_weights);
2135 for (buf[0..len]) |*o| {
2136 o.* = @intCast(f.weightedValue(byte_weights, weights_incl_sum));
2137 }
2138 return len;
2139 },
2140 }
2141 }
2142
2143 pub fn nextSlice(
2144 f: *Fuzzer,
2145 uid: Uid,
2146 buf: []u8,
2147 len_weights: []const abi.Weight,
2148 byte_weights: []const abi.Weight,
2149 ) u32 {
2150 const t = &f.tests[f.test_i];
2151 f.req_values += 1;
2152 if (@backingInt(t.corpus_pos) >= @backingInt(Input.Index.reserved_start)) {
2153 @branchHint(.unlikely);
2154 const n = f.bytes_input.sliceWeightedWithHash(
2155 buf,
2156 len_weights,
2157 byte_weights,
2158 undefined,
2159 );
2160 if (t.corpus_pos == .bytes_fresh) {
2161 f.input_builder.checkSmithedLen(@as(usize, 4) + n);
2162 f.input_builder.addBytes(uid, buf[0..n]);
2163 }
2164 return n;
2165 }
2166
2167 const n = f.nextSliceInner(uid, buf, len_weights, byte_weights);
2168 f.mmap_input.appendLittleInt(u32, n);
2169 f.mmap_input.appendSlice(buf[0..n]);
2170 f.req_bytes += n;
2171 return n;
2172 }
2173};
2174
2175export fn fuzzer_init(cache_dir_path: abi.Slice) void {
2176 exec = .init(cache_dir_path.toSlice());
2177}
2178
2179export fn fuzzer_coverage() abi.Coverage {
2180 const coverage_id = exec.pc_digest;
2181 const header = @volatileCast(exec.seenPcsHeader());
2182
2183 var seen_count: usize = 0;
2184 for (header.seenBits()) |chunk| {
2185 seen_count += @popCount(chunk);
2186 }
2187
2188 return .{
2189 .id = coverage_id,
2190 .runs = header.n_runs,
2191 .unique = header.unique_runs,
2192 .seen = seen_count,
2193 };
2194}
2195
2196export fn fuzzer_main(
2197 n_tests: u32,
2198 seed: u32,
2199 limit_kind: abi.LimitKind,
2200 amount_or_instance: u64,
2201) void {
2202 fuzzer = .init(
2203 n_tests,
2204 seed ^ amount_or_instance, // seed is otherwise the same for all instances
2205 if (limit_kind == .forever) @as(u32, @intCast(amount_or_instance)) else 0,
2206 if (limit_kind == .forever) null else amount_or_instance,
2207 );
2208 defer fuzzer.deinit();
2209 abi.runner_start_input_poller();
2210 defer abi.runner_stop_input_poller();
2211
2212 if (n_tests == 1) {
2213 // no swapping between fuzz tests
2214 runTest(0);
2215 } else {
2216 while (fuzzer.select()) |i| {
2217 runTest(i);
2218 }
2219 }
2220}
2221
2222export fn fuzzer_receive_input(test_i: u32, bytes_slice: abi.Slice) bool {
2223 const recv = &fuzzer.tests[test_i].received;
2224 if (recv.state.startWrite()) return true;
2225 defer recv.state.finishWrite();
2226
2227 const bytes = bytes_slice.toSlice();
2228 const len: u32 = @intCast(bytes.len);
2229 recv.inputs.ensureUnusedCapacity(gpa, 4 + bytes.len) catch @panic("OOM");
2230 recv.inputs.appendSliceAssumeCapacity(@ptrCast(&len));
2231 recv.inputs.appendSliceAssumeCapacity(bytes);
2232
2233 return false;
2234}
2235
2236fn runTest(i: u32) void {
2237 fuzzer.test_i = i;
2238 fuzzer.mmap_input.setTest(i);
2239 current_test_name = abi.runner_test_name(i).toSlice();
2240 abi.runner_test_run(i);
2241}
2242
2243export fn fuzzer_set_test(test_one: abi.TestOne) void {
2244 fuzzer.test_one = test_one;
2245}
2246
2247export fn fuzzer_new_input(bytes: abi.Slice) void {
2248 if (bytes.len == 0) return; // An entry of length zero is always present
2249 if (fuzzer.tests[fuzzer.test_i].start_mut_corpus != math.maxInt(u32)) return; // Test ran previously
2250 fuzzer.newInputExternal(bytes.toSlice());
2251}
2252
2253export fn fuzzer_start_test() void {
2254 fuzzer.ensureCorpusLoaded();
2255 fuzzer.batch();
2256}
2257
2258export fn fuzzer_int(uid: Uid, weights: abi.Weights) u64 {
2259 assert(uid.kind == .int);
2260 return fuzzer.nextInt(uid, weights.toSlice());
2261}
2262
2263export fn fuzzer_eos(uid: Uid, weights: abi.Weights) bool {
2264 assert(uid.kind == .int);
2265 return fuzzer.nextEos(uid, weights.toSlice());
2266}
2267
2268export fn fuzzer_bytes(uid: Uid, out: abi.MutSlice, weights: abi.Weights) void {
2269 assert(uid.kind == .bytes);
2270 return fuzzer.nextBytes(uid, out.toSlice(), weights.toSlice());
2271}
2272
2273export fn fuzzer_slice(
2274 uid: Uid,
2275 buf: abi.MutSlice,
2276 len_weights: abi.Weights,
2277 byte_weights: abi.Weights,
2278) u32 {
2279 assert(uid.kind == .bytes);
2280 return fuzzer.nextSlice(uid, buf.toSlice(), len_weights.toSlice(), byte_weights.toSlice());
2281}
2282
2283export fn fuzzer_unslide_address(addr: usize) usize {
2284 const si = std.debug.getSelfDebugInfo() catch @compileError("unsupported");
2285 const slide = si.getModuleSlide(io, addr) catch |err| {
2286 // The LLVM backend seems to insert placeholder values of `1` in __sancov_pcs1
2287 if (addr == 1) return 1;
2288 panic("failed to find virtual address slide for address 0x{x}: {t}", .{ addr, err });
2289 };
2290 return addr - slide;
2291}
2292
2293/// Helps determine run uniqueness in the face of recursion.
2294/// Currently not used by the fuzzer.
2295export threadlocal var __sancov_lowest_stack: usize = 0;
2296
2297export fn __sanitizer_cov_trace_pc_indir(callee: usize) void {
2298 // Not valuable because we already have pc tracing via 8bit counters.
2299 _ = callee;
2300}
2301export fn __sanitizer_cov_8bit_counters_init(start: usize, end: usize) void {
2302 // clang will emit a call to this function when compiling with code coverage instrumentation.
2303 // however, fuzzer_init() does not need this information since it directly reads from the
2304 // symbol table.
2305 _ = start;
2306 _ = end;
2307}
2308export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void {
2309 // clang will emit a call to this function when compiling with code coverage instrumentation.
2310 // however, fuzzer_init() does not need this information since it directly reads from the
2311 // symbol table.
2312 _ = start;
2313 _ = end;
2314}
2315
2316/// Reusable and recoverable input.
2317///
2318/// Has a 32-bit limit on the input length. This has the nice side effect that `u32`
2319/// can be used in most placed in `fuzzer` with the last `@sizeOf(abi.MmapInputHeader)`
2320/// values reserved.
2321const MemoryMappedInput = struct {
2322 const Header = abi.MmapInputHeader;
2323
2324 len: u32,
2325 /// Directly accessing `memory` is unsafe, use either `inputSlice` or `writeSlice`.
2326 mmap: Io.File.MemoryMap,
2327 in_i: u32,
2328
2329 /// `file` becomes owned by the returned `MemoryMappedInput`
2330 pub fn init(file: Io.File, instance_id: u32, in_i: u32) MemoryMappedInput {
2331 var size = file.length(io) catch |e|
2332 panic("failed to get length of 'in{x}': {t}", .{ in_i, e });
2333 if (size < std.heap.page_size_max) {
2334 size = std.heap.page_size_max;
2335 file.setLength(io, size) catch |e|
2336 panic("failed to resize 'in{x}': {t}", .{ in_i, e });
2337 }
2338 const map = file.createMemoryMap(io, .{ .len = size }) catch |e|
2339 panic("failed to memmap input file 'in{x}': {t}", .{ in_i, e });
2340 @as(*volatile Header, @ptrCast(map.memory)).* = .{
2341 .pc_digest = mem.nativeToLittle(u64, exec.pc_digest),
2342 .instance_id = mem.nativeToLittle(u32, instance_id),
2343 .test_i = 0,
2344 .len = 0,
2345 };
2346 return .{
2347 .len = 0,
2348 .mmap = map,
2349 .in_i = in_i,
2350 };
2351 }
2352
2353 pub fn deinit(l: *MemoryMappedInput) void {
2354 const f = l.mmap.file;
2355 l.mmap.write(io) catch |e| panic("failed to write memory map of 'in{x}': {t}", .{ l.in_i, e });
2356 l.mmap.destroy(io);
2357 f.close(io);
2358 l.* = undefined;
2359 }
2360
2361 /// Modify the array so that it can hold at least `additional_count` **more** items.
2362 ///
2363 /// Invalidates element pointers if additional memory is needed.
2364 pub fn ensureUnusedCapacity(l: *MemoryMappedInput, additional_count: usize) void {
2365 return l.ensureSize(@sizeOf(Header) + l.len + additional_count);
2366 }
2367
2368 fn ensureSize(l: *MemoryMappedInput, min_capacity: usize) void {
2369 if (l.mmap.memory.len < min_capacity) {
2370 @branchHint(.unlikely);
2371
2372 const max_capacity = 1 << 32; // The size of the header is not added
2373 // in order to keep the capacity page aligned and to allow those values to
2374 // reserved for other places.
2375 if (min_capacity > max_capacity) @panic("too much smith data requested");
2376
2377 const new_capacity = @min(growCapacity(min_capacity), max_capacity);
2378 l.mmap.file.setLength(io, new_capacity) catch |e|
2379 panic("failed to resize 'in{x}': {t}", .{ l.in_i, e });
2380 l.mmap.setLength(io, new_capacity) catch |se| switch (se) {
2381 error.OperationUnsupported => {
2382 const f = l.mmap.file;
2383 l.mmap.destroy(io);
2384 l.mmap = f.createMemoryMap(io, .{ .len = new_capacity }) catch |e|
2385 panic("failed to memory map 'in{x}': {t}", .{ l.in_i, e });
2386 },
2387 else => panic("failed to resize memory map of 'in{x}': {t}", .{ l.in_i, se }),
2388 };
2389 }
2390 }
2391
2392 // Only writing has side effects, so volatile is not needed
2393 pub fn inputSlice(l: *MemoryMappedInput) []const u8 {
2394 return l.mmap.memory[@sizeOf(Header)..][0..l.len];
2395 }
2396
2397 // Writing has side effectsd, so volatile is necessary
2398 pub fn writeSlice(l: *MemoryMappedInput) []volatile u8 {
2399 return l.mmap.memory;
2400 }
2401
2402 fn writeLen(l: *MemoryMappedInput) void {
2403 l.writeSlice()[@offsetOf(Header, "len")..][0..4].* =
2404 @bitCast(mem.nativeToLittle(u32, l.len));
2405 }
2406
2407 pub fn setTest(l: *MemoryMappedInput, i: u32) void {
2408 l.writeSlice()[@offsetOf(Header, "test_i")..][0..4].* =
2409 @bitCast(mem.nativeToLittle(u32, i));
2410 }
2411
2412 /// Invalidates all element pointers.
2413 pub fn clearRetainingCapacity(l: *MemoryMappedInput) void {
2414 l.len = 0;
2415 l.writeLen();
2416 }
2417
2418 /// Append the slice of items to the list.
2419 ///
2420 /// Invalidates item pointers if more space is required.
2421 pub fn appendSlice(l: *MemoryMappedInput, items: []const u8) void {
2422 l.ensureUnusedCapacity(items.len);
2423 @memcpy(l.writeSlice()[@sizeOf(Header) + l.len ..][0..items.len], items);
2424 l.len += @as(u32, @intCast(items.len));
2425 l.writeLen();
2426 }
2427
2428 /// Append the little-endian integer to the list.
2429 ///
2430 /// Invalidates item pointers if more space is required.
2431 pub fn appendLittleInt(l: *MemoryMappedInput, T: type, x: T) void {
2432 l.ensureUnusedCapacity(@sizeOf(T));
2433 l.writeSlice()[@sizeOf(Header) + l.len ..][0..@sizeOf(T)].* =
2434 @bitCast(mem.nativeToLittle(T, x));
2435 l.len += @sizeOf(T);
2436 l.writeLen();
2437 }
2438
2439 /// Called when memory growth is necessary. Returns a capacity larger than
2440 /// minimum that grows super-linearly.
2441 fn growCapacity(minimum: usize) usize {
2442 return mem.alignForward(
2443 usize,
2444 minimum +| (minimum / 2 + std.heap.page_size_max),
2445 std.heap.page_size_max,
2446 );
2447 }
2448};