authorgravatar for goon.pri.low@gmail.comKendall Condon <goon.pri.low@gmail.com> 2026-02-13 17:48:57-05:00
committergravatar for goon.pri.low@gmail.comKendall Condon <goon.pri.low@gmail.com> 2026-02-13 22:12:19-05:00
log5d583061625c1c413e11a3d14e796602ce752687
treed5d4660fdf98206a3417dc2ffa7366f9fcecf7e3
parent5b9bb0a4045aaae387b049f4b28f350aef0dbed2

rework fuzz testing to be smith based

-- On the standard library side: The `input: []const u8` parameter of functions passed to `testing.fuzz` has changed to `smith: *testing.Smith`. `Smith` is used to generate values from libfuzzer or input bytes generated by libfuzzer. `Smith` contains the following base methods: * `value` as a generic method for generating any type * `eos` for generating end-of-stream markers. Provides the additional guarantee `true` will eventually by provided. * `bytes` for filling a byte array. * `slice` for filling part of a buffer and providing the length. `Smith.Weight` is used for giving value ranges a higher probability of being selected. By default, every value has a weight of zero (i.e. they will not be selected). Weights can only apply to values that fit within a u64. The above functions have corresponding ones that accept weights. Additionally, the following functions are provided: * `baselineWeights` which provides a set of weights containing every possible value of a type. * `eosSimpleWeighted` for unique weights for `true` and `false` * `valueRangeAtMost` and `valueRangeLessThan` for weighing only a range of values. -- On the libfuzzer and abi side: --- Uids These are u32s which are used to classify requested values. This solves the problem of a mutation causing a new value to be requested and shifting all future values; for example: 1. An initial input contains the values 1, 2, 3 which are interpreted as a, b, and c respectively by the test. 2. The 1 is mutated to a 4 which causes the test to request an extra value interpreted as d. The input is now 4, 2, 3, 5 (new value) which the test corresponds to a, d, b, c; however, b and c no longer correspond to their original values. Uids contain a hash component and type component. The hash component is currently determined in `Smith` by taking a hash of the calling `@returnAddress()` or via an argument in the corresponding `WithHash` functions. The type component is used extensively in libfuzzer with its hashmaps. --- Mutations At the start of a cycle (a run), a random number of values to mutate is selected with less being exponentially more likely. The indexes of the values are selected from a selected uid with a logarithmic bias to uids with more values. Mutations may change a single values, several consecutive values in a uid, or several consecutive values in the uid-independent order they were requested. They may generate random values, mutate from previous ones, or copy from other values in the same uid from the same input or spliced from another. For integers, mutations from previous ones currently only generates random values. For bytes, mutations from previous mix new random data and previous bytes with a set number of mutations. --- Passive Minimization A different approach has been taken for minimizing inputs: instead of trying a fixed set of mutations when a fresh input is found, the input is instead simply added to the corpus and removed when it is no longer valuable. The quality of an input is measured based off how many unique pcs it hit and how many values it needed from the fuzzer. It is tracked which inputs hold the best qualities for each pc for hitting the minimum and maximum unique pcs while needing the least values. Once all an input's qualities have been superseded for the pcs it hit, it is removed from the corpus. -- Comparison to byte-based smith A byte-based smith would be much more inefficient and complex than this solution. It would be unable to solve the shifting problem that Uids do. It is unable to provide values from the fuzzer past end-of-stream. Even with feedback, it would be unable to act on dynamic weights which have proven essential with the updated tests (e.g. to constrain values to a range). -- Test updates All the standard library tests have been updated to use the new smith interface. For `Deque`, an ad hoc allocator was written to improve performance and remove reliance on heap allocation. `TokenSmith` has been added to aid in testing Ast and help inform decisions on the smith interface.

17 files changed, 3324 insertions(+), 1516 deletions(-)

lib/compiler/test_runner.zig+8-8
......@@ -379,7 +379,7 @@ var fuzz_amount_or_instance: u64 = undefined;
379379
380380pub fn fuzz(
381381 context: anytype,
382 comptime testOne: fn (context: @TypeOf(context), []const u8) anyerror!void,
382 comptime testOne: fn (context: @TypeOf(context), *std.testing.Smith) anyerror!void,
383383 options: testing.FuzzInputOptions,
384384) anyerror!void {
385385 // Prevent this function from confusing the fuzzer by omitting its own code
......@@ -406,12 +406,12 @@ pub fn fuzz(
406406 const global = struct {
407407 var ctx: @TypeOf(context) = undefined;
408408
409 fn test_one(input: fuzz_abi.Slice) callconv(.c) void {
409 fn test_one() callconv(.c) void {
410410 @disableInstrumentation();
411411 testing.allocator_instance = .{};
412412 defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
413413 log_err_count = 0;
414 testOne(ctx, input.toSlice()) catch |err| switch (err) {
414 testOne(ctx, @constCast(&testing.Smith{ .in = null })) catch |err| switch (err) {
415415 error.SkipZigTest => return,
416416 else => {
417417 const stderr = std.debug.lockStderr(&.{}).terminal();
......@@ -435,13 +435,11 @@ pub fn fuzz(
435435 const prev_allocator_state = testing.allocator_instance;
436436 testing.allocator_instance = .{};
437437 defer testing.allocator_instance = prev_allocator_state;
438
439438 global.ctx = context;
440 fuzz_abi.fuzzer_init_test(&global.test_one, .fromSlice(builtin.test_functions[fuzz_test_index].name));
441439
440 fuzz_abi.fuzzer_set_test(&global.test_one, .fromSlice(builtin.test_functions[fuzz_test_index].name));
442441 for (options.corpus) |elem|
443442 fuzz_abi.fuzzer_new_input(.fromSlice(elem));
444
445443 fuzz_abi.fuzzer_main(fuzz_mode, fuzz_amount_or_instance);
446444 return;
447445 }
......@@ -449,10 +447,12 @@ pub fn fuzz(
449447 // When the unit test executable is not built in fuzz mode, only run the
450448 // provided corpus.
451449 for (options.corpus) |input| {
452 try testOne(context, input);
450 var smith: testing.Smith = .{ .in = input };
451 try testOne(context, &smith);
453452 }
454453
455454 // In case there is no provided corpus, also use an empty
456455 // string as a smoke test.
457 try testOne(context, "");
456 var smith: testing.Smith = .{ .in = "" };
457 try testOne(context, &smith);
458458}
lib/fuzzer.zig+1494-1094
......@@ -1,15 +1,13 @@
11const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
32
43const std = @import("std");
54const Io = std.Io;
6const fatal = std.process.fatal;
75const mem = std.mem;
86const math = std.math;
9const Allocator = std.mem.Allocator;
107const assert = std.debug.assert;
118const panic = std.debug.panic;
129const abi = std.Build.abi.fuzz;
10const Uid = abi.Uid;
1311
1412pub const std_options = std.Options{
1513 .logFn = logOverride,
......@@ -23,8 +21,7 @@ fn logOverride(
2321 comptime format: []const u8,
2422 args: anytype,
2523) void {
26 const f = log_f orelse
27 panic("attempt to use log before initialization, message:\n" ++ format, args);
24 const f = log_f orelse panic("log before initialization, message:\n" ++ format, args);
2825 f.lock(io, .exclusive) catch |e| panic("failed to lock logging file: {t}", .{e});
2926 defer f.unlock(io);
3027
......@@ -48,10 +45,9 @@ const gpa = switch (builtin.mode) {
4845 .ReleaseFast, .ReleaseSmall, .ReleaseSafe => std.heap.smp_allocator,
4946};
5047
51/// Part of `exec`, however seperate to allow it to be set before `exec` is.
48// Seperate from `exec` to allow initialization before `exec` is.
5249var log_f: ?Io.File = null;
53var exec: Executable = .preinit;
54var inst: Instrumentation = .preinit;
50var exec: Executable = undefined;
5551var fuzzer: Fuzzer = undefined;
5652var current_test_name: ?[]const u8 = null;
5753
......@@ -60,36 +56,28 @@ fn bitsetUsizes(elems: usize) usize {
6056}
6157
6258const Executable = struct {
63 /// Tracks the hit count for each pc as updated by the process's instrumentation.
59 /// Tracks the hit count for each pc as updated by the test's instrumentation.
6460 pc_counters: []u8,
6561
6662 cache_f: Io.Dir,
6763 /// Shared copy of all pcs that have been hit stored in a memory-mapped file that can viewed
6864 /// while the fuzzer is running.
69 shared_seen_pcs: MemoryMappedList,
65 shared_seen_pcs: []align(std.heap.page_size_min) volatile u8,
7066 /// Hash of pcs used to uniquely identify the shared coverage file
7167 pc_digest: u64,
7268
73 /// A minimal state for this struct which instrumentation can function on.
74 /// Used before this structure is initialized to avoid illegal behavior
75 /// from instrumentation functions being called and using undefined values.
76 pub const preinit: Executable = .{
77 .pc_counters = undefined, // instrumentation works off the __sancov_cntrs section
78 .cache_f = undefined,
79 .shared_seen_pcs = undefined,
80 .pc_digest = undefined,
81 };
82
83 fn getCoverageFile(cache_dir: Io.Dir, pcs: []const usize, pc_digest: u64) MemoryMappedList {
84 const pc_bitset_usizes = bitsetUsizes(pcs.len);
85 const coverage_file_name = std.fmt.hex(pc_digest);
86 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
87 comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);
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);
8875
8976 var v = cache_dir.createDirPathOpen(io, "v", .{}) catch |e|
9077 panic("failed to create directory 'v': {t}", .{e});
9178 defer v.close(io);
92 const coverage_file, const populate = if (v.createFile(io, &coverage_file_name, .{
79
80 const coverage_file, const populate = if (v.createFile(io, &file_name, .{
9381 .read = true,
9482 // If we create the file, we want to block other processes while we populate it
9583 .lock = .exclusive,
......@@ -97,71 +85,76 @@ const Executable = struct {
9785 })) |f|
9886 .{ f, true }
9987 else |e| switch (e) {
100 error.PathAlreadyExists => .{ v.openFile(io, &coverage_file_name, .{
88 error.PathAlreadyExists => .{ v.openFile(io, &file_name, .{
10189 .mode = .read_write,
10290 .lock = .shared,
10391 }) catch |e2| panic(
10492 "failed to open existing coverage file '{s}': {t}",
105 .{ &coverage_file_name, e2 },
93 .{ &file_name, e2 },
10694 ), false },
107 else => panic("failed to create coverage file '{s}': {t}", .{ &coverage_file_name, e }),
95 else => panic("failed to create coverage file '{s}': {t}", .{ &file_name, e }),
10896 };
10997
98 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
99 comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);
100 const pc_bitset_usizes = bitsetUsizes(pcs.len);
110101 const coverage_file_len = @sizeOf(abi.SeenPcsHeader) +
111102 pc_bitset_usizes * @sizeOf(usize) +
112103 pcs.len * @sizeOf(usize);
113104
114105 if (populate) {
115 defer coverage_file.lock(io, .shared) catch |e| panic(
116 "failed to demote lock for coverage file '{s}': {t}",
117 .{ &coverage_file_name, e },
118 );
119 var map = MemoryMappedList.create(coverage_file, 0, coverage_file_len) catch |e| panic(
120 "failed to init memory map for coverage file '{s}': {t}",
121 .{ &coverage_file_name, e },
122 );
123 map.appendSliceAssumeCapacity(@ptrCast(&abi.SeenPcsHeader{
124 .n_runs = 0,
125 .unique_runs = 0,
126 .pcs_len = pcs.len,
127 }));
128 map.appendNTimesAssumeCapacity(0, pc_bitset_usizes * @sizeOf(usize));
129 // Relocations have been applied to `pcs` so it contains runtime addresses (with slide
130 // applied). We need to translate these to the virtual addresses as on disk.
131 for (pcs) |pc| {
132 const pc_vaddr = fuzzer_unslide_address(pc);
133 map.appendSliceAssumeCapacity(@ptrCast(&pc_vaddr));
134 }
135 return map;
106 coverage_file.setLength(io, coverage_file_len) catch |e|
107 panic("failed to resize new coverage file '{s}': {t}", .{ &file_name, e });
136108 } else {
137109 const size = coverage_file.length(io) catch |e|
138 panic("failed to stat coverage file '{s}': {t}", .{ &coverage_file_name, e });
110 panic("failed to stat coverage file '{s}': {t}", .{ &file_name, e });
139111 if (size != coverage_file_len) panic(
140112 "incompatible existing coverage file '{s}' (differing lengths: {} != {})",
141 .{ &coverage_file_name, size, coverage_file_len },
113 .{ &file_name, size, coverage_file_len },
142114 );
115 }
143116
144 const map = MemoryMappedList.init(
145 coverage_file,
146 coverage_file_len,
147 coverage_file_len,
148 ) catch |e| panic(
149 "failed to init memory map for coverage file '{s}': {t}",
150 .{ &coverage_file_name, e },
151 );
117 var io_map = coverage_file.createMemoryMap(io, .{ .len = coverage_file_len }) catch |e|
118 panic("failed to memmap coverage file '{s}': {t}", .{ &file_name, e });
119 const map = io_map.memory;
152120
153 const seen_pcs_header: *const abi.SeenPcsHeader = @ptrCast(@volatileCast(map.items));
154 if (seen_pcs_header.pcs_len != pcs.len) panic(
155 "incompatible existing coverage file '{s}' (differing pcs length: {} != {})",
156 .{ &coverage_file_name, seen_pcs_header.pcs_len, pcs.len },
121 const header: *abi.SeenPcsHeader = @ptrCast(map[0..@sizeOf(abi.SeenPcsHeader)]);
122 const trailing = map[@sizeOf(abi.SeenPcsHeader)..];
123 const trailing_bitset_end = pc_bitset_usizes * @sizeOf(usize);
124 const trailing_bitset: []usize = @ptrCast(@alignCast(trailing[0..trailing_bitset_end]));
125 const trailing_addresses: []usize = @ptrCast(@alignCast(trailing[trailing_bitset_end..]));
126
127 if (populate) {
128 header.* = .{
129 .n_runs = 0,
130 .unique_runs = 0,
131 .pcs_len = pcs.len,
132 };
133 @memset(trailing_bitset, 0);
134 for (trailing_addresses, pcs) |*cov_pc, slided_pc| {
135 cov_pc.* = fuzzer_unslide_address(slided_pc);
136 }
137 io_map.write(io) catch |e|
138 panic("failed to write memory map of '{s}': {t}", .{ &file_name, e });
139
140 coverage_file.lock(io, .shared) catch |e| panic(
141 "failed to demote lock for coverage file '{s}': {t}",
142 .{ &file_name, e },
157143 );
158 if (mem.indexOfDiff(usize, seen_pcs_header.pcAddrs(), pcs)) |i| panic(
159 "incompatible existing coverage file '{s}' (differing pc at index {d}: {x} != {x})",
160 .{ &coverage_file_name, i, seen_pcs_header.pcAddrs()[i], pcs[i] },
144 } else { // Check expected contents
145 if (header.pcs_len != pcs.len) panic(
146 "incompatible existing coverage file '{s}' (differing pcs length: {} != {})",
147 .{ &file_name, header.pcs_len, pcs.len },
161148 );
162
163 return map;
149 for (0.., header.pcAddrs(), pcs) |i, cov_pc, slided_pc| {
150 const pc = fuzzer_unslide_address(slided_pc);
151 if (cov_pc != pc) panic(
152 "incompatible existing coverage file '{s}' (differing pc at index {d}: {x} != {x})",
153 .{ &file_name, i, cov_pc, pc },
154 );
155 }
164156 }
157 return map;
165158 }
166159
167160 pub fn init(cache_dir_path: []const u8) Executable {
......@@ -230,7 +223,7 @@ const Executable = struct {
230223 }
231224 break :digest h.final();
232225 };
233 self.shared_seen_pcs = getCoverageFile(cache_dir, pcs, self.pc_digest);
226 self.shared_seen_pcs = getCoverageMap(cache_dir, pcs, self.pc_digest);
234227
235228 return self;
236229 }
......@@ -244,14 +237,14 @@ const Executable = struct {
244237 index: usize = 0,
245238 pc_counters: []u8,
246239
247 pub fn next(self: *PcBitsetIterator) usize {
248 const rest = self.pc_counters[self.index..];
240 pub fn next(i: *PcBitsetIterator) usize {
241 const rest = i.pc_counters[i.index..];
249242 if (rest.len >= @bitSizeOf(usize)) {
250 defer self.index += @bitSizeOf(usize);
243 defer i.index += @bitSizeOf(usize);
251244 const V = @Vector(@bitSizeOf(usize), u8);
252245 return @as(usize, @bitCast(@as(V, @splat(0)) != rest[0..@bitSizeOf(usize)].*));
253246 } else if (rest.len != 0) {
254 defer self.index += rest.len;
247 defer i.index += rest.len;
255248 var res: usize = 0;
256249 for (0.., rest) |bit_index, byte| {
257250 res |= @shlExact(@as(usize, @intFromBool(byte != 0)), @intCast(bit_index));
......@@ -260,155 +253,414 @@ const Executable = struct {
260253 } else unreachable;
261254 }
262255 };
256
257 pub fn seenPcsHeader(e: Executable) *align(std.heap.page_size_min) volatile abi.SeenPcsHeader {
258 return mem.bytesAsValue(
259 abi.SeenPcsHeader,
260 e.shared_seen_pcs[0..@sizeOf(abi.SeenPcsHeader)],
261 );
262 }
263263};
264264
265/// Data gathered from instrumentation functions.
266/// Seperate from Executable since its state is resetable and changes.
267/// Seperate from Fuzzer since it may be needed before fuzzing starts.
268const Instrumentation = struct {
269 /// Bitset of seen pcs across all runs excluding fresh pcs.
270 /// This is seperate then shared_seen_pcs because multiple fuzzing processes are likely using
271 /// it which causes contention and unrelated pcs to our campaign being set.
272 seen_pcs: []usize,
265const Fuzzer = struct {
266 // The default PRNG is not used here since going through `Random` can be very expensive
267 // since LLVM often fails to devirtualize and inline `fill`. Additionally, optimization
268 // is simpler since integers are not serialized then deserialized in the random stream.
269 //
270 // This acounts for a 30% performance improvement with LLVM 21.
271 xoshiro: std.Random.Xoshiro256,
272 test_one: abi.TestOne,
273273
274 /// Stores a fresh input's new pcs
275 fresh_pcs: []usize,
276
277 /// Pcs which __sanitizer_cov_trace_switch and __sanitizer_cov_trace_const_cmpx
278 /// have been called from and have had their already been added to const_x_vals
279 const_pcs: std.AutoArrayHashMapUnmanaged(usize, void) = .empty,
280 /// Values that have been constant operands in comparisons and switch cases.
281 /// There may be duplicates in this array if they came from different addresses, which is
282 /// fine as they are likely more important and hence more likely to be selected.
283 const_vals2: std.ArrayList(u16) = .empty,
284 const_vals4: std.ArrayList(u32) = .empty,
285 const_vals8: std.ArrayList(u64) = .empty,
286 const_vals16: std.ArrayList(u128) = .empty,
287
288 /// A minimal state for this struct which instrumentation can function on.
289 /// Used before this structure is initialized to avoid illegal behavior
290 /// from instrumentation functions being called and using undefined values.
291 pub const preinit: Instrumentation = .{
292 .seen_pcs = undefined, // currently only updated by `Fuzzer`
293 .fresh_pcs = undefined,
274 seen_pcs: []usize,
275 bests: struct {
276 len: u32,
277 quality_buf: []Input.Best,
278 input_buf: []Input.Best.Map,
279 },
280 seen_uids: std.ArrayHashMapUnmanaged(Uid, struct {
281 slices: union {
282 ints: std.ArrayList([]u64),
283 bytes: std.ArrayList(Input.Data.Bytes),
284 },
285 }, Uid.hashmap_ctx, false),
286
287 /// Past inputs leading to new pc or uid hits.
288 /// These are randomly mutated in round-robin fashion.
289 corpus: std.MultiArrayList(Input),
290 corpus_pos: Input.Index,
291
292 bytes_input: std.testing.Smith,
293 input_builder: Input.Builder,
294 /// Number of data calls the current run has made.
295 req_values: u32,
296 /// Number of bytes provided to the current run.
297 req_bytes: u32,
298 /// Index into the uid slices the current run is at.
299 /// `uid_data_i[i]` corresponds to `corpus[corpus_pos].data.uid_slices.values()[i]`.
300 uid_data_i: std.ArrayList(u32),
301 mut_data: struct {
302 /// Untyped indexes of `corpus[corpus_pos].data` that should be mutated.
303 ///
304 /// If an index appears multiple times, the first should be prioritized.
305 i: [4]u32,
306 /// For mutations which are a sequential mutation, the state is stored here.
307 seq: [4]struct {
308 kind: packed struct {
309 class: enum(u1) { replace, insert },
310 copy: bool,
311 /// If set then `.copy = true` and `.class = .replace`
312 ordered_mutate: bool,
313 /// If set then all other bits are undefined
314 none: bool,
315 },
316 len: u32,
317 copy: SeqCopy,
318 },
319 },
320
321 /// As values are provided to the Smith, they are appended to this. If the test
322 /// crashes, this can be recovered and used to obtain the crashing values.
323 mmap_input: MemoryMappedInput,
324 /// Filesystem directory containing found inputs for future runs
325 corpus_dir: Io.Dir,
326 /// The values in `corpus` past this point directly correspond to what is found
327 /// in `corpus_dir`.
328 start_corpus_dir: u32,
329
330 const SeqCopy = union {
331 order_i: u32,
332 ints: []u64,
333 bytes: Input.Data.Bytes,
294334 };
295335
296 pub fn depreinit(self: *Instrumentation) void {
297 self.const_vals2.deinit(gpa);
298 self.const_vals4.deinit(gpa);
299 self.const_vals8.deinit(gpa);
300 self.const_vals16.deinit(gpa);
301 self.* = undefined;
302 }
336 const Input = struct {
337 /// Untyped indexes into this are formed as follows: If the index is less than `ints.len`
338 /// it indexes into `ints`, otherwise it indexes into `bytes` subtracted by `ints.len`.
339 /// `math.maxInt(u32)` is reserved and impossible normally.
340 data: Data,
341 /// Corresponds with `data.uid_slices`.
342 /// Values are the indexes of `seen_uids` with the same uid.
343 seen_uid_i: []u32,
344 /// Used to select a random uid to mutate from.
345 ///
346 /// The number of times a uid is present in this array is logarithmic
347 /// to its data length in order to avoid long inputs from only being
348 /// selected while still having some bias towards longer ones.
349 weighted_uid_slice_i: []u32,
350
351 ref: struct {
352 /// Values are indexes of `Fuzzer.bests`.
353 best_i_buf: []u32,
354 best_i_len: u32,
355 },
356
357 pub const Data = struct {
358 uid_slices: Data.UidSlices,
359 ints: []u64,
360 bytes: Bytes,
361 /// Contains untyped indexes in the order they were requested.
362 order: []u32,
363
364 pub const Bytes = struct {
365 entries: []Entry,
366 table: []u8,
367
368 pub const Entry = struct {
369 off: u32,
370 len: u32,
371 };
303372
304 pub fn init() Instrumentation {
305 const pc_bitset_usizes = bitsetUsizes(exec.pc_counters.len);
306 const alloc_usizes = pc_bitset_usizes * 2;
307 const buf = gpa.alloc(u8, alloc_usizes * @sizeOf(usize)) catch @panic("OOM");
308 var fba_ctx: std.heap.FixedBufferAllocator = .init(buf);
309 const fba = fba_ctx.allocator();
373 pub fn deinit(b: Bytes) void {
374 gpa.free(b.entries);
375 gpa.free(b.table);
376 }
377 };
310378
311 var self: Instrumentation = .{
312 .seen_pcs = fba.alloc(usize, pc_bitset_usizes) catch unreachable,
313 .fresh_pcs = fba.alloc(usize, pc_bitset_usizes) catch unreachable,
379 pub const UidSlices = std.ArrayHashMapUnmanaged(Uid, struct {
380 base: u32,
381 len: u32,
382 }, Uid.hashmap_ctx, false);
314383 };
315 self.reset();
316 return self;
317 }
318384
319 pub fn reset(self: *Instrumentation) void {
320 @memset(self.seen_pcs, 0);
321 @memset(self.fresh_pcs, 0);
322 self.const_pcs.clearRetainingCapacity();
323 self.const_vals2.clearRetainingCapacity();
324 self.const_vals4.clearRetainingCapacity();
325 self.const_vals8.clearRetainingCapacity();
326 self.const_vals16.clearRetainingCapacity();
327 }
385 pub fn deinit(i: *Input) void {
386 i.data.uid_slices.deinit(gpa);
387 gpa.free(i.data.ints);
388 i.data.bytes.deinit();
389 gpa.free(i.data.order);
390 gpa.free(i.seen_uid_i);
391 gpa.free(i.weighted_uid_slice_i);
392 gpa.free(i.ref.best_i_buf);
393 i.* = undefined;
394 }
328395
329 /// If false is returned, then the pc is marked as seen
330 pub fn constPcSeen(self: *Instrumentation, pc: usize) bool {
331 return (self.const_pcs.getOrPut(gpa, pc) catch @panic("OOM")).found_existing;
332 }
396 pub const none: Input = .{
397 .data = .{
398 .uid_slices = .empty,
399 .ints = &.{},
400 .bytes = .{
401 .entries = &.{},
402 .table = undefined,
403 },
404 .order = &.{},
405 },
406 .seen_uid_i = &.{},
407 .weighted_uid_slice_i = &.{},
333408
334 pub fn isFresh(self: *Instrumentation) bool {
335 var hit_pcs = exec.pcBitsetIterator();
336 for (self.seen_pcs) |seen_pcs| {
337 if (hit_pcs.next() & ~seen_pcs != 0) return true;
338 }
409 // Empty input is not referenced by `Fuzzer`
410 .ref = undefined,
411 };
339412
340 return false;
341 }
413 pub const Index = enum(u32) {
414 pub const reserved_start: Index = .bytes_dry;
415 /// Only touches `Fuzzer.smith`.
416 bytes_dry = math.maxInt(u32) - 1,
417 /// Only touches `Fuzzer.smith` and `Fuzzer.input_builder`.
418 bytes_fresh = math.maxInt(u32),
419 _,
420 };
342421
343 /// Updates `fresh_pcs`
344 pub fn setFresh(self: *Instrumentation) void {
345 var hit_pcs = exec.pcBitsetIterator();
346 for (self.seen_pcs, self.fresh_pcs) |seen_pcs, *fresh_pcs| {
347 fresh_pcs.* = hit_pcs.next() & ~seen_pcs;
348 }
349 }
422 pub const Best = struct {
423 pc: u32,
424 min: Quality,
425 max: Quality,
426
427 /// Order of significance:
428 /// * n_pcs
429 /// * req.values
430 /// * req.bytes
431 pub const Quality = struct {
432 n_pcs: u32,
433 req: packed struct(u64) {
434 bytes: u32,
435 values: u32,
436
437 pub fn int(r: @This()) u64 {
438 return @bitCast(r);
439 }
440 },
350441
351 /// Returns if `exec.pc_counters` is a superset of `fresh_pcs`.
352 pub fn atleastFresh(self: *Instrumentation) bool {
353 var hit_pcs = exec.pcBitsetIterator();
354 for (self.fresh_pcs) |fresh_pcs| {
355 if (fresh_pcs & hit_pcs.next() != fresh_pcs) return false;
356 }
357 return true;
358 }
442 pub fn betterLess(a: Quality, b: Quality) bool {
443 return (a.n_pcs < b.n_pcs) | ((a.n_pcs == b.n_pcs) & (a.req.int() < b.req.int()));
444 }
359445
360 /// Updates based off `fresh_pcs`
361 fn updateSeen(self: *Instrumentation) void {
362 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
363 const shared_seen_pcs: [*]volatile usize = @ptrCast(
364 exec.shared_seen_pcs.items[@sizeOf(abi.SeenPcsHeader)..].ptr,
365 );
446 pub fn betterMore(a: Quality, b: Quality) bool {
447 return (a.n_pcs > b.n_pcs) | ((a.n_pcs == b.n_pcs) & (a.req.int() < b.req.int()));
448 }
449 };
366450
367 for (self.seen_pcs, shared_seen_pcs, self.fresh_pcs) |*seen, *shared_seen, fresh| {
368 seen.* |= fresh;
369 if (fresh != 0)
370 _ = @atomicRmw(usize, shared_seen, .Or, fresh, .monotonic);
371 }
372 }
373};
451 pub const Map = struct {
452 min: Input.Index,
453 max: Input.Index,
454 };
455 };
374456
375const Fuzzer = struct {
376 arena_ctx: std.heap.ArenaAllocator = .init(gpa),
377 rng: std.Random.DefaultPrng = .init(0),
378 test_one: abi.TestOne,
379 /// The next input that will be given to the testOne function. When the
380 /// current process crashes, this memory-mapped file is used to recover the
381 /// input.
382 input: MemoryMappedList,
383
384 /// Minimized past inputs leading to new pc hits.
385 /// These are randomly mutated in round-robin fashion
386 /// Element zero is always an empty input. It is gauraunteed no other elements are empty.
387 corpus: std.ArrayList([]const u8),
388 corpus_pos: usize,
389 /// List of past mutations that have led to new inputs. This way, the mutations that are the
390 /// most effective are the most likely to be selected again. Starts with one of each mutation.
391 mutations: std.ArrayList(Mutation) = .empty,
457 pub const Builder = struct {
458 uid_slices: std.ArrayHashMapUnmanaged(Uid, union {
459 ints: std.MultiArrayList(struct {
460 value: u64,
461 order_i: u32,
462 }),
463 bytes: std.MultiArrayList(struct {
464 value: Data.Bytes.Entry,
465 order_i: u32,
466 }),
467 }, Uid.hashmap_ctx, false),
468 bytes_table: std.ArrayList(u8),
469 // These will not overflow due to the 32-bit constraint on `MemoryMappedInput`
470 total_ints: u32,
471 total_bytes: u32,
472 weighted_len: u32,
473 /// Used to ensure that the 32-bit constraint in
474 /// `MemoryMappedInput` applies to this run.
475 smithed_len: u32,
476
477 pub const init: Builder = .{
478 .uid_slices = .empty,
479 .bytes_table = .empty,
480 .total_ints = 0,
481 .total_bytes = 0,
482 .weighted_len = 0,
483 .smithed_len = 4,
484 };
392485
393 /// Filesystem directory containing found inputs for future runs
394 corpus_dir: Io.Dir,
395 corpus_dir_idx: usize = 0,
486 pub fn addInt(b: *Builder, uid: Uid, int: u64) void {
487 const u = &b.uid_slices;
488 const gop = u.getOrPutValue(gpa, uid, .{ .ints = .empty }) catch @panic("OOM");
489 gop.value_ptr.ints.append(gpa, .{
490 .value = int,
491 .order_i = b.total_ints + b.total_bytes,
492 }) catch @panic("OOM");
493 b.total_ints += 1;
494 b.weighted_len += @intFromBool(math.isPowerOfTwo(gop.value_ptr.ints.len));
495 }
496
497 pub fn addBytes(b: *Builder, uid: Uid, bytes: []const u8) void {
498 const u = &b.uid_slices;
499 const gop = u.getOrPutValue(gpa, uid, .{ .bytes = .empty }) catch @panic("OOM");
500 gop.value_ptr.bytes.append(gpa, .{
501 .value = .{
502 .off = @intCast(b.bytes_table.items.len),
503 .len = @intCast(bytes.len),
504 },
505 .order_i = b.total_ints + b.total_bytes,
506 }) catch @panic("OOM");
507 b.bytes_table.appendSlice(gpa, bytes) catch @panic("OOM");
508 b.total_bytes += 1;
509 b.weighted_len += @intFromBool(math.isPowerOfTwo(gop.value_ptr.bytes.len));
510 }
511
512 pub fn checkSmithedLen(b: *Builder, n: usize) void {
513 const n32 = @min(n, math.maxInt(u32)); // second will overflow
514 b.smithed_len, const ov = @addWithOverflow(b.smithed_len, n32);
515 if (ov == 1) @panic("too much smith data requested (non-deterministic)");
516 }
517
518 /// Additionally resets the state of this structure.
519 ///
520 /// The callee must populate
521 /// * `.seen_uid_i`
522 /// * `.ref`
523 pub fn build(b: *Builder) Input {
524 const uid_slices = b.uid_slices.entries.slice();
525 var input: Input = .{
526 .data = .{
527 .uid_slices = Data.UidSlices.init(gpa, uid_slices.items(.key), &.{}) catch
528 @panic("OOM"),
529 .ints = gpa.alloc(u64, b.total_ints) catch @panic("OOM"),
530 .bytes = .{
531 .entries = gpa.alloc(Data.Bytes.Entry, b.total_bytes) catch @panic("OOM"),
532 .table = b.bytes_table.toOwnedSlice(gpa) catch @panic("OOM"),
533 },
534 .order = gpa.alloc(u32, b.total_ints + b.total_bytes) catch @panic("OOM"),
535 },
536 .seen_uid_i = gpa.alloc(u32, uid_slices.len) catch @panic("OOM"),
537 .weighted_uid_slice_i = gpa.alloc(u32, b.weighted_len) catch @panic("OOM"),
538 .ref = undefined,
539 };
540 var ints_pos: u32 = 0;
541 var bytes_pos: u32 = 0;
542 var weighted_pos: u32 = 0;
543
544 assert(mem.eql(Uid, uid_slices.items(.key), input.data.uid_slices.keys()));
545 for (
546 0..,
547 uid_slices.items(.key),
548 uid_slices.items(.value),
549 input.data.uid_slices.values(),
550 ) |uid_i, uid, *uid_data, *slice| {
551 const weighted_len = 1 + math.log2_int(u32, len: switch (uid.kind) {
552 .int => {
553 const ints = uid_data.ints.slice();
554 @memcpy(input.data.ints[ints_pos..][0..ints.len], ints.items(.value));
555 for (ints.items(.order_i), ints_pos..) |order_i, data_i| {
556 input.data.order[order_i] = @intCast(data_i);
557 }
558 uid_data.ints.deinit(gpa);
559 slice.* = .{ .base = ints_pos, .len = @intCast(ints.len) };
560 ints_pos += @intCast(ints.len);
561 break :len @intCast(ints.len);
562 },
563 .bytes => {
564 const bytes = uid_data.bytes.slice();
565 @memcpy(
566 input.data.bytes.entries[bytes_pos..][0..bytes.len],
567 bytes.items(.value),
568 );
569 for (
570 bytes.items(.order_i),
571 b.total_ints + bytes_pos..,
572 ) |order_i, data_i| {
573 input.data.order[order_i] = @intCast(data_i);
574 }
575 uid_data.bytes.deinit(gpa);
576 slice.* = .{ .base = bytes_pos, .len = @intCast(bytes.len) };
577 bytes_pos += @intCast(bytes.len);
578 break :len @intCast(bytes.len);
579 },
580 });
581 const weighted = input.weighted_uid_slice_i[weighted_pos..][0..weighted_len];
582 @memset(weighted, @intCast(uid_i));
583 weighted_pos += weighted_len;
584 }
585
586 assert(ints_pos == b.total_ints);
587 assert(bytes_pos == b.total_bytes);
588 assert(weighted_pos == b.weighted_len);
589
590 b.uid_slices.clearRetainingCapacity();
591 b.total_ints = 0;
592 b.total_bytes = 0;
593 b.weighted_len = 0;
594 b.smithed_len = 4;
595 return input;
596 }
597 };
598 };
599
600 pub fn init() Fuzzer {
601 if (exec.pc_counters.len > math.maxInt(u32)) @panic("too many pcs");
602 const f: Fuzzer = .{
603 .xoshiro = .init(0),
604 .test_one = undefined,
605
606 .seen_pcs = gpa.alloc(usize, bitsetUsizes(exec.pc_counters.len)) catch @panic("OOM"),
607 .bests = .{
608 .len = 0,
609 .quality_buf = gpa.alloc(Input.Best, exec.pc_counters.len) catch @panic("OOM"),
610 .input_buf = gpa.alloc(Input.Best.Map, exec.pc_counters.len) catch @panic("OOM"),
611 },
612 .seen_uids = .empty,
396613
397 pub fn init(test_one: abi.TestOne, unit_test_name: []const u8) Fuzzer {
398 var self: Fuzzer = .{
399 .test_one = test_one,
400 .input = undefined,
401614 .corpus = .empty,
402 .corpus_pos = 0,
403 .mutations = .empty,
615 .corpus_pos = undefined,
616
617 .bytes_input = undefined,
618 .input_builder = .init,
619 .req_values = undefined,
620 .req_bytes = undefined,
621 .uid_data_i = .empty,
622 .mut_data = undefined,
623
624 .mmap_input = undefined,
404625 .corpus_dir = undefined,
626 .start_corpus_dir = undefined,
405627 };
406 const arena = self.arena_ctx.allocator();
628 @memset(f.seen_pcs, 0);
629 return f;
630 }
407631
408 self.corpus_dir = exec.cache_f.createDirPathOpen(io, unit_test_name, .{}) catch |e|
632 /// May only be called after `f.setTest` has been called
633 pub fn reset(f: *Fuzzer) void {
634 f.test_one = undefined;
635
636 @memset(f.seen_pcs, 0);
637 f.bests.len = 0;
638 @memset(f.bests.quality_buf, undefined);
639 @memset(f.bests.input_buf, undefined);
640 for (f.seen_uids.keys(), f.seen_uids.values()) |uid, *u| {
641 switch (uid.kind) {
642 .int => u.slices.ints.deinit(gpa),
643 .bytes => u.slices.bytes.deinit(gpa),
644 }
645 }
646 f.seen_uids.clearRetainingCapacity();
647
648 f.corpus.clearRetainingCapacity();
649 f.corpus_pos = undefined;
650
651 f.uid_data_i.clearRetainingCapacity();
652
653 f.mmap_input.deinit();
654 f.corpus_dir.close(io);
655 f.start_corpus_dir = undefined;
656 }
657
658 pub fn setTest(f: *Fuzzer, test_one: abi.TestOne, unit_test_name: []const u8) void {
659 f.test_one = test_one;
660 f.corpus_dir = exec.cache_f.createDirPathOpen(io, unit_test_name, .{}) catch |e|
409661 panic("failed to open directory '{s}': {t}", .{ unit_test_name, e });
410 self.input = in: {
411 const f = self.corpus_dir.createFile(io, "in", .{
662 f.mmap_input = map: {
663 const input = f.corpus_dir.createFile(io, "in", .{
412664 .read = true,
413665 .truncate = false,
414666 // In case any other fuzz tests are running under the same test name,
......@@ -419,181 +671,979 @@ const Fuzzer = struct {
419671 error.WouldBlock => @panic("input file 'in' is in use by another fuzzing process"),
420672 else => panic("failed to create input file 'in': {t}", .{e}),
421673 };
422 const size = f.length(io) catch |e| panic("failed to stat input file 'in': {t}", .{e});
423 const map = (if (size < std.heap.page_size_max)
424 MemoryMappedList.create(f, 8, std.heap.page_size_max)
425 else
426 MemoryMappedList.init(f, size, size)) catch |e|
427 panic("failed to memory map input file 'in': {t}", .{e});
428
429 // Perform a dry-run of the stored input if there was one in case it might reproduce a
430 // crash.
431 const old_in_len = mem.littleToNative(usize, mem.bytesAsValue(usize, map.items[0..8]).*);
432 if (size >= 8 and old_in_len != 0 and map.items.len - 8 < old_in_len) {
433 test_one(.fromSlice(@volatileCast(map.items[8..][0..old_in_len])));
674
675 var size = input.length(io) catch |e| panic("failed to stat input file 'in': {t}", .{e});
676 if (size < std.heap.page_size_max) {
677 size = std.heap.page_size_max;
678 input.setLength(io, size) catch |e| panic("failed to resize input file 'in': {t}", .{e});
434679 }
435680
436 break :in map;
681 break :map MemoryMappedInput.init(input, size) catch |e|
682 panic("failed to memmap input file 'in': {t}", .{e});
437683 };
438 inst.reset();
439684
440 self.mutations.appendSlice(gpa, std.meta.tags(Mutation)) catch @panic("OOM");
441 // Ensure there is never an empty corpus. Additionally, an empty input usually leads to
442 // new inputs.
443 self.addInput(&.{});
685 // Perform a dry-run of the stored input in case it might reproduce a crash.
686 const len = mem.readInt(u32, f.mmap_input.mmap.memory[0..4], .little);
687 if (len < f.mmap_input.mmap.memory[4..].len) {
688 f.mmap_input.len = len;
689 f.runBytes(f.mmap_input.inputSlice(), .bytes_dry);
690 f.mmap_input.clearRetainingCapacity();
691 }
692 }
444693
694 pub fn loadCorpus(f: *Fuzzer) void {
695 f.corpus_pos = @enumFromInt(f.corpus.len);
696 f.corpus.append(gpa, .none) catch @panic("OOM"); // Also ensures the corpus is not empty
697 f.start_corpus_dir = @intCast(f.corpus.len);
445698 while (true) {
446 var name_buf: [@sizeOf(usize) * 2]u8 = undefined;
447 const bytes = self.corpus_dir.readFileAlloc(
448 io,
449 std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable,
450 arena,
451 .unlimited,
452 ) catch |e| switch (e) {
699 var name_buf: [8]u8 = undefined;
700 const name = f.corpusFileName(&name_buf, @enumFromInt(f.corpus.len));
701 const bytes = f.corpus_dir.readFileAlloc(io, name, gpa, .unlimited) catch |e| switch (e) {
453702 error.FileNotFound => break,
454 else => panic("failed to read corpus file '{x}': {t}", .{ self.corpus_dir_idx, e }),
703 else => panic("failed to read corpus file '{s}': {t}", .{ name, e }),
455704 };
456 // No corpus file of length zero will ever be created
457 if (bytes.len == 0)
458 panic("corrupt corpus file '{x}' (len of zero)", .{self.corpus_dir_idx});
459 self.addInput(bytes);
460 self.corpus_dir_idx += 1;
705 defer gpa.free(bytes);
706 f.newInput(bytes, false);
461707 }
708 f.corpus_pos = @enumFromInt(0);
709 }
462710
463 return self;
711 fn corpusFileName(f: *Fuzzer, buf: *[8]u8, i: Input.Index) []u8 {
712 const dir_i = @intFromEnum(i) - f.start_corpus_dir;
713 return std.fmt.bufPrint(buf, "{x}", .{dir_i}) catch unreachable;
464714 }
465715
466 pub fn deinit(self: *Fuzzer) void {
467 self.input.deinit();
468 self.corpus.deinit(gpa);
469 self.mutations.deinit(gpa);
470 self.corpus_dir.close(io);
471 self.arena_ctx.deinit();
472 self.* = undefined;
716 fn rngInt(f: *Fuzzer, T: type) T {
717 comptime assert(@bitSizeOf(T) <= 64);
718 const Unsigned = @Int(.unsigned, @bitSizeOf(T));
719 return @bitCast(@as(Unsigned, @truncate(f.xoshiro.next())));
473720 }
474721
475 pub fn addInput(self: *Fuzzer, bytes: []const u8) void {
476 self.corpus.append(gpa, bytes) catch @panic("OOM");
477 self.input.clearRetainingCapacity();
478 self.input.ensureTotalCapacity(8 + bytes.len) catch |e|
479 panic("could not resize shared input file: {t}", .{e});
480 self.input.items.len = 8;
481 self.input.appendSliceAssumeCapacity(bytes);
482 self.run();
483 inst.setFresh();
484 inst.updateSeen();
722 fn rngLessThan(f: *Fuzzer, T: type, limit: T) T {
723 return std.Random.limitRangeBiased(T, f.rngInt(T), limit);
724 }
725
726 /// Used for generating small values rather than making many calls into the prng.
727 const SmallEntronopy = struct {
728 bits: u64,
729
730 pub fn take(e: *SmallEntronopy, T: type) T {
731 defer e.bits >>= @bitSizeOf(T);
732 return @truncate(e.bits);
733 }
734 };
735
736 fn isFresh(f: *Fuzzer) bool {
737 // Store as a bool instead of returning immediately to aid optimizations
738 // by reducing branching since a fresh input is the unlikely case.
739 var fresh: bool = false;
740
741 var n_pcs: u32 = 0;
742 var hit_pcs = exec.pcBitsetIterator();
743 for (f.seen_pcs) |seen| {
744 const hits = hit_pcs.next();
745 fresh |= hits & ~seen != 0;
746 n_pcs += @popCount(hits);
747 }
748
749 const quality: Input.Best.Quality = .{
750 .n_pcs = n_pcs,
751 .req = .{
752 .values = f.req_values,
753 .bytes = f.req_bytes,
754 },
755 };
756 for (f.bests.quality_buf[0..f.bests.len]) |best| {
757 if (exec.pc_counters[best.pc] == 0) continue;
758 fresh |= quality.betterLess(best.min) | quality.betterMore(best.max);
759 }
760
761 return fresh;
762 }
763
764 fn runBytes(f: *Fuzzer, bytes: []const u8, mode: Input.Index) void {
765 assert(mode == .bytes_dry or mode == .bytes_fresh);
766
767 f.bytes_input = .{ .in = bytes };
768 f.corpus_pos = mode;
769 f.run(0); // 0 since `f.uid_data` is unused
770 }
771
772 fn updateSeenPcs(f: *Fuzzer) void {
773 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
774 const shared_seen_pcs: [*]volatile usize = @ptrCast(
775 exec.shared_seen_pcs[@sizeOf(abi.SeenPcsHeader)..].ptr,
776 );
777
778 var hit_pcs = exec.pcBitsetIterator();
779 for (f.seen_pcs, shared_seen_pcs) |*seen, *shared_seen| {
780 const new = hit_pcs.next() & ~seen.*;
781 if (new != 0) {
782 seen.* |= new;
783 _ = @atomicRmw(usize, shared_seen, .Or, new, .monotonic);
784 }
785 }
786 }
787
788 fn removeBest(f: *Fuzzer, i: Input.Index, best_i: u32, modify_fs_corpus: bool) void {
789 const ref = &f.corpus.items(.ref)[@intFromEnum(i)];
790 const list_i = mem.indexOfScalar(u32, ref.best_i_buf[0..ref.best_i_len], best_i).?;
791 ref.best_i_len -= 1;
792 ref.best_i_buf[list_i] = ref.best_i_buf[ref.best_i_len];
793
794 if (ref.best_i_len == 0 and @intFromEnum(i) >= f.start_corpus_dir and modify_fs_corpus) {
795 // The input is no longer valuable, so remove it.
796 var removed_input = f.corpus.get(@intFromEnum(i));
797 for (
798 removed_input.data.uid_slices.keys(),
799 removed_input.data.uid_slices.values(),
800 removed_input.seen_uid_i,
801 ) |uid, slice, seen_uid_i| {
802 switch (uid.kind) {
803 .int => {
804 const seen_ints = &f.seen_uids.values()[seen_uid_i].slices.ints;
805 const removed_ints = removed_input.data.ints[slice.base..][0..slice.len];
806 _ = seen_ints.swapRemove(for (0.., seen_ints.items) |idx, ints| {
807 if (removed_ints.ptr == ints.ptr) {
808 assert(removed_ints.len == ints.len);
809 break idx;
810 }
811 } else unreachable);
812 },
813 .bytes => {
814 const seen_bytes = &f.seen_uids.values()[seen_uid_i].slices.bytes;
815 const removed_bytes: Input.Data.Bytes = .{
816 .entries = removed_input.data.bytes.entries[slice.base..][0..slice.len],
817 .table = removed_input.data.bytes.table,
818 };
819 _ = seen_bytes.swapRemove(for (0.., seen_bytes.items) |idx, bytes| {
820 if (removed_bytes.entries.ptr == bytes.entries.ptr) {
821 assert(removed_bytes.entries.len == bytes.entries.len);
822 assert(removed_bytes.table.ptr == bytes.table.ptr);
823 assert(removed_bytes.table.len == bytes.table.len);
824 break idx;
825 }
826 } else unreachable);
827 },
828 }
829 }
830 removed_input.deinit();
831 f.corpus.swapRemove(@intFromEnum(i));
832
833 var removed_name_buf: [8]u8 = undefined;
834 const removed_name = f.corpusFileName(&removed_name_buf, i);
835
836 if (@intFromEnum(i) == f.corpus.len) {
837 f.corpus_dir.deleteFile(io, removed_name) catch |e| panic(
838 "failed to remove corpus file '{s}': {t}",
839 .{ removed_name, e },
840 );
841 return; // No item moved so no refs to update
842 }
843
844 var swapped_name_buf: [8]u8 = undefined;
845 const swapped_name = f.corpusFileName(&swapped_name_buf, @enumFromInt(f.corpus.len));
846
847 f.corpus_dir.rename(swapped_name, f.corpus_dir, removed_name, io) catch |e| panic(
848 "failed to rename corpus file '{s}' to '{s}': {t}",
849 .{ swapped_name, removed_name, e },
850 );
851
852 // Update refrences. `ref` can be reused since it was a swap remove
853 for (ref.best_i_buf[0..ref.best_i_len]) |update_pc_i| {
854 const best = &f.bests.input_buf[update_pc_i];
855 assert(@intFromEnum(best.min) == f.corpus.len or
856 @intFromEnum(best.max) == f.corpus.len);
857
858 if (@intFromEnum(best.min) == f.corpus.len) best.min = i;
859 if (@intFromEnum(best.max) == f.corpus.len) best.max = i;
860 }
861 }
485862 }
486863
487 /// Assumes `fresh_pcs` correspond to the input
488 fn minimizeInput(self: *Fuzzer) void {
489 // The minimization technique is kept relatively simple, we sequentially try to remove each
490 // byte and check that the new pcs and memory loads are still hit.
491 var i = self.input.items.len;
492 while (i != 8) {
493 i -= 1;
494 const old = self.input.orderedRemove(i);
864 pub fn newInput(f: *Fuzzer, bytes: []const u8, modify_fs_corpus: bool) void {
865 f.runBytes(bytes, .bytes_fresh);
866 f.req_values = f.input_builder.total_ints + f.input_builder.total_bytes;
867 f.req_bytes = @intCast(f.input_builder.bytes_table.items.len);
868 var input = f.input_builder.build();
869
870 f.uid_data_i.ensureTotalCapacity(gpa, input.data.uid_slices.entries.len) catch @panic("OOM");
871 for (
872 input.seen_uid_i,
873 input.data.uid_slices.keys(),
874 input.data.uid_slices.values(),
875 ) |*i, uid, slice| {
876 const gop = f.seen_uids.getOrPutValue(gpa, uid, switch (uid.kind) {
877 .int => .{ .slices = .{ .ints = .empty } },
878 .bytes => .{ .slices = .{ .bytes = .empty } },
879 }) catch @panic("OOM");
880 switch (uid.kind) {
881 .int => f.seen_uids.values()[gop.index].slices.ints.append(
882 gpa,
883 input.data.ints[slice.base..][0..slice.len],
884 ) catch @panic("OOM"),
885 .bytes => f.seen_uids.values()[gop.index].slices.bytes.append(gpa, .{
886 .entries = input.data.bytes.entries[slice.base..][0..slice.len],
887 .table = input.data.bytes.table,
888 }) catch @panic("OOM"),
889 }
890 i.* = @intCast(gop.index);
891 }
892
893 const quality: Input.Best.Quality = .{
894 .n_pcs = n_pcs: {
895 @setRuntimeSafety(builtin.mode == .Debug); // Necessary for vectorization
896 var n: u32 = 0;
897 for (exec.pc_counters) |c| {
898 n += @intFromBool(c != 0);
899 }
900 break :n_pcs n;
901 },
902 .req = .{
903 .values = f.req_values,
904 .bytes = f.req_bytes,
905 },
906 };
907
908 var best_i_list: std.ArrayList(u32) = .empty;
909 for (0.., f.bests.quality_buf[0..f.bests.len]) |best_i, best| {
910 if (exec.pc_counters[best.pc] == 0) continue;
495911
496 @memset(exec.pc_counters, 0);
497 self.run();
912 const better_min = quality.betterLess(best.min);
913 const better_max = quality.betterMore(best.max);
914 if (!better_min and !better_max) {
915 @branchHint(.likely);
916 continue;
917 }
918 best_i_list.append(gpa, @intCast(best_i)) catch @panic("OOM");
498919
499 if (!inst.atleastFresh()) {
500 self.input.insertAssumeCapacity(i, old);
920 const map = &f.bests.input_buf[best_i];
921 if (map.min != map.max) {
922 if (better_min) {
923 f.removeBest(map.min, @intCast(best_i), modify_fs_corpus);
924 }
925 if (better_max) {
926 f.removeBest(map.max, @intCast(best_i), modify_fs_corpus);
927 }
501928 } else {
502 // This removal may have led to new pcs or memory loads being hit, so we need to
503 // update them to avoid duplicates.
504 inst.setFresh();
929 if (better_min and better_max) {
930 f.removeBest(map.min, @intCast(best_i), modify_fs_corpus);
931 }
505932 }
506933 }
934
935 // Must come after the above since some inputs may be removed
936 const input_i: Input.Index = @enumFromInt(f.corpus.len);
937 if (input_i == Input.Index.reserved_start) {
938 @panic("corpus size limit exceeded");
939 }
940
941 for (best_i_list.items) |i| {
942 const best_qual = &f.bests.quality_buf[i];
943 const best_map = &f.bests.input_buf[i];
944
945 if (quality.betterLess(best_qual.min)) {
946 best_qual.min = quality;
947 best_map.min = input_i;
948 }
949 if (quality.betterMore(best_qual.max)) {
950 best_qual.max = quality;
951 best_map.max = input_i;
952 }
953 }
954
955 for (0.., exec.pc_counters) |i, hits| {
956 if (hits == 0) {
957 @branchHint(.likely);
958 continue;
959 }
960
961 if ((f.seen_pcs[i / @bitSizeOf(usize)] >> @intCast(i % @bitSizeOf(usize))) & 1 == 0) {
962 @branchHint(.unlikely);
963 best_i_list.append(gpa, f.bests.len) catch @panic("OOM");
964 f.bests.quality_buf[f.bests.len] = .{
965 .pc = @intCast(i),
966 .min = quality,
967 .max = quality,
968 };
969 f.bests.input_buf[f.bests.len] = .{ .min = input_i, .max = input_i };
970 f.bests.len += 1;
971 }
972 }
973
974 if (best_i_list.items.len == 0 and
975 modify_fs_corpus // Found by freshness; otherwise, it does not need to be better
976 ) {
977 @branchHint(.cold); // Nondeterministic test
978 std.log.warn("nondeterministic rerun", .{});
979 return;
980 }
981
982 input.ref.best_i_buf = best_i_list.toOwnedSlice(gpa) catch @panic("OOM");
983 input.ref.best_i_len = @intCast(input.ref.best_i_buf.len);
984 f.corpus.append(gpa, input) catch @panic("OOM");
985 f.corpus_pos = input_i;
986
987 // Must come after the above since `seen_pcs` is used
988 f.updateSeenPcs();
989
990 if (!modify_fs_corpus) return;
991
992 // Write new input to cache
993 var name_buf: [8]u8 = undefined;
994 const name = f.corpusFileName(&name_buf, input_i);
995 f.corpus_dir.writeFile(io, .{ .sub_path = name, .data = bytes }) catch |e|
996 panic("failed to write corpus file '{s}': {t}", .{ name, e });
507997 }
508998
509 fn run(self: *Fuzzer) void {
510 // `pc_counters` is not cleared since only new hits are relevant.
999 fn run(f: *Fuzzer, input_uids: usize) void {
1000 @memset(exec.pc_counters, 0);
1001 f.uid_data_i.items.len = input_uids;
1002 @memset(f.uid_data_i.items, 0);
1003 f.req_values = 0;
1004 f.req_bytes = 0;
5111005
512 mem.bytesAsValue(usize, self.input.items[0..8]).* =
513 mem.nativeToLittle(usize, self.input.items.len - 8);
514 self.test_one(.fromSlice(@volatileCast(self.input.items[8..])));
1006 f.test_one();
1007 _ = @atomicRmw(usize, &exec.seenPcsHeader().n_runs, .Add, 1, .monotonic);
1008 }
5151009
516 const header = mem.bytesAsValue(
517 abi.SeenPcsHeader,
518 exec.shared_seen_pcs.items[0..@sizeOf(abi.SeenPcsHeader)],
519 );
520 _ = @atomicRmw(usize, &header.n_runs, .Add, 1, .monotonic);
1010 /// Returns a number of mutations to perform from 1-4
1011 /// with smaller values exponentially more likely.
1012 pub fn mutCount(rng: u16) u8 {
1013 // The below provides the following distribution
1014 // @clz(@clz( range mapped percentage ratio
1015 // 0 -> 0 -> 4 1 = 93.750% (15 / 16 )
1016 // 1 -> 1 - 255 -> 3 2 = 5.859% (15 / 256 )
1017 // 2 -> 256 - 4095 -> 2 3 = .391% (<1 / 256 )
1018 // 3 -> 4096 - 16383 -> 1 4 = .002% ( 1 / 65536)
1019 // 4 -> 16384 - 32767 -> 1
1020 // 5 -> 32768 - 65535 -> 1
1021 return @as(u8, 4) - @min(@clz(@clz(rng)), 3);
5211022 }
5221023
523 pub fn cycle(self: *Fuzzer) void {
524 const input = self.corpus.items[self.corpus_pos];
525 self.corpus_pos += 1;
526 if (self.corpus_pos == self.corpus.items.len)
527 self.corpus_pos = 0;
528
529 const rng = self.rng.random();
530 const m = while (true) {
531 const m = self.mutations.items[rng.uintLessThanBiased(usize, self.mutations.items.len)];
532 if (!m.mutate(
533 rng,
534 input,
535 &self.input,
536 self.corpus.items,
537 inst.const_vals2.items,
538 inst.const_vals4.items,
539 inst.const_vals8.items,
540 inst.const_vals16.items,
541 )) continue;
542 break m;
1024 pub fn cycle(f: *Fuzzer) void {
1025 assert(f.mmap_input.len == 0);
1026 const corpus = f.corpus.slice();
1027 const corpus_i = @intFromEnum(f.corpus_pos);
1028
1029 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
1030 var n_mutate = mutCount(small_entronopy.take(u16));
1031 const data = &corpus.items(.data)[corpus_i];
1032 const weighted_uid_slice_i = corpus.items(.weighted_uid_slice_i)[corpus_i];
1033 n_mutate *= @intFromBool(weighted_uid_slice_i.len != 0); // No static mutations on empty
1034
1035 f.mut_data = .{
1036 .i = @splat(math.maxInt(u32)),
1037 .seq = @splat(.{
1038 .kind = .{
1039 .class = undefined,
1040 .copy = undefined,
1041 .ordered_mutate = undefined,
1042 .none = true,
1043 },
1044 .len = undefined,
1045 .copy = undefined,
1046 }),
5431047 };
5441048
545 self.run();
1049 const uid_slices = data.uid_slices.entries.slice();
1050 for (
1051 f.mut_data.i[0..n_mutate],
1052 f.mut_data.seq[0..n_mutate],
1053 ) |*i, *s| if ((data.order.len < 2) | (small_entronopy.take(u3) != 0)) {
1054 // Mutation on uid
1055 const uid_slice_wi = f.rngLessThan(u32, @intCast(weighted_uid_slice_i.len));
1056 const uid_slice_i = weighted_uid_slice_i[uid_slice_wi];
1057
1058 const is_bytes = uid_slices.items(.key)[uid_slice_i].kind == .bytes;
1059 const data_slice = uid_slices.items(.value)[uid_slice_i];
1060 i.* = @as(u32, @intCast(data.ints.len)) * @intFromBool(is_bytes) +
1061 data_slice.base + f.rngLessThan(u32, data_slice.len);
1062 } else {
1063 // Sequence mutation on order
1064 const order_len: u32 = @intCast(data.order.len);
1065 const order_i = f.rngLessThan(u32, order_len - 1);
1066 s.* = .{
1067 .kind = .{
1068 .class = .replace,
1069 .copy = true,
1070 .ordered_mutate = true,
1071 .none = false,
1072 },
1073 .len = @min(@clz(f.rngInt(u16)) + 1, order_len - order_i),
1074 .copy = .{ .order_i = order_i },
1075 };
1076 i.* = data.order[order_i];
1077 };
5461078
547 if (inst.isFresh()) {
1079 f.run(data.uid_slices.entries.len);
1080 if (f.isFresh()) {
5481081 @branchHint(.unlikely);
5491082
550 const header = mem.bytesAsValue(
551 abi.SeenPcsHeader,
552 exec.shared_seen_pcs.items[0..@sizeOf(abi.SeenPcsHeader)],
553 );
554 _ = @atomicRmw(usize, &header.unique_runs, .Add, 1, .monotonic);
555
556 inst.setFresh();
557 self.minimizeInput();
558 inst.updateSeen();
559
560 // An empty-input has always been tried, so if an empty input is fresh then the
561 // test has to be non-deterministic. This has to be checked as duplicate empty
562 // entries are not allowed.
563 if (self.input.items.len - 8 == 0) {
564 std.log.warn("non-deterministic test (empty input produces different hits)", .{});
565 _ = @atomicRmw(usize, &header.unique_runs, .Sub, 1, .monotonic);
566 return;
1083 _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);
1084 f.newInput(f.mmap_input.inputSlice(), true);
1085 }
1086 f.mmap_input.clearRetainingCapacity();
1087
1088 assert(@intFromEnum(f.corpus_pos) < f.corpus.len);
1089 f.corpus_pos = @enumFromInt((@intFromEnum(f.corpus_pos) + 1) % f.corpus.len);
1090 }
1091
1092 fn weightsContain(int: u64, weights: []const abi.Weight) bool {
1093 var contains: bool = false;
1094 for (weights) |w| {
1095 contains |= w.min <= int and int <= w.max;
1096 }
1097 return contains;
1098 }
1099
1100 fn weightsContainBytes(bytes: []const u8, weights: []const abi.Weight) bool {
1101 if (weights[0].min == 0 and weights[0].max == 0xff) {
1102 // Fast path: all bytes are valid
1103 return true;
1104 }
1105
1106 var contains: bool = true;
1107 for (bytes) |b| {
1108 contains &= weightsContain(b, weights);
1109 }
1110 return contains;
1111 }
1112
1113 fn sumWeightsInclusive(weights: []const abi.Weight) u64 {
1114 var sum: u64 = math.maxInt(u64);
1115 for (weights) |w| {
1116 sum +%= (w.max - w.min +% 1) *% w.weight;
1117 }
1118 return sum;
1119 }
1120
1121 fn weightedValue(f: *Fuzzer, weights: []const abi.Weight, incl_sum: u64) u64 {
1122 var incl_n: u64 = f.rngInt(u64);
1123 const limit = incl_sum +% 1;
1124 if (limit != 0) incl_n = std.Random.limitRangeBiased(u64, incl_n, limit);
1125
1126 for (weights) |w| {
1127 // (w.max - w.min + 1) * w.weight - 1
1128 const incl_vals = (w.max - w.min) * w.weight + (w.weight - 1);
1129 if (incl_n > incl_vals) {
1130 incl_n -= incl_vals + 1;
1131 } else {
1132 const val = w.min + incl_n / w.weight;
1133 assert(val <= w.max);
1134 return val;
1135 }
1136 } else unreachable;
1137 }
1138
1139 const Untyped = union {
1140 int: u64,
1141 bytes: []u8,
1142 };
1143
1144 fn nextUntyped(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) union(enum) {
1145 copy: Untyped,
1146 mutate: Untyped,
1147 fresh: void,
1148 } {
1149 const corpus = f.corpus.slice();
1150 const corpus_i = @intFromEnum(f.corpus_pos);
1151 const data = &corpus.items(.data)[corpus_i];
1152 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
1153
1154 const uid_i = data.uid_slices.getIndex(uid) orelse {
1155 @branchHint(.unlikely);
1156 return .fresh;
1157 };
1158 const data_slice = data.uid_slices.values()[uid_i];
1159 var slice_i = f.uid_data_i.items[uid_i];
1160 var data_i = data_slice.base + slice_i;
1161
1162 new_data: while (true) {
1163 assert(slice_i == f.uid_data_i.items[uid_i] and data_i == data_slice.base + slice_i);
1164 if (slice_i == data_slice.len) break :new_data;
1165 assert(slice_i < data_slice.len);
1166
1167 f.uid_data_i.items[uid_i] += 1;
1168 const mut_i = std.simd.firstIndexOfValue(
1169 @as(@Vector(4, u32), f.mut_data.i),
1170 data_i + @as(u32, @intCast(data.ints.len)) * @intFromEnum(uid.kind),
1171 ) orelse {
1172 @branchHint(.likely);
1173 switch (uid.kind) {
1174 .int => {
1175 const int = data.ints[data_i];
1176 if (weightsContain(int, weights)) {
1177 @branchHint(.likely);
1178 return .{ .copy = .{ .int = int } };
1179 }
1180 },
1181 .bytes => {
1182 const entry = data.bytes.entries[data_i];
1183 const bytes = data.bytes.table[entry.off..][0..entry.len];
1184 if (weightsContainBytes(bytes, weights)) {
1185 @branchHint(.likely);
1186 return .{ .copy = .{ .bytes = bytes } };
1187 }
1188 },
1189 }
1190 break :new_data;
1191 };
1192
1193 const seq = &f.mut_data.seq[mut_i];
1194 new_seq: {
1195 if (!seq.kind.none) break :new_seq;
1196
1197 var opts: packed struct(u6) {
1198 // Matches layout as `mut_data.seq.kind`
1199 insert: bool,
1200 copy: bool,
1201
1202 seq: u2,
1203 delete: bool,
1204 splice: bool,
1205 } = @bitCast(small_entronopy.take(u6));
1206 if (opts.seq != 0) break :new_data;
1207
1208 const max_consume = data_slice.len - slice_i; // inclusive
1209 if (opts.delete) {
1210 f.uid_data_i.items[uid_i] += f.rngLessThan(u32, max_consume);
1211 slice_i = f.uid_data_i.items[uid_i];
1212 data_i = data_slice.base + slice_i;
1213 continue;
1214 }
1215 opts.insert |= max_consume == 0;
1216 seq.kind = .{
1217 .class = if (opts.insert) .replace else .insert,
1218 .copy = opts.copy,
1219 .ordered_mutate = false,
1220 .none = false,
1221 };
1222
1223 if (!seq.kind.copy) {
1224 seq.len = switch (seq.kind.class) {
1225 .replace => f.rngLessThan(u32, max_consume) + 1,
1226 .insert => @clz(f.rngInt(u16)) + 1,
1227 };
1228 seq.copy = undefined;
1229 } else {
1230 const src: SeqCopy, const src_len: u32 = if (!opts.splice) .{
1231 switch (uid.kind) {
1232 .int => .{ .ints = data.ints[data_slice.base..][0..data_slice.len] },
1233 .bytes => .{ .bytes = .{
1234 .entries = data.bytes.entries[data_slice.base..][0..data_slice.len],
1235 .table = data.bytes.table,
1236 } },
1237 },
1238 data_slice.len,
1239 } else src: {
1240 const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];
1241 const untyped_slices = f.seen_uids.values()[seen_uid_i].slices;
1242 switch (uid.kind) {
1243 .int => {
1244 const slices = untyped_slices.ints.items;
1245 const i = f.rngLessThan(u32, @intCast(slices.len));
1246 break :src .{
1247 .{ .ints = slices[i] },
1248 @intCast(slices[i].len),
1249 };
1250 },
1251 .bytes => {
1252 const slices = untyped_slices.bytes.items;
1253 const i = f.rngLessThan(u32, @intCast(slices.len));
1254 break :src .{
1255 .{ .bytes = slices[i] },
1256 @intCast(slices[i].entries.len),
1257 };
1258 },
1259 }
1260 };
1261
1262 const off = f.rngLessThan(u32, src_len);
1263 seq.len = f.rngLessThan(u32, src_len - off) + 1;
1264 if (seq.kind.class == .replace) seq.len = @min(seq.len, max_consume);
1265 seq.copy = switch (uid.kind) {
1266 .int => .{ .ints = src.ints[off..][0..seq.len] },
1267 .bytes => .{ .bytes = .{
1268 .entries = src.bytes.entries[off..][0..seq.len],
1269 .table = src.bytes.table,
1270 } },
1271 };
1272 }
1273 }
1274
1275 assert(!seq.kind.none);
1276 f.uid_data_i.items[uid_i] -= @intFromBool(seq.kind.class == .insert);
1277 seq.len -= 1;
1278 seq.kind.none |= seq.len == 0;
1279 f.mut_data.i[mut_i] += @intFromBool(seq.kind.class == .replace and seq.len != 0);
1280
1281 if (!seq.kind.copy) {
1282 assert(!seq.kind.ordered_mutate);
1283 break :new_data;
1284 }
1285 if (seq.kind.ordered_mutate) {
1286 assert(seq.kind.class == .replace);
1287 seq.copy.order_i += @intFromBool(seq.len != 0);
1288 f.mut_data.i[mut_i] = data.order[seq.copy.order_i];
1289 break :new_data;
1290 }
1291 switch (uid.kind) {
1292 .int => {
1293 const int = seq.copy.ints[0];
1294 seq.copy.ints = seq.copy.ints[1..];
1295 if (weightsContain(int, weights)) {
1296 @branchHint(.likely);
1297 return .{ .copy = .{ .int = int } };
1298 }
1299 },
1300 .bytes => {
1301 const entry = seq.copy.bytes.entries[0];
1302 const bytes = seq.copy.bytes.table[entry.off..][0..entry.len];
1303 seq.copy.bytes.entries = seq.copy.bytes.entries[1..];
1304 if (weightsContainBytes(bytes, weights)) {
1305 @branchHint(.likely);
1306 return .{ .copy = .{ .bytes = bytes } };
1307 }
1308 },
1309 }
1310 break;
1311 }
1312
1313 const opts: packed struct(u10) {
1314 copy: u2,
1315 fresh: u2,
1316 splice: bool,
1317 local_far: bool,
1318 local_off: i4,
1319 } = @bitCast(small_entronopy.take(u10));
1320
1321 if (opts.copy != 0) {
1322 if (opts.fresh == 0 or slice_i == data_slice.len) return .fresh;
1323 return .{ .mutate = switch (uid.kind) {
1324 .int => .{ .int = data.ints[data_i] },
1325 .bytes => .{ .bytes = b: {
1326 const entry = data.bytes.entries[data_i];
1327 break :b data.bytes.table[entry.off..][0..entry.len];
1328 } },
1329 } };
1330 }
1331
1332 if (!opts.splice) {
1333 const src_data_i = data_slice.base + if (!opts.local_far) i: {
1334 const off = opts.local_off;
1335 break :i if (off >= 0) @min(
1336 f.uid_data_i.items[uid_i] +| @as(u4, @intCast(off)),
1337 data_slice.len - 1,
1338 ) else f.uid_data_i.items[uid_i] -| @abs(off);
1339 } else f.rngLessThan(u32, data_slice.len);
1340 switch (uid.kind) {
1341 .int => {
1342 const int = data.ints[src_data_i];
1343 if (weightsContain(int, weights)) {
1344 @branchHint(.likely);
1345 return .{ .copy = .{ .int = int } };
1346 }
1347 },
1348 .bytes => {
1349 const entry = data.bytes.entries[src_data_i];
1350 const bytes = data.bytes.table[entry.off..][0..entry.len];
1351 if (weightsContainBytes(bytes, weights)) {
1352 @branchHint(.likely);
1353 return .{ .copy = .{ .bytes = bytes } };
1354 }
1355 },
1356 }
1357 } else {
1358 const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];
1359 const untyped_slices = f.seen_uids.values()[seen_uid_i].slices;
1360 switch (uid.kind) {
1361 .int => {
1362 const slices = untyped_slices.ints.items;
1363 const from = slices[f.rngLessThan(u32, @intCast(slices.len))];
1364 const int = from[f.rngLessThan(u32, @intCast(from.len))];
1365 if (weightsContain(int, weights)) {
1366 @branchHint(.likely);
1367 return .{ .copy = .{ .int = int } };
1368 }
1369 },
1370 .bytes => {
1371 const slices = untyped_slices.bytes.items;
1372 const from = slices[f.rngLessThan(u32, @intCast(slices.len))];
1373 const entry_i = f.rngLessThan(u32, @intCast(from.entries.len));
1374 const entry = from.entries[entry_i];
1375 const bytes = from.table[entry.off..][0..entry.len];
1376 if (weightsContainBytes(bytes, weights)) {
1377 @branchHint(.likely);
1378 return .{ .copy = .{ .bytes = bytes } };
1379 }
1380 },
1381 }
1382 }
1383 return .fresh;
1384 }
1385
1386 pub fn nextInt(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) u64 {
1387 f.req_values += 1;
1388 if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1389 @branchHint(.unlikely);
1390 const int = f.bytes_input.valueWeightedWithHash(u64, weights, undefined);
1391 if (f.corpus_pos == .bytes_fresh) {
1392 f.input_builder.checkSmithedLen(8);
1393 f.input_builder.addInt(uid, int);
1394 }
1395 return int;
1396 }
1397 const int = f.nextIntInner(uid, weights);
1398 f.mmap_input.appendLittleInt(u64, int);
1399 return int;
1400 }
1401
1402 fn nextIntInner(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) u64 {
1403 return switch (f.nextUntyped(uid, weights)) {
1404 .copy => |u| u.int,
1405 .mutate, .fresh => f.weightedValue(weights, sumWeightsInclusive(weights)),
1406 };
1407 }
1408
1409 pub fn nextEos(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) bool {
1410 f.req_values += 1;
1411 if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1412 @branchHint(.unlikely);
1413 const eos = f.bytes_input.eosWeightedWithHash(weights, undefined);
1414 if (f.corpus_pos == .bytes_fresh) {
1415 f.input_builder.checkSmithedLen(1);
1416 f.input_builder.addInt(uid, @intFromBool(eos));
1417 }
1418 return eos;
1419 }
1420 // `nextIntInner` is already gauraunteed to eventually return `1`
1421 const eos = @as(u1, @intCast(f.nextIntInner(uid, weights))) != 0;
1422 f.mmap_input.appendLittleInt(u8, @intFromBool(eos));
1423 return eos;
1424 }
1425
1426 fn mutateBytes(f: *Fuzzer, in: []u8, out: []u8, weights: []const abi.Weight) void {
1427 assert(in.len != 0);
1428 const weights_incl_sum = sumWeightsInclusive(weights);
1429
1430 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
1431 var muts = mutCount(small_entronopy.take(u16));
1432 var rem_out = out;
1433 var rem_copy = in;
1434 while (rem_out.len != 0 and muts != 0) {
1435 muts -= 1;
1436 const opts: packed struct(u4) {
1437 kind: enum(u2) {
1438 random,
1439 stream_copy,
1440 stream_discard,
1441 absolute_copy,
1442 },
1443 small: u2,
1444
1445 pub fn limitSmall(o: @This(), n: usize) u32 {
1446 return @min(
1447 @as(u32, @intCast(n)),
1448 @as(u32, if (o.small != 0) 8 else math.maxInt(u32)),
1449 );
1450 }
1451 } = @bitCast(small_entronopy.take(u4));
1452 s: switch (opts.kind) {
1453 .random => {
1454 const n = f.rngLessThan(u32, opts.limitSmall(rem_out.len)) + 1;
1455 for (rem_out[0..n]) |*o| {
1456 o.* = @intCast(f.weightedValue(weights, weights_incl_sum));
1457 }
1458 rem_out = rem_out[n..];
1459 },
1460 .stream_copy => {
1461 if (rem_copy.len == 0) continue :s .random;
1462 const n = @min(
1463 f.rngLessThan(u32, opts.limitSmall(rem_copy.len)) + 1,
1464 rem_out.len,
1465 );
1466 @memcpy(rem_out[0..n], rem_copy[0..n]);
1467 rem_out = rem_out[n..];
1468 rem_copy = rem_copy[n..];
1469 },
1470 .stream_discard => {
1471 if (rem_copy.len == 0) continue :s .random;
1472 const n = f.rngLessThan(u32, opts.limitSmall(rem_copy.len)) + 1;
1473 rem_copy = rem_copy[n..];
1474 },
1475 .absolute_copy => {
1476 const in_len: u32 = @intCast(in.len);
1477 const off = f.rngLessThan(u32, in_len);
1478 const len = @min(
1479 f.rngLessThan(u32, in_len - off) + 1,
1480 opts.limitSmall(rem_out.len),
1481 );
1482 @memcpy(rem_out[0..len], in[off..][0..len]);
1483 rem_out = rem_out[len..];
1484 },
5671485 }
1486 }
5681487
569 const arena = self.arena_ctx.allocator();
570 const bytes = arena.dupe(u8, @volatileCast(self.input.items[8..])) catch @panic("OOM");
1488 const copy = @min(rem_out.len, rem_copy.len);
1489 @memcpy(rem_out[0..copy], rem_copy[0..copy]);
1490 for (rem_out[copy..]) |*o| {
1491 o.* = @intCast(f.weightedValue(weights, weights_incl_sum));
1492 }
1493 }
5711494
572 self.corpus.append(gpa, bytes) catch @panic("OOM");
573 self.mutations.appendNTimes(gpa, m, 6) catch @panic("OOM");
1495 fn nextBytesInner(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void {
1496 so: switch (f.nextUntyped(uid, weights)) {
1497 .copy => |u| {
1498 if (u.bytes.len >= out.len) {
1499 @branchHint(.likely);
1500 @memcpy(out, u.bytes[0..out.len]);
1501 return;
1502 }
5741503
575 // Write new corpus to cache
576 var name_buf: [@sizeOf(usize) * 2]u8 = undefined;
577 self.corpus_dir.writeFile(io, .{
578 .sub_path = std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable,
579 .data = bytes,
580 }) catch |e| panic("failed to write corpus file '{x}': {t}", .{ self.corpus_dir_idx, e });
581 self.corpus_dir_idx += 1;
1504 @memcpy(out[0..u.bytes.len], u.bytes);
1505 const weights_incl_sum = sumWeightsInclusive(weights);
1506 for (out[u.bytes.len..]) |*o| {
1507 o.* = @intCast(f.weightedValue(weights, weights_incl_sum));
1508 }
1509 },
1510 .mutate => |u| {
1511 if (u.bytes.len == 0) continue :so .fresh;
1512 f.mutateBytes(u.bytes, out, weights);
1513 },
1514 .fresh => {
1515 const weights_incl_sum = sumWeightsInclusive(weights);
1516 for (out) |*o| {
1517 o.* = @intCast(f.weightedValue(weights, weights_incl_sum));
1518 }
1519 },
5821520 }
5831521 }
1522
1523 pub fn nextBytes(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void {
1524 f.req_values += 1;
1525 f.req_bytes +%= @truncate(out.len); // This function should panic since the 32-bit
1526 // data limit is exceeded, so wrapping is fine.
1527 if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1528 @branchHint(.unlikely);
1529 f.bytes_input.bytesWeightedWithHash(out, weights, undefined);
1530 if (f.corpus_pos == .bytes_fresh) {
1531 f.input_builder.checkSmithedLen(out.len);
1532 f.input_builder.addBytes(uid, out);
1533 }
1534 return;
1535 }
1536
1537 f.nextBytesInner(uid, out, weights);
1538 f.mmap_input.appendSlice(out);
1539 }
1540
1541 fn nextSliceInner(
1542 f: *Fuzzer,
1543 uid: Uid,
1544 buf: []u8,
1545 len_weights: []const abi.Weight,
1546 byte_weights: []const abi.Weight,
1547 ) u32 {
1548 so: switch (f.nextUntyped(uid, byte_weights)) {
1549 .copy => |u| {
1550 var len: u32 = @intCast(u.bytes.len);
1551 if (!weightsContain(len, len_weights)) {
1552 @branchHint(.unlikely);
1553 len = @intCast(f.weightedValue(len_weights, sumWeightsInclusive(len_weights)));
1554 }
1555
1556 if (u.bytes.len >= len) {
1557 @branchHint(.likely);
1558 @memcpy(buf[0..len], u.bytes[0..len]);
1559 return len;
1560 }
1561
1562 @memcpy(buf[0..u.bytes.len], u.bytes);
1563 const weights_incl_sum = sumWeightsInclusive(byte_weights);
1564 for (buf[u.bytes.len..len]) |*o| {
1565 o.* = @intCast(f.weightedValue(byte_weights, weights_incl_sum));
1566 }
1567 return len;
1568 },
1569 .mutate => |u| {
1570 if (u.bytes.len == 0) continue :so .fresh;
1571 const len: u32 = len: {
1572 const offseted: packed struct {
1573 is: u3,
1574 sub: bool,
1575 by: u3,
1576 } = @bitCast(f.rngInt(u7));
1577 if (offseted.is != 0) {
1578 const len = if (offseted.sub)
1579 @as(u32, @intCast(u.bytes.len)) -| offseted.by
1580 else
1581 @min(u.bytes.len + offseted.by, @as(u32, @intCast(buf.len)));
1582 if (weightsContain(len, len_weights)) {
1583 break :len len;
1584 }
1585 }
1586 break :len @intCast(f.weightedValue(
1587 len_weights,
1588 sumWeightsInclusive(len_weights),
1589 ));
1590 };
1591 f.mutateBytes(u.bytes, buf[0..len], byte_weights);
1592 return len;
1593 },
1594 .fresh => {
1595 const len: u32 = @intCast(f.weightedValue(
1596 len_weights,
1597 sumWeightsInclusive(len_weights),
1598 ));
1599 const weights_incl_sum = sumWeightsInclusive(byte_weights);
1600 for (buf[0..len]) |*o| {
1601 o.* = @intCast(f.weightedValue(byte_weights, weights_incl_sum));
1602 }
1603 return len;
1604 },
1605 }
1606 }
1607
1608 pub fn nextSlice(
1609 f: *Fuzzer,
1610 uid: Uid,
1611 buf: []u8,
1612 len_weights: []const abi.Weight,
1613 byte_weights: []const abi.Weight,
1614 ) u32 {
1615 f.req_values += 1;
1616 if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1617 @branchHint(.unlikely);
1618 const n = f.bytes_input.sliceWeightedWithHash(
1619 buf,
1620 len_weights,
1621 byte_weights,
1622 undefined,
1623 );
1624 if (f.corpus_pos == .bytes_fresh) {
1625 f.input_builder.checkSmithedLen(@as(usize, 4) + n);
1626 f.input_builder.addBytes(uid, buf[0..n]);
1627 }
1628 return n;
1629 }
1630
1631 const n = f.nextSliceInner(uid, buf, len_weights, byte_weights);
1632 f.mmap_input.appendLittleInt(u32, n);
1633 f.mmap_input.appendSlice(buf[0..n]);
1634 f.req_bytes += n;
1635 return n;
1636 }
5841637};
5851638
586/// Instrumentation must not be triggered before this function is called
5871639export fn fuzzer_init(cache_dir_path: abi.Slice) void {
588 inst.depreinit();
5891640 exec = .init(cache_dir_path.toSlice());
590 inst = .init();
1641 fuzzer = .init();
5911642}
5921643
593/// Invalid until `fuzzer_init` is called.
5941644export fn fuzzer_coverage() abi.Coverage {
5951645 const coverage_id = exec.pc_digest;
596 const header: *const abi.SeenPcsHeader = @ptrCast(@volatileCast(exec.shared_seen_pcs.items.ptr));
1646 const header = @volatileCast(exec.seenPcsHeader());
5971647
5981648 var seen_count: usize = 0;
5991649 for (header.seenBits()) |chunk| {
......@@ -608,107 +1658,63 @@ export fn fuzzer_coverage() abi.Coverage {
6081658 };
6091659}
6101660
611/// fuzzer_init must be called beforehand
612export fn fuzzer_init_test(test_one: abi.TestOne, unit_test_name: abi.Slice) void {
1661export fn fuzzer_set_test(test_one: abi.TestOne, unit_test_name: abi.Slice) void {
6131662 current_test_name = unit_test_name.toSlice();
614 fuzzer = .init(test_one, unit_test_name.toSlice());
1663 fuzzer.setTest(test_one, unit_test_name.toSlice());
6151664}
6161665
617/// fuzzer_init_test must be called beforehand
618/// The callee owns the memory of bytes and must not free it until the fuzzer is finished.
6191666export fn fuzzer_new_input(bytes: abi.Slice) void {
620 // An entry of length zero is always added and duplicates of it are not allowed.
621 if (bytes.len != 0)
622 fuzzer.addInput(bytes.toSlice());
1667 if (bytes.len == 0) return; // An entry of length zero is always present
1668 fuzzer.newInput(bytes.toSlice(), false);
6231669}
6241670
625/// fuzzer_init_test must be called first
6261671export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void {
1672 fuzzer.loadCorpus();
6271673 switch (limit_kind) {
6281674 .forever => while (true) fuzzer.cycle(),
6291675 .iterations => for (0..amount) |_| fuzzer.cycle(),
6301676 }
1677 fuzzer.reset();
6311678}
6321679
633export fn fuzzer_unslide_address(addr: usize) usize {
634 const si = std.debug.getSelfDebugInfo() catch @compileError("unsupported");
635 const slide = si.getModuleSlide(io, addr) catch |err| {
636 std.debug.panic("failed to find virtual address slide: {t}", .{err});
637 };
638 return addr - slide;
639}
640
641/// Helps determine run uniqueness in the face of recursion.
642/// Currently not used by the fuzzer.
643export threadlocal var __sancov_lowest_stack: usize = 0;
644
645/// Inline since the return address of the callee is required
646inline fn genericConstCmp(T: anytype, val: T, comptime const_vals_field: []const u8) void {
647 if (!inst.constPcSeen(@returnAddress())) {
648 @branchHint(.unlikely);
649 @field(inst, const_vals_field).append(gpa, val) catch @panic("OOM");
650 }
651}
652
653export fn __sanitizer_cov_trace_const_cmp1(const_arg: u8, arg: u8) void {
654 _ = const_arg;
655 _ = arg;
1680export fn fuzzer_int(uid: Uid, weights: abi.Weights) u64 {
1681 assert(uid.kind == .int);
1682 return fuzzer.nextInt(uid, weights.toSlice());
6561683}
6571684
658export fn __sanitizer_cov_trace_const_cmp2(const_arg: u16, arg: u16) void {
659 _ = arg;
660 genericConstCmp(u16, const_arg, "const_vals2");
1685export fn fuzzer_eos(uid: Uid, weights: abi.Weights) bool {
1686 assert(uid.kind == .int);
1687 return fuzzer.nextEos(uid, weights.toSlice());
6611688}
6621689
663export fn __sanitizer_cov_trace_const_cmp4(const_arg: u32, arg: u32) void {
664 _ = arg;
665 genericConstCmp(u32, const_arg, "const_vals4");
1690export fn fuzzer_bytes(uid: Uid, out: abi.MutSlice, weights: abi.Weights) void {
1691 assert(uid.kind == .bytes);
1692 return fuzzer.nextBytes(uid, out.toSlice(), weights.toSlice());
6661693}
6671694
668export fn __sanitizer_cov_trace_const_cmp8(const_arg: u64, arg: u64) void {
669 _ = arg;
670 genericConstCmp(u64, const_arg, "const_vals8");
1695export fn fuzzer_slice(
1696 uid: Uid,
1697 buf: abi.MutSlice,
1698 len_weights: abi.Weights,
1699 byte_weights: abi.Weights,
1700) u32 {
1701 assert(uid.kind == .bytes);
1702 return fuzzer.nextSlice(uid, buf.toSlice(), len_weights.toSlice(), byte_weights.toSlice());
6711703}
6721704
673export fn __sanitizer_cov_trace_switch(val: u64, cases: [*]const u64) void {
674 _ = val;
675 if (!inst.constPcSeen(@returnAddress())) {
676 @branchHint(.unlikely);
677 const case_bits = cases[1];
678 const cases_slice = cases[2..][0..cases[0]];
679 switch (case_bits) {
680 // 8-bit cases are ignored because they are likely to be randomly generated
681 0...8 => {},
682 9...16 => for (cases_slice) |c|
683 inst.const_vals2.append(gpa, @truncate(c)) catch @panic("OOM"),
684 17...32 => for (cases_slice) |c|
685 inst.const_vals4.append(gpa, @truncate(c)) catch @panic("OOM"),
686 33...64 => for (cases_slice) |c|
687 inst.const_vals8.append(gpa, @truncate(c)) catch @panic("OOM"),
688 else => {}, // Should be impossible
689 }
690 }
691}
692
693export fn __sanitizer_cov_trace_cmp1(arg1: u8, arg2: u8) void {
694 _ = arg1;
695 _ = arg2;
696}
697
698export fn __sanitizer_cov_trace_cmp2(arg1: u16, arg2: u16) void {
699 _ = arg1;
700 _ = arg2;
701}
702
703export fn __sanitizer_cov_trace_cmp4(arg1: u32, arg2: u32) void {
704 _ = arg1;
705 _ = arg2;
1705export fn fuzzer_unslide_address(addr: usize) usize {
1706 const si = std.debug.getSelfDebugInfo() catch @compileError("unsupported");
1707 const slide = si.getModuleSlide(io, addr) catch |err| {
1708 // The LLVM backend seems to insert placeholder values of `1` in __sancov_pcs1
1709 if (addr == 1) return 1;
1710 panic("failed to find virtual address slide for address 0x{x}: {t}", .{ addr, err });
1711 };
1712 return addr - slide;
7061713}
7071714
708export fn __sanitizer_cov_trace_cmp8(arg1: u64, arg2: u64) void {
709 _ = arg1;
710 _ = arg2;
711}
1715/// Helps determine run uniqueness in the face of recursion.
1716/// Currently not used by the fuzzer.
1717export threadlocal var __sancov_lowest_stack: usize = 0;
7121718
7131719export fn __sanitizer_cov_trace_pc_indir(callee: usize) void {
7141720 // Not valuable because we already have pc tracing via 8bit counters.
......@@ -729,723 +1735,117 @@ export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void {
7291735 _ = end;
7301736}
7311737
732/// Copy all of source into dest at position 0.
733/// If the slices overlap, dest.ptr must be <= src.ptr.
734fn volatileCopyForwards(comptime T: type, dest: []volatile T, source: []const volatile T) void {
735 for (dest, source) |*d, s| d.* = s;
736}
737
738/// Copy all of source into dest at position 0.
739/// If the slices overlap, dest.ptr must be >= src.ptr.
740fn volatileCopyBackwards(comptime T: type, dest: []volatile T, source: []const volatile T) void {
741 var i = source.len;
742 while (i > 0) {
743 i -= 1;
744 dest[i] = source[i];
745 }
746}
747
748const Mutation = enum {
749 /// Applies .insert_*_span, .push_*_span
750 /// For wtf-8, this limits code units, not code points
751 const max_insert_len = 12;
752 /// Applies to .insert_large_*_span and .push_large_*_span
753 /// 4096 is used as it is a common sector size
754 const max_large_insert_len = 4096;
755 /// Applies to .delete_span and .pop_span
756 const max_delete_len = 16;
757 /// Applies to .set_*span, .move_span, .set_existing_span
758 const max_set_len = 12;
759 const max_replicate_len = 64;
760 const AddValue = i6;
761 const SmallValue = i10;
762
763 delete_byte,
764 delete_span,
765 /// Removes the last byte from the input
766 pop_byte,
767 pop_span,
768 /// Inserts a group of bytes which is already in the input and removes the original copy.
769 move_span,
770 /// Replaces a group of bytes in the input with another group of bytes in the input
771 set_existing_span,
772 insert_existing_span,
773 push_existing_span,
774 set_rng_byte,
775 set_rng_span,
776 insert_rng_byte,
777 insert_rng_span,
778 /// Adds a byte to the end of the input
779 push_rng_byte,
780 push_rng_span,
781 set_zero_byte,
782 set_zero_span,
783 insert_zero_byte,
784 insert_zero_span,
785 push_zero_byte,
786 push_zero_span,
787 /// Inserts a lot of zeros to the end of the input
788 /// This is intended to work with fuzz tests that require data in (large) blocks
789 push_large_zero_span,
790 /// Inserts a group of ascii printable character
791 insert_print_span,
792 /// Inserts a group of character from a...z, A...Z, 0...9, _, and ' '
793 insert_common_span,
794 /// Inserts a group of ascii digits possibly preceded by a `-`
795 insert_integer,
796 /// Code units are evenly distributed between one to four
797 insert_wtf8_char,
798 insert_wtf8_span,
799 /// Inserts a group of bytes from another input
800 insert_splice_span,
801 // utf16 is not yet included since insertion of random bytes should adaquetly check
802 // BMP character, surrogate handling, and occasionally chacters outside of the BMP.
803 set_print_span,
804 set_common_span,
805 set_splice_span,
806 /// Similar to set_splice_span, but the bytes are copied to the same index instead of a random
807 replicate_splice_span,
808 push_print_span,
809 push_common_span,
810 push_integer,
811 push_wtf8_char,
812 push_wtf8_span,
813 push_splice_span,
814 /// Clears a random amount of high bits of a byte
815 truncate_8,
816 truncate_16le,
817 truncate_16be,
818 truncate_32le,
819 truncate_32be,
820 truncate_64le,
821 truncate_64be,
822 /// Flips a random bit
823 xor_1,
824 /// Swaps up to three bits of a byte biased to less bits
825 xor_few_8,
826 /// Swaps up to six bits of a 16-bit value biased to less bits
827 xor_few_16,
828 /// Swaps up to nine bits of a 32-bit value biased to less bits
829 xor_few_32,
830 /// Swaps up to twelve bits of 64-bit value biased to less bits
831 xor_few_64,
832 /// Adds to a byte a value of type AddValue
833 add_8,
834 add_16le,
835 add_16be,
836 add_32le,
837 add_32be,
838 add_64le,
839 add_64be,
840 /// Sets a 16-bit little-endian value to a value of type SmallValue
841 set_small_16le,
842 set_small_16be,
843 set_small_32le,
844 set_small_32be,
845 set_small_64le,
846 set_small_64be,
847 insert_small_16le,
848 insert_small_16be,
849 insert_small_32le,
850 insert_small_32be,
851 insert_small_64le,
852 insert_small_64be,
853 push_small_16le,
854 push_small_16be,
855 push_small_32le,
856 push_small_32be,
857 push_small_64le,
858 push_small_64be,
859 set_const_16,
860 set_const_32,
861 set_const_64,
862 set_const_128,
863 insert_const_16,
864 insert_const_32,
865 insert_const_64,
866 insert_const_128,
867 push_const_16,
868 push_const_32,
869 push_const_64,
870 push_const_128,
871 /// Sets a byte with up to three bits set biased to less bits
872 set_few_8,
873 /// Sets a 16-bit value with up to six bits set biased to less bits
874 set_few_16,
875 /// Sets a 32-bit value with up to nine bits set biased to less bits
876 set_few_32,
877 /// Sets a 64-bit value with up to twelve bits set biased to less bits
878 set_few_64,
879 insert_few_8,
880 insert_few_16,
881 insert_few_32,
882 insert_few_64,
883 push_few_8,
884 push_few_16,
885 push_few_32,
886 push_few_64,
887 /// Randomizes a random contigous group of bits in a byte
888 packed_set_rng_8,
889 packed_set_rng_16le,
890 packed_set_rng_16be,
891 packed_set_rng_32le,
892 packed_set_rng_32be,
893 packed_set_rng_64le,
894 packed_set_rng_64be,
895
896 fn fewValue(rng: std.Random, T: type, comptime bits: u16) T {
897 var result: T = 0;
898 var remaining_bits = rng.intRangeAtMostBiased(u16, 1, bits);
899 while (remaining_bits > 0) {
900 result |= @shlExact(@as(T, 1), rng.int(math.Log2Int(T)));
901 remaining_bits -= 1;
902 }
903 return result;
904 }
905
906 /// Returns if the mutation was applicable to the input
907 pub fn mutate(
908 mutation: Mutation,
909 rng: std.Random,
910 in: []const u8,
911 out: *MemoryMappedList,
912 corpus: []const []const u8,
913 const_vals2: []const u16,
914 const_vals4: []const u32,
915 const_vals8: []const u64,
916 const_vals16: []const u128,
917 ) bool {
918 out.clearRetainingCapacity();
919 const new_capacity = 8 + in.len + @max(
920 16, // builtin 128 value
921 Mutation.max_insert_len,
922 Mutation.max_large_insert_len,
923 );
924 out.ensureTotalCapacity(new_capacity) catch |e|
925 panic("could not resize shared input file: {t}", .{e});
926 out.items.len = 8; // Length field
927
928 const applied = switch (mutation) {
929 inline else => |m| m.comptimeMutate(
930 rng,
931 in,
932 out,
933 corpus,
934 const_vals2,
935 const_vals4,
936 const_vals8,
937 const_vals16,
938 ),
939 };
940 if (!applied)
941 assert(out.items.len == 8)
942 else
943 assert(out.items.len <= new_capacity);
944 return applied;
945 }
946
947 /// Assumes out has already been cleared
948 fn comptimeMutate(
949 comptime mutation: Mutation,
950 rng: std.Random,
951 in: []const u8,
952 out: *MemoryMappedList,
953 corpus: []const []const u8,
954 const_vals2: []const u16,
955 const_vals4: []const u32,
956 const_vals8: []const u64,
957 const_vals16: []const u128,
958 ) bool {
959 const Class = enum { new, remove, rmw, move_span, replicate_splice_span };
960 const class: Class, const class_ctx = switch (mutation) {
961 // zig fmt: off
962 .move_span => .{ .move_span, null },
963 .replicate_splice_span => .{ .replicate_splice_span, null },
964
965 .delete_byte => .{ .remove, .{ .delete, 1 } },
966 .delete_span => .{ .remove, .{ .delete, max_delete_len } },
967
968 .pop_byte => .{ .remove, .{ .pop, 1 } },
969 .pop_span => .{ .remove, .{ .pop, max_delete_len } },
970
971 .set_rng_byte => .{ .new, .{ .set , 1, .rng , .one } },
972 .set_zero_byte => .{ .new, .{ .set , 1, .zero , .one } },
973 .set_rng_span => .{ .new, .{ .set , 1, .rng , .many } },
974 .set_zero_span => .{ .new, .{ .set , 1, .zero , .many } },
975 .set_common_span => .{ .new, .{ .set , 1, .common , .many } },
976 .set_print_span => .{ .new, .{ .set , 1, .print , .many } },
977 .set_existing_span => .{ .new, .{ .set , 2, .existing, .many } },
978 .set_splice_span => .{ .new, .{ .set , 1, .splice , .many } },
979 .set_const_16 => .{ .new, .{ .set , 2, .@"const", const_vals2 } },
980 .set_const_32 => .{ .new, .{ .set , 4, .@"const", const_vals4 } },
981 .set_const_64 => .{ .new, .{ .set , 8, .@"const", const_vals8 } },
982 .set_const_128 => .{ .new, .{ .set , 16, .@"const", const_vals16 } },
983 .set_small_16le => .{ .new, .{ .set , 2, .small , .{ i16, .little } } },
984 .set_small_32le => .{ .new, .{ .set , 4, .small , .{ i32, .little } } },
985 .set_small_64le => .{ .new, .{ .set , 8, .small , .{ i64, .little } } },
986 .set_small_16be => .{ .new, .{ .set , 2, .small , .{ i16, .big } } },
987 .set_small_32be => .{ .new, .{ .set , 4, .small , .{ i32, .big } } },
988 .set_small_64be => .{ .new, .{ .set , 8, .small , .{ i64, .big } } },
989 .set_few_8 => .{ .new, .{ .set , 1, .few , .{ u8 , 3 } } },
990 .set_few_16 => .{ .new, .{ .set , 2, .few , .{ u16, 6 } } },
991 .set_few_32 => .{ .new, .{ .set , 4, .few , .{ u32, 9 } } },
992 .set_few_64 => .{ .new, .{ .set , 8, .few , .{ u64, 12 } } },
993
994 .insert_rng_byte => .{ .new, .{ .insert, 0, .rng , .one } },
995 .insert_zero_byte => .{ .new, .{ .insert, 0, .zero , .one } },
996 .insert_rng_span => .{ .new, .{ .insert, 0, .rng , .many } },
997 .insert_zero_span => .{ .new, .{ .insert, 0, .zero , .many } },
998 .insert_print_span => .{ .new, .{ .insert, 0, .print , .many } },
999 .insert_common_span => .{ .new, .{ .insert, 0, .common , .many } },
1000 .insert_integer => .{ .new, .{ .insert, 0, .integer , .many } },
1001 .insert_wtf8_char => .{ .new, .{ .insert, 0, .wtf8 , .one } },
1002 .insert_wtf8_span => .{ .new, .{ .insert, 0, .wtf8 , .many } },
1003 .insert_existing_span => .{ .new, .{ .insert, 1, .existing, .many } },
1004 .insert_splice_span => .{ .new, .{ .insert, 0, .splice , .many } },
1005 .insert_const_16 => .{ .new, .{ .insert, 0, .@"const", const_vals2 } },
1006 .insert_const_32 => .{ .new, .{ .insert, 0, .@"const", const_vals4 } },
1007 .insert_const_64 => .{ .new, .{ .insert, 0, .@"const", const_vals8 } },
1008 .insert_const_128 => .{ .new, .{ .insert, 0, .@"const", const_vals16 } },
1009 .insert_small_16le => .{ .new, .{ .insert, 0, .small , .{ i16, .little } } },
1010 .insert_small_32le => .{ .new, .{ .insert, 0, .small , .{ i32, .little } } },
1011 .insert_small_64le => .{ .new, .{ .insert, 0, .small , .{ i64, .little } } },
1012 .insert_small_16be => .{ .new, .{ .insert, 0, .small , .{ i16, .big } } },
1013 .insert_small_32be => .{ .new, .{ .insert, 0, .small , .{ i32, .big } } },
1014 .insert_small_64be => .{ .new, .{ .insert, 0, .small , .{ i64, .big } } },
1015 .insert_few_8 => .{ .new, .{ .insert, 0, .few , .{ u8 , 3 } } },
1016 .insert_few_16 => .{ .new, .{ .insert, 0, .few , .{ u16, 6 } } },
1017 .insert_few_32 => .{ .new, .{ .insert, 0, .few , .{ u32, 9 } } },
1018 .insert_few_64 => .{ .new, .{ .insert, 0, .few , .{ u64, 12 } } },
1019
1020 .push_rng_byte => .{ .new, .{ .push , 0, .rng , .one } },
1021 .push_zero_byte => .{ .new, .{ .push , 0, .zero , .one } },
1022 .push_rng_span => .{ .new, .{ .push , 0, .rng , .many } },
1023 .push_zero_span => .{ .new, .{ .push , 0, .zero , .many } },
1024 .push_print_span => .{ .new, .{ .push , 0, .print , .many } },
1025 .push_common_span => .{ .new, .{ .push , 0, .common , .many } },
1026 .push_integer => .{ .new, .{ .push , 0, .integer , .many } },
1027 .push_large_zero_span => .{ .new, .{ .push , 0, .zero , .large } },
1028 .push_wtf8_char => .{ .new, .{ .push , 0, .wtf8 , .one } },
1029 .push_wtf8_span => .{ .new, .{ .push , 0, .wtf8 , .many } },
1030 .push_existing_span => .{ .new, .{ .push , 1, .existing, .many } },
1031 .push_splice_span => .{ .new, .{ .push , 0, .splice , .many } },
1032 .push_const_16 => .{ .new, .{ .push , 0, .@"const", const_vals2 } },
1033 .push_const_32 => .{ .new, .{ .push , 0, .@"const", const_vals4 } },
1034 .push_const_64 => .{ .new, .{ .push , 0, .@"const", const_vals8 } },
1035 .push_const_128 => .{ .new, .{ .push , 0, .@"const", const_vals16 } },
1036 .push_small_16le => .{ .new, .{ .push , 0, .small , .{ i16, .little } } },
1037 .push_small_32le => .{ .new, .{ .push , 0, .small , .{ i32, .little } } },
1038 .push_small_64le => .{ .new, .{ .push , 0, .small , .{ i64, .little } } },
1039 .push_small_16be => .{ .new, .{ .push , 0, .small , .{ i16, .big } } },
1040 .push_small_32be => .{ .new, .{ .push , 0, .small , .{ i32, .big } } },
1041 .push_small_64be => .{ .new, .{ .push , 0, .small , .{ i64, .big } } },
1042 .push_few_8 => .{ .new, .{ .push , 0, .few , .{ u8 , 3 } } },
1043 .push_few_16 => .{ .new, .{ .push , 0, .few , .{ u16, 6 } } },
1044 .push_few_32 => .{ .new, .{ .push , 0, .few , .{ u32, 9 } } },
1045 .push_few_64 => .{ .new, .{ .push , 0, .few , .{ u64, 12 } } },
1046
1047 .xor_1 => .{ .rmw, .{ .xor , u8 , native_endian, 1 } },
1048 .xor_few_8 => .{ .rmw, .{ .xor , u8 , native_endian, 3 } },
1049 .xor_few_16 => .{ .rmw, .{ .xor , u16, native_endian, 6 } },
1050 .xor_few_32 => .{ .rmw, .{ .xor , u32, native_endian, 9 } },
1051 .xor_few_64 => .{ .rmw, .{ .xor , u64, native_endian, 12 } },
1052
1053 .truncate_8 => .{ .rmw, .{ .truncate , u8 , native_endian, {} } },
1054 .truncate_16le => .{ .rmw, .{ .truncate , u16, .little , {} } },
1055 .truncate_32le => .{ .rmw, .{ .truncate , u32, .little , {} } },
1056 .truncate_64le => .{ .rmw, .{ .truncate , u64, .little , {} } },
1057 .truncate_16be => .{ .rmw, .{ .truncate , u16, .big , {} } },
1058 .truncate_32be => .{ .rmw, .{ .truncate , u32, .big , {} } },
1059 .truncate_64be => .{ .rmw, .{ .truncate , u64, .big , {} } },
1060
1061 .add_8 => .{ .rmw, .{ .add , i8 , native_endian, {} } },
1062 .add_16le => .{ .rmw, .{ .add , i16, .little , {} } },
1063 .add_32le => .{ .rmw, .{ .add , i32, .little , {} } },
1064 .add_64le => .{ .rmw, .{ .add , i64, .little , {} } },
1065 .add_16be => .{ .rmw, .{ .add , i16, .big , {} } },
1066 .add_32be => .{ .rmw, .{ .add , i32, .big , {} } },
1067 .add_64be => .{ .rmw, .{ .add , i64, .big , {} } },
1068
1069 .packed_set_rng_8 => .{ .rmw, .{ .packed_rng, u8 , native_endian, {} } },
1070 .packed_set_rng_16le => .{ .rmw, .{ .packed_rng, u16, .little , {} } },
1071 .packed_set_rng_32le => .{ .rmw, .{ .packed_rng, u32, .little , {} } },
1072 .packed_set_rng_64le => .{ .rmw, .{ .packed_rng, u64, .little , {} } },
1073 .packed_set_rng_16be => .{ .rmw, .{ .packed_rng, u16, .big , {} } },
1074 .packed_set_rng_32be => .{ .rmw, .{ .packed_rng, u32, .big , {} } },
1075 .packed_set_rng_64be => .{ .rmw, .{ .packed_rng, u64, .big , {} } },
1076 // zig fmt: on
1077 };
1078
1079 switch (class) {
1080 .new => {
1081 const op: enum {
1082 set,
1083 insert,
1084 push,
1085
1086 pub fn maxLen(comptime op: @This(), in_len: usize) usize {
1087 return switch (op) {
1088 .set => @min(in_len, max_set_len),
1089 .insert, .push => max_insert_len,
1090 };
1091 }
1092 }, const min_in_len, const data: enum {
1093 rng,
1094 zero,
1095 common,
1096 print,
1097 integer,
1098 wtf8,
1099 existing,
1100 splice,
1101 @"const",
1102 small,
1103 few,
1104 }, const data_ctx = class_ctx;
1105 const Size = enum { one, many, large };
1106 if (in.len < min_in_len) return false;
1107 if (data == .@"const" and data_ctx.len == 0) return false;
1108
1109 const splice_i = if (data == .splice) blk: {
1110 // Element zero always holds an empty input, so we do not select it
1111 if (corpus.len == 1) return false;
1112 break :blk rng.intRangeLessThanBiased(usize, 1, corpus.len);
1113 } else undefined;
1114
1115 // Only needs to be followed for set
1116 const len = switch (data) {
1117 else => switch (@as(Size, data_ctx)) {
1118 .one => 1,
1119 .many => rng.intRangeAtMostBiased(usize, 1, op.maxLen(in.len)),
1120 .large => rng.intRangeAtMostBiased(usize, 1, max_large_insert_len),
1121 },
1122 .wtf8 => undefined, // varies by size of each code unit
1123 .splice => rng.intRangeAtMostBiased(usize, 1, @min(
1124 corpus[splice_i].len,
1125 op.maxLen(in.len),
1126 )),
1127 .existing => rng.intRangeAtMostBiased(usize, 1, @min(
1128 in.len,
1129 op.maxLen(in.len),
1130 )),
1131 .@"const" => @sizeOf(@typeInfo(@TypeOf(data_ctx)).pointer.child),
1132 .small, .few => @sizeOf(data_ctx[0]),
1133 };
1134
1135 const i = switch (op) {
1136 .set => rng.uintAtMostBiased(usize, in.len - len),
1137 .insert => rng.uintAtMostBiased(usize, in.len),
1138 .push => in.len,
1139 };
1140
1141 out.appendSliceAssumeCapacity(in[0..i]);
1142 switch (data) {
1143 .rng => {
1144 var bytes: [@max(max_insert_len, max_set_len)]u8 = undefined;
1145 rng.bytes(bytes[0..len]);
1146 out.appendSliceAssumeCapacity(bytes[0..len]);
1147 },
1148 .zero => out.appendNTimesAssumeCapacity(0, len),
1149 .common => for (out.addManyAsSliceAssumeCapacity(len)) |*c| {
1150 c.* = switch (rng.int(u6)) {
1151 0 => ' ',
1152 1...10 => |x| '0' + (@as(u8, x) - 1),
1153 11...36 => |x| 'A' + (@as(u8, x) - 11),
1154 37 => '_',
1155 38...63 => |x| 'a' + (@as(u8, x) - 38),
1156 };
1157 },
1158 .print => for (out.addManyAsSliceAssumeCapacity(len)) |*c| {
1159 c.* = rng.intRangeAtMostBiased(u8, 0x20, 0x7E);
1160 },
1161 .integer => {
1162 const negative = len != 0 and rng.boolean();
1163 if (negative) {
1164 out.appendAssumeCapacity('-');
1165 }
1166
1167 for (out.addManyAsSliceAssumeCapacity(len - @intFromBool(negative))) |*c| {
1168 c.* = rng.intRangeAtMostBiased(u8, '0', '9');
1169 }
1170 },
1171 .wtf8 => {
1172 comptime assert(op != .set);
1173 var codepoints: usize = if (data_ctx == .one)
1174 1
1175 else
1176 rng.intRangeAtMostBiased(usize, 1, Mutation.max_insert_len / 4);
1177
1178 while (true) {
1179 const units1 = rng.int(u2);
1180 const value = switch (units1) {
1181 0 => rng.int(u7),
1182 1 => rng.intRangeAtMostBiased(u11, 0x000080, 0x0007FF),
1183 2 => rng.intRangeAtMostBiased(u16, 0x000800, 0x00FFFF),
1184 3 => rng.intRangeAtMostBiased(u21, 0x010000, 0x10FFFF),
1185 };
1186 const units = @as(u3, units1) + 1;
1187
1188 var buf: [4]u8 = undefined;
1189 assert(std.unicode.wtf8Encode(value, &buf) catch unreachable == units);
1190 out.appendSliceAssumeCapacity(buf[0..units]);
1191
1192 codepoints -= 1;
1193 if (codepoints == 0) break;
1194 }
1195 },
1196 .existing => {
1197 const j = rng.uintAtMostBiased(usize, in.len - len);
1198 out.appendSliceAssumeCapacity(in[j..][0..len]);
1199 },
1200 .splice => {
1201 const j = rng.uintAtMostBiased(usize, corpus[splice_i].len - len);
1202 out.appendSliceAssumeCapacity(corpus[splice_i][j..][0..len]);
1203 },
1204 .@"const" => out.appendSliceAssumeCapacity(@ptrCast(
1205 &data_ctx[rng.uintLessThanBiased(usize, data_ctx.len)],
1206 )),
1207 .small => out.appendSliceAssumeCapacity(@ptrCast(
1208 &mem.nativeTo(data_ctx[0], rng.int(SmallValue), data_ctx[1]),
1209 )),
1210 .few => out.appendSliceAssumeCapacity(@ptrCast(
1211 &fewValue(rng, data_ctx[0], data_ctx[1]),
1212 )),
1213 }
1214 switch (op) {
1215 .set => out.appendSliceAssumeCapacity(in[i + len ..]),
1216 .insert => out.appendSliceAssumeCapacity(in[i..]),
1217 .push => {},
1218 }
1219 },
1220 .remove => {
1221 if (in.len == 0) return false;
1222 const Op = enum { delete, pop };
1223 const op: Op, const max_len = class_ctx;
1224 // LessThan is used so we don't delete the entire span (which is unproductive since
1225 // an empty input has always been tried)
1226 const len = if (max_len == 1) 1 else rng.uintLessThanBiased(
1227 usize,
1228 @min(max_len + 1, in.len),
1229 );
1230 switch (op) {
1231 .delete => {
1232 const i = rng.uintAtMostBiased(usize, in.len - len);
1233 out.appendSliceAssumeCapacity(in[0..i]);
1234 out.appendSliceAssumeCapacity(in[i + len ..]);
1235 },
1236 .pop => out.appendSliceAssumeCapacity(in[0 .. in.len - len]),
1237 }
1238 },
1239 .rmw => {
1240 const Op = enum { xor, truncate, add, packed_rng };
1241 const op: Op, const T, const endian, const xor_bits = class_ctx;
1242 if (in.len < @sizeOf(T)) return false;
1243 const Log2T = math.Log2Int(T);
1244
1245 const idx = rng.uintAtMostBiased(usize, in.len - @sizeOf(T));
1246 const old = mem.readInt(T, in[idx..][0..@sizeOf(T)], endian);
1247 const new = switch (op) {
1248 .xor => old ^ fewValue(rng, T, xor_bits),
1249 .truncate => old & (@as(T, math.maxInt(T)) >> rng.int(Log2T)),
1250 .add => old +% addend: {
1251 const val = rng.int(Mutation.AddValue);
1252 break :addend if (val == 0) 1 else val;
1253 },
1254 .packed_rng => blk: {
1255 const bits = rng.int(math.Log2Int(T)) +| 1;
1256 break :blk old ^ (rng.int(T) >> bits << rng.uintAtMostBiased(Log2T, bits));
1257 },
1258 };
1259 out.appendSliceAssumeCapacity(in);
1260 mem.bytesAsValue(T, out.items[8..][idx..][0..@sizeOf(T)]).* =
1261 mem.nativeTo(T, new, endian);
1262 },
1263 .move_span => {
1264 if (in.len < 2) return false;
1265 // One less since moving whole output will never change anything
1266 const len = rng.intRangeAtMostBiased(usize, 1, @min(
1267 in.len - 1,
1268 Mutation.max_set_len,
1269 ));
1270
1271 const src = rng.uintAtMostBiased(usize, in.len - len);
1272 // This indexes into the final input
1273 const dst = blk: {
1274 const res = rng.uintAtMostBiased(usize, in.len - len - 1);
1275 break :blk res + @intFromBool(res >= src);
1276 };
1277
1278 if (src < dst) {
1279 out.appendSliceAssumeCapacity(in[0..src]);
1280 out.appendSliceAssumeCapacity(in[src + len .. dst + len]);
1281 out.appendSliceAssumeCapacity(in[src..][0..len]);
1282 out.appendSliceAssumeCapacity(in[dst + len ..]);
1283 } else {
1284 out.appendSliceAssumeCapacity(in[0..dst]);
1285 out.appendSliceAssumeCapacity(in[src..][0..len]);
1286 out.appendSliceAssumeCapacity(in[dst..src]);
1287 out.appendSliceAssumeCapacity(in[src + len ..]);
1288 }
1289 },
1290 .replicate_splice_span => {
1291 if (in.len == 0) return false;
1292 if (corpus.len == 1) return false;
1293 const from = corpus[rng.intRangeLessThanBiased(usize, 1, corpus.len)];
1294 const len = rng.uintLessThanBiased(usize, @min(in.len, from.len, max_replicate_len));
1295 const i = rng.uintAtMostBiased(usize, @min(in.len, from.len) - len);
1296 out.appendSliceAssumeCapacity(in[0..i]);
1297 out.appendSliceAssumeCapacity(from[i..][0..len]);
1298 out.appendSliceAssumeCapacity(in[i + len ..]);
1299 },
1300 }
1301 return true;
1302 }
1303};
1304
1305/// Like `std.ArrayList(u8)` but backed by memory mapping.
1306pub const MemoryMappedList = struct {
1307 /// Contents of the list.
1738/// Reusable and recoverable input.
1739///
1740/// Has a 32-bit limit on the input length. This has the nice side effect that `u32`
1741/// can be used in most placed in `fuzzer` with the last four values reserved.
1742const MemoryMappedInput = struct {
1743 len: u32,
1744 /// Directly accessing `memory` is unsafe, use either `inputSlice` or `writeSlice`.
13081745 ///
1309 /// Pointers to elements in this slice are invalidated by various functions
1310 /// of this ArrayList in accordance with the respective documentation. In
1311 /// all cases, "invalidated" means that the memory has been passed to this
1312 /// allocator's resize or free function.
1313 items: []align(std.heap.page_size_min) volatile u8,
1314 /// How many bytes this list can hold without allocating additional memory.
1315 capacity: usize,
1316 /// The file is kept open so that it can be resized.
1317 file: Io.File,
1318
1319 pub fn init(file: Io.File, length: usize, capacity: usize) !MemoryMappedList {
1320 const ptr = try std.posix.mmap(
1321 null,
1322 capacity,
1323 .{ .READ = true, .WRITE = true },
1324 .{ .TYPE = .SHARED },
1325 file.handle,
1326 0,
1327 );
1746 /// `memory` starts with the length of the input as a little-endian 32-bit integer.
1747 mmap: Io.File.MemoryMap,
1748
1749 /// `file` becomes owned by the returned `MemoryMappedInput`
1750 pub fn init(file: Io.File, size: usize) !MemoryMappedInput {
1751 assert(size >= 4);
13281752 return .{
1329 .file = file,
1330 .items = ptr[0..length],
1331 .capacity = capacity,
1753 .len = 0,
1754 .mmap = try file.createMemoryMap(io, .{ .len = size }),
13321755 };
13331756 }
13341757
1335 pub fn create(file: Io.File, length: usize, capacity: usize) !MemoryMappedList {
1336 try file.setLength(io, capacity);
1337 return init(file, length, capacity);
1338 }
1339
1340 pub fn deinit(l: *MemoryMappedList) void {
1341 l.file.close(io);
1342 std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity]));
1758 pub fn deinit(l: *MemoryMappedInput) void {
1759 const f = l.mmap.file;
1760 l.mmap.write(io) catch |e| panic("failed to write memory map of 'in': {t}", .{e});
1761 l.mmap.destroy(io);
1762 f.close(io);
13431763 l.* = undefined;
13441764 }
13451765
13461766 /// Modify the array so that it can hold at least `additional_count` **more** items.
1767 ///
13471768 /// Invalidates element pointers if additional memory is needed.
1348 pub fn ensureUnusedCapacity(l: *MemoryMappedList, additional_count: usize) !void {
1349 return l.ensureTotalCapacity(l.items.len + additional_count);
1769 pub fn ensureUnusedCapacity(l: *MemoryMappedInput, additional_count: usize) void {
1770 return l.ensureTotalCapacity(4 + l.len + additional_count);
13501771 }
13511772
1352 /// If the current capacity is less than `new_capacity`, this function will
1353 /// modify the array so that it can hold at least `new_capacity` items.
1773 /// If the current capacity is less than `min_capacity`, this function will
1774 /// modify the array so that it can hold at least `min_capacity` items.
1775 ///
13541776 /// Invalidates element pointers if additional memory is needed.
1355 pub fn ensureTotalCapacity(l: *MemoryMappedList, new_capacity: usize) !void {
1356 if (l.capacity >= new_capacity) return;
1357
1358 const better_capacity = growCapacity(l.capacity, new_capacity);
1359 return l.ensureTotalCapacityPrecise(better_capacity);
1360 }
1361
1362 pub fn ensureTotalCapacityPrecise(l: *MemoryMappedList, new_capacity: usize) !void {
1363 if (l.capacity >= new_capacity) return;
1777 pub fn ensureTotalCapacity(l: *MemoryMappedInput, min_capacity: usize) void {
1778 if (l.mmap.memory.len < min_capacity) {
1779 @branchHint(.unlikely);
13641780
1365 std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity]));
1366 try l.file.setLength(io, new_capacity);
1367 l.* = try init(l.file, l.items.len, new_capacity);
1781 const max_capacity = 1 << 32; // The size of the length header is not added
1782 // in order to keep the capacity page aligned and to allow those values to
1783 // reserved for other places.
1784 if (min_capacity > max_capacity) @panic("too much smith data requested");
1785
1786 const new_capacity = @min(growCapacity(min_capacity), max_capacity);
1787 l.mmap.file.setLength(io, new_capacity) catch |e|
1788 panic("failed to resize 'in': {t}", .{e});
1789 l.mmap.setLength(io, new_capacity) catch |se| switch (se) {
1790 error.OperationUnsupported => {
1791 const f = l.mmap.file;
1792 l.mmap.destroy(io);
1793 l.mmap = f.createMemoryMap(io, .{ .len = new_capacity }) catch |e|
1794 panic("failed to memory map 'in': {t}", .{e});
1795 },
1796 else => panic("failed to resize memory map of 'in': {t}", .{se}),
1797 };
1798 }
13681799 }
13691800
1370 /// Invalidates all element pointers.
1371 pub fn clearRetainingCapacity(l: *MemoryMappedList) void {
1372 l.items.len = 0;
1801 // Only writing has side effects, so volatile is not needed
1802 pub fn inputSlice(l: *MemoryMappedInput) []const u8 {
1803 return l.mmap.memory[4..][0..l.len];
13731804 }
13741805
1375 /// Append the slice of items to the list.
1376 /// Asserts that the list can hold the additional items.
1377 pub fn appendSliceAssumeCapacity(l: *MemoryMappedList, items: []const u8) void {
1378 const old_len = l.items.len;
1379 const new_len = old_len + items.len;
1380 assert(new_len <= l.capacity);
1381 l.items.len = new_len;
1382 @memcpy(l.items[old_len..][0..items.len], items);
1806 // Writing has side effectsd, so volatile is necessary
1807 pub fn writeSlice(l: *MemoryMappedInput) []volatile u8 {
1808 return l.mmap.memory;
13831809 }
13841810
1385 /// Extends the list by 1 element.
1386 /// Never invalidates element pointers.
1387 /// Asserts that the list can hold one additional item.
1388 pub fn appendAssumeCapacity(l: *MemoryMappedList, item: u8) void {
1389 const new_item_ptr = l.addOneAssumeCapacity();
1390 new_item_ptr.* = item;
1811 fn writeLen(l: *MemoryMappedInput) void {
1812 l.writeSlice()[0..4].* = @bitCast(mem.nativeToLittle(u32, l.len));
13911813 }
13921814
1393 /// Increase length by 1, returning pointer to the new item.
1394 /// The returned pointer becomes invalid when the list is resized.
1395 /// Never invalidates element pointers.
1396 /// Asserts that the list can hold one additional item.
1397 pub fn addOneAssumeCapacity(l: *MemoryMappedList) *volatile u8 {
1398 assert(l.items.len < l.capacity);
1399 l.items.len += 1;
1400 return &l.items[l.items.len - 1];
1815 /// Invalidates all element pointers.
1816 pub fn clearRetainingCapacity(l: *MemoryMappedInput) void {
1817 l.len = 0;
1818 l.writeLen();
14011819 }
14021820
1403 /// Append a value to the list `n` times.
1404 /// Never invalidates element pointers.
1405 /// The function is inline so that a comptime-known `value` parameter will
1406 /// have better memset codegen in case it has a repeated byte pattern.
1407 /// Asserts that the list can hold the additional items.
1408 pub inline fn appendNTimesAssumeCapacity(l: *MemoryMappedList, value: u8, n: usize) void {
1409 const new_len = l.items.len + n;
1410 assert(new_len <= l.capacity);
1411 @memset(l.items.ptr[l.items.len..new_len], value);
1412 l.items.len = new_len;
1821 /// Append the slice of items to the list.
1822 ///
1823 /// Invalidates item pointers if more space is required.
1824 pub fn appendSlice(l: *MemoryMappedInput, items: []const u8) void {
1825 l.ensureUnusedCapacity(items.len);
1826 @memcpy(l.writeSlice()[4 + l.len ..][0..items.len], items);
1827 l.len += @as(u32, @intCast(items.len));
1828 l.writeLen();
14131829 }
14141830
1415 /// Resize the array, adding `n` new elements, which have `undefined` values.
1416 /// The return value is a slice pointing to the newly allocated elements.
1417 /// Never invalidates element pointers.
1418 /// The returned pointer becomes invalid when the list is resized.
1419 /// Asserts that the list can hold the additional items.
1420 pub fn addManyAsSliceAssumeCapacity(l: *MemoryMappedList, n: usize) []volatile u8 {
1421 assert(l.items.len + n <= l.capacity);
1422 const prev_len = l.items.len;
1423 l.items.len += n;
1424 return l.items[prev_len..][0..n];
1831 /// Append the little-endian integer to the list.
1832 ///
1833 /// Invalidates item pointers if more space is required.
1834 pub fn appendLittleInt(l: *MemoryMappedInput, T: type, x: T) void {
1835 l.ensureUnusedCapacity(@sizeOf(T));
1836 //std.log.debug("{} {} {}", .{ l.writeSlice().len, l.len, @sizeOf(T) });
1837 l.writeSlice()[4 + l.len ..][0..@sizeOf(T)].* = @bitCast(mem.nativeToLittle(T, x));
1838 l.len += @sizeOf(T);
1839 l.writeLen();
14251840 }
14261841
14271842 /// Called when memory growth is necessary. Returns a capacity larger than
14281843 /// minimum that grows super-linearly.
1429 fn growCapacity(current: usize, minimum: usize) usize {
1430 var new = current;
1431 while (true) {
1432 new = mem.alignForward(usize, new + new / 2, std.heap.page_size_max);
1433 if (new >= minimum) return new;
1434 }
1435 }
1436
1437 pub fn insertAssumeCapacity(l: *MemoryMappedList, i: usize, item: u8) void {
1438 assert(l.items.len + 1 <= l.capacity);
1439 l.items.len += 1;
1440 volatileCopyBackwards(u8, l.items[i + 1 ..], l.items[i .. l.items.len - 1]);
1441 l.items[i] = item;
1442 }
1443
1444 pub fn orderedRemove(l: *MemoryMappedList, i: usize) u8 {
1445 assert(l.items.len + 1 <= l.capacity);
1446 const old = l.items[i];
1447 volatileCopyForwards(u8, l.items[i .. l.items.len - 1], l.items[i + 1 ..]);
1448 l.items.len -= 1;
1449 return old;
1844 fn growCapacity(minimum: usize) usize {
1845 return mem.alignForward(
1846 usize,
1847 minimum +| (minimum / 2 + std.heap.page_size_max),
1848 std.heap.page_size_max,
1849 );
14501850 }
14511851};
lib/init/src/main.zig+27-7
......@@ -40,12 +40,32 @@ test "simple test" {
4040}
4141
4242test "fuzz example" {
43 const Context = struct {
44 fn testOne(context: @This(), input: []const u8) anyerror!void {
45 _ = context;
46 // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case!
47 try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input));
48 }
43 try std.testing.fuzz({}, testOne, .{});
44}
45
46fn testOne(context: void, smith: *std.testing.Smith) !void {
47 _ = context;
48 // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case!
49
50 const gpa = std.testing.allocator;
51 var list: std.ArrayList(u8) = .empty;
52 defer list.deinit(gpa);
53 while (!smith.eos()) switch (smith.value(enum { add_data, dup_data })) {
54 .add_data => {
55 const slice = try list.addManyAsSlice(gpa, smith.value(u4));
56 smith.bytes(slice);
57 },
58 .dup_data => {
59 if (list.items.len == 0) continue;
60 if (list.items.len > std.math.maxInt(u32)) return error.SkipZigTest;
61 const len = smith.valueRangeAtMost(u32, 1, @min(32, list.items.len));
62 const off = smith.valueRangeAtMost(u32, 0, @intCast(list.items.len - len));
63 try list.appendSlice(gpa, list.items[off..][0..len]);
64 try std.testing.expectEqualSlices(
65 u8,
66 list.items[off..][0..len],
67 list.items[list.items.len - len ..],
68 );
69 },
4970 };
50 try std.testing.fuzz(Context{}, Context.testOne, .{});
5171}
lib/std/Build/abi.zig+131-3
......@@ -6,6 +6,7 @@
66//! All of these components interface to some degree via an ABI:
77//! * The build runner communicates with the web interface over a WebSocket connection
88//! * The build runner communicates with `libfuzzer` over a shared memory-mapped file
9const std = @import("std");
910
1011// Check that no WebSocket message type has implicit padding bits. This ensures we never send any
1112// undefined bits over the wire, and also helps validate that the layout doesn't differ between, for
......@@ -13,7 +14,6 @@
1314comptime {
1415 const check = struct {
1516 fn check(comptime T: type) void {
16 const std = @import("std");
1717 std.debug.assert(@typeInfo(T) == .@"struct");
1818 std.debug.assert(@typeInfo(T).@"struct".layout == .@"extern");
1919 std.debug.assert(std.meta.hasUniqueRepresentation(T));
......@@ -139,14 +139,48 @@ pub const Rebuild = extern struct {
139139
140140/// ABI bits specifically relating to the fuzzer interface.
141141pub const fuzz = struct {
142 pub const TestOne = *const fn (Slice) callconv(.c) void;
142 pub const TestOne = *const fn () callconv(.c) void;
143
144 /// A unique value to identify the related requests across runs
145 pub const Uid = packed struct(u32) {
146 kind: enum(u1) { int, bytes },
147 hash: u31,
148
149 pub const hashmap_ctx = struct {
150 pub fn hash(_: @This(), u: Uid) u32 {
151 // We can ignore `kind` since `hash` should be unique regardless
152 return u.hash;
153 }
154
155 pub fn eql(_: @This(), a: Uid, b: Uid, _: usize) bool {
156 return a == b;
157 }
158 };
159 };
160
143161 pub extern fn fuzzer_init(cache_dir_path: Slice) void;
162 /// `fuzzer_init` must be called first.
144163 pub extern fn fuzzer_coverage() Coverage;
145 pub extern fn fuzzer_init_test(test_one: TestOne, unit_test_name: Slice) void;
164 /// `fuzzer_init` must be called first.
165 pub extern fn fuzzer_set_test(test_one: TestOne, unit_test_name: Slice) void;
166 /// `fuzzer_set_test` must be called first.
167 /// The callee owns the memory of bytes and must not free it until `fuzzer_main` returns
146168 pub extern fn fuzzer_new_input(bytes: Slice) void;
169 /// `fuzzer_set_test` must be called first.
170 /// Resets the fuzzer's state to that of `fuzzer_init`.
147171 pub extern fn fuzzer_main(limit_kind: LimitKind, amount: u64) void;
148172 pub extern fn fuzzer_unslide_address(addr: usize) usize;
149173
174 pub extern fn fuzzer_int(uid: Uid, weights: Weights) u64;
175 pub extern fn fuzzer_eos(uid: Uid, weights: Weights) bool;
176 pub extern fn fuzzer_bytes(uid: Uid, out: MutSlice, weights: Weights) void;
177 pub extern fn fuzzer_slice(
178 uid: Uid,
179 buf: MutSlice,
180 len_weights: Weights,
181 byte_weights: Weights,
182 ) u32;
183
150184 pub const Slice = extern struct {
151185 ptr: [*]const u8,
152186 len: usize,
......@@ -160,6 +194,100 @@ pub const fuzz = struct {
160194 }
161195 };
162196
197 pub const MutSlice = extern struct {
198 ptr: [*]u8,
199 len: usize,
200
201 pub fn toSlice(s: MutSlice) []u8 {
202 return s.ptr[0..s.len];
203 }
204
205 pub fn fromSlice(s: []u8) MutSlice {
206 return .{ .ptr = s.ptr, .len = s.len };
207 }
208 };
209
210 pub const Weights = extern struct {
211 ptr: [*]const Weight,
212 len: usize,
213
214 pub fn toSlice(s: Weights) []const Weight {
215 return s.ptr[0..s.len];
216 }
217
218 pub fn fromSlice(s: []const Weight) Weights {
219 return .{ .ptr = s.ptr, .len = s.len };
220 }
221 };
222
223 /// Increases the probability of values being selected by the fuzzer.
224 ///
225 /// `weight` applies to each value in the range (i.e. not evenly across
226 /// the range) and must be nonzero.
227 ///
228 /// In a set of weights, the total weight must not exceed 2^64 and be
229 /// nonzero.
230 pub const Weight = extern struct {
231 /// Inclusive
232 min: u64,
233 /// Inclusive
234 max: u64,
235 weight: u64,
236
237 fn intFromValue(x: anytype) u64 {
238 const T = @TypeOf(x);
239 return switch (@typeInfo(T)) {
240 .comptime_int => x,
241 .bool => @intFromBool(x),
242 .@"enum" => @intFromEnum(x),
243 else => @as(std.meta.Int(.unsigned, @bitSizeOf(T)), @bitCast(x)),
244
245 .int => |i| x: {
246 comptime {
247 if (i.signedness == .signed) {
248 @compileError("type does not have a continous range: " ++ @typeName(T));
249 }
250 // Reject types that don't have a fixed bitsize (esp. usize)
251 // since they are not gauraunteed to fit in a u64 across targets.
252 if (std.mem.indexOfScalar(type, &.{
253 usize, c_char, c_ushort, c_uint, c_ulong, c_ulonglong,
254 }, T) != null) {
255 @compileError("type does not have a fixed bitsize: " ++ @typeName(T));
256 }
257 }
258 break :x x;
259 },
260
261 .comptime_float,
262 .float,
263 => @compileError("type does not have a continous range: " ++ @typeName(T)),
264 .pointer => @compileError("type does not have a fixed bitsize: " ++ @typeName(T)),
265 };
266 }
267
268 pub fn value(T: type, x: T, weight: u64) Weight {
269 return .{ .min = intFromValue(x), .max = intFromValue(x), .weight = weight };
270 }
271
272 pub fn rangeAtMost(T: type, at_least: T, at_most: T, weight: u64) Weight {
273 std.debug.assert(intFromValue(at_least) <= intFromValue(at_most));
274 return .{
275 .min = intFromValue(at_least),
276 .max = intFromValue(at_most),
277 .weight = weight,
278 };
279 }
280
281 pub fn rangeLessThan(T: type, at_least: T, less_than: T, weight: u64) Weight {
282 std.debug.assert(intFromValue(at_least) < intFromValue(less_than));
283 return .{
284 .min = intFromValue(at_least),
285 .max = intFromValue(less_than) - 1,
286 .weight = weight,
287 };
288 }
289 };
290
163291 pub const LimitKind = enum(u8) { forever, iterations };
164292
165293 /// libfuzzer uses this and its usize is the one that counts. To match the ABI,
lib/std/compress/flate/Compress.zig+320-324
......@@ -279,7 +279,7 @@ pub fn init(
279279 assert(buffer.len >= flate.max_window_len);
280280
281281 // note that disallowing some of these simplifies matching logic
282 assert(opts.chain != 0); // use `Huffman`, disallowing this simplies matching
282 assert(opts.chain != 0); // use `Huffman`; disallowing this simplies matching
283283 assert(opts.good >= 3 and opts.nice >= 3); // a match will (usually) not be found
284284 assert(opts.good <= 258 and opts.nice <= 258); // a longer match will not be found
285285 assert(opts.lazy <= opts.nice); // a longer match will (usually) not be found
......@@ -558,45 +558,35 @@ test betterMatchLen {
558558 try std.testing.fuzz({}, testFuzzedMatchLen, .{});
559559}
560560
561fn testFuzzedMatchLen(_: void, input: []const u8) !void {
561fn testFuzzedMatchLen(_: void, smith: *std.testing.Smith) !void {
562562 @disableInstrumentation();
563 var r: Io.Reader = .fixed(input);
564563 var buf: [1024]u8 = undefined;
565564 var w: Writer = .fixed(&buf);
566 var old = r.takeLeb128(u9) catch 0;
567 var bytes_off = @max(1, r.takeLeb128(u10) catch 258);
568 const prev_back = @max(1, r.takeLeb128(u10) catch 258);
569565
570 while (r.takeByte()) |byte| {
571 const op: packed struct(u8) {
572 kind: enum(u2) { splat, copy, insert_imm, insert },
573 imm: u6,
574
575 pub fn immOrByte(op_s: @This(), r_s: *Io.Reader) usize {
576 return if (op_s.imm == 0) op_s.imm else @as(usize, r_s.takeByte() catch 0) + 64;
577 }
578 } = @bitCast(byte);
579 (switch (op.kind) {
580 .splat => w.splatByteAll(r.takeByte() catch 0, op.immOrByte(&r)),
566 while (w.unusedCapacityLen() != 0 and !smith.eosWeightedSimple(7, 1)) {
567 switch (smith.value(enum(u2) { splat, copy, insert })) {
568 .splat => w.splatByteAll(
569 smith.value(u8),
570 smith.valueRangeAtMost(u9, 1, @min(511, w.unusedCapacityLen())),
571 ) catch unreachable,
581572 .copy => write: {
582 const start = w.buffered().len -| op.immOrByte(&r);
583 const len = @min(w.buffered().len - start, r.takeByte() catch 3);
584 break :write w.writeAll(w.buffered()[start..][0..len]);
573 if (w.buffered().len == 0) continue;
574 const start = smith.valueRangeAtMost(u10, 0, @intCast(w.buffered().len - 1));
575 const max_len = @min(w.unusedCapacityLen(), w.buffered().len - start);
576 const len = smith.valueRangeAtMost(u10, 1, @intCast(max_len));
577 break :write w.writeAll(w.buffered()[start..][0..len]) catch unreachable;
585578 },
586 .insert_imm => w.writeByte(op.imm),
587 .insert => w.writeAll(r.take(
588 @min(r.bufferedLen(), @as(usize, op.imm) + 1),
589 ) catch unreachable),
590 }) catch break;
591 } else |_| {}
592
593 w.splatByteAll(0, (1 + 3) -| w.buffered().len) catch unreachable;
594 bytes_off = @min(bytes_off, @as(u10, @intCast(w.buffered().len - 3)));
595 const prev_off = bytes_off -| prev_back;
596 assert(prev_off < bytes_off);
579 .insert => w.advance(smith.slice(w.unusedCapacitySlice())),
580 }
581 }
582 w.splatByteAll(0, (1 + token.min_length) -| w.buffered().len) catch unreachable;
583
584 const max_start = w.buffered().len - token.min_length;
585 const bytes_off = smith.valueRangeAtMost(u10, 1, @intCast(max_start));
586 const prev_off = smith.valueRangeAtMost(u10, 0, bytes_off - 1);
597587 const prev = w.buffered()[prev_off..];
598588 const bytes = w.buffered()[bytes_off..];
599 old = @min(old, bytes.len - 1, token.max_length - 1);
589 const old = smith.valueRangeLessThan(u10, 0, @min(bytes.len, token.max_length));
600590
601591 const diff_index = mem.findDiff(u8, prev, bytes).?; // unwrap since lengths are not same
602592 const expected_len = @min(diff_index, 258);
......@@ -1036,7 +1026,7 @@ const huffman = struct {
10361026 max_bits: u4,
10371027 incomplete_allowed: bool,
10381028 ) struct { u32, u16 } {
1039 assert(out_codes.len - 1 >= @intFromBool(incomplete_allowed));
1029 assert(out_codes.len - 1 >= @intFromBool(!incomplete_allowed));
10401030 // freqs and out_codes are in the loop to assert they are all the same length
10411031 for (freqs, out_codes, out_bits) |_, _, n| assert(n == 0);
10421032 assert(out_codes.len <= @as(u16, 1) << max_bits);
......@@ -1255,40 +1245,35 @@ const huffman = struct {
12551245 try std.testing.fuzz({}, checkFuzzedBuildFreqs, .{});
12561246 }
12571247
1258 fn checkFuzzedBuildFreqs(_: void, freqs: []const u8) !void {
1248 fn checkFuzzedBuildFreqs(_: void, smith: *std.testing.Smith) !void {
12591249 @disableInstrumentation();
1260 var r: Io.Reader = .fixed(freqs);
12611250 var freqs_limit: u16 = 65535;
12621251 var freqs_buf: [max_leafs]u16 = undefined;
12631252 var nfreqs: u15 = 0;
12641253
1265 const params: packed struct(u8) {
1266 max_bits: u4,
1267 _: u3,
1268 incomplete_allowed: bool,
1269 } = @bitCast(r.takeByte() catch 255);
1270 while (nfreqs != freqs_buf.len) {
1271 const leb = r.takeLeb128(u16);
1272 const f = if (leb) |f| @min(f, freqs_limit) else |e| switch (e) {
1273 error.ReadFailed => unreachable,
1274 error.EndOfStream => 0,
1275 error.Overflow => freqs_limit,
1276 };
1254 const incomplete_allowed = smith.value(bool);
1255 while (nfreqs < @as(u8, @intFromBool(!incomplete_allowed)) + 1 or
1256 nfreqs != freqs_buf.len and freqs_limit != 0 and
1257 smith.eosWeightedSimple(15, 1))
1258 {
1259 const f = smith.valueWeighted(u16, &.{
1260 .rangeAtMost(u16, 0, @min(31, freqs_limit), @max(freqs_limit, 1)),
1261 .rangeAtMost(u16, 0, freqs_limit, 1),
1262 });
12771263 freqs_buf[nfreqs] = f;
1278 nfreqs += 1;
12791264 freqs_limit -= f;
1280 if (leb == error.EndOfStream and nfreqs - 1 > @intFromBool(params.incomplete_allowed))
1281 break;
1265 nfreqs += 1;
12821266 }
12831267
12841268 var codes_buf: [max_leafs]u16 = undefined;
12851269 var bits_buf: [max_leafs]u4 = @splat(0);
1270 const max_bits = smith.valueRangeAtMost(u4, math.log2_int_ceil(u15, nfreqs), 15);
12861271 const total_bits, const last_nonzero = build(
12871272 freqs_buf[0..nfreqs],
12881273 codes_buf[0..nfreqs],
12891274 bits_buf[0..nfreqs],
1290 @max(math.log2_int_ceil(u15, nfreqs), params.max_bits),
1291 params.incomplete_allowed,
1275 max_bits,
1276 incomplete_allowed,
12921277 );
12931278
12941279 var has_bitlen_one: bool = false;
......@@ -1303,21 +1288,21 @@ const huffman = struct {
13031288 }
13041289
13051290 errdefer std.log.err(
1306 \\ params: {}
1291 \\ incomplete_allowed: {}
1292 \\ max_bits: {}
13071293 \\ freqs: {any}
13081294 \\ bits: {any}
13091295 \\ # freqs: {}
1310 \\ max bits: {}
13111296 \\ weighted sum: {}
13121297 \\ has_bitlen_one: {}
13131298 \\ expected/actual total bits: {}/{}
13141299 \\ expected/actual last nonzero: {?}/{}
13151300 ++ "\n", .{
1316 params,
1301 incomplete_allowed,
1302 max_bits,
13171303 freqs_buf[0..nfreqs],
13181304 bits_buf[0..nfreqs],
13191305 nfreqs,
1320 @max(math.log2_int_ceil(u15, nfreqs), params.max_bits),
13211306 weighted_sum,
13221307 has_bitlen_one,
13231308 expected_total_bits,
......@@ -1331,7 +1316,7 @@ const huffman = struct {
13311316 if (weighted_sum > 1 << 15)
13321317 return error.OversubscribedHuffmanTree;
13331318 if (weighted_sum < 1 << 15 and
1334 !(params.incomplete_allowed and has_bitlen_one and weighted_sum == 1 << 14))
1319 !(incomplete_allowed and has_bitlen_one and weighted_sum == 1 << 14))
13351320 return error.IncompleteHuffmanTree;
13361321 }
13371322};
......@@ -1353,6 +1338,7 @@ fn testingFreqBufs() !*[2][65536]u8 {
13531338 }
13541339 return fbufs;
13551340}
1341const FreqBufIndex = enum(u1) { gradient, random };
13561342
13571343fn testingCheckDecompressedMatches(
13581344 flate_bytes: []const u8,
......@@ -1426,34 +1412,31 @@ test Compress {
14261412 try std.testing.fuzz(fbufs, testFuzzedCompressInput, .{});
14271413}
14281414
1429fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, input: []const u8) !void {
1430 var in: Io.Reader = .fixed(input);
1431 var opts: packed struct(u51) {
1432 container: PackedContainer,
1433 buf_size: u16,
1434 good: u8,
1435 nice: u8,
1436 lazy: u8,
1437 /// Not a `u16` to limit it for performance
1438 chain: u9,
1439 } = @bitCast(in.takeLeb128(u51) catch 0);
1440 var expected_hash: flate.Container.Hasher = .init(opts.container.val());
1415fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith) !void {
1416 @disableInstrumentation();
1417 const container = smith.value(flate.Container);
1418 const good = smith.valueRangeAtMost(u16, 3, 258);
1419 const nice = smith.valueRangeAtMost(u16, 3, 258);
1420 const lazy = smith.valueRangeAtMost(u16, 3, nice);
1421 const chain = smith.valueWeighted(u16, &.{
1422 .rangeAtMost(u16, if (good <= lazy) 4 else 1, 255, 65536),
1423 // The following weights are greatly reduced since they increasing take more time to run
1424 .rangeAtMost(u16, 256, 4095, 256),
1425 .rangeAtMost(u16, 4096, 32767 + 256, 1),
1426 });
1427 var expected_hash: flate.Container.Hasher = .init(container);
14411428 var expected_size: u32 = 0;
14421429
14431430 var flate_buf: [128 * 1024]u8 = undefined;
14441431 var flate_w: Writer = .fixed(&flate_buf);
14451432 var deflate_buf: [flate.max_window_len * 2]u8 = undefined;
1446 var deflate_w = try Compress.init(
1447 &flate_w,
1448 deflate_buf[0 .. flate.max_window_len + @as(usize, opts.buf_size)],
1449 opts.container.val(),
1450 .{
1451 .good = @as(u16, opts.good) + 3,
1452 .nice = @as(u16, opts.nice) + 3,
1453 .lazy = @as(u16, @min(opts.lazy, opts.nice)) + 3,
1454 .chain = @max(1, opts.chain, @as(u8, 4) * @intFromBool(opts.good <= opts.lazy)),
1455 },
1456 );
1433 const bufsize = smith.valueRangeAtMost(u32, flate.max_window_len, @intCast(deflate_buf.len));
1434 var deflate_w = try Compress.init(&flate_w, deflate_buf[0..bufsize], container, .{
1435 .good = good,
1436 .nice = nice,
1437 .lazy = lazy,
1438 .chain = chain,
1439 });
14571440
14581441 // It is ensured that more bytes are not written then this to ensure this run
14591442 // does not take too long and that `flate_buf` does not run out of space.
......@@ -1465,79 +1448,57 @@ fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, input: []const u8) !void
14651448 // extra 32 bytes is reserved on top of that for container headers and footers.
14661449 const max_size = flate_buf.len - (flate_buf_blocks * 64 + 32);
14671450
1468 while (true) {
1469 const data: packed struct(u36) {
1470 is_rebase: bool,
1471 is_bytes: bool,
1472 params: packed union {
1473 copy: packed struct(u34) {
1474 len_lo: u5,
1475 dist: u15,
1476 len_hi: u4,
1477 _: u10,
1478 },
1479 bytes: packed struct(u34) {
1480 kind: enum(u1) { gradient, random },
1481 off_hi: u4,
1482 len_lo: u10,
1483 off_mi: u4,
1484 len_hi: u5,
1485 off_lo: u8,
1486 _: u2,
1487 },
1488 rebase: packed struct(u34) {
1489 preserve: u17,
1490 capacity: u17,
1491 },
1492 },
1493 } = @bitCast(in.takeLeb128(u36) catch |e| switch (e) {
1494 error.ReadFailed => unreachable,
1495 error.Overflow => 0,
1496 error.EndOfStream => break,
1497 });
1451 while (!smith.eosWeightedSimple(7, 1)) {
1452 const max_bytes = max_size -| expected_size;
1453 if (max_bytes == 0) break;
14981454
14991455 const buffered = deflate_w.writer.buffered();
15001456 // Required for repeating patterns and since writing from `buffered` is illegal
15011457 var copy_buf: [512]u8 = undefined;
15021458
1503 if (data.is_rebase) {
1504 const usable_capacity = deflate_w.writer.buffer.len - rebase_reserved_capacity;
1505 const preserve = @min(data.params.rebase.preserve, usable_capacity);
1506 const capacity = @min(data.params.rebase.capacity, usable_capacity -
1507 @max(rebase_min_preserve, preserve));
1508 try deflate_w.writer.rebase(preserve, capacity);
1509 continue;
1510 }
1511
1512 const max_bytes = max_size -| expected_size;
1513 const bytes = if (!data.is_bytes and buffered.len != 0) bytes: {
1514 const dist = @min(buffered.len, @as(u32, data.params.copy.dist) + 1);
1515 const len = @min(
1516 @max(@shlExact(@as(u9, data.params.copy.len_hi), 5) | data.params.copy.len_lo, 1),
1517 max_bytes,
1518 );
1519 // Reuse the implementation's history. Otherwise our own would need maintained.
1520 const bytes_start = buffered[buffered.len - dist ..];
1521 const history_bytes = bytes_start[0..@min(bytes_start.len, len)];
1522
1523 @memcpy(copy_buf[0..history_bytes.len], history_bytes);
1524 const new_history = len - history_bytes.len;
1525 if (history_bytes.len != len) for ( // check needed for `- dist`
1526 copy_buf[history_bytes.len..][0..new_history],
1527 copy_buf[history_bytes.len - dist ..][0..new_history],
1528 ) |*next, prev| {
1529 next.* = prev;
1530 };
1531 break :bytes copy_buf[0..len];
1532 } else bytes: {
1533 const off = @shlExact(@as(u16, data.params.bytes.off_hi), 12) |
1534 @shlExact(@as(u16, data.params.bytes.off_mi), 8) |
1535 data.params.bytes.off_lo;
1536 const len = @shlExact(@as(u16, data.params.bytes.len_hi), 10) |
1537 data.params.bytes.len_lo;
1538 const fbuf = &fbufs[@intFromEnum(data.params.bytes.kind)];
1539 break :bytes fbuf[off..][0..@min(len, fbuf.len - off, max_bytes)];
1459 const bytes = bytes: switch (smith.valueRangeAtMost(
1460 u2,
1461 @intFromBool(buffered.len == 0),
1462 2,
1463 )) {
1464 0 => { // Copy
1465 const start = smith.valueRangeLessThan(u32, 0, @intCast(buffered.len));
1466 // Reuse the implementation's history; otherwise, our own would need maintained.
1467 const from = buffered[start..];
1468 const len = smith.valueRangeAtMost(u16, 1, @min(copy_buf.len, max_bytes));
1469
1470 const history_bytes = from[0..@min(from.len, len)];
1471 @memcpy(copy_buf[0..history_bytes.len], history_bytes);
1472 const repeat_len = len - history_bytes.len;
1473 for (
1474 copy_buf[history_bytes.len..][0..repeat_len],
1475 copy_buf[0..repeat_len],
1476 ) |*next, prev| {
1477 next.* = prev;
1478 }
1479 break :bytes copy_buf[0..len];
1480 },
1481 1 => { // Bytes
1482 const fbuf = &fbufs[
1483 smith.valueWeighted(u1, &.{
1484 .value(FreqBufIndex, .gradient, 3),
1485 .value(FreqBufIndex, .random, 1),
1486 })
1487 ];
1488 const len = smith.valueRangeAtMost(u32, 1, @min(fbuf.len, max_bytes));
1489 const off = smith.valueRangeAtMost(u32, 0, @intCast(fbuf.len - len));
1490 break :bytes fbuf[off..][0..len];
1491 },
1492 2 => { // Rebase
1493 const rebaseable = bufsize - rebase_reserved_capacity;
1494 const capacity = smith.valueRangeAtMost(u32, 1, rebaseable - rebase_min_preserve);
1495 const preserve = smith.valueRangeAtMost(u32, 0, rebaseable - capacity);
1496 try deflate_w.writer.rebase(preserve, capacity);
1497 continue;
1498 },
1499 else => unreachable,
15401500 };
1501
15411502 assert(bytes.len <= max_bytes);
15421503 try deflate_w.writer.writeAll(bytes);
15431504 expected_hash.update(bytes);
......@@ -1780,7 +1741,8 @@ fn countVec(data: []const []const u8) usize {
17801741 return bytes;
17811742}
17821743
1783fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, input: []const u8) !void {
1744fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, smith: *std.testing.Smith) !void {
1745 @disableInstrumentation();
17841746 const HashedStoreWriter = struct {
17851747 writer: Writer,
17861748 state: enum {
......@@ -1819,8 +1781,8 @@ fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, input: []const u8) !void {
18191781
18201782 /// Note that this implementation is somewhat dependent on the implementation of
18211783 /// `Raw` by expecting headers / footers to be continous in data elements. It
1822 /// also expects the header to be the same as `flate.Container.header` and not
1823 /// for multiple streams to be concatenated.
1784 /// also expects the header to be the same as `flate.Container.header` and for
1785 /// multiple streams to not be concatenated.
18241786 fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
18251787 errdefer w.* = .failing;
18261788 var h: *@This() = @fieldParentPtr("writer", w);
......@@ -1909,102 +1871,110 @@ fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, input: []const u8) !void {
19091871 }
19101872
19111873 fn flush(w: *Writer) Writer.Error!void {
1912 defer w.* = .failing; // Clears buffer even if state hasn't reached `end`
1874 defer w.* = .failing; // Empties buffer even if state hasn't reached `end`
19131875 _ = try @This().drain(w, &.{""}, 0);
19141876 }
19151877 };
19161878
1917 var in: Io.Reader = .fixed(input);
1918 const opts: packed struct(u19) {
1919 container: PackedContainer,
1920 buf_len: u17,
1921 } = @bitCast(in.takeLeb128(u19) catch 0);
1922 var output: HashedStoreWriter = .init(&.{}, opts.container.val());
1923 var r_buf: [2 * 65536]u8 = undefined;
1924 var r: Raw = try .init(
1925 &output.writer,
1926 r_buf[0 .. opts.buf_len +% flate.max_window_len],
1927 opts.container.val(),
1928 );
1929
1930 var data_base: u18 = 0;
1931 var expected_hash: flate.Container.Hasher = .init(opts.container.val());
1879 const container = smith.value(flate.Container);
1880 var output: HashedStoreWriter = .init(&.{}, container);
1881 var expected_hash: flate.Container.Hasher = .init(container);
19321882 var expected_size: u32 = 0;
1883 // 10 maximum blocks is the choosen limit since it is two more
1884 // than the maximum the implementation can output in one drain.
1885 const max_size = 10 * @as(u32, Raw.max_block_size);
1886
1887 var raw_buf: [2 * @as(usize, Raw.max_block_size)]u8 = undefined;
1888 const raw_buf_len = smith.valueWeighted(u32, &.{
1889 .value(u32, 0, @intCast(raw_buf.len)), // unbuffered
1890 .rangeAtMost(u32, 0, @intCast(raw_buf.len), 1),
1891 });
1892 var raw: Raw = try .init(&output.writer, raw_buf[0..raw_buf_len], container);
1893
1894 const data_buf_len: u32 = @intCast(data_buf.len);
19331895 var vecs: [32][]const u8 = undefined;
19341896 var vecs_n: usize = 0;
19351897
1936 while (in.seek != in.end) {
1937 const VecInfo = packed struct(u58) {
1938 output: bool,
1939 /// If set, `data_len` and `splat` are reinterpreted as `capacity`
1940 /// and `preserve_len` respectively and `output` is treated as set.
1941 rebase: bool,
1942 block_aligning_len: bool,
1943 block_aligning_splat: bool,
1944 data_len: u18,
1945 splat: u18,
1946 data_off: u18,
1898 while (true) {
1899 const Op = packed struct {
1900 drain: bool = false,
1901 add_vec: bool = false,
1902 rebase: bool = false,
1903
1904 pub const drain_only: @This() = .{ .drain = true };
1905 pub const add_vec_only: @This() = .{ .add_vec = true };
1906 pub const add_vec_and_drain: @This() = .{ .add_vec = true, .drain = true };
1907 pub const drain_and_rebase: @This() = .{ .drain = true, .rebase = true };
19471908 };
1948 var vec_info: VecInfo = @bitCast(in.takeLeb128(u58) catch |e| switch (e) {
1949 error.ReadFailed => unreachable,
1950 error.Overflow, error.EndOfStream => 0,
1951 });
1952
1953 {
1954 const buffered = r.writer.buffered().len + countVec(vecs[0..vecs_n]);
1955 const to_align = mem.alignForwardAnyAlign(usize, buffered, Raw.max_block_size) - buffered;
1956 assert((buffered + to_align) % Raw.max_block_size == 0);
1957
1958 if (vec_info.block_aligning_len) {
1959 vec_info.data_len = @intCast(to_align);
1960 } else if (vec_info.block_aligning_splat and vec_info.data_len != 0 and
1961 to_align % vec_info.data_len == 0)
1962 {
1963 vec_info.splat = @divExact(@as(u18, @intCast(to_align)), vec_info.data_len) -% 1;
1964 }
1965 }
1966
1967 var splat = if (vec_info.output and !vec_info.rebase) vec_info.splat +% 1 else 1;
1968 add_vec: {
1969 if (vec_info.rebase) break :add_vec;
1970 if (expected_size +| math.mulWide(u18, vec_info.data_len, splat) >
1971 10 * (1 << 16))
1972 {
1973 // Skip this vector to avoid this test taking too long.
1974 // 10 maximum sized blocks is choosen as the limit since it is two more
1975 // than the maximum the implementation can output in one drain.
1976 splat = 1;
1977 break :add_vec;
1978 }
1979
1980 vecs[vecs_n] = data_buf[@min(
1981 data_base +% vec_info.data_off,
1982 data_buf.len - vec_info.data_len,
1983 )..][0..vec_info.data_len];
1984
1985 data_base +%= vec_info.data_len +% 3; // extra 3 to help catch aliasing bugs
19861909
1987 for (0..splat) |_| expected_hash.update(vecs[vecs_n]);
1988 expected_size += @as(u32, @intCast(vecs[vecs_n].len)) * splat;
1910 const is_eos = expected_size == max_size or smith.eosWeightedSimple(7, 1);
1911 var op: Op = if (!is_eos) smith.valueWeighted(Op, &.{
1912 .value(Op, .add_vec_only, 6),
1913 .value(Op, .add_vec_and_drain, 1),
1914 .value(Op, .drain_and_rebase, 1),
1915 }) else .drain_only;
1916
1917 if (op.add_vec) {
1918 const max_write = max_size - expected_size;
1919 const buffered: u32 = @intCast(raw.writer.buffered().len + countVec(vecs[0..vecs_n]));
1920 const to_align = Raw.max_block_size - buffered % Raw.max_block_size;
1921 assert(to_align != 0); // otherwise, not helpful.
1922
1923 const max_data = @min(data_buf_len, max_write);
1924 const len = smith.valueWeighted(u32, &.{
1925 .rangeAtMost(u32, 0, max_data, 1),
1926 .rangeAtMost(u32, 0, @min(Raw.max_block_size, max_data), 4),
1927 .value(u32, @min(to_align, max_data), max_data), // @min 2nd arg is an edge-case
1928 });
1929 const off = smith.valueRangeAtMost(u32, 0, data_buf_len - len);
1930
1931 expected_size += len;
1932 vecs[vecs_n] = data_buf[off..][0..len];
19891933 vecs_n += 1;
1934 op.drain |= vecs_n == vecs.len;
19901935 }
19911936
1992 const want_drain = vecs_n == vecs.len or vec_info.output or vec_info.rebase or
1993 in.seek == in.end;
1994 if (want_drain and vecs_n != 0) {
1995 try r.writer.writeSplatAll(vecs[0..vecs_n], splat);
1937 op.drain |= is_eos;
1938 op.drain &= vecs_n != 0;
1939 if (op.drain) {
1940 const pattern_len: u32 = @intCast(vecs[vecs_n - 1].len);
1941 const pattern_len_z = @max(pattern_len, 1);
1942
1943 const max_write = max_size - (expected_size - pattern_len);
1944 const buffered: u32 = @intCast(raw.writer.buffered().len + countVec(vecs[0 .. vecs_n - 1]));
1945 const to_align = Raw.max_block_size - buffered % Raw.max_block_size;
1946 assert(to_align != 0); // otherwise, not helpful.
1947
1948 const max_splat = max_write / pattern_len_z;
1949 const weights: [3]std.testing.Smith.Weight = .{
1950 .rangeAtMost(u32, 0, max_splat, 1),
1951 .rangeAtMost(u32, 0, @min(
1952 Raw.max_block_size + pattern_len_z,
1953 max_write,
1954 ) / pattern_len_z, 4),
1955 .value(u32, to_align / pattern_len_z, max_splat * 4),
1956 };
1957 const align_weight = to_align % pattern_len_z == 0 and to_align <= max_write;
1958 const n_weights = @as(u8, 2) + @intFromBool(align_weight);
1959 const splat = smith.valueWeighted(u32, weights[0..n_weights]);
1960
1961 expected_size = expected_size - pattern_len + pattern_len * splat; // splat may be zero
1962 for (vecs[0 .. vecs_n - 1]) |v| expected_hash.update(v);
1963 for (0..splat) |_| expected_hash.update(vecs[vecs_n - 1]);
1964 try raw.writer.writeSplatAll(vecs[0..vecs_n], splat);
19961965 vecs_n = 0;
1997 } else assert(splat == 1);
1966 }
19981967
1999 if (vec_info.rebase) {
2000 try r.writer.rebase(vec_info.data_len, @min(
2001 r.writer.buffer.len -| vec_info.data_len,
2002 vec_info.splat,
2003 ));
1968 if (op.rebase) {
1969 const capacity = smith.valueRangeAtMost(u32, 0, raw_buf_len);
1970 const preserve = smith.valueRangeAtMost(u32, 0, raw_buf_len - capacity);
1971 try raw.writer.rebase(preserve, capacity);
20041972 }
1973
1974 if (is_eos) break;
20051975 }
20061976
2007 try r.writer.flush();
1977 try raw.writer.flush();
20081978 try output.writer.flush();
20091979
20101980 try std.testing.expectEqual(.end, output.state);
......@@ -2432,120 +2402,146 @@ test Huffman {
24322402 try std.testing.fuzz(fbufs, testFuzzedHuffmanInput, .{});
24332403}
24342404
2405fn fuzzedHuffmanDrainSpaceLimit(max_drain: usize, written: usize, eos: bool) usize {
2406 var block_lim = math.divCeil(usize, max_drain, Huffman.max_tokens) catch unreachable;
2407 block_lim = @max(block_lim, @intFromBool(eos));
2408 const footer_overhead = @as(u8, 8) * @intFromBool(eos);
2409 // 6 for a raw block header (the block header may span two bytes)
2410 return written + 6 * block_lim + max_drain + footer_overhead;
2411}
2412
24352413/// This function is derived from `testFuzzedRawInput` with a few changes for fuzzing `Huffman`.
2436fn testFuzzedHuffmanInput(fbufs: *const [2][65536]u8, input: []const u8) !void {
2437 var in: Io.Reader = .fixed(input);
2438 const opts: packed struct(u19) {
2439 container: PackedContainer,
2440 buf_len: u17,
2441 } = @bitCast(in.takeLeb128(u19) catch 0);
2414fn testFuzzedHuffmanInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith) !void {
2415 @disableInstrumentation();
2416 const container = smith.value(flate.Container);
24422417 var flate_buf: [2 * 65536]u8 = undefined;
24432418 var flate_w: Writer = .fixed(&flate_buf);
2444 var h_buf: [2 * 65536]u8 = undefined;
2445 var h: Huffman = try .init(
2446 &flate_w,
2447 h_buf[0 .. opts.buf_len +% flate.max_window_len],
2448 opts.container.val(),
2449 );
2450
2451 var expected_hash: flate.Container.Hasher = .init(opts.container.val());
2419 var expected_hash: flate.Container.Hasher = .init(container);
24522420 var expected_size: u32 = 0;
2421 const max_size = 4 * @as(u32, Huffman.max_tokens);
2422
2423 var h_buf: [2 * @as(usize, Huffman.max_tokens)]u8 = undefined;
2424 const h_buf_len = smith.valueWeighted(u32, &.{
2425 .value(u32, 0, @intCast(h_buf.len)), // unbuffered
2426 .rangeAtMost(u32, 0, @intCast(h_buf.len), 1),
2427 });
2428 var h: Huffman = try .init(&flate_w, h_buf[0..h_buf_len], container);
2429
24532430 var vecs: [32][]const u8 = undefined;
24542431 var vecs_n: usize = 0;
24552432
2456 while (in.seek != in.end) {
2457 const VecInfo = packed struct(u55) {
2458 output: bool,
2459 /// If set, `data_len` and `splat` are reinterpreted as `capacity`
2460 /// and `preserve_len` respectively and `output` is treated as set.
2461 rebase: bool,
2462 block_aligning_len: bool,
2463 block_aligning_splat: bool,
2464 data_off_hi: u8,
2465 random_data: u1,
2466 data_len: u16,
2467 splat: u18,
2468 /// This is less useful as each value is part of the same gradient 'step'
2469 data_off_lo: u8,
2433 while (true) {
2434 const Op = packed struct {
2435 drain: bool = false,
2436 add_vec: bool = false,
2437 rebase: bool = false,
2438
2439 pub const drain_only: @This() = .{ .drain = true };
2440 pub const add_vec_only: @This() = .{ .add_vec = true };
2441 pub const add_vec_and_drain: @This() = .{ .add_vec = true, .drain = true };
2442 pub const drain_and_rebase: @This() = .{ .drain = true, .rebase = true };
24702443 };
2471 var vec_info: VecInfo = @bitCast(in.takeLeb128(u55) catch |e| switch (e) {
2472 error.ReadFailed => unreachable,
2473 error.Overflow, error.EndOfStream => 0,
2474 });
24752444
2476 {
2477 const buffered = h.writer.buffered().len + countVec(vecs[0..vecs_n]);
2478 const to_align = mem.alignForwardAnyAlign(usize, buffered, Huffman.max_tokens) - buffered;
2479 assert((buffered + to_align) % Huffman.max_tokens == 0);
2480
2481 if (vec_info.block_aligning_len) {
2482 vec_info.data_len = @intCast(to_align);
2483 } else if (vec_info.block_aligning_splat and vec_info.data_len != 0 and
2484 to_align % vec_info.data_len == 0)
2485 {
2486 vec_info.splat = @divExact(@as(u18, @intCast(to_align)), vec_info.data_len) -% 1;
2487 }
2445 const is_eos = expected_size == max_size or smith.eosWeightedSimple(7, 1);
2446 var op: Op = if (!is_eos) smith.valueWeighted(Op, &.{
2447 .value(Op, .add_vec_only, 6),
2448 .value(Op, .add_vec_and_drain, 1),
2449 .value(Op, .drain_and_rebase, 1),
2450 }) else .drain_only;
2451
2452 if (op.add_vec) {
2453 const max_write = max_size - expected_size;
2454 const buffered: u32 = @intCast(h.writer.buffered().len + countVec(vecs[0..vecs_n]));
2455 const to_align = Huffman.max_tokens - buffered % Huffman.max_tokens;
2456 assert(to_align != 0); // otherwise, not helpful.
2457
2458 const data_buf = &fbufs[
2459 smith.valueWeighted(u1, &.{
2460 .value(FreqBufIndex, .gradient, 3),
2461 .value(FreqBufIndex, .random, 1),
2462 })
2463 ];
2464 const data_buf_len: u32 = @intCast(data_buf.len);
2465
2466 const max_data = @min(data_buf_len, max_write);
2467 const len = smith.valueWeighted(u32, &.{
2468 .rangeAtMost(u32, 0, max_data, 1),
2469 .rangeAtMost(u32, 0, @min(Huffman.max_tokens, max_data), 4),
2470 .value(u32, @min(to_align, max_data), max_data), // @min 2nd arg is an edge-case
2471 });
2472 const off = smith.valueRangeAtMost(u32, 0, data_buf_len - len);
2473
2474 expected_size += len;
2475 vecs[vecs_n] = data_buf[off..][0..len];
2476 vecs_n += 1;
2477 op.drain |= vecs_n == vecs.len;
24882478 }
24892479
2490 var splat = if (vec_info.output and !vec_info.rebase) vec_info.splat +% 1 else 1;
2491 add_vec: {
2492 if (vec_info.rebase) break :add_vec;
2493 if (expected_size +| math.mulWide(u18, vec_info.data_len, splat) > 4 * (1 << 16)) {
2494 // Skip this vector to avoid this test taking too long.
2495 splat = 1;
2496 break :add_vec;
2497 }
2498
2499 const data_buf = &fbufs[vec_info.random_data];
2500 vecs[vecs_n] = data_buf[@min(
2501 (@as(u16, vec_info.data_off_hi) << 8) | vec_info.data_off_lo,
2502 data_buf.len - vec_info.data_len,
2503 )..][0..vec_info.data_len];
2480 op.drain |= is_eos;
2481 op.drain &= vecs_n != 0;
2482 if (op.drain) {
2483 const pattern_len: u32 = @intCast(vecs[vecs_n - 1].len);
2484 const pattern_len_z = @max(pattern_len, 1);
2485
2486 const max_write = max_size - (expected_size - pattern_len);
2487 const buffered: u32 = @intCast(h.writer.buffered().len + countVec(vecs[0 .. vecs_n - 1]));
2488 const to_align = Huffman.max_tokens - buffered % Huffman.max_tokens;
2489 assert(to_align != 0); // otherwise, not helpful.
2490
2491 const max_splat = max_write / pattern_len_z;
2492 const weights: [3]std.testing.Smith.Weight = .{
2493 .rangeAtMost(u32, 0, max_splat, 1),
2494 .rangeAtMost(u32, 0, @min(
2495 Huffman.max_tokens + pattern_len_z,
2496 max_write,
2497 ) / pattern_len_z, 4),
2498 .value(u32, to_align / pattern_len_z, max_splat * 4),
2499 };
2500 const align_weight = to_align % pattern_len_z == 0 and to_align <= max_write;
2501 const n_weights = @as(u8, 2) + @intFromBool(align_weight);
2502 const splat = smith.valueWeighted(u32, weights[0..n_weights]);
2503
2504 expected_size = expected_size - pattern_len + pattern_len * splat; // splat may be zero
2505 for (vecs[0 .. vecs_n - 1]) |v| expected_hash.update(v);
2506 for (0..splat) |_| expected_hash.update(vecs[vecs_n - 1]);
2507
2508 const max_space = fuzzedHuffmanDrainSpaceLimit(
2509 buffered + pattern_len * splat,
2510 flate_w.buffered().len,
2511 false,
2512 );
2513 h.writer.writeSplatAll(vecs[0..vecs_n], splat) catch
2514 return if (max_space <= flate_w.buffer.len) error.OverheadTooLarge else {};
2515 if (flate_w.buffered().len > max_space) return error.OverheadTooLarge;
25042516
2505 for (0..splat) |_| expected_hash.update(vecs[vecs_n]);
2506 expected_size += @as(u32, @intCast(vecs[vecs_n].len)) * splat;
2507 vecs_n += 1;
2517 vecs_n = 0;
25082518 }
25092519
2510 const want_drain = vecs_n == vecs.len or vec_info.output or vec_info.rebase or
2511 in.seek == in.end;
2512 if (want_drain and vecs_n != 0) {
2513 var n = h.writer.buffered().len + Writer.countSplat(vecs[0..vecs_n], splat);
2514 const oos = h.writer.writeSplatAll(vecs[0..vecs_n], splat) == error.WriteFailed;
2515 n -= h.writer.buffered().len;
2516 const block_lim = math.divCeil(usize, n, Huffman.max_tokens) catch unreachable;
2517 const lim = flate_w.end + 6 * block_lim + n; // 6 since block header may span two bytes
2518 if (flate_w.end > lim) return error.OverheadTooLarge;
2519 if (oos) return;
2520 if (op.rebase) {
2521 const capacity = smith.valueRangeAtMost(u32, 0, h_buf_len);
2522 const preserve = smith.valueRangeAtMost(u32, 0, h_buf_len - capacity);
25202523
2521 vecs_n = 0;
2522 } else assert(splat == 1);
2523
2524 if (vec_info.rebase) {
2525 const old_end = flate_w.end;
2526 var n = h.writer.buffered().len;
2527 const oos = h.writer.rebase(vec_info.data_len, @min(
2528 h.writer.buffer.len -| vec_info.data_len,
2529 vec_info.splat,
2530 )) == error.WriteFailed;
2531 n -= h.writer.buffered().len;
2532 const block_lim = math.divCeil(usize, n, Huffman.max_tokens) catch unreachable;
2533 const lim = old_end + 6 * block_lim + n; // 6 since block header may span two bytes
2534 if (flate_w.end > lim) return error.OverheadTooLarge;
2535 if (oos) return;
2524 const max_space = fuzzedHuffmanDrainSpaceLimit(
2525 h.writer.buffered().len,
2526 flate_w.buffered().len,
2527 false,
2528 );
2529 h.writer.rebase(preserve, capacity) catch
2530 return if (max_space <= flate_w.buffer.len) error.OverheadTooLarge else {};
2531 if (flate_w.buffered().len > max_space) return error.OverheadTooLarge;
25362532 }
2537 }
25382533
2539 {
2540 const old_end = flate_w.end;
2541 const n = h.writer.buffered().len;
2542 const oos = h.writer.flush() == error.WriteFailed;
2543 assert(h.writer.buffered().len == 0);
2544 const block_lim = @max(1, math.divCeil(usize, n, Huffman.max_tokens) catch unreachable);
2545 const lim = old_end + 6 * block_lim + n + opts.container.val().footerSize();
2546 if (flate_w.end > lim) return error.OverheadTooLarge;
2547 if (oos) return;
2534 if (is_eos) break;
25482535 }
25492536
2537 const max_space = fuzzedHuffmanDrainSpaceLimit(
2538 h.writer.buffered().len,
2539 flate_w.buffered().len,
2540 true,
2541 );
2542 h.writer.flush() catch
2543 return if (max_space <= flate_w.buffer.len) error.OverheadTooLarge else {};
2544 if (flate_w.buffered().len > max_space) return error.OverheadTooLarge;
2545
25502546 try testingCheckDecompressedMatches(flate_w.buffered(), expected_size, expected_hash);
25512547}
lib/std/debug.zig+1
......@@ -417,6 +417,7 @@ pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *con
417417/// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert
418418/// function is the correct function to use.
419419pub fn assert(ok: bool) void {
420 @disableInstrumentation();
420421 if (!ok) unreachable; // assertion failure
421422}
422423
lib/std/deque.zig+120-34
......@@ -518,55 +518,139 @@ test "fuzz against ArrayList oracle" {
518518 try std.testing.fuzz({}, fuzzAgainstArrayList, .{});
519519}
520520
521test "dumb fuzz against ArrayList oracle" {
522 const testing = std.testing;
523 const gpa = testing.allocator;
521const FuzzAllocator = struct {
522 smith: *std.testing.Smith,
523 bufs: [2][256 * 4]u8 align(4),
524 used_bitmap: u2,
525 used_len: [2]usize,
526
527 pub fn init(smith: *std.testing.Smith) FuzzAllocator {
528 return .{
529 .smith = smith,
530 .bufs = undefined,
531 .used_len = undefined,
532 .used_bitmap = 0,
533 };
534 }
535
536 pub fn allocator(f: *FuzzAllocator) std.mem.Allocator {
537 return .{
538 .ptr = f,
539 .vtable = &.{
540 .alloc = alloc,
541 .resize = resize,
542 .remap = remap,
543 .free = free,
544 },
545 };
546 }
524547
525 const input = try gpa.alloc(u8, 1024);
526 defer gpa.free(input);
548 pub fn allocCount(f: *FuzzAllocator) u2 {
549 return @popCount(f.used_bitmap);
550 }
527551
528 var prng = std.Random.DefaultPrng.init(testing.random_seed);
529 prng.random().bytes(input);
552 fn alloc(ctx: *anyopaque, len: usize, a: std.mem.Alignment, _: usize) ?[*]u8 {
553 const f: *FuzzAllocator = @ptrCast(@alignCast(ctx));
554 assert(a == .@"4");
555 assert(len % 4 == 0);
556
557 const slot: u1 = @intCast(@ctz(~f.used_bitmap));
558 const buf: []u8 = &f.bufs[slot];
559 if (len > buf.len) return null;
560 f.used_bitmap |= @as(u2, 1) << slot;
561 f.used_len[slot] = len;
562 return buf.ptr;
563 }
530564
531 try fuzzAgainstArrayList({}, input);
532}
565 fn memSlot(f: *FuzzAllocator, mem: []u8) u1 {
566 const slot: u1 = if (&mem[0] == &f.bufs[0][0])
567 0
568 else if (&mem[0] == &f.bufs[1][0])
569 1
570 else
571 unreachable;
572 assert((f.used_bitmap >> slot) & 1 == 1);
573 assert(mem.len == f.used_len[slot]);
574 return slot;
575 }
576
577 fn resize(ctx: *anyopaque, mem: []u8, a: std.mem.Alignment, new_len: usize, _: usize) bool {
578 const f: *FuzzAllocator = @ptrCast(@alignCast(ctx));
579 assert(a == .@"4");
580 assert(f.allocCount() == 1);
581
582 const slot = f.memSlot(mem);
583 if (new_len > f.bufs[slot].len or f.smith.value(bool)) return false;
584 f.used_len[slot] = new_len;
585 return true;
586 }
587
588 fn remap(ctx: *anyopaque, mem: []u8, a: std.mem.Alignment, new_len: usize, _: usize) ?[*]u8 {
589 const f: *FuzzAllocator = @ptrCast(@alignCast(ctx));
590 assert(a == .@"4");
591 assert(f.allocCount() == 1);
533592
534fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {
593 const slot = f.memSlot(mem);
594 if (new_len > f.bufs[slot].len or f.smith.value(bool)) return null;
595
596 if (f.smith.value(bool)) {
597 f.used_len[slot] = new_len;
598 // remap in place
599 return mem.ptr;
600 } else {
601 // moving remap
602 const new_slot = ~slot;
603 f.used_bitmap = ~f.used_bitmap;
604 f.used_len[new_slot] = new_len;
605
606 const new_buf = &f.bufs[new_slot];
607 @memcpy(new_buf[0..mem.len], mem);
608 return new_buf.ptr;
609 }
610 }
611
612 fn free(ctx: *anyopaque, mem: []u8, a: std.mem.Alignment, _: usize) void {
613 const f: *FuzzAllocator = @ptrCast(@alignCast(ctx));
614 assert(a == .@"4");
615 f.used_bitmap ^= @as(u2, 1) << f.memSlot(mem);
616 }
617};
618
619fn fuzzAgainstArrayList(_: void, smith: *std.testing.Smith) anyerror!void {
535620 const testing = std.testing;
536 const gpa = testing.allocator;
621
622 var q_gpa_inst: FuzzAllocator = .init(smith);
623 var l_gpa_buf: [q_gpa_inst.bufs[0].len]u8 align(4) = undefined;
624 var l_gpa_inst: std.heap.FixedBufferAllocator = .init(&l_gpa_buf);
625 const q_gpa = q_gpa_inst.allocator();
626 const l_gpa = l_gpa_inst.allocator();
537627
538628 var q: Deque(u32) = .empty;
539 defer q.deinit(gpa);
540629 var l: std.ArrayList(u32) = .empty;
541 defer l.deinit(gpa);
542
543 if (input.len < 2) return;
544
545 var prng = std.Random.DefaultPrng.init(input[0]);
546 const random = prng.random();
547630
548 const Action = enum {
631 const Action = enum(u8) {
632 grow,
549633 push_back,
550634 push_front,
551635 push_back_slice,
552636 push_front_slice,
553637 pop_back,
554638 pop_front,
555 grow,
556 /// Sentinel to avoid hardcoding the cast below
557 max,
558639 };
559 for (input[1..]) |byte| {
560 switch (@as(Action, @enumFromInt(byte % (@intFromEnum(Action.max))))) {
640
641 while (!smith.eosWeightedSimple(15, 1)) {
642 const baseline = testing.Smith.baselineWeights(Action);
643 const grow_weight: testing.Smith.Weight = .value(Action, .grow, 3);
644 switch (smith.valueWeighted(Action, baseline ++ .{grow_weight})) {
561645 .push_back => {
562 const item = random.int(u8);
646 const item = smith.value(u32);
563647 try testing.expectEqual(
564648 l.appendBounded(item),
565649 q.pushBackBounded(item),
566650 );
567651 },
568652 .push_front => {
569 const item = random.int(u8);
653 const item = smith.value(u32);
570654 try testing.expectEqual(
571655 l.insertBounded(0, item),
572656 q.pushFrontBounded(item),
......@@ -574,9 +658,9 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {
574658 },
575659 .push_back_slice => {
576660 var buffer: [std.math.maxInt(u3)]u32 = undefined;
577 const items = buffer[0..random.int(u3)];
661 const items = buffer[0..smith.value(u3)];
578662 for (items) |*item| {
579 item.* = random.int(u8);
663 item.* = smith.value(u32);
580664 }
581665 try testing.expectEqual(
582666 l.appendSliceBounded(items),
......@@ -585,9 +669,9 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {
585669 },
586670 .push_front_slice => {
587671 var buffer: [std.math.maxInt(u3)]u32 = undefined;
588 const items = buffer[0..random.int(u3)];
672 const items = buffer[0..smith.value(u3)];
589673 for (items) |*item| {
590 item.* = random.int(u8);
674 item.* = smith.value(u32);
591675 }
592676 try testing.expectEqual(
593677 l.insertSliceBounded(0, items),
......@@ -607,11 +691,10 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {
607691 // ensureTotalCapacityPrecise(), which is the most complex part
608692 // of the Deque implementation.
609693 .grow => {
610 const growth = random.int(u3);
611 try l.ensureTotalCapacityPrecise(gpa, l.items.len + growth);
612 try q.ensureTotalCapacityPrecise(gpa, q.len + growth);
694 const growth = smith.value(u3);
695 try l.ensureTotalCapacityPrecise(l_gpa, l.items.len + growth);
696 try q.ensureTotalCapacityPrecise(q_gpa, q.len + growth);
613697 },
614 .max => unreachable,
615698 }
616699 try testing.expectEqual(l.getLastOrNull(), q.back());
617700 try testing.expectEqual(
......@@ -627,5 +710,8 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {
627710 }
628711 try testing.expectEqual(null, it.next());
629712 }
713 try testing.expectEqual(@intFromBool(q.buffer.len != 0), q_gpa_inst.allocCount());
630714 }
715 q.deinit(q_gpa);
716 try testing.expectEqual(0, q_gpa_inst.allocCount());
631717}
lib/std/json/scanner_test.zig-17
......@@ -490,20 +490,3 @@ test isNumberFormattedLikeAnInteger {
490490 try std.testing.expect(!isNumberFormattedLikeAnInteger("1e10"));
491491 try std.testing.expect(!isNumberFormattedLikeAnInteger("1E10"));
492492}
493
494test "fuzz" {
495 try std.testing.fuzz({}, fuzzTestOne, .{});
496}
497
498fn fuzzTestOne(_: void, input: []const u8) !void {
499 var buf: [16384]u8 = undefined;
500 var fba: std.heap.FixedBufferAllocator = .init(&buf);
501
502 var scanner = Scanner.initCompleteInput(fba.allocator(), input);
503 // Property: There are at most input.len tokens
504 var tokens: usize = 0;
505 while ((scanner.next() catch return) != .end_of_document) {
506 tokens += 1;
507 if (tokens > input.len) return error.Overflow;
508 }
509}
lib/std/testing.zig+7-1
......@@ -1203,6 +1203,8 @@ pub fn refAllDecls(comptime T: type) void {
12031203 }
12041204}
12051205
1206pub const Smith = @import("testing/Smith.zig");
1207
12061208pub const FuzzInputOptions = struct {
12071209 corpus: []const []const u8 = &.{},
12081210};
......@@ -1210,7 +1212,7 @@ pub const FuzzInputOptions = struct {
12101212/// Inline to avoid coverage instrumentation.
12111213pub inline fn fuzz(
12121214 context: anytype,
1213 comptime testOne: fn (context: @TypeOf(context), input: []const u8) anyerror!void,
1215 comptime testOne: fn (context: @TypeOf(context), smith: *Smith) anyerror!void,
12141216 options: FuzzInputOptions,
12151217) anyerror!void {
12161218 return @import("root").fuzz(context, testOne, options);
......@@ -1317,3 +1319,7 @@ pub const ReaderIndirect = struct {
13171319 };
13181320 }
13191321};
1322
1323test {
1324 _ = &Smith;
1325}
lib/std/testing/Smith.zig created+895
......@@ -0,0 +1,895 @@
1//! Used in conjuncation with `std.testing.fuzz` to generate values
2
3const builtin = @import("builtin");
4const std = @import("../std.zig");
5const assert = std.debug.assert;
6const fuzz_abi = std.Build.abi.fuzz;
7const Smith = @This();
8
9/// Null if the fuzzer is being used, in which case this struct will not be mutated.
10///
11/// Intended to be initialized directly.
12in: ?[]const u8,
13
14pub const Weight = fuzz_abi.Weight;
15
16fn intUid(hash: u32) fuzz_abi.Uid {
17 @disableInstrumentation();
18 return @bitCast(hash << 1);
19}
20
21fn bytesUid(hash: u32) fuzz_abi.Uid {
22 @disableInstrumentation();
23 return @bitCast(hash | 1);
24}
25
26fn Backing(T: type) type {
27 return @Int(.unsigned, @bitSizeOf(T));
28}
29
30fn toExcessK(T: type, x: T) Backing(T) {
31 return @bitCast(x -% std.math.minInt(T));
32}
33
34fn fromExcessK(T: type, x: Backing(T)) T {
35 return @as(T, @bitCast(x)) +% std.math.minInt(T);
36}
37
38fn enumFieldLessThan(_: void, a: std.builtin.Type.EnumField, b: std.builtin.Type.EnumField) bool {
39 return a.value < b.value;
40}
41
42/// Returns an array of weights containing each possible value of `T`.
43//
44// `inline` to propogate the `comptime`ness of the result
45pub inline fn baselineWeights(T: type) []const Weight {
46 return comptime switch (@typeInfo(T)) {
47 .bool, .int, .float => i: {
48 // Reject types that don't have a fixed bitsize (esp. usize)
49 // since they are not gauraunteed to fit in a u64 across targets.
50 if (std.mem.indexOfScalar(type, &.{
51 isize, usize,
52 c_char, c_longdouble,
53 c_short, c_ushort,
54 c_int, c_uint,
55 c_long, c_ulong,
56 c_longlong, c_ulonglong,
57 }, T) != null) {
58 @compileError("type does not have a fixed bitsize: " ++ @typeName(T));
59 }
60 break :i &.{.rangeAtMost(Backing(T), 0, (1 << @bitSizeOf(T)) - 1, 1)};
61 },
62 .@"struct" => |s| if (s.backing_integer) |B|
63 baselineWeights(B)
64 else
65 @compileError("non-packed structs cannot be weighted"),
66 .@"union" => |u| if (u.layout == .@"packed")
67 baselineWeights(Backing(T))
68 else
69 @compileError("non-packed unions cannot be weighted"),
70 .@"enum" => |e| if (!e.is_exhaustive)
71 baselineWeights(e.tag_type)
72 else if (e.fields.len == 0)
73 // Cannot be included in below branch due to `log2_int_ceil`
74 @compileError("exhaustive zero-field enums cannot be weighted")
75 else e: {
76 @setEvalBranchQuota(@intCast(4 * e.fields.len *
77 std.math.log2_int_ceil(usize, e.fields.len)));
78
79 var sorted_fields = e.fields[0..e.fields.len].*;
80 std.mem.sortUnstable(std.builtin.Type.EnumField, &sorted_fields, {}, enumFieldLessThan);
81
82 var weights: []const Weight = &.{};
83 var seq_first: u64 = sorted_fields[0].value;
84 for (sorted_fields[0 .. sorted_fields.len - 1], sorted_fields[1..]) |prev, field| {
85 if (field.value != prev.value + 1) {
86 weights = weights ++ .{Weight.rangeAtMost(u64, seq_first, prev.value, 1)};
87 seq_first = field.value;
88 }
89 }
90 weights = weights ++ .{Weight.rangeAtMost(
91 u64,
92 seq_first,
93 sorted_fields[sorted_fields.len - 1].value,
94 1,
95 )};
96
97 break :e weights;
98 },
99 else => @compileError("unexpected type: " ++ @typeName(T)),
100 };
101}
102
103test baselineWeights {
104 try std.testing.expectEqualSlices(
105 Weight,
106 &.{.rangeAtMost(bool, false, true, 1)},
107 baselineWeights(bool),
108 );
109 try std.testing.expectEqualSlices(
110 Weight,
111 &.{.rangeAtMost(u4, 0, 15, 1)},
112 baselineWeights(u4),
113 );
114 try std.testing.expectEqualSlices(
115 Weight,
116 &.{.rangeAtMost(u4, 0, 15, 1)},
117 baselineWeights(i4),
118 );
119 try std.testing.expectEqualSlices(
120 Weight,
121 &.{.rangeAtMost(u16, 0, 0xffff, 1)},
122 baselineWeights(f16),
123 );
124 try std.testing.expectEqualSlices(
125 Weight,
126 &.{.rangeAtMost(u4, 0, 15, 1)},
127 baselineWeights(packed struct(u4) { _: u4 }),
128 );
129 try std.testing.expectEqualSlices(
130 Weight,
131 &.{.rangeAtMost(u4, 0, 15, 1)},
132 baselineWeights(packed union { _: u4 }),
133 );
134 try std.testing.expectEqualSlices(
135 Weight,
136 &.{.rangeAtMost(u4, 0, 15, 1)},
137 baselineWeights(enum(u4) { _ }),
138 );
139 try std.testing.expectEqualSlices(Weight, &.{
140 .rangeAtMost(u4, 0, 1, 1),
141 .value(u4, 3, 1),
142 .value(u4, 5, 1),
143 .rangeAtMost(u4, 8, 10, 1),
144 }, baselineWeights(enum(u4) {
145 a = 1,
146 b = 5,
147 c = 8,
148 d = 3,
149 e = 0,
150 f = 9,
151 g = 10,
152 }));
153}
154
155fn valueFromInt(T: anytype, int: Backing(T)) T {
156 @disableInstrumentation();
157 return switch (@typeInfo(T)) {
158 .@"enum" => @enumFromInt(int),
159 else => @bitCast(int),
160 };
161}
162
163fn checkWeights(weights: []const Weight, max_incl: u64) void {
164 @disableInstrumentation();
165 const w0 = weights[0]; // Sum of weights is zero
166 assert(w0.weight != 0);
167 assert(w0.max <= max_incl);
168
169 var incl_sum: u64 = (w0.max - w0.min) * w0.weight + (w0.weight - 1); // Sum of weights greater than 2^64
170 for (weights[1..]) |w| {
171 assert(w.weight != 0);
172 assert(w.max <= max_incl);
173 // This addition will not overflow except with an illegal combination of weights since
174 // the exclusive sum must be at least one so a span of all values is impossible.
175 incl_sum += (w.max - w.min + 1) * w.weight; // Sum of weights greater than 2^64
176 }
177}
178
179// `inline` to propogate callee's unique return address
180inline fn firstHash() u32 {
181 return @truncate(std.hash.int(@returnAddress()));
182}
183
184// `noinline` to capture a unique return address
185pub noinline fn value(s: *Smith, T: type) T {
186 @disableInstrumentation();
187 return s.valueWithHash(T, firstHash());
188}
189
190// `noinline` to capture a unique return address
191pub noinline fn valueWeighted(s: *Smith, T: type, weights: []const Weight) T {
192 @disableInstrumentation();
193 return s.valueWeightedWithHash(T, weights, firstHash());
194}
195
196// `noinline` to capture a unique return address
197pub noinline fn valueRangeAtMost(s: *Smith, T: type, at_least: T, at_most: T) T {
198 @disableInstrumentation();
199 return s.valueRangeAtMostWithHash(T, at_least, at_most, firstHash());
200}
201
202// `noinline` to capture a unique return address
203pub noinline fn valueRangeLessThan(s: *Smith, T: type, at_least: T, less_than: T) T {
204 @disableInstrumentation();
205 return s.valueRangeLessThanWithHash(T, at_least, less_than, firstHash());
206}
207
208/// This is similar to `value(bool)` however it is gauraunteed to eventually
209/// return `true` and provides the fuzzer with an extra hint about the data.
210//
211// `noinline` to capture a unique return address
212pub noinline fn eos(s: *Smith) bool {
213 @disableInstrumentation();
214 return s.eosWithHash(firstHash());
215}
216
217/// This is similar to `value(bool)` however it is gauraunteed to eventually
218/// return `true` and provides the fuzzer with an extra hint about the data.
219///
220/// It is asserted that the weight of `true` is non-zero.
221//
222// `noinline` to capture a unique return address
223pub noinline fn eosWeighted(s: *Smith, weights: []const Weight) bool {
224 @disableInstrumentation();
225 return s.eosWeightedWithHash(weights, firstHash());
226}
227
228/// This is similar to `value(bool)` however it is gauraunteed to eventually
229/// return `true` and provides the fuzzer with an extra hint about the data.
230///
231/// It is asserted that the weight of `true` is non-zero.
232//
233// `noinline` to capture a unique return address
234pub noinline fn eosWeightedSimple(s: *Smith, false_weight: u64, true_weight: u64) bool {
235 @disableInstrumentation();
236 return s.eosWeightedSimpleWithHash(false_weight, true_weight, firstHash());
237}
238
239// `noinline` to capture a unique return address
240pub noinline fn bytes(s: *Smith, out: []u8) void {
241 @disableInstrumentation();
242 return s.bytesWithHash(out, firstHash());
243}
244
245// `noinline` to capture a unique return address
246pub noinline fn bytesWeighted(s: *Smith, out: []u8, weights: []const Weight) void {
247 @disableInstrumentation();
248 return s.bytesWeightedWithHash(out, weights, firstHash());
249}
250
251/// Returns the length of the filled slice
252///
253/// It is asserted that `buf.len` fits within a u32
254// `noinline` to capture a unique return address
255pub noinline fn slice(s: *Smith, buf: []u8) u32 {
256 @disableInstrumentation();
257 return s.sliceWithHash(buf, firstHash());
258}
259
260/// Returns the length of the filled slice
261///
262/// It is asserted that `buf.len` fits within a u32
263//
264// `noinline` to capture a unique return address
265pub noinline fn sliceWeightedBytes(s: *Smith, buf: []u8, byte_weights: []const Weight) u32 {
266 @disableInstrumentation();
267 return s.sliceWeightedBytesWithHash(buf, byte_weights, firstHash());
268}
269
270/// Returns the length of the filled slice
271///
272/// It is asserted that `buf.len` fits within a u32
273//
274// `noinline` to capture a unique return address
275pub noinline fn sliceWeighted(
276 s: *Smith,
277 buf: []u8,
278 len_weights: []const Weight,
279 byte_weights: []const Weight,
280) u32 {
281 @disableInstrumentation();
282 return s.sliceWeightedWithHash(buf, len_weights, byte_weights, firstHash());
283}
284
285fn weightsContain(int: u64, weights: []const Weight) bool {
286 @disableInstrumentation();
287 var contains: bool = false;
288 for (weights) |w| {
289 contains |= w.min <= int and int <= w.max;
290 }
291 return contains;
292}
293
294/// Asserts `T` can be a member of a packed type
295//
296// `inline` to propogate the `comptime`ness of the result
297inline fn allBitPatternsValid(T: type) bool {
298 return comptime switch (@typeInfo(T)) {
299 .void, .bool, .int, .float => true,
300 inline .@"struct", .@"union" => |c| c.layout == .@"packed" and for (c.fields) |f| {
301 if (!allBitPatternsValid(f.type)) break false;
302 } else true,
303 .@"enum" => |e| !e.is_exhaustive,
304 else => unreachable,
305 };
306}
307
308test allBitPatternsValid {
309 try std.testing.expect(allBitPatternsValid(packed struct {
310 a: void,
311 b: u8,
312 c: f16,
313 d: packed union {
314 a: u16,
315 b: i16,
316 c: f16,
317 },
318 e: enum(u4) { _ },
319 }));
320 try std.testing.expect(!allBitPatternsValid(packed union {
321 a: i4,
322 b: enum(u4) { a },
323 }));
324}
325
326fn UnionTagWithoutUninitializable(T: type) type {
327 const u = @typeInfo(T).@"union";
328 const Tag = u.tag_type orelse @compileError("union must have tag");
329 const e = @typeInfo(Tag).@"enum";
330 var field_names: [e.fields.len][]const u8 = undefined;
331 var field_values: [e.fields.len]e.tag_type = undefined;
332 var n_fields = 0;
333 for (u.fields) |f| {
334 switch (f.type) {
335 noreturn => continue,
336 else => {},
337 }
338 field_names[n_fields] = f.name;
339 field_values[n_fields] = @intFromEnum(@field(Tag, f.name));
340 n_fields += 1;
341 }
342 return @Enum(e.tag_type, .exhaustive, field_names[0..n_fields], field_values[0..n_fields]);
343}
344
345pub fn valueWithHash(s: *Smith, T: type, hash: u32) T {
346 @disableInstrumentation();
347 return switch (@typeInfo(T)) {
348 .void => {},
349 .bool, .int, .float => full: {
350 var int: Backing(T) = 0;
351 comptime var biti = 0;
352 var rhash = hash; // 'running' hash
353 inline while (biti < @bitSizeOf(T)) {
354 const n = @min(@bitSizeOf(T) - biti, 64);
355 const P = @Int(.unsigned, n);
356 int |= @as(
357 @TypeOf(int),
358 s.valueWeightedWithHash(P, baselineWeights(P), rhash),
359 ) << biti;
360 biti += n;
361 rhash = std.hash.int(rhash);
362 }
363 break :full @bitCast(int);
364 },
365 .@"enum" => |e| if (e.is_exhaustive) v: {
366 if (@bitSizeOf(e.tag_type) <= 64) {
367 break :v s.valueWeightedWithHash(T, baselineWeights(T), hash);
368 }
369 break :v std.enums.fromInt(T, s.valueWithHash(e.tag_type, hash)) orelse
370 @enumFromInt(e.fields[0].value);
371 } else @enumFromInt(s.valueWithHash(e.tag_type, hash)),
372 .optional => |o| if (s.valueWithHash(bool, hash))
373 null
374 else
375 s.valueWithHash(o.child, std.hash.int(hash)),
376 inline .array, .vector => |a| arr: {
377 var arr: [a.len]a.child = undefined; // `T` cannot be used due to the vector case
378 if (a.child != u8) {
379 for (&arr) |*v| {
380 v.* = s.valueWithHash(a.child, hash);
381 }
382 } else {
383 s.bytesWithHash(&arr, hash);
384 }
385 break :arr arr;
386 },
387 .@"struct" => |st| if (!allBitPatternsValid(T)) v: {
388 var v: T = undefined;
389 var rhash = hash;
390 inline for (st.fields) |f| {
391 // rhash is incremented in the call so our rhash state is not reused (e.g. with
392 // two nested structs. note that xor cannot work for this case as the bit would
393 // be flipped back here)
394 @field(v, f.name) = s.valueWithHash(f.type, rhash +% 1);
395 rhash = std.hash.int(rhash);
396 }
397 break :v v;
398 } else @bitCast(s.valueWithHash(st.backing_integer.?, hash)),
399 .@"union" => if (!allBitPatternsValid(T))
400 switch (s.valueWithHash(
401 UnionTagWithoutUninitializable(T),
402 // hash is incremented in the call so our hash state is not reused for below
403 std.hash.int(hash +% 1),
404 )) {
405 inline else => |t| @unionInit(
406 T,
407 @tagName(t),
408 s.valueWithHash(@FieldType(T, @tagName(t)), hash),
409 ),
410 }
411 else
412 @bitCast(s.valueWithHash(Backing(T), hash)),
413 else => @compileError("unexpected type '" ++ @typeName(T) ++ "'"),
414 };
415}
416
417pub fn valueWeightedWithHash(s: *Smith, T: type, weights: []const Weight, hash: u32) T {
418 @disableInstrumentation();
419 checkWeights(weights, (1 << @bitSizeOf(T)) - 1);
420 return valueFromInt(T, @intCast(s.valueWeightedWithHashInner(weights, hash)));
421}
422
423fn valueWeightedWithHashInner(s: *Smith, weights: []const Weight, hash: u32) u64 {
424 @disableInstrumentation();
425 return if (s.in) |*in| int: {
426 if (in.len < 8) {
427 @branchHint(.unlikely);
428 in.* = &.{};
429 break :int weights[0].min;
430 }
431 const int = std.mem.readInt(u64, in.*[0..8], .little);
432 in.* = in.*[8..];
433 break :int if (weightsContain(int, weights)) int else weights[0].min;
434 } else if (builtin.fuzz) int: {
435 @branchHint(.likely);
436 break :int fuzz_abi.fuzzer_int(intUid(hash), .fromSlice(weights));
437 } else unreachable;
438}
439
440pub fn valueRangeAtMostWithHash(s: *Smith, T: type, at_least: T, at_most: T, hash: u32) T {
441 @disableInstrumentation();
442 if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed) {
443 return fromExcessK(T, s.valueRangeAtMostWithHash(
444 Backing(T),
445 toExcessK(T, at_least),
446 toExcessK(T, at_most),
447 hash,
448 ));
449 }
450 return s.valueWeightedWithHash(T, &.{.rangeAtMost(T, at_least, at_most, 1)}, hash);
451}
452
453pub fn valueRangeLessThanWithHash(s: *Smith, T: type, at_least: T, less_than: T, hash: u32) T {
454 @disableInstrumentation();
455 if (@typeInfo(T) == .int and @typeInfo(T).int.signedness == .signed) {
456 return fromExcessK(T, s.valueRangeLessThanWithHash(
457 Backing(T),
458 toExcessK(T, at_least),
459 toExcessK(T, less_than),
460 hash,
461 ));
462 }
463 return s.valueWeightedWithHash(T, &.{.rangeLessThan(T, at_least, less_than, 1)}, hash);
464}
465
466/// This is similar to `value(bool)` however it is gauraunteed to eventually
467/// return `true` and provides the fuzzer with an extra hint about the data.
468pub fn eosWithHash(s: *Smith, hash: u32) bool {
469 @disableInstrumentation();
470 return s.eosWeightedWithHash(baselineWeights(bool), hash);
471}
472
473/// This is similar to `value(bool)` however it is gauraunteed to eventually
474/// return `true` and provides the fuzzer with an extra hint about the data.
475///
476/// It is asserted that the weight of `true` is non-zero.
477pub fn eosWeightedWithHash(s: *Smith, weights: []const Weight, hash: u32) bool {
478 @disableInstrumentation();
479 checkWeights(weights, 1);
480 for (weights) |w| (if (w.max == 1) break) else unreachable; // `true` must have non-zero weight
481
482 if (s.in) |*in| {
483 if (in.len == 0) {
484 @branchHint(.unlikely);
485 return true;
486 }
487 const eos_val = in.*[0] != 0;
488 in.* = in.*[1..];
489 return eos_val or b: {
490 var only_true: bool = true;
491 for (weights) |w| {
492 only_true &= @as(u1, @intCast(w.min)) == 1;
493 }
494 break :b only_true;
495 };
496 } else if (builtin.fuzz) {
497 @branchHint(.likely);
498 return fuzz_abi.fuzzer_eos(intUid(hash), .fromSlice(weights));
499 } else unreachable;
500}
501
502/// This is similar to `value(bool)` however it is gauraunteed to eventually
503/// return `true` and provides the fuzzer with an extra hint about the data.
504///
505/// It is asserted that the weight of `false` is non-zero.
506/// It is asserted that the weight of `true` is non-zero.
507//
508// `noinline` to capture a unique return address
509pub fn eosWeightedSimpleWithHash(s: *Smith, false_weight: u64, true_weight: u64, hash: u32) bool {
510 @disableInstrumentation();
511 return s.eosWeightedWithHash(&.{
512 .value(bool, false, false_weight),
513 .value(bool, true, true_weight),
514 }, hash);
515}
516
517pub fn bytesWithHash(s: *Smith, out: []u8, hash: u32) void {
518 @disableInstrumentation();
519 return s.bytesWeightedWithHash(out, baselineWeights(u8), hash);
520}
521
522pub fn bytesWeightedWithHash(s: *Smith, out: []u8, weights: []const Weight, hash: u32) void {
523 @disableInstrumentation();
524 checkWeights(weights, 255);
525
526 if (s.in) |*in| {
527 var present_weights: [256]bool = @splat(false);
528 for (weights) |w| {
529 @memset(present_weights[@intCast(w.min)..@intCast(w.max + 1)], true);
530 }
531 const default: u8 = @intCast(weights[0].min);
532
533 const copy_len = @min(out.len, in.len);
534 for (in.*[0..copy_len], out[0..copy_len]) |i, *o| {
535 o.* = if (present_weights[i]) i else default;
536 }
537 in.* = in.*[copy_len..];
538 @memset(out[copy_len..], default);
539 } else if (builtin.fuzz) {
540 @branchHint(.likely);
541 fuzz_abi.fuzzer_bytes(bytesUid(hash), .fromSlice(out), .fromSlice(weights));
542 } else unreachable;
543}
544
545/// Returns the length of the filled slice
546///
547/// It is asserted that `buf.len` fits within a u32
548pub fn sliceWithHash(s: *Smith, buf: []u8, hash: u32) u32 {
549 @disableInstrumentation();
550 return s.sliceWeightedBytesWithHash(buf, baselineWeights(u8), hash);
551}
552
553/// Returns the length of the filled slice
554///
555/// It is asserted that `buf.len` fits within a u32
556pub fn sliceWeightedBytesWithHash(
557 s: *Smith,
558 buf: []u8,
559 byte_weights: []const Weight,
560 hash: u32,
561) u32 {
562 @disableInstrumentation();
563 return s.sliceWeightedWithHash(
564 buf,
565 &.{.rangeAtMost(u32, 0, @intCast(buf.len), 1)},
566 byte_weights,
567 hash,
568 );
569}
570
571/// Returns the length of the filled slice
572///
573/// It is asserted that `buf.len` fits within a u32
574pub fn sliceWeightedWithHash(
575 s: *Smith,
576 buf: []u8,
577 len_weights: []const Weight,
578 byte_weights: []const Weight,
579 hash: u32,
580) u32 {
581 @disableInstrumentation();
582 checkWeights(byte_weights, 255);
583 checkWeights(len_weights, @as(u32, @intCast(buf.len)));
584
585 if (s.in) |*in| {
586 const in_len = len: {
587 if (in.len < 4) {
588 @branchHint(.unlikely);
589 in.* = &.{};
590 break :len 0;
591 }
592 const len = std.mem.readInt(u32, in.*[0..4], .little);
593 in.* = in.*[4..];
594 break :len @min(len, in.len);
595 };
596 const out_len: u32 = if (weightsContain(in_len, len_weights))
597 in_len
598 else
599 @intCast(len_weights[0].min);
600
601 var present_weights: [256]bool = @splat(false);
602 for (byte_weights) |w| {
603 @memset(present_weights[@intCast(w.min)..@intCast(w.max + 1)], true);
604 }
605 const default: u8 = @intCast(byte_weights[0].min);
606
607 const copy_len = @min(out_len, in_len);
608 for (in.*[0..copy_len], buf[0..copy_len]) |i, *o| {
609 o.* = if (present_weights[i]) i else default;
610 }
611 in.* = in.*[in_len..];
612 @memset(buf[copy_len..], default);
613 return out_len;
614 } else if (builtin.fuzz) {
615 @branchHint(.likely);
616 return fuzz_abi.fuzzer_slice(
617 bytesUid(hash),
618 .fromSlice(buf),
619 .fromSlice(len_weights),
620 .fromSlice(byte_weights),
621 );
622 } else unreachable;
623}
624
625fn constructInput(comptime values: []const union(enum) {
626 eos: bool,
627 int: u64,
628 bytes: []const u8,
629 slice: []const u8,
630}) []const u8 {
631 const result = comptime result: {
632 var result: [
633 len: {
634 var len = 0;
635 for (values) |v| len += switch (v) {
636 .eos => 1,
637 .int => 8,
638 .bytes => |b| b.len,
639 .slice => |s| 4 + s.len,
640 };
641 break :len len;
642 }
643 ]u8 = undefined;
644 var w: std.Io.Writer = .fixed(&result);
645
646 for (values) |v| switch (v) {
647 .eos => |e| w.writeByte(@intFromBool(e)) catch unreachable,
648 .int => |i| w.writeInt(u64, i, .little) catch unreachable,
649 .bytes => |b| w.writeAll(b) catch unreachable,
650 .slice => |s| {
651 w.writeInt(u32, @intCast(s.len), .little) catch unreachable;
652 w.writeAll(s) catch unreachable;
653 },
654 };
655
656 break :result result;
657 };
658 return &result;
659}
660
661test value {
662 if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest; // TODO
663
664 const S = struct {
665 v: void = {},
666 b: bool = true,
667 ih: u16 = 123,
668 iq: u64 = 55555,
669 io: u128 = (1 << 80) | (1 << 23),
670 fd: f64 = std.math.pi,
671 ft: f80 = std.math.e,
672 eh: enum(u16) { a, _ } = @enumFromInt(999),
673 eo: enum(u128) { a, b, _ } = .b,
674 aw: [3]u32 = .{ 1 << 30, 1 << 20, 1 << 10 },
675 vw: @Vector(3, u32) = .{ 1 << 10, 1 << 20, 1 << 30 },
676 ab: [3]u8 = .{ 55, 33, 88 },
677 vb: @Vector(3, u8) = .{ 22, 44, 99 },
678 s: struct { q: u64 } = .{ .q = 1 },
679 sz: struct {} = .{},
680 sp: packed struct(u8) { a: u5, b: u3 } = .{ .a = 31, .b = 3 },
681 si: packed struct(u8) { a: u5, b: enum(u3) { a, b } } = .{ .a = 15, .b = .b },
682 u: union(enum(u2)) {
683 a: u64,
684 b: u64,
685 c: noreturn,
686 } = .{ .b = 777777 },
687 up: packed union {
688 a: u16,
689 b: f16,
690 } = .{ .b = std.math.phi },
691
692 invalid: struct {
693 ib: u8 = 0,
694 eb: enum(u8) { a, b } = .a,
695 eo: enum(u128) { a, b } = .a,
696 u: union(enum(u1)) { a: noreturn, b: void } = .{ .b = {} },
697 } = .{},
698 };
699 const s: S = .{};
700 const ft_bits: u80 = @bitCast(s.ft);
701 const eo_bits = @intFromEnum(s.eo);
702
703 var smith: Smith = .{
704 .in = constructInput(&.{
705 // v
706 .{ .int = @intFromBool(s.b) }, // b
707 .{ .int = s.ih }, // ih
708 .{ .int = s.iq }, // iq
709 .{ .int = @truncate(s.io) }, .{ .int = @intCast(s.io >> 64) }, // io
710 .{ .int = @bitCast(s.fd) }, // fd
711 .{ .int = @truncate(ft_bits) }, .{ .int = @intCast(ft_bits >> 64) }, // ft
712 .{ .int = @intFromEnum(s.eh) }, // eh
713 .{ .int = @truncate(eo_bits) }, .{ .int = @intCast(eo_bits >> 64) }, // eo
714 .{ .int = s.aw[0] }, .{ .int = s.aw[1] }, .{ .int = s.aw[2] }, // aw
715 .{ .int = s.vw[0] }, .{ .int = s.vw[1] }, .{ .int = s.vw[2] }, // vw
716 .{ .bytes = &s.ab }, // ab
717 .{ .bytes = &@as([3]u8, s.vb) }, // vb
718 .{ .int = s.s.q }, // s.q
719 //sz
720 .{ .int = @as(u8, @bitCast(s.sp)) }, // sp
721 .{ .int = s.si.a }, .{ .int = @intFromEnum(s.si.b) }, // si
722 .{ .int = @intFromEnum(s.u) }, .{ .int = s.u.b }, // u
723 .{ .int = @as(u16, @bitCast(s.up)) }, // up
724 // invalid values
725 .{ .int = 555 }, // invalid.ib
726 .{ .int = 123 }, // invalid.eb
727 .{ .int = 0 }, .{ .int = 1 }, // invalid.eo
728 .{ .int = 0 }, // invalid.u
729 }),
730 };
731
732 try std.testing.expectEqual(s, smith.value(S));
733}
734
735test valueWeighted {
736 var smith: Smith = .{
737 .in = constructInput(&.{
738 .{ .int = 200 },
739 .{ .int = 200 },
740 .{ .int = 300 },
741 .{ .int = 400 },
742 }),
743 };
744
745 try std.testing.expectEqual(200, smith.valueWeighted(u8, &.{.rangeAtMost(u8, 50, 200, 1)}));
746 try std.testing.expectEqual(50, smith.valueWeighted(u8, &.{.rangeLessThan(u8, 50, 200, 1)}));
747 const E = enum(u64) { a = 100, b = 200, c = 300 };
748 try std.testing.expectEqual(E.c, smith.valueWeighted(E, baselineWeights(E)));
749 try std.testing.expectEqual(E.a, smith.valueWeighted(E, baselineWeights(E)));
750 try std.testing.expectEqual(12345, smith.valueWeighted(u64, &.{.value(u64, 12345, 1)}));
751}
752
753test valueRangeAtMost {
754 var smith: Smith = .{
755 .in = constructInput(&.{
756 .{ .int = 100 },
757 .{ .int = 100 },
758 .{ .int = 200 },
759 .{ .int = 100 },
760 .{ .int = 200 },
761 .{ .int = 0 },
762 }),
763 };
764 try std.testing.expectEqual(100, smith.valueRangeAtMost(u8, 0, 250));
765 try std.testing.expectEqual(100, smith.valueRangeAtMost(u8, 100, 100));
766 try std.testing.expectEqual(0, smith.valueRangeAtMost(u8, 0, 100));
767 try std.testing.expectEqual(100 - 128, smith.valueRangeAtMost(i8, -100, 100));
768 try std.testing.expectEqual(200 - 128, smith.valueRangeAtMost(i8, -100, 100));
769 try std.testing.expectEqual(-100, smith.valueRangeAtMost(i8, -100, 100));
770}
771
772test valueRangeLessThan {
773 var smith: Smith = .{
774 .in = constructInput(&.{
775 .{ .int = 100 },
776 .{ .int = 100 },
777 .{ .int = 100 },
778 .{ .int = 100 + 128 },
779 }),
780 };
781 try std.testing.expectEqual(100, smith.valueRangeLessThan(u8, 0, 250));
782 try std.testing.expectEqual(0, smith.valueRangeLessThan(u8, 0, 100));
783 try std.testing.expectEqual(100 - 128, smith.valueRangeLessThan(i8, -100, 100));
784 try std.testing.expectEqual(-100, smith.valueRangeLessThan(i8, -100, 100));
785}
786
787test eos {
788 var smith: Smith = .{
789 .in = constructInput(&.{
790 .{ .eos = false },
791 .{ .eos = true },
792 }),
793 };
794 try std.testing.expect(!smith.eos());
795 try std.testing.expect(smith.eos());
796 try std.testing.expect(smith.eos());
797}
798
799test eosWeighted {
800 var smith: Smith = .{ .in = constructInput(&.{.{ .eos = false }}) };
801 try std.testing.expect(smith.eosWeighted(&.{.value(bool, true, std.math.maxInt(u64))}));
802}
803
804test bytes {
805 var smith: Smith = .{ .in = constructInput(&.{
806 .{ .bytes = "testing!" },
807 .{ .bytes = "ab" },
808 }) };
809 var buf: [8]u8 = undefined;
810
811 smith.bytes(&buf);
812 try std.testing.expectEqualSlices(u8, "testing!", &buf);
813 smith.bytes(buf[0..0]);
814 smith.bytes(buf[0..3]);
815 try std.testing.expectEqualSlices(u8, "ab\x00", buf[0..3]);
816}
817
818test bytesWeighted {
819 var smith: Smith = .{ .in = constructInput(&.{
820 .{ .bytes = "testing!" },
821 .{ .bytes = "ab" },
822 }) };
823 const weights: []const Weight = &.{.rangeAtMost(u8, 'a', 'z', 1)};
824 var buf: [8]u8 = undefined;
825
826 smith.bytesWeighted(&buf, weights);
827 try std.testing.expectEqualSlices(u8, "testinga", &buf);
828 smith.bytesWeighted(buf[0..0], weights);
829 smith.bytesWeighted(buf[0..3], weights);
830 try std.testing.expectEqualSlices(u8, "aba", buf[0..3]);
831}
832
833test slice {
834 var smith: Smith = .{
835 .in = constructInput(&.{
836 .{ .slice = "testing!" },
837 .{ .slice = "" },
838 .{ .slice = "ab" },
839 .{ .bytes = std.mem.asBytes(&std.mem.nativeToLittle(u32, 4)) }, // length past end
840 }),
841 };
842 var buf: [8]u8 = undefined;
843
844 try std.testing.expectEqualSlices(u8, "testing!", buf[0..smith.slice(&buf)]);
845 try std.testing.expectEqualSlices(u8, "", buf[0..smith.slice(&buf)]);
846 try std.testing.expectEqualSlices(u8, "ab", buf[0..smith.slice(&buf)]);
847 try std.testing.expectEqualSlices(u8, "", buf[0..smith.slice(&buf)]);
848}
849
850test sliceWeightedBytes {
851 const weights: []const Weight = &.{.rangeAtMost(u8, 'a', 'z', 1)};
852 var smith: Smith = .{ .in = constructInput(&.{
853 .{ .slice = "testing!" },
854 }) };
855 var buf: [8]u8 = undefined;
856
857 try std.testing.expectEqualSlices(
858 u8,
859 "testinga",
860 buf[0..smith.sliceWeightedBytes(&buf, weights)],
861 );
862 try std.testing.expectEqualSlices(u8, "", buf[0..smith.sliceWeightedBytes(&buf, weights)]);
863}
864
865test sliceWeighted {
866 const len_weights: []const Weight = &.{.rangeAtMost(u8, 3, 6, 1)};
867 const weights: []const Weight = &.{.rangeAtMost(u8, 'a', 'z', 1)};
868 var smith: Smith = .{ .in = constructInput(&.{
869 .{ .slice = "testing!" },
870 .{ .slice = "ing!" },
871 .{ .slice = "ab" },
872 }) };
873 var buf: [8]u8 = undefined;
874
875 try std.testing.expectEqualSlices(
876 u8,
877 "tes",
878 buf[0..smith.sliceWeighted(&buf, len_weights, weights)],
879 );
880 try std.testing.expectEqualSlices(
881 u8,
882 "inga",
883 buf[0..smith.sliceWeighted(&buf, len_weights, weights)],
884 );
885 try std.testing.expectEqualSlices(
886 u8,
887 "aba",
888 buf[0..smith.sliceWeighted(&buf, len_weights, weights)],
889 );
890 try std.testing.expectEqualSlices(
891 u8,
892 "aaa",
893 buf[0..smith.sliceWeighted(&buf, len_weights, weights)],
894 );
895}
lib/std/zig.zig+2
......@@ -14,6 +14,7 @@ pub const Server = @import("zig/Server.zig");
1414pub const Client = @import("zig/Client.zig");
1515pub const Token = tokenizer.Token;
1616pub const Tokenizer = tokenizer.Tokenizer;
17pub const TokenSmith = @import("zig/TokenSmith.zig");
1718pub const string_literal = @import("zig/string_literal.zig");
1819pub const number_literal = @import("zig/number_literal.zig");
1920pub const primitives = @import("zig/primitives.zig");
......@@ -991,6 +992,7 @@ test {
991992 _ = LibCDirs;
992993 _ = LibCInstallation;
993994 _ = Server;
995 _ = TokenSmith;
994996 _ = WindowsSdk;
995997 _ = number_literal;
996998 _ = primitives;
lib/std/zig/Ast.zig+13-2
......@@ -160,10 +160,21 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A
160160 if (token.tag == .eof) break;
161161 }
162162
163 var tokens_slice = tokens.toOwnedSlice();
164 errdefer tokens_slice.deinit(gpa);
165 return parseTokens(gpa, source, tokens_slice, mode);
166}
167
168pub fn parseTokens(
169 gpa: Allocator,
170 source: [:0]const u8,
171 tokens: Ast.TokenList.Slice,
172 mode: Mode,
173) Allocator.Error!Ast {
163174 var parser: Parse = .{
164175 .source = source,
165176 .gpa = gpa,
166 .tokens = tokens.slice(),
177 .tokens = tokens,
167178 .errors = .{},
168179 .nodes = .{},
169180 .extra_data = .{},
......@@ -194,7 +205,7 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A
194205 return Ast{
195206 .source = source,
196207 .mode = mode,
197 .tokens = tokens.toOwnedSlice(),
208 .tokens = tokens,
198209 .nodes = parser.nodes.toOwnedSlice(),
199210 .extra_data = extra_data,
200211 .errors = errors,
lib/std/zig/TokenSmith.zig created+277
......@@ -0,0 +1,277 @@
1//! Generates a list of tokens and a valid corresponding source.
2//! Smithed intertoken content is a non-goal of this.
3
4const std = @import("../std.zig");
5const Smith = std.testing.Smith;
6const Token = std.zig.Token;
7const TokenList = std.zig.Ast.TokenList;
8const TokenSmith = @This();
9
10source_buf: [4096]u8,
11source_len: u32,
12tag_buf: [512]Token.Tag,
13start_buf: [512]std.zig.Ast.ByteOffset,
14tags_len: u16,
15
16fn symbolLenWeights(t: *TokenSmith, min: u32, reserve: u32) [2]Smith.Weight {
17 @disableInstrumentation();
18 const space = @as(u32, t.source_buf.len - 1) - t.source_len - reserve;
19 std.debug.assert(space >= 15);
20 return .{
21 .rangeAtMost(u32, min, space, 1),
22 .rangeAtMost(u32, min, 15, space),
23 };
24}
25
26pub fn gen(smith: *Smith) TokenSmith {
27 @disableInstrumentation();
28 var t: TokenSmith = .{
29 .source_buf = undefined,
30 .source_len = 0,
31 .tag_buf = undefined,
32 .start_buf = undefined,
33 .tags_len = 0,
34 };
35
36 const max_lexeme_len = comptime max: {
37 var max: usize = 0;
38 for (std.meta.tags(Token.Tag)) |tag| {
39 max = @max(max, if (tag.lexeme()) |s| s.len else 0);
40 }
41 break :max max;
42 } + 1; // + space
43 const symbol_reserved = 15 + 4; // 4 = doc comment: "///\n"
44 const max_output_bytes = @max(symbol_reserved, max_lexeme_len);
45
46 while (t.tags_len + 2 < t.tag_buf.len - 1 and
47 t.source_len + max_output_bytes < t.source_buf.len - 1 and
48 !smith.eosWeightedSimple(7, 1))
49 {
50 const tag = smith.value(Token.Tag);
51 if (tag == .eof) continue;
52 t.tag_buf[t.tags_len] = tag;
53 t.start_buf[t.tags_len] = t.source_len;
54 t.tags_len += 1;
55
56 if (tag.lexeme()) |lexeme| {
57 @memcpy(t.source_buf[t.source_len..][0..lexeme.len], lexeme);
58 t.source_len += @intCast(lexeme.len);
59
60 if (tag == .invalid_periodasterisks) {
61 t.tag_buf[t.tags_len] = .asterisk;
62 t.start_buf[t.tags_len] = t.source_len - 1;
63 t.tags_len += 1;
64 }
65
66 t.source_buf[t.source_len] = '\n';
67 t.source_len += 1;
68 } else sw: switch (tag) {
69 .invalid => {
70 // While their are multiple ways invalid may be hit,
71 // it is unlikely the source will be inspected.
72 t.source_buf[t.source_len] = 0;
73 t.source_len += 1;
74 },
75 .identifier => {
76 const start = smith.valueWeighted(u8, &.{
77 .rangeAtMost(u8, 'a', 'z', 1),
78 .rangeAtMost(u8, '@', 'Z', 1), // @, A...Z
79 .value(u8, '_', 1),
80 });
81 t.source_buf[t.source_len] = start;
82 t.source_len += 1;
83 if (start == '@') continue :sw .string_literal;
84
85 const len_weights = t.symbolLenWeights(0, 1);
86 const len = smith.sliceWeighted(
87 t.source_buf[t.source_len..],
88 &len_weights,
89 &.{
90 .rangeAtMost(u8, 'a', 'z', 1),
91 .rangeAtMost(u8, 'A', 'Z', 1),
92 .rangeAtMost(u8, '0', '9', 1),
93 .value(u8, '_', 1),
94 },
95 );
96 if (Token.getKeyword(t.source_buf[t.source_len - 1 ..][0 .. len + 1]) != null) {
97 t.source_buf[t.source_len - 1] = '_';
98 }
99 t.source_len += len;
100
101 t.source_buf[t.source_len] = '\n';
102 t.source_len += 1;
103 },
104 .char_literal, .string_literal => |kind| {
105 const end: u8 = switch (kind) {
106 .char_literal => '\'',
107 .string_literal => '"',
108 else => unreachable,
109 };
110
111 t.source_buf[t.source_len] = end;
112 t.source_len += 1;
113
114 const len_weights = t.symbolLenWeights(0, 2);
115 const len = smith.sliceWeighted(
116 t.source_buf[t.source_len..],
117 &len_weights,
118 &.{
119 .rangeAtMost(u8, 0x20, 0x7e, 1),
120 .value(u8, '\\', 15),
121 },
122 );
123 var start_escape = false;
124 for (t.source_buf[t.source_len..][0..len]) |*c| {
125 if (!start_escape and c.* == end) c.* = ' ';
126 start_escape = !start_escape and c.* == '\\';
127 }
128 if (start_escape) t.source_buf[t.source_len..][len - 1] = ' ';
129 t.source_len += len;
130
131 t.source_buf[t.source_len] = end;
132 t.source_buf[t.source_len + 1] = '\n';
133 t.source_len += 2;
134 },
135 .multiline_string_literal_line => {
136 t.source_buf[t.source_len..][0..2].* = @splat('\\');
137 t.source_len += 2;
138
139 const len_weights = t.symbolLenWeights(0, 1);
140 t.source_len += smith.sliceWeighted(
141 t.source_buf[t.source_len..],
142 &len_weights,
143 &.{.rangeAtMost(u8, 0x20, 0x7e, 1)},
144 );
145
146 t.source_buf[t.source_len] = '\n';
147 t.source_len += 1;
148 },
149 .number_literal => {
150 t.source_buf[t.source_len] = smith.valueRangeAtMost(u8, '0', '9');
151 t.source_len += 1;
152
153 const len_weights = t.symbolLenWeights(0, 1);
154 const len = smith.sliceWeighted(
155 t.source_buf[t.source_len..],
156 &len_weights,
157 &.{
158 .rangeAtMost(u8, '0', '9', 8),
159 .rangeAtMost(u8, 'a', 'z', 1),
160 .rangeAtMost(u8, 'A', 'Z', 1),
161 .value(u8, '+', 1),
162 .rangeAtMost(u8, '-', '.', 1), // -, .
163 },
164 );
165
166 var no_period = false;
167 var not_exponent = true;
168 for (t.source_buf[t.source_len..][0..len], 0..) |*c, i| {
169 const invalid_period = no_period and c.* == '.' or i + 1 == len;
170 const is_exponent = c.* == '-' or c.* == '+';
171 const invalid_exponent = not_exponent and is_exponent;
172 const valid_exponent = !not_exponent and is_exponent;
173 if (invalid_period or invalid_exponent) c.* = '0';
174 no_period |= c.* == '.' or valid_exponent;
175 not_exponent = switch (c.*) {
176 'e', 'E', 'p', 'P' => false,
177 else => true,
178 };
179 }
180
181 t.source_len += len;
182 t.source_buf[t.source_len] = '\n';
183 t.source_len += 1;
184 },
185 .builtin => {
186 t.source_buf[t.source_len] = '@';
187 t.source_len += 1;
188
189 const len_weights = t.symbolLenWeights(1, 1);
190 const len = smith.sliceWeighted(
191 t.source_buf[t.source_len..],
192 &len_weights,
193 &.{
194 .rangeAtMost(u8, 'a', 'z', 1),
195 .rangeAtMost(u8, 'A', 'Z', 1),
196 .rangeAtMost(u8, '0', '9', 1),
197 .value(u8, '_', 1),
198 },
199 );
200 if (t.source_buf[t.source_len] >= '0' and t.source_buf[t.source_len] <= '9') {
201 t.source_buf[t.source_len] = '_';
202 }
203 t.source_len += len;
204
205 t.source_buf[t.source_len] = '\n';
206 t.source_len += 1;
207 },
208 .doc_comment, .container_doc_comment => |kind| {
209 t.source_buf[t.source_len..][0..2].* = "//".*;
210 t.source_buf[t.source_len..][2] = switch (kind) {
211 .doc_comment => '/',
212 .container_doc_comment => '!',
213 else => unreachable,
214 };
215 t.source_len += 3;
216
217 const len_weights = t.symbolLenWeights(0, 1);
218 const len = smith.sliceWeighted(
219 t.source_buf[t.source_len..],
220 &len_weights,
221 &.{
222 .rangeAtMost(u8, 0x20, 0x7e, 1),
223 .rangeAtMost(u8, 0x80, 0xff, 1),
224 },
225 );
226 if (kind == .doc_comment and len != 0 and t.source_buf[t.source_len] == '/') {
227 t.source_buf[t.source_len] = ' ';
228 }
229 t.source_len += len;
230
231 t.source_buf[t.source_len] = '\n';
232 t.source_len += 1;
233 },
234 else => unreachable,
235 }
236 }
237
238 t.tag_buf[t.tags_len] = .eof;
239 t.start_buf[t.tags_len] = t.source_len;
240 t.tags_len += 1;
241 t.source_buf[t.source_len] = 0;
242 return t;
243}
244
245pub fn source(t: *TokenSmith) [:0]u8 {
246 return t.source_buf[0..t.source_len :0];
247}
248
249/// The Slice is not backed by a MultiArrayList, so calling deinit or toMultiArrayList is illegal.
250pub fn list(t: *TokenSmith) TokenList.Slice {
251 var slice: TokenList.Slice = .{
252 .ptrs = undefined,
253 .len = t.tags_len,
254 .capacity = t.tags_len,
255 };
256 comptime std.debug.assert(slice.ptrs.len == 2);
257 slice.ptrs[@intFromEnum(TokenList.Field.tag)] = @ptrCast(&t.tag_buf);
258 slice.ptrs[@intFromEnum(TokenList.Field.start)] = @ptrCast(&t.start_buf);
259 return slice;
260}
261
262test TokenSmith {
263 try std.testing.fuzz({}, checkSource, .{});
264}
265
266fn checkSource(_: void, smith: *Smith) !void {
267 var t: TokenSmith = .gen(smith);
268 try std.testing.expectEqual(Token.Tag.eof, t.tag_buf[t.tags_len - 1]);
269
270 var tokenizer: std.zig.Tokenizer = .init(t.source());
271 for (t.tag_buf[0..t.tags_len], t.start_buf[0..t.tags_len]) |tag, start| {
272 const tok = tokenizer.next();
273 try std.testing.expectEqual(tok.tag, tag);
274 try std.testing.expectEqual(tok.loc.start, start);
275 if (tag == .invalid) break;
276 }
277}
lib/std/zig/parser_test.zig+4-9
......@@ -6420,14 +6420,9 @@ test "fuzz ast parse" {
64206420 try std.testing.fuzz({}, fuzzTestOneParse, .{});
64216421}
64226422
6423fn fuzzTestOneParse(_: void, input: []const u8) !void {
6424 // The first byte holds if zig / zon
6425 if (input.len == 0) return;
6426 const mode: std.zig.Ast.Mode = if (input[0] & 1 == 0) .zig else .zon;
6427 const bytes = input[1..];
6428
6423fn fuzzTestOneParse(_: void, smith: *std.testing.Smith) !void {
6424 const mode = smith.value(std.zig.Ast.Mode);
6425 var tokens: std.zig.TokenSmith = .gen(smith);
64296426 var fba: std.heap.FixedBufferAllocator = .init(&fixed_buffer_mem);
6430 const allocator = fba.allocator();
6431 const source = allocator.dupeZ(u8, bytes) catch return;
6432 _ = std.zig.Ast.parse(allocator, source, mode) catch return;
6427 _ = std.zig.Ast.parseTokens(fba.allocator(), tokens.source(), tokens.list(), mode) catch return;
64336428}
lib/std/zig/tokenizer.zig+22-12
......@@ -713,6 +713,9 @@ pub const Tokenizer = struct {
713713 self.index += 1;
714714 switch (self.buffer[self.index]) {
715715 0, '\n' => result.tag = .invalid,
716 0x01...0x09, 0x0b...0x1f, 0x7f => {
717 continue :state .invalid;
718 },
716719 else => continue :state .string_literal,
717720 }
718721 },
......@@ -1721,15 +1724,22 @@ fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !v
17211724 try std.testing.expectEqual(source.len, last_token.loc.end);
17221725}
17231726
1724fn testPropertiesUpheld(_: void, source: []const u8) !void {
1725 var source0_buf: [512]u8 = undefined;
1726 if (source.len + 1 > source0_buf.len)
1727 return;
1728 @memcpy(source0_buf[0..source.len], source);
1729 source0_buf[source.len] = 0;
1730 const source0 = source0_buf[0..source.len :0];
1727fn testPropertiesUpheld(_: void, smith: *std.testing.Smith) !void {
1728 @disableInstrumentation();
1729 var source_buf: [512]u8 = undefined;
1730 const len = smith.sliceWeightedBytes(source_buf[0 .. source_buf.len - 1], &.{
1731 .rangeAtMost(u8, 0x00, 0xff, 1),
1732 .rangeAtMost(u8, 0x20, 0x7e, 4),
1733 .rangeAtMost(u8, 0x00, 0x1f, 1),
1734 .value(u8, 0, 6),
1735 .value(u8, ' ', 6),
1736 .rangeAtMost(u8, '\t', '\n', 6), // \t, \n
1737 .value(u8, '\r', 3),
1738 });
1739 source_buf[len] = 0;
1740 const source = source_buf[0..len :0];
17311741
1732 var tokenizer = Tokenizer.init(source0);
1742 var tokenizer = Tokenizer.init(source);
17331743 var tokenization_failed = false;
17341744 while (true) {
17351745 const token = tokenizer.next();
......@@ -1742,12 +1752,12 @@ fn testPropertiesUpheld(_: void, source: []const u8) !void {
17421752 tokenization_failed = true;
17431753
17441754 // Property: invalid token always ends at newline or eof
1745 try std.testing.expect(source0[token.loc.end] == '\n' or source0[token.loc.end] == 0);
1755 try std.testing.expect(source[token.loc.end] == '\n' or source[token.loc.end] == 0);
17461756 },
17471757 .eof => {
17481758 // Property: EOF token is always 0-length at end of source.
1749 try std.testing.expectEqual(source0.len, token.loc.start);
1750 try std.testing.expectEqual(source0.len, token.loc.end);
1759 try std.testing.expectEqual(source.len, token.loc.start);
1760 try std.testing.expectEqual(source.len, token.loc.end);
17511761 break;
17521762 },
17531763 else => continue,
......@@ -1755,7 +1765,7 @@ fn testPropertiesUpheld(_: void, source: []const u8) !void {
17551765 }
17561766
17571767 if (tokenization_failed) return;
1758 for (source0) |cur| {
1768 for (source) |cur| {
17591769 // Property: No null byte allowed except at end.
17601770 if (cur == 0) {
17611771 return error.TestUnexpectedResult;
src/codegen/llvm.zig+1-1
......@@ -1116,7 +1116,7 @@ pub const Object = struct {
11161116 // needs to for better fuzzing logic.
11171117 .IndirectCalls = false,
11181118 .TraceBB = false,
1119 .TraceCmp = options.fuzz,
1119 .TraceCmp = false,
11201120 .TraceDiv = false,
11211121 .TraceGep = false,
11221122 .Use8bitCounters = false,
test/standalone/libfuzzer/main.zig+2-4
......@@ -2,9 +2,7 @@ const std = @import("std");
22const abi = std.Build.abi.fuzz;
33const native_endian = @import("builtin").cpu.arch.endian();
44
5fn testOne(in: abi.Slice) callconv(.c) void {
6 std.debug.assertReadable(in.toSlice());
7}
5fn testOne() callconv(.c) void {}
86
97pub fn main(init: std.process.Init) !void {
108 const gpa = init.gpa;
......@@ -19,7 +17,7 @@ pub fn main(init: std.process.Init) !void {
1917 defer cache_dir.close(io);
2018
2119 abi.fuzzer_init(.fromSlice(cache_dir_path));
22 abi.fuzzer_init_test(testOne, .fromSlice("test"));
20 abi.fuzzer_set_test(testOne, .fromSlice("test"));
2321 abi.fuzzer_new_input(.fromSlice(""));
2422 abi.fuzzer_new_input(.fromSlice("hello"));
2523