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;...@@ -379,7 +379,7 @@ var fuzz_amount_or_instance: u64 = undefined;
379379
380pub fn fuzz(380pub fn fuzz(
381 context: anytype,381 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,
383 options: testing.FuzzInputOptions,383 options: testing.FuzzInputOptions,
384) anyerror!void {384) anyerror!void {
385 // Prevent this function from confusing the fuzzer by omitting its own code385 // Prevent this function from confusing the fuzzer by omitting its own code
...@@ -406,12 +406,12 @@ pub fn fuzz(...@@ -406,12 +406,12 @@ pub fn fuzz(
406 const global = struct {406 const global = struct {
407 var ctx: @TypeOf(context) = undefined;407 var ctx: @TypeOf(context) = undefined;
408408
409 fn test_one(input: fuzz_abi.Slice) callconv(.c) void {409 fn test_one() callconv(.c) void {
410 @disableInstrumentation();410 @disableInstrumentation();
411 testing.allocator_instance = .{};411 testing.allocator_instance = .{};
412 defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);412 defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
413 log_err_count = 0;413 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) {
415 error.SkipZigTest => return,415 error.SkipZigTest => return,
416 else => {416 else => {
417 const stderr = std.debug.lockStderr(&.{}).terminal();417 const stderr = std.debug.lockStderr(&.{}).terminal();
...@@ -435,13 +435,11 @@ pub fn fuzz(...@@ -435,13 +435,11 @@ pub fn fuzz(
435 const prev_allocator_state = testing.allocator_instance;435 const prev_allocator_state = testing.allocator_instance;
436 testing.allocator_instance = .{};436 testing.allocator_instance = .{};
437 defer testing.allocator_instance = prev_allocator_state;437 defer testing.allocator_instance = prev_allocator_state;
438
439 global.ctx = context;438 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));
442 for (options.corpus) |elem|441 for (options.corpus) |elem|
443 fuzz_abi.fuzzer_new_input(.fromSlice(elem));442 fuzz_abi.fuzzer_new_input(.fromSlice(elem));
444
445 fuzz_abi.fuzzer_main(fuzz_mode, fuzz_amount_or_instance);443 fuzz_abi.fuzzer_main(fuzz_mode, fuzz_amount_or_instance);
446 return;444 return;
447 }445 }
...@@ -449,10 +447,12 @@ pub fn fuzz(...@@ -449,10 +447,12 @@ pub fn fuzz(
449 // When the unit test executable is not built in fuzz mode, only run the447 // When the unit test executable is not built in fuzz mode, only run the
450 // provided corpus.448 // provided corpus.
451 for (options.corpus) |input| {449 for (options.corpus) |input| {
452 try testOne(context, input);450 var smith: testing.Smith = .{ .in = input };
451 try testOne(context, &smith);
453 }452 }
454453
455 // In case there is no provided corpus, also use an empty454 // In case there is no provided corpus, also use an empty
456 // string as a smoke test.455 // string as a smoke test.
457 try testOne(context, "");456 var smith: testing.Smith = .{ .in = "" };
457 try testOne(context, &smith);
458}458}
lib/fuzzer.zig+1494-1094
...@@ -1,15 +1,13 @@...@@ -1,15 +1,13 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
32
4const std = @import("std");3const std = @import("std");
5const Io = std.Io;4const Io = std.Io;
6const fatal = std.process.fatal;
7const mem = std.mem;5const mem = std.mem;
8const math = std.math;6const math = std.math;
9const Allocator = std.mem.Allocator;
10const assert = std.debug.assert;7const assert = std.debug.assert;
11const panic = std.debug.panic;8const panic = std.debug.panic;
12const abi = std.Build.abi.fuzz;9const abi = std.Build.abi.fuzz;
10const Uid = abi.Uid;
1311
14pub const std_options = std.Options{12pub const std_options = std.Options{
15 .logFn = logOverride,13 .logFn = logOverride,
...@@ -23,8 +21,7 @@ fn logOverride(...@@ -23,8 +21,7 @@ fn logOverride(
23 comptime format: []const u8,21 comptime format: []const u8,
24 args: anytype,22 args: anytype,
25) void {23) void {
26 const f = log_f orelse24 const f = log_f orelse panic("log before initialization, message:\n" ++ format, args);
27 panic("attempt to use log before initialization, message:\n" ++ format, args);
28 f.lock(io, .exclusive) catch |e| panic("failed to lock logging file: {t}", .{e});25 f.lock(io, .exclusive) catch |e| panic("failed to lock logging file: {t}", .{e});
29 defer f.unlock(io);26 defer f.unlock(io);
3027
...@@ -48,10 +45,9 @@ const gpa = switch (builtin.mode) {...@@ -48,10 +45,9 @@ const gpa = switch (builtin.mode) {
48 .ReleaseFast, .ReleaseSmall, .ReleaseSafe => std.heap.smp_allocator,45 .ReleaseFast, .ReleaseSmall, .ReleaseSafe => std.heap.smp_allocator,
49};46};
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.
52var log_f: ?Io.File = null;49var log_f: ?Io.File = null;
53var exec: Executable = .preinit;50var exec: Executable = undefined;
54var inst: Instrumentation = .preinit;
55var fuzzer: Fuzzer = undefined;51var fuzzer: Fuzzer = undefined;
56var current_test_name: ?[]const u8 = null;52var current_test_name: ?[]const u8 = null;
5753
...@@ -60,36 +56,28 @@ fn bitsetUsizes(elems: usize) usize {...@@ -60,36 +56,28 @@ fn bitsetUsizes(elems: usize) usize {
60}56}
6157
62const Executable = struct {58const 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.
64 pc_counters: []u8,60 pc_counters: []u8,
6561
66 cache_f: Io.Dir,62 cache_f: Io.Dir,
67 /// Shared copy of all pcs that have been hit stored in a memory-mapped file that can viewed63 /// Shared copy of all pcs that have been hit stored in a memory-mapped file that can viewed
68 /// while the fuzzer is running.64 /// while the fuzzer is running.
69 shared_seen_pcs: MemoryMappedList,65 shared_seen_pcs: []align(std.heap.page_size_min) volatile u8,
70 /// Hash of pcs used to uniquely identify the shared coverage file66 /// Hash of pcs used to uniquely identify the shared coverage file
71 pc_digest: u64,67 pc_digest: u64,
7268
73 /// A minimal state for this struct which instrumentation can function on.69 fn getCoverageMap(
74 /// Used before this structure is initialized to avoid illegal behavior70 cache_dir: Io.Dir,
75 /// from instrumentation functions being called and using undefined values.71 pcs: []const usize,
76 pub const preinit: Executable = .{72 pc_digest: u64,
77 .pc_counters = undefined, // instrumentation works off the __sancov_cntrs section73 ) []align(std.heap.page_size_min) volatile u8 {
78 .cache_f = undefined,74 const file_name = std.fmt.hex(pc_digest);
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);
8875
89 var v = cache_dir.createDirPathOpen(io, "v", .{}) catch |e|76 var v = cache_dir.createDirPathOpen(io, "v", .{}) catch |e|
90 panic("failed to create directory 'v': {t}", .{e});77 panic("failed to create directory 'v': {t}", .{e});
91 defer v.close(io);78 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, .{
93 .read = true,81 .read = true,
94 // If we create the file, we want to block other processes while we populate it82 // If we create the file, we want to block other processes while we populate it
95 .lock = .exclusive,83 .lock = .exclusive,
...@@ -97,71 +85,76 @@ const Executable = struct {...@@ -97,71 +85,76 @@ const Executable = struct {
97 })) |f|85 })) |f|
98 .{ f, true }86 .{ f, true }
99 else |e| switch (e) {87 else |e| switch (e) {
100 error.PathAlreadyExists => .{ v.openFile(io, &coverage_file_name, .{88 error.PathAlreadyExists => .{ v.openFile(io, &file_name, .{
101 .mode = .read_write,89 .mode = .read_write,
102 .lock = .shared,90 .lock = .shared,
103 }) catch |e2| panic(91 }) catch |e2| panic(
104 "failed to open existing coverage file '{s}': {t}",92 "failed to open existing coverage file '{s}': {t}",
105 .{ &coverage_file_name, e2 },93 .{ &file_name, e2 },
106 ), false },94 ), 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 }),
108 };96 };
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);
110 const coverage_file_len = @sizeOf(abi.SeenPcsHeader) +101 const coverage_file_len = @sizeOf(abi.SeenPcsHeader) +
111 pc_bitset_usizes * @sizeOf(usize) +102 pc_bitset_usizes * @sizeOf(usize) +
112 pcs.len * @sizeOf(usize);103 pcs.len * @sizeOf(usize);
113104
114 if (populate) {105 if (populate) {
115 defer coverage_file.lock(io, .shared) catch |e| panic(106 coverage_file.setLength(io, coverage_file_len) catch |e|
116 "failed to demote lock for coverage file '{s}': {t}",107 panic("failed to resize new coverage file '{s}': {t}", .{ &file_name, e });
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;
136 } else {108 } else {
137 const size = coverage_file.length(io) catch |e|109 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 });
139 if (size != coverage_file_len) panic(111 if (size != coverage_file_len) panic(
140 "incompatible existing coverage file '{s}' (differing lengths: {} != {})",112 "incompatible existing coverage file '{s}' (differing lengths: {} != {})",
141 .{ &coverage_file_name, size, coverage_file_len },113 .{ &file_name, size, coverage_file_len },
142 );114 );
115 }
143116
144 const map = MemoryMappedList.init(117 var io_map = coverage_file.createMemoryMap(io, .{ .len = coverage_file_len }) catch |e|
145 coverage_file,118 panic("failed to memmap coverage file '{s}': {t}", .{ &file_name, e });
146 coverage_file_len,119 const map = io_map.memory;
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 );
152120
153 const seen_pcs_header: *const abi.SeenPcsHeader = @ptrCast(@volatileCast(map.items));121 const header: *abi.SeenPcsHeader = @ptrCast(map[0..@sizeOf(abi.SeenPcsHeader)]);
154 if (seen_pcs_header.pcs_len != pcs.len) panic(122 const trailing = map[@sizeOf(abi.SeenPcsHeader)..];
155 "incompatible existing coverage file '{s}' (differing pcs length: {} != {})",123 const trailing_bitset_end = pc_bitset_usizes * @sizeOf(usize);
156 .{ &coverage_file_name, seen_pcs_header.pcs_len, pcs.len },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 },
157 );143 );
158 if (mem.indexOfDiff(usize, seen_pcs_header.pcAddrs(), pcs)) |i| panic(144 } else { // Check expected contents
159 "incompatible existing coverage file '{s}' (differing pc at index {d}: {x} != {x})",145 if (header.pcs_len != pcs.len) panic(
160 .{ &coverage_file_name, i, seen_pcs_header.pcAddrs()[i], pcs[i] },146 "incompatible existing coverage file '{s}' (differing pcs length: {} != {})",
147 .{ &file_name, header.pcs_len, pcs.len },
161 );148 );
162149 for (0.., header.pcAddrs(), pcs) |i, cov_pc, slided_pc| {
163 return map;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 }
164 }156 }
157 return map;
165 }158 }
166159
167 pub fn init(cache_dir_path: []const u8) Executable {160 pub fn init(cache_dir_path: []const u8) Executable {
...@@ -230,7 +223,7 @@ const Executable = struct {...@@ -230,7 +223,7 @@ const Executable = struct {
230 }223 }
231 break :digest h.final();224 break :digest h.final();
232 };225 };
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
235 return self;228 return self;
236 }229 }
...@@ -244,14 +237,14 @@ const Executable = struct {...@@ -244,14 +237,14 @@ const Executable = struct {
244 index: usize = 0,237 index: usize = 0,
245 pc_counters: []u8,238 pc_counters: []u8,
246239
247 pub fn next(self: *PcBitsetIterator) usize {240 pub fn next(i: *PcBitsetIterator) usize {
248 const rest = self.pc_counters[self.index..];241 const rest = i.pc_counters[i.index..];
249 if (rest.len >= @bitSizeOf(usize)) {242 if (rest.len >= @bitSizeOf(usize)) {
250 defer self.index += @bitSizeOf(usize);243 defer i.index += @bitSizeOf(usize);
251 const V = @Vector(@bitSizeOf(usize), u8);244 const V = @Vector(@bitSizeOf(usize), u8);
252 return @as(usize, @bitCast(@as(V, @splat(0)) != rest[0..@bitSizeOf(usize)].*));245 return @as(usize, @bitCast(@as(V, @splat(0)) != rest[0..@bitSizeOf(usize)].*));
253 } else if (rest.len != 0) {246 } else if (rest.len != 0) {
254 defer self.index += rest.len;247 defer i.index += rest.len;
255 var res: usize = 0;248 var res: usize = 0;
256 for (0.., rest) |bit_index, byte| {249 for (0.., rest) |bit_index, byte| {
257 res |= @shlExact(@as(usize, @intFromBool(byte != 0)), @intCast(bit_index));250 res |= @shlExact(@as(usize, @intFromBool(byte != 0)), @intCast(bit_index));
...@@ -260,155 +253,414 @@ const Executable = struct {...@@ -260,155 +253,414 @@ const Executable = struct {
260 } else unreachable;253 } else unreachable;
261 }254 }
262 };255 };
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 }
263};263};
264264
265/// Data gathered from instrumentation functions.265const Fuzzer = struct {
266/// Seperate from Executable since its state is resetable and changes.266 // The default PRNG is not used here since going through `Random` can be very expensive
267/// Seperate from Fuzzer since it may be needed before fuzzing starts.267 // since LLVM often fails to devirtualize and inline `fill`. Additionally, optimization
268const Instrumentation = struct {268 // is simpler since integers are not serialized then deserialized in the random stream.
269 /// Bitset of seen pcs across all runs excluding fresh pcs.269 //
270 /// This is seperate then shared_seen_pcs because multiple fuzzing processes are likely using270 // This acounts for a 30% performance improvement with LLVM 21.
271 /// it which causes contention and unrelated pcs to our campaign being set.271 xoshiro: std.Random.Xoshiro256,
272 seen_pcs: []usize,272 test_one: abi.TestOne,
273273
274 /// Stores a fresh input's new pcs274 seen_pcs: []usize,
275 fresh_pcs: []usize,275 bests: struct {
276276 len: u32,
277 /// Pcs which __sanitizer_cov_trace_switch and __sanitizer_cov_trace_const_cmpx277 quality_buf: []Input.Best,
278 /// have been called from and have had their already been added to const_x_vals278 input_buf: []Input.Best.Map,
279 const_pcs: std.AutoArrayHashMapUnmanaged(usize, void) = .empty,279 },
280 /// Values that have been constant operands in comparisons and switch cases.280 seen_uids: std.ArrayHashMapUnmanaged(Uid, struct {
281 /// There may be duplicates in this array if they came from different addresses, which is281 slices: union {
282 /// fine as they are likely more important and hence more likely to be selected.282 ints: std.ArrayList([]u64),
283 const_vals2: std.ArrayList(u16) = .empty,283 bytes: std.ArrayList(Input.Data.Bytes),
284 const_vals4: std.ArrayList(u32) = .empty,284 },
285 const_vals8: std.ArrayList(u64) = .empty,285 }, Uid.hashmap_ctx, false),
286 const_vals16: std.ArrayList(u128) = .empty,286
287287 /// Past inputs leading to new pc or uid hits.
288 /// A minimal state for this struct which instrumentation can function on.288 /// These are randomly mutated in round-robin fashion.
289 /// Used before this structure is initialized to avoid illegal behavior289 corpus: std.MultiArrayList(Input),
290 /// from instrumentation functions being called and using undefined values.290 corpus_pos: Input.Index,
291 pub const preinit: Instrumentation = .{291
292 .seen_pcs = undefined, // currently only updated by `Fuzzer`292 bytes_input: std.testing.Smith,
293 .fresh_pcs = undefined,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,
294 };334 };
295335
296 pub fn depreinit(self: *Instrumentation) void {336 const Input = struct {
297 self.const_vals2.deinit(gpa);337 /// Untyped indexes into this are formed as follows: If the index is less than `ints.len`
298 self.const_vals4.deinit(gpa);338 /// it indexes into `ints`, otherwise it indexes into `bytes` subtracted by `ints.len`.
299 self.const_vals8.deinit(gpa);339 /// `math.maxInt(u32)` is reserved and impossible normally.
300 self.const_vals16.deinit(gpa);340 data: Data,
301 self.* = undefined;341 /// Corresponds with `data.uid_slices`.
302 }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 {373 pub fn deinit(b: Bytes) void {
305 const pc_bitset_usizes = bitsetUsizes(exec.pc_counters.len);374 gpa.free(b.entries);
306 const alloc_usizes = pc_bitset_usizes * 2;375 gpa.free(b.table);
307 const buf = gpa.alloc(u8, alloc_usizes * @sizeOf(usize)) catch @panic("OOM");376 }
308 var fba_ctx: std.heap.FixedBufferAllocator = .init(buf);377 };
309 const fba = fba_ctx.allocator();
310378
311 var self: Instrumentation = .{379 pub const UidSlices = std.ArrayHashMapUnmanaged(Uid, struct {
312 .seen_pcs = fba.alloc(usize, pc_bitset_usizes) catch unreachable,380 base: u32,
313 .fresh_pcs = fba.alloc(usize, pc_bitset_usizes) catch unreachable,381 len: u32,
382 }, Uid.hashmap_ctx, false);
314 };383 };
315 self.reset();
316 return self;
317 }
318384
319 pub fn reset(self: *Instrumentation) void {385 pub fn deinit(i: *Input) void {
320 @memset(self.seen_pcs, 0);386 i.data.uid_slices.deinit(gpa);
321 @memset(self.fresh_pcs, 0);387 gpa.free(i.data.ints);
322 self.const_pcs.clearRetainingCapacity();388 i.data.bytes.deinit();
323 self.const_vals2.clearRetainingCapacity();389 gpa.free(i.data.order);
324 self.const_vals4.clearRetainingCapacity();390 gpa.free(i.seen_uid_i);
325 self.const_vals8.clearRetainingCapacity();391 gpa.free(i.weighted_uid_slice_i);
326 self.const_vals16.clearRetainingCapacity();392 gpa.free(i.ref.best_i_buf);
327 }393 i.* = undefined;
394 }
328395
329 /// If false is returned, then the pc is marked as seen396 pub const none: Input = .{
330 pub fn constPcSeen(self: *Instrumentation, pc: usize) bool {397 .data = .{
331 return (self.const_pcs.getOrPut(gpa, pc) catch @panic("OOM")).found_existing;398 .uid_slices = .empty,
332 }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 {409 // Empty input is not referenced by `Fuzzer`
335 var hit_pcs = exec.pcBitsetIterator();410 .ref = undefined,
336 for (self.seen_pcs) |seen_pcs| {411 };
337 if (hit_pcs.next() & ~seen_pcs != 0) return true;
338 }
339412
340 return false;413 pub const Index = enum(u32) {
341 }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`422 pub const Best = struct {
344 pub fn setFresh(self: *Instrumentation) void {423 pc: u32,
345 var hit_pcs = exec.pcBitsetIterator();424 min: Quality,
346 for (self.seen_pcs, self.fresh_pcs) |seen_pcs, *fresh_pcs| {425 max: Quality,
347 fresh_pcs.* = hit_pcs.next() & ~seen_pcs;426
348 }427 /// Order of significance:
349 }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`.442 pub fn betterLess(a: Quality, b: Quality) bool {
352 pub fn atleastFresh(self: *Instrumentation) bool {443 return (a.n_pcs < b.n_pcs) | ((a.n_pcs == b.n_pcs) & (a.req.int() < b.req.int()));
353 var hit_pcs = exec.pcBitsetIterator();444 }
354 for (self.fresh_pcs) |fresh_pcs| {
355 if (fresh_pcs & hit_pcs.next() != fresh_pcs) return false;
356 }
357 return true;
358 }
359445
360 /// Updates based off `fresh_pcs`446 pub fn betterMore(a: Quality, b: Quality) bool {
361 fn updateSeen(self: *Instrumentation) void {447 return (a.n_pcs > b.n_pcs) | ((a.n_pcs == b.n_pcs) & (a.req.int() < b.req.int()));
362 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);448 }
363 const shared_seen_pcs: [*]volatile usize = @ptrCast(449 };
364 exec.shared_seen_pcs.items[@sizeOf(abi.SeenPcsHeader)..].ptr,
365 );
366450
367 for (self.seen_pcs, shared_seen_pcs, self.fresh_pcs) |*seen, *shared_seen, fresh| {451 pub const Map = struct {
368 seen.* |= fresh;452 min: Input.Index,
369 if (fresh != 0)453 max: Input.Index,
370 _ = @atomicRmw(usize, shared_seen, .Or, fresh, .monotonic);454 };
371 }455 };
372 }
373};
374456
375const Fuzzer = struct {457 pub const Builder = struct {
376 arena_ctx: std.heap.ArenaAllocator = .init(gpa),458 uid_slices: std.ArrayHashMapUnmanaged(Uid, union {
377 rng: std.Random.DefaultPrng = .init(0),459 ints: std.MultiArrayList(struct {
378 test_one: abi.TestOne,460 value: u64,
379 /// The next input that will be given to the testOne function. When the461 order_i: u32,
380 /// current process crashes, this memory-mapped file is used to recover the462 }),
381 /// input.463 bytes: std.MultiArrayList(struct {
382 input: MemoryMappedList,464 value: Data.Bytes.Entry,
383465 order_i: u32,
384 /// Minimized past inputs leading to new pc hits.466 }),
385 /// These are randomly mutated in round-robin fashion467 }, Uid.hashmap_ctx, false),
386 /// Element zero is always an empty input. It is gauraunteed no other elements are empty.468 bytes_table: std.ArrayList(u8),
387 corpus: std.ArrayList([]const u8),469 // These will not overflow due to the 32-bit constraint on `MemoryMappedInput`
388 corpus_pos: usize,470 total_ints: u32,
389 /// List of past mutations that have led to new inputs. This way, the mutations that are the471 total_bytes: u32,
390 /// most effective are the most likely to be selected again. Starts with one of each mutation.472 weighted_len: u32,
391 mutations: std.ArrayList(Mutation) = .empty,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 runs486 pub fn addInt(b: *Builder, uid: Uid, int: u64) void {
394 corpus_dir: Io.Dir,487 const u = &b.uid_slices;
395 corpus_dir_idx: usize = 0,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,
401 .corpus = .empty,614 .corpus = .empty,
402 .corpus_pos = 0,615 .corpus_pos = undefined,
403 .mutations = .empty,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,
404 .corpus_dir = undefined,625 .corpus_dir = undefined,
626 .start_corpus_dir = undefined,
405 };627 };
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|
409 panic("failed to open directory '{s}': {t}", .{ unit_test_name, e });661 panic("failed to open directory '{s}': {t}", .{ unit_test_name, e });
410 self.input = in: {662 f.mmap_input = map: {
411 const f = self.corpus_dir.createFile(io, "in", .{663 const input = f.corpus_dir.createFile(io, "in", .{
412 .read = true,664 .read = true,
413 .truncate = false,665 .truncate = false,
414 // In case any other fuzz tests are running under the same test name,666 // In case any other fuzz tests are running under the same test name,
...@@ -419,181 +671,979 @@ const Fuzzer = struct {...@@ -419,181 +671,979 @@ const Fuzzer = struct {
419 error.WouldBlock => @panic("input file 'in' is in use by another fuzzing process"),671 error.WouldBlock => @panic("input file 'in' is in use by another fuzzing process"),
420 else => panic("failed to create input file 'in': {t}", .{e}),672 else => panic("failed to create input file 'in': {t}", .{e}),
421 };673 };
422 const size = f.length(io) catch |e| panic("failed to stat input file 'in': {t}", .{e});674
423 const map = (if (size < std.heap.page_size_max)675 var size = input.length(io) catch |e| panic("failed to stat input file 'in': {t}", .{e});
424 MemoryMappedList.create(f, 8, std.heap.page_size_max)676 if (size < std.heap.page_size_max) {
425 else677 size = std.heap.page_size_max;
426 MemoryMappedList.init(f, size, size)) catch |e|678 input.setLength(io, size) catch |e| panic("failed to resize input file 'in': {t}", .{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])));
434 }679 }
435680
436 break :in map;681 break :map MemoryMappedInput.init(input, size) catch |e|
682 panic("failed to memmap input file 'in': {t}", .{e});
437 };683 };
438 inst.reset();
439684
440 self.mutations.appendSlice(gpa, std.meta.tags(Mutation)) catch @panic("OOM");685 // Perform a dry-run of the stored input in case it might reproduce a crash.
441 // Ensure there is never an empty corpus. Additionally, an empty input usually leads to686 const len = mem.readInt(u32, f.mmap_input.mmap.memory[0..4], .little);
442 // new inputs.687 if (len < f.mmap_input.mmap.memory[4..].len) {
443 self.addInput(&.{});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);
445 while (true) {698 while (true) {
446 var name_buf: [@sizeOf(usize) * 2]u8 = undefined;699 var name_buf: [8]u8 = undefined;
447 const bytes = self.corpus_dir.readFileAlloc(700 const name = f.corpusFileName(&name_buf, @enumFromInt(f.corpus.len));
448 io,701 const bytes = f.corpus_dir.readFileAlloc(io, name, gpa, .unlimited) catch |e| switch (e) {
449 std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable,
450 arena,
451 .unlimited,
452 ) catch |e| switch (e) {
453 error.FileNotFound => break,702 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 }),
455 };704 };
456 // No corpus file of length zero will ever be created705 defer gpa.free(bytes);
457 if (bytes.len == 0)706 f.newInput(bytes, false);
458 panic("corrupt corpus file '{x}' (len of zero)", .{self.corpus_dir_idx});
459 self.addInput(bytes);
460 self.corpus_dir_idx += 1;
461 }707 }
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;
464 }714 }
465715
466 pub fn deinit(self: *Fuzzer) void {716 fn rngInt(f: *Fuzzer, T: type) T {
467 self.input.deinit();717 comptime assert(@bitSizeOf(T) <= 64);
468 self.corpus.deinit(gpa);718 const Unsigned = @Int(.unsigned, @bitSizeOf(T));
469 self.mutations.deinit(gpa);719 return @bitCast(@as(Unsigned, @truncate(f.xoshiro.next())));
470 self.corpus_dir.close(io);
471 self.arena_ctx.deinit();
472 self.* = undefined;
473 }720 }
474721
475 pub fn addInput(self: *Fuzzer, bytes: []const u8) void {722 fn rngLessThan(f: *Fuzzer, T: type, limit: T) T {
476 self.corpus.append(gpa, bytes) catch @panic("OOM");723 return std.Random.limitRangeBiased(T, f.rngInt(T), limit);
477 self.input.clearRetainingCapacity();724 }
478 self.input.ensureTotalCapacity(8 + bytes.len) catch |e|725
479 panic("could not resize shared input file: {t}", .{e});726 /// Used for generating small values rather than making many calls into the prng.
480 self.input.items.len = 8;727 const SmallEntronopy = struct {
481 self.input.appendSliceAssumeCapacity(bytes);728 bits: u64,
482 self.run();729
483 inst.setFresh();730 pub fn take(e: *SmallEntronopy, T: type) T {
484 inst.updateSeen();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 }
485 }862 }
486863
487 /// Assumes `fresh_pcs` correspond to the input864 pub fn newInput(f: *Fuzzer, bytes: []const u8, modify_fs_corpus: bool) void {
488 fn minimizeInput(self: *Fuzzer) void {865 f.runBytes(bytes, .bytes_fresh);
489 // The minimization technique is kept relatively simple, we sequentially try to remove each866 f.req_values = f.input_builder.total_ints + f.input_builder.total_bytes;
490 // byte and check that the new pcs and memory loads are still hit.867 f.req_bytes = @intCast(f.input_builder.bytes_table.items.len);
491 var i = self.input.items.len;868 var input = f.input_builder.build();
492 while (i != 8) {869
493 i -= 1;870 f.uid_data_i.ensureTotalCapacity(gpa, input.data.uid_slices.entries.len) catch @panic("OOM");
494 const old = self.input.orderedRemove(i);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);912 const better_min = quality.betterLess(best.min);
497 self.run();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()) {920 const map = &f.bests.input_buf[best_i];
500 self.input.insertAssumeCapacity(i, old);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 }
501 } else {928 } else {
502 // This removal may have led to new pcs or memory loads being hit, so we need to929 if (better_min and better_max) {
503 // update them to avoid duplicates.930 f.removeBest(map.min, @intCast(best_i), modify_fs_corpus);
504 inst.setFresh();931 }
505 }932 }
506 }933 }
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 });
507 }997 }
508998
509 fn run(self: *Fuzzer) void {999 fn run(f: *Fuzzer, input_uids: usize) void {
510 // `pc_counters` is not cleared since only new hits are relevant.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]).* =1006 f.test_one();
513 mem.nativeToLittle(usize, self.input.items.len - 8);1007 _ = @atomicRmw(usize, &exec.seenPcsHeader().n_runs, .Add, 1, .monotonic);
514 self.test_one(.fromSlice(@volatileCast(self.input.items[8..])));1008 }
5151009
516 const header = mem.bytesAsValue(1010 /// Returns a number of mutations to perform from 1-4
517 abi.SeenPcsHeader,1011 /// with smaller values exponentially more likely.
518 exec.shared_seen_pcs.items[0..@sizeOf(abi.SeenPcsHeader)],1012 pub fn mutCount(rng: u16) u8 {
519 );1013 // The below provides the following distribution
520 _ = @atomicRmw(usize, &header.n_runs, .Add, 1, .monotonic);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);
521 }1022 }
5221023
523 pub fn cycle(self: *Fuzzer) void {1024 pub fn cycle(f: *Fuzzer) void {
524 const input = self.corpus.items[self.corpus_pos];1025 assert(f.mmap_input.len == 0);
525 self.corpus_pos += 1;1026 const corpus = f.corpus.slice();
526 if (self.corpus_pos == self.corpus.items.len)1027 const corpus_i = @intFromEnum(f.corpus_pos);
527 self.corpus_pos = 0;1028
5281029 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
529 const rng = self.rng.random();1030 var n_mutate = mutCount(small_entronopy.take(u16));
530 const m = while (true) {1031 const data = &corpus.items(.data)[corpus_i];
531 const m = self.mutations.items[rng.uintLessThanBiased(usize, self.mutations.items.len)];1032 const weighted_uid_slice_i = corpus.items(.weighted_uid_slice_i)[corpus_i];
532 if (!m.mutate(1033 n_mutate *= @intFromBool(weighted_uid_slice_i.len != 0); // No static mutations on empty
533 rng,1034
534 input,1035 f.mut_data = .{
535 &self.input,1036 .i = @splat(math.maxInt(u32)),
536 self.corpus.items,1037 .seq = @splat(.{
537 inst.const_vals2.items,1038 .kind = .{
538 inst.const_vals4.items,1039 .class = undefined,
539 inst.const_vals8.items,1040 .copy = undefined,
540 inst.const_vals16.items,1041 .ordered_mutate = undefined,
541 )) continue;1042 .none = true,
542 break m;1043 },
1044 .len = undefined,
1045 .copy = undefined,
1046 }),
543 };1047 };
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()) {
548 @branchHint(.unlikely);1081 @branchHint(.unlikely);
5491082
550 const header = mem.bytesAsValue(1083 _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);
551 abi.SeenPcsHeader,1084 f.newInput(f.mmap_input.inputSlice(), true);
552 exec.shared_seen_pcs.items[0..@sizeOf(abi.SeenPcsHeader)],1085 }
553 );1086 f.mmap_input.clearRetainingCapacity();
554 _ = @atomicRmw(usize, &header.unique_runs, .Add, 1, .monotonic);1087
5551088 assert(@intFromEnum(f.corpus_pos) < f.corpus.len);
556 inst.setFresh();1089 f.corpus_pos = @enumFromInt((@intFromEnum(f.corpus_pos) + 1) % f.corpus.len);
557 self.minimizeInput();1090 }
558 inst.updateSeen();1091
5591092 fn weightsContain(int: u64, weights: []const abi.Weight) bool {
560 // An empty-input has always been tried, so if an empty input is fresh then the1093 var contains: bool = false;
561 // test has to be non-deterministic. This has to be checked as duplicate empty1094 for (weights) |w| {
562 // entries are not allowed.1095 contains |= w.min <= int and int <= w.max;
563 if (self.input.items.len - 8 == 0) {1096 }
564 std.log.warn("non-deterministic test (empty input produces different hits)", .{});1097 return contains;
565 _ = @atomicRmw(usize, &header.unique_runs, .Sub, 1, .monotonic);1098 }
566 return;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 },
567 }1485 }
1486 }
5681487
569 const arena = self.arena_ctx.allocator();1488 const copy = @min(rem_out.len, rem_copy.len);
570 const bytes = arena.dupe(u8, @volatileCast(self.input.items[8..])) catch @panic("OOM");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");1495 fn nextBytesInner(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void {
573 self.mutations.appendNTimes(gpa, m, 6) catch @panic("OOM");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 cache1504 @memcpy(out[0..u.bytes.len], u.bytes);
576 var name_buf: [@sizeOf(usize) * 2]u8 = undefined;1505 const weights_incl_sum = sumWeightsInclusive(weights);
577 self.corpus_dir.writeFile(io, .{1506 for (out[u.bytes.len..]) |*o| {
578 .sub_path = std.fmt.bufPrint(&name_buf, "{x}", .{self.corpus_dir_idx}) catch unreachable,1507 o.* = @intCast(f.weightedValue(weights, weights_incl_sum));
579 .data = bytes,1508 }
580 }) catch |e| panic("failed to write corpus file '{x}': {t}", .{ self.corpus_dir_idx, e });1509 },
581 self.corpus_dir_idx += 1;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 },
582 }1520 }
583 }1521 }
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 }
584};1637};
5851638
586/// Instrumentation must not be triggered before this function is called
587export fn fuzzer_init(cache_dir_path: abi.Slice) void {1639export fn fuzzer_init(cache_dir_path: abi.Slice) void {
588 inst.depreinit();
589 exec = .init(cache_dir_path.toSlice());1640 exec = .init(cache_dir_path.toSlice());
590 inst = .init();1641 fuzzer = .init();
591}1642}
5921643
593/// Invalid until `fuzzer_init` is called.
594export fn fuzzer_coverage() abi.Coverage {1644export fn fuzzer_coverage() abi.Coverage {
595 const coverage_id = exec.pc_digest;1645 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
598 var seen_count: usize = 0;1648 var seen_count: usize = 0;
599 for (header.seenBits()) |chunk| {1649 for (header.seenBits()) |chunk| {
...@@ -608,107 +1658,63 @@ export fn fuzzer_coverage() abi.Coverage {...@@ -608,107 +1658,63 @@ export fn fuzzer_coverage() abi.Coverage {
608 };1658 };
609}1659}
6101660
611/// fuzzer_init must be called beforehand1661export fn fuzzer_set_test(test_one: abi.TestOne, unit_test_name: abi.Slice) void {
612export fn fuzzer_init_test(test_one: abi.TestOne, unit_test_name: abi.Slice) void {
613 current_test_name = unit_test_name.toSlice();1662 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());
615}1664}
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.
619export fn fuzzer_new_input(bytes: abi.Slice) void {1666export fn fuzzer_new_input(bytes: abi.Slice) void {
620 // An entry of length zero is always added and duplicates of it are not allowed.1667 if (bytes.len == 0) return; // An entry of length zero is always present
621 if (bytes.len != 0)1668 fuzzer.newInput(bytes.toSlice(), false);
622 fuzzer.addInput(bytes.toSlice());
623}1669}
6241670
625/// fuzzer_init_test must be called first
626export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void {1671export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void {
1672 fuzzer.loadCorpus();
627 switch (limit_kind) {1673 switch (limit_kind) {
628 .forever => while (true) fuzzer.cycle(),1674 .forever => while (true) fuzzer.cycle(),
629 .iterations => for (0..amount) |_| fuzzer.cycle(),1675 .iterations => for (0..amount) |_| fuzzer.cycle(),
630 }1676 }
1677 fuzzer.reset();
631}1678}
6321679
633export fn fuzzer_unslide_address(addr: usize) usize {1680export fn fuzzer_int(uid: Uid, weights: abi.Weights) u64 {
634 const si = std.debug.getSelfDebugInfo() catch @compileError("unsupported");1681 assert(uid.kind == .int);
635 const slide = si.getModuleSlide(io, addr) catch |err| {1682 return fuzzer.nextInt(uid, weights.toSlice());
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;
656}1683}
6571684
658export fn __sanitizer_cov_trace_const_cmp2(const_arg: u16, arg: u16) void {1685export fn fuzzer_eos(uid: Uid, weights: abi.Weights) bool {
659 _ = arg;1686 assert(uid.kind == .int);
660 genericConstCmp(u16, const_arg, "const_vals2");1687 return fuzzer.nextEos(uid, weights.toSlice());
661}1688}
6621689
663export fn __sanitizer_cov_trace_const_cmp4(const_arg: u32, arg: u32) void {1690export fn fuzzer_bytes(uid: Uid, out: abi.MutSlice, weights: abi.Weights) void {
664 _ = arg;1691 assert(uid.kind == .bytes);
665 genericConstCmp(u32, const_arg, "const_vals4");1692 return fuzzer.nextBytes(uid, out.toSlice(), weights.toSlice());
666}1693}
6671694
668export fn __sanitizer_cov_trace_const_cmp8(const_arg: u64, arg: u64) void {1695export fn fuzzer_slice(
669 _ = arg;1696 uid: Uid,
670 genericConstCmp(u64, const_arg, "const_vals8");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());
671}1703}
6721704
673export fn __sanitizer_cov_trace_switch(val: u64, cases: [*]const u64) void {1705export fn fuzzer_unslide_address(addr: usize) usize {
674 _ = val;1706 const si = std.debug.getSelfDebugInfo() catch @compileError("unsupported");
675 if (!inst.constPcSeen(@returnAddress())) {1707 const slide = si.getModuleSlide(io, addr) catch |err| {
676 @branchHint(.unlikely);1708 // The LLVM backend seems to insert placeholder values of `1` in __sancov_pcs1
677 const case_bits = cases[1];1709 if (addr == 1) return 1;
678 const cases_slice = cases[2..][0..cases[0]];1710 panic("failed to find virtual address slide for address 0x{x}: {t}", .{ addr, err });
679 switch (case_bits) {1711 };
680 // 8-bit cases are ignored because they are likely to be randomly generated1712 return addr - slide;
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;
706}1713}
7071714
708export fn __sanitizer_cov_trace_cmp8(arg1: u64, arg2: u64) void {1715/// Helps determine run uniqueness in the face of recursion.
709 _ = arg1;1716/// Currently not used by the fuzzer.
710 _ = arg2;1717export threadlocal var __sancov_lowest_stack: usize = 0;
711}
7121718
713export fn __sanitizer_cov_trace_pc_indir(callee: usize) void {1719export fn __sanitizer_cov_trace_pc_indir(callee: usize) void {
714 // Not valuable because we already have pc tracing via 8bit counters.1720 // 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 {...@@ -729,723 +1735,117 @@ export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void {
729 _ = end;1735 _ = end;
730}1736}
7311737
732/// Copy all of source into dest at position 0.1738/// Reusable and recoverable input.
733/// If the slices overlap, dest.ptr must be <= src.ptr.1739///
734fn volatileCopyForwards(comptime T: type, dest: []volatile T, source: []const volatile T) void {1740/// Has a 32-bit limit on the input length. This has the nice side effect that `u32`
735 for (dest, source) |*d, s| d.* = s;1741/// can be used in most placed in `fuzzer` with the last four values reserved.
736}1742const MemoryMappedInput = struct {
7371743 len: u32,
738/// Copy all of source into dest at position 0.1744 /// Directly accessing `memory` is unsafe, use either `inputSlice` or `writeSlice`.
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.
1308 ///1745 ///
1309 /// Pointers to elements in this slice are invalidated by various functions1746 /// `memory` starts with the length of the input as a little-endian 32-bit integer.
1310 /// of this ArrayList in accordance with the respective documentation. In1747 mmap: Io.File.MemoryMap,
1311 /// all cases, "invalidated" means that the memory has been passed to this1748
1312 /// allocator's resize or free function.1749 /// `file` becomes owned by the returned `MemoryMappedInput`
1313 items: []align(std.heap.page_size_min) volatile u8,1750 pub fn init(file: Io.File, size: usize) !MemoryMappedInput {
1314 /// How many bytes this list can hold without allocating additional memory.1751 assert(size >= 4);
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 );
1328 return .{1752 return .{
1329 .file = file,1753 .len = 0,
1330 .items = ptr[0..length],1754 .mmap = try file.createMemoryMap(io, .{ .len = size }),
1331 .capacity = capacity,
1332 };1755 };
1333 }1756 }
13341757
1335 pub fn create(file: Io.File, length: usize, capacity: usize) !MemoryMappedList {1758 pub fn deinit(l: *MemoryMappedInput) void {
1336 try file.setLength(io, capacity);1759 const f = l.mmap.file;
1337 return init(file, length, capacity);1760 l.mmap.write(io) catch |e| panic("failed to write memory map of 'in': {t}", .{e});
1338 }1761 l.mmap.destroy(io);
13391762 f.close(io);
1340 pub fn deinit(l: *MemoryMappedList) void {
1341 l.file.close(io);
1342 std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity]));
1343 l.* = undefined;1763 l.* = undefined;
1344 }1764 }
13451765
1346 /// Modify the array so that it can hold at least `additional_count` **more** items.1766 /// Modify the array so that it can hold at least `additional_count` **more** items.
1767 ///
1347 /// Invalidates element pointers if additional memory is needed.1768 /// Invalidates element pointers if additional memory is needed.
1348 pub fn ensureUnusedCapacity(l: *MemoryMappedList, additional_count: usize) !void {1769 pub fn ensureUnusedCapacity(l: *MemoryMappedInput, additional_count: usize) void {
1349 return l.ensureTotalCapacity(l.items.len + additional_count);1770 return l.ensureTotalCapacity(4 + l.len + additional_count);
1350 }1771 }
13511772
1352 /// If the current capacity is less than `new_capacity`, this function will1773 /// If the current capacity is less than `min_capacity`, this function will
1353 /// modify the array so that it can hold at least `new_capacity` items.1774 /// modify the array so that it can hold at least `min_capacity` items.
1775 ///
1354 /// Invalidates element pointers if additional memory is needed.1776 /// Invalidates element pointers if additional memory is needed.
1355 pub fn ensureTotalCapacity(l: *MemoryMappedList, new_capacity: usize) !void {1777 pub fn ensureTotalCapacity(l: *MemoryMappedInput, min_capacity: usize) void {
1356 if (l.capacity >= new_capacity) return;1778 if (l.mmap.memory.len < min_capacity) {
13571779 @branchHint(.unlikely);
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;
13641780
1365 std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity]));1781 const max_capacity = 1 << 32; // The size of the length header is not added
1366 try l.file.setLength(io, new_capacity);1782 // in order to keep the capacity page aligned and to allow those values to
1367 l.* = try init(l.file, l.items.len, new_capacity);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 }
1368 }1799 }
13691800
1370 /// Invalidates all element pointers.1801 // Only writing has side effects, so volatile is not needed
1371 pub fn clearRetainingCapacity(l: *MemoryMappedList) void {1802 pub fn inputSlice(l: *MemoryMappedInput) []const u8 {
1372 l.items.len = 0;1803 return l.mmap.memory[4..][0..l.len];
1373 }1804 }
13741805
1375 /// Append the slice of items to the list.1806 // Writing has side effectsd, so volatile is necessary
1376 /// Asserts that the list can hold the additional items.1807 pub fn writeSlice(l: *MemoryMappedInput) []volatile u8 {
1377 pub fn appendSliceAssumeCapacity(l: *MemoryMappedList, items: []const u8) void {1808 return l.mmap.memory;
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);
1383 }1809 }
13841810
1385 /// Extends the list by 1 element.1811 fn writeLen(l: *MemoryMappedInput) void {
1386 /// Never invalidates element pointers.1812 l.writeSlice()[0..4].* = @bitCast(mem.nativeToLittle(u32, l.len));
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;
1391 }1813 }
13921814
1393 /// Increase length by 1, returning pointer to the new item.1815 /// Invalidates all element pointers.
1394 /// The returned pointer becomes invalid when the list is resized.1816 pub fn clearRetainingCapacity(l: *MemoryMappedInput) void {
1395 /// Never invalidates element pointers.1817 l.len = 0;
1396 /// Asserts that the list can hold one additional item.1818 l.writeLen();
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];
1401 }1819 }
14021820
1403 /// Append a value to the list `n` times.1821 /// Append the slice of items to the list.
1404 /// Never invalidates element pointers.1822 ///
1405 /// The function is inline so that a comptime-known `value` parameter will1823 /// Invalidates item pointers if more space is required.
1406 /// have better memset codegen in case it has a repeated byte pattern.1824 pub fn appendSlice(l: *MemoryMappedInput, items: []const u8) void {
1407 /// Asserts that the list can hold the additional items.1825 l.ensureUnusedCapacity(items.len);
1408 pub inline fn appendNTimesAssumeCapacity(l: *MemoryMappedList, value: u8, n: usize) void {1826 @memcpy(l.writeSlice()[4 + l.len ..][0..items.len], items);
1409 const new_len = l.items.len + n;1827 l.len += @as(u32, @intCast(items.len));
1410 assert(new_len <= l.capacity);1828 l.writeLen();
1411 @memset(l.items.ptr[l.items.len..new_len], value);
1412 l.items.len = new_len;
1413 }1829 }
14141830
1415 /// Resize the array, adding `n` new elements, which have `undefined` values.1831 /// Append the little-endian integer to the list.
1416 /// The return value is a slice pointing to the newly allocated elements.1832 ///
1417 /// Never invalidates element pointers.1833 /// Invalidates item pointers if more space is required.
1418 /// The returned pointer becomes invalid when the list is resized.1834 pub fn appendLittleInt(l: *MemoryMappedInput, T: type, x: T) void {
1419 /// Asserts that the list can hold the additional items.1835 l.ensureUnusedCapacity(@sizeOf(T));
1420 pub fn addManyAsSliceAssumeCapacity(l: *MemoryMappedList, n: usize) []volatile u8 {1836 //std.log.debug("{} {} {}", .{ l.writeSlice().len, l.len, @sizeOf(T) });
1421 assert(l.items.len + n <= l.capacity);1837 l.writeSlice()[4 + l.len ..][0..@sizeOf(T)].* = @bitCast(mem.nativeToLittle(T, x));
1422 const prev_len = l.items.len;1838 l.len += @sizeOf(T);
1423 l.items.len += n;1839 l.writeLen();
1424 return l.items[prev_len..][0..n];
1425 }1840 }
14261841
1427 /// Called when memory growth is necessary. Returns a capacity larger than1842 /// Called when memory growth is necessary. Returns a capacity larger than
1428 /// minimum that grows super-linearly.1843 /// minimum that grows super-linearly.
1429 fn growCapacity(current: usize, minimum: usize) usize {1844 fn growCapacity(minimum: usize) usize {
1430 var new = current;1845 return mem.alignForward(
1431 while (true) {1846 usize,
1432 new = mem.alignForward(usize, new + new / 2, std.heap.page_size_max);1847 minimum +| (minimum / 2 + std.heap.page_size_max),
1433 if (new >= minimum) return new;1848 std.heap.page_size_max,
1434 }1849 );
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;
1450 }1850 }
1451};1851};
lib/init/src/main.zig+27-7
...@@ -40,12 +40,32 @@ test "simple test" {...@@ -40,12 +40,32 @@ test "simple test" {
40}40}
4141
42test "fuzz example" {42test "fuzz example" {
43 const Context = struct {43 try std.testing.fuzz({}, testOne, .{});
44 fn testOne(context: @This(), input: []const u8) anyerror!void {44}
45 _ = context;45
46 // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case!46fn testOne(context: void, smith: *std.testing.Smith) !void {
47 try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input));47 _ = context;
48 }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 },
49 };70 };
50 try std.testing.fuzz(Context{}, Context.testOne, .{});
51}71}
lib/std/Build/abi.zig+131-3
...@@ -6,6 +6,7 @@...@@ -6,6 +6,7 @@
6//! All of these components interface to some degree via an ABI:6//! All of these components interface to some degree via an ABI:
7//! * The build runner communicates with the web interface over a WebSocket connection7//! * The build runner communicates with the web interface over a WebSocket connection
8//! * The build runner communicates with `libfuzzer` over a shared memory-mapped file8//! * The build runner communicates with `libfuzzer` over a shared memory-mapped file
9const std = @import("std");
910
10// Check that no WebSocket message type has implicit padding bits. This ensures we never send any11// Check that no WebSocket message type has implicit padding bits. This ensures we never send any
11// undefined bits over the wire, and also helps validate that the layout doesn't differ between, for12// undefined bits over the wire, and also helps validate that the layout doesn't differ between, for
...@@ -13,7 +14,6 @@...@@ -13,7 +14,6 @@
13comptime {14comptime {
14 const check = struct {15 const check = struct {
15 fn check(comptime T: type) void {16 fn check(comptime T: type) void {
16 const std = @import("std");
17 std.debug.assert(@typeInfo(T) == .@"struct");17 std.debug.assert(@typeInfo(T) == .@"struct");
18 std.debug.assert(@typeInfo(T).@"struct".layout == .@"extern");18 std.debug.assert(@typeInfo(T).@"struct".layout == .@"extern");
19 std.debug.assert(std.meta.hasUniqueRepresentation(T));19 std.debug.assert(std.meta.hasUniqueRepresentation(T));
...@@ -139,14 +139,48 @@ pub const Rebuild = extern struct {...@@ -139,14 +139,48 @@ pub const Rebuild = extern struct {
139139
140/// ABI bits specifically relating to the fuzzer interface.140/// ABI bits specifically relating to the fuzzer interface.
141pub const fuzz = struct {141pub 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
143 pub extern fn fuzzer_init(cache_dir_path: Slice) void;161 pub extern fn fuzzer_init(cache_dir_path: Slice) void;
162 /// `fuzzer_init` must be called first.
144 pub extern fn fuzzer_coverage() Coverage;163 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
146 pub extern fn fuzzer_new_input(bytes: Slice) void;168 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`.
147 pub extern fn fuzzer_main(limit_kind: LimitKind, amount: u64) void;171 pub extern fn fuzzer_main(limit_kind: LimitKind, amount: u64) void;
148 pub extern fn fuzzer_unslide_address(addr: usize) usize;172 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
150 pub const Slice = extern struct {184 pub const Slice = extern struct {
151 ptr: [*]const u8,185 ptr: [*]const u8,
152 len: usize,186 len: usize,
...@@ -160,6 +194,100 @@ pub const fuzz = struct {...@@ -160,6 +194,100 @@ pub const fuzz = struct {
160 }194 }
161 };195 };
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
163 pub const LimitKind = enum(u8) { forever, iterations };291 pub const LimitKind = enum(u8) { forever, iterations };
164292
165 /// libfuzzer uses this and its usize is the one that counts. To match the ABI,293 /// 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(...@@ -279,7 +279,7 @@ pub fn init(
279 assert(buffer.len >= flate.max_window_len);279 assert(buffer.len >= flate.max_window_len);
280280
281 // note that disallowing some of these simplifies matching logic281 // note that disallowing some of these simplifies matching logic
282 assert(opts.chain != 0); // use `Huffman`, disallowing this simplies matching282 assert(opts.chain != 0); // use `Huffman`; disallowing this simplies matching
283 assert(opts.good >= 3 and opts.nice >= 3); // a match will (usually) not be found283 assert(opts.good >= 3 and opts.nice >= 3); // a match will (usually) not be found
284 assert(opts.good <= 258 and opts.nice <= 258); // a longer match will not be found284 assert(opts.good <= 258 and opts.nice <= 258); // a longer match will not be found
285 assert(opts.lazy <= opts.nice); // a longer match will (usually) not be found285 assert(opts.lazy <= opts.nice); // a longer match will (usually) not be found
...@@ -558,45 +558,35 @@ test betterMatchLen {...@@ -558,45 +558,35 @@ test betterMatchLen {
558 try std.testing.fuzz({}, testFuzzedMatchLen, .{});558 try std.testing.fuzz({}, testFuzzedMatchLen, .{});
559}559}
560560
561fn testFuzzedMatchLen(_: void, input: []const u8) !void {561fn testFuzzedMatchLen(_: void, smith: *std.testing.Smith) !void {
562 @disableInstrumentation();562 @disableInstrumentation();
563 var r: Io.Reader = .fixed(input);
564 var buf: [1024]u8 = undefined;563 var buf: [1024]u8 = undefined;
565 var w: Writer = .fixed(&buf);564 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| {566 while (w.unusedCapacityLen() != 0 and !smith.eosWeightedSimple(7, 1)) {
571 const op: packed struct(u8) {567 switch (smith.value(enum(u2) { splat, copy, insert })) {
572 kind: enum(u2) { splat, copy, insert_imm, insert },568 .splat => w.splatByteAll(
573 imm: u6,569 smith.value(u8),
574570 smith.valueRangeAtMost(u9, 1, @min(511, w.unusedCapacityLen())),
575 pub fn immOrByte(op_s: @This(), r_s: *Io.Reader) usize {571 ) catch unreachable,
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)),
581 .copy => write: {572 .copy => write: {
582 const start = w.buffered().len -| op.immOrByte(&r);573 if (w.buffered().len == 0) continue;
583 const len = @min(w.buffered().len - start, r.takeByte() catch 3);574 const start = smith.valueRangeAtMost(u10, 0, @intCast(w.buffered().len - 1));
584 break :write w.writeAll(w.buffered()[start..][0..len]);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;
585 },578 },
586 .insert_imm => w.writeByte(op.imm),579 .insert => w.advance(smith.slice(w.unusedCapacitySlice())),
587 .insert => w.writeAll(r.take(580 }
588 @min(r.bufferedLen(), @as(usize, op.imm) + 1),581 }
589 ) catch unreachable),582 w.splatByteAll(0, (1 + token.min_length) -| w.buffered().len) catch unreachable;
590 }) catch break;583
591 } else |_| {}584 const max_start = w.buffered().len - token.min_length;
592585 const bytes_off = smith.valueRangeAtMost(u10, 1, @intCast(max_start));
593 w.splatByteAll(0, (1 + 3) -| w.buffered().len) catch unreachable;586 const prev_off = smith.valueRangeAtMost(u10, 0, bytes_off - 1);
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);
597 const prev = w.buffered()[prev_off..];587 const prev = w.buffered()[prev_off..];
598 const bytes = w.buffered()[bytes_off..];588 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
601 const diff_index = mem.findDiff(u8, prev, bytes).?; // unwrap since lengths are not same591 const diff_index = mem.findDiff(u8, prev, bytes).?; // unwrap since lengths are not same
602 const expected_len = @min(diff_index, 258);592 const expected_len = @min(diff_index, 258);
...@@ -1036,7 +1026,7 @@ const huffman = struct {...@@ -1036,7 +1026,7 @@ const huffman = struct {
1036 max_bits: u4,1026 max_bits: u4,
1037 incomplete_allowed: bool,1027 incomplete_allowed: bool,
1038 ) struct { u32, u16 } {1028 ) struct { u32, u16 } {
1039 assert(out_codes.len - 1 >= @intFromBool(incomplete_allowed));1029 assert(out_codes.len - 1 >= @intFromBool(!incomplete_allowed));
1040 // freqs and out_codes are in the loop to assert they are all the same length1030 // freqs and out_codes are in the loop to assert they are all the same length
1041 for (freqs, out_codes, out_bits) |_, _, n| assert(n == 0);1031 for (freqs, out_codes, out_bits) |_, _, n| assert(n == 0);
1042 assert(out_codes.len <= @as(u16, 1) << max_bits);1032 assert(out_codes.len <= @as(u16, 1) << max_bits);
...@@ -1255,40 +1245,35 @@ const huffman = struct {...@@ -1255,40 +1245,35 @@ const huffman = struct {
1255 try std.testing.fuzz({}, checkFuzzedBuildFreqs, .{});1245 try std.testing.fuzz({}, checkFuzzedBuildFreqs, .{});
1256 }1246 }
12571247
1258 fn checkFuzzedBuildFreqs(_: void, freqs: []const u8) !void {1248 fn checkFuzzedBuildFreqs(_: void, smith: *std.testing.Smith) !void {
1259 @disableInstrumentation();1249 @disableInstrumentation();
1260 var r: Io.Reader = .fixed(freqs);
1261 var freqs_limit: u16 = 65535;1250 var freqs_limit: u16 = 65535;
1262 var freqs_buf: [max_leafs]u16 = undefined;1251 var freqs_buf: [max_leafs]u16 = undefined;
1263 var nfreqs: u15 = 0;1252 var nfreqs: u15 = 0;
12641253
1265 const params: packed struct(u8) {1254 const incomplete_allowed = smith.value(bool);
1266 max_bits: u4,1255 while (nfreqs < @as(u8, @intFromBool(!incomplete_allowed)) + 1 or
1267 _: u3,1256 nfreqs != freqs_buf.len and freqs_limit != 0 and
1268 incomplete_allowed: bool,1257 smith.eosWeightedSimple(15, 1))
1269 } = @bitCast(r.takeByte() catch 255);1258 {
1270 while (nfreqs != freqs_buf.len) {1259 const f = smith.valueWeighted(u16, &.{
1271 const leb = r.takeLeb128(u16);1260 .rangeAtMost(u16, 0, @min(31, freqs_limit), @max(freqs_limit, 1)),
1272 const f = if (leb) |f| @min(f, freqs_limit) else |e| switch (e) {1261 .rangeAtMost(u16, 0, freqs_limit, 1),
1273 error.ReadFailed => unreachable,1262 });
1274 error.EndOfStream => 0,
1275 error.Overflow => freqs_limit,
1276 };
1277 freqs_buf[nfreqs] = f;1263 freqs_buf[nfreqs] = f;
1278 nfreqs += 1;
1279 freqs_limit -= f;1264 freqs_limit -= f;
1280 if (leb == error.EndOfStream and nfreqs - 1 > @intFromBool(params.incomplete_allowed))1265 nfreqs += 1;
1281 break;
1282 }1266 }
12831267
1284 var codes_buf: [max_leafs]u16 = undefined;1268 var codes_buf: [max_leafs]u16 = undefined;
1285 var bits_buf: [max_leafs]u4 = @splat(0);1269 var bits_buf: [max_leafs]u4 = @splat(0);
1270 const max_bits = smith.valueRangeAtMost(u4, math.log2_int_ceil(u15, nfreqs), 15);
1286 const total_bits, const last_nonzero = build(1271 const total_bits, const last_nonzero = build(
1287 freqs_buf[0..nfreqs],1272 freqs_buf[0..nfreqs],
1288 codes_buf[0..nfreqs],1273 codes_buf[0..nfreqs],
1289 bits_buf[0..nfreqs],1274 bits_buf[0..nfreqs],
1290 @max(math.log2_int_ceil(u15, nfreqs), params.max_bits),1275 max_bits,
1291 params.incomplete_allowed,1276 incomplete_allowed,
1292 );1277 );
12931278
1294 var has_bitlen_one: bool = false;1279 var has_bitlen_one: bool = false;
...@@ -1303,21 +1288,21 @@ const huffman = struct {...@@ -1303,21 +1288,21 @@ const huffman = struct {
1303 }1288 }
13041289
1305 errdefer std.log.err(1290 errdefer std.log.err(
1306 \\ params: {}1291 \\ incomplete_allowed: {}
1292 \\ max_bits: {}
1307 \\ freqs: {any}1293 \\ freqs: {any}
1308 \\ bits: {any}1294 \\ bits: {any}
1309 \\ # freqs: {}1295 \\ # freqs: {}
1310 \\ max bits: {}
1311 \\ weighted sum: {}1296 \\ weighted sum: {}
1312 \\ has_bitlen_one: {}1297 \\ has_bitlen_one: {}
1313 \\ expected/actual total bits: {}/{}1298 \\ expected/actual total bits: {}/{}
1314 \\ expected/actual last nonzero: {?}/{}1299 \\ expected/actual last nonzero: {?}/{}
1315 ++ "\n", .{1300 ++ "\n", .{
1316 params,1301 incomplete_allowed,
1302 max_bits,
1317 freqs_buf[0..nfreqs],1303 freqs_buf[0..nfreqs],
1318 bits_buf[0..nfreqs],1304 bits_buf[0..nfreqs],
1319 nfreqs,1305 nfreqs,
1320 @max(math.log2_int_ceil(u15, nfreqs), params.max_bits),
1321 weighted_sum,1306 weighted_sum,
1322 has_bitlen_one,1307 has_bitlen_one,
1323 expected_total_bits,1308 expected_total_bits,
...@@ -1331,7 +1316,7 @@ const huffman = struct {...@@ -1331,7 +1316,7 @@ const huffman = struct {
1331 if (weighted_sum > 1 << 15)1316 if (weighted_sum > 1 << 15)
1332 return error.OversubscribedHuffmanTree;1317 return error.OversubscribedHuffmanTree;
1333 if (weighted_sum < 1 << 15 and1318 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))
1335 return error.IncompleteHuffmanTree;1320 return error.IncompleteHuffmanTree;
1336 }1321 }
1337};1322};
...@@ -1353,6 +1338,7 @@ fn testingFreqBufs() !*[2][65536]u8 {...@@ -1353,6 +1338,7 @@ fn testingFreqBufs() !*[2][65536]u8 {
1353 }1338 }
1354 return fbufs;1339 return fbufs;
1355}1340}
1341const FreqBufIndex = enum(u1) { gradient, random };
13561342
1357fn testingCheckDecompressedMatches(1343fn testingCheckDecompressedMatches(
1358 flate_bytes: []const u8,1344 flate_bytes: []const u8,
...@@ -1426,34 +1412,31 @@ test Compress {...@@ -1426,34 +1412,31 @@ test Compress {
1426 try std.testing.fuzz(fbufs, testFuzzedCompressInput, .{});1412 try std.testing.fuzz(fbufs, testFuzzedCompressInput, .{});
1427}1413}
14281414
1429fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, input: []const u8) !void {1415fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith) !void {
1430 var in: Io.Reader = .fixed(input);1416 @disableInstrumentation();
1431 var opts: packed struct(u51) {1417 const container = smith.value(flate.Container);
1432 container: PackedContainer,1418 const good = smith.valueRangeAtMost(u16, 3, 258);
1433 buf_size: u16,1419 const nice = smith.valueRangeAtMost(u16, 3, 258);
1434 good: u8,1420 const lazy = smith.valueRangeAtMost(u16, 3, nice);
1435 nice: u8,1421 const chain = smith.valueWeighted(u16, &.{
1436 lazy: u8,1422 .rangeAtMost(u16, if (good <= lazy) 4 else 1, 255, 65536),
1437 /// Not a `u16` to limit it for performance1423 // The following weights are greatly reduced since they increasing take more time to run
1438 chain: u9,1424 .rangeAtMost(u16, 256, 4095, 256),
1439 } = @bitCast(in.takeLeb128(u51) catch 0);1425 .rangeAtMost(u16, 4096, 32767 + 256, 1),
1440 var expected_hash: flate.Container.Hasher = .init(opts.container.val());1426 });
1427 var expected_hash: flate.Container.Hasher = .init(container);
1441 var expected_size: u32 = 0;1428 var expected_size: u32 = 0;
14421429
1443 var flate_buf: [128 * 1024]u8 = undefined;1430 var flate_buf: [128 * 1024]u8 = undefined;
1444 var flate_w: Writer = .fixed(&flate_buf);1431 var flate_w: Writer = .fixed(&flate_buf);
1445 var deflate_buf: [flate.max_window_len * 2]u8 = undefined;1432 var deflate_buf: [flate.max_window_len * 2]u8 = undefined;
1446 var deflate_w = try Compress.init(1433 const bufsize = smith.valueRangeAtMost(u32, flate.max_window_len, @intCast(deflate_buf.len));
1447 &flate_w,1434 var deflate_w = try Compress.init(&flate_w, deflate_buf[0..bufsize], container, .{
1448 deflate_buf[0 .. flate.max_window_len + @as(usize, opts.buf_size)],1435 .good = good,
1449 opts.container.val(),1436 .nice = nice,
1450 .{1437 .lazy = lazy,
1451 .good = @as(u16, opts.good) + 3,1438 .chain = chain,
1452 .nice = @as(u16, opts.nice) + 3,1439 });
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 );
14571440
1458 // It is ensured that more bytes are not written then this to ensure this run1441 // It is ensured that more bytes are not written then this to ensure this run
1459 // does not take too long and that `flate_buf` does not run out of space.1442 // 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...@@ -1465,79 +1448,57 @@ fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, input: []const u8) !void
1465 // extra 32 bytes is reserved on top of that for container headers and footers.1448 // extra 32 bytes is reserved on top of that for container headers and footers.
1466 const max_size = flate_buf.len - (flate_buf_blocks * 64 + 32);1449 const max_size = flate_buf.len - (flate_buf_blocks * 64 + 32);
14671450
1468 while (true) {1451 while (!smith.eosWeightedSimple(7, 1)) {
1469 const data: packed struct(u36) {1452 const max_bytes = max_size -| expected_size;
1470 is_rebase: bool,1453 if (max_bytes == 0) break;
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 });
14981454
1499 const buffered = deflate_w.writer.buffered();1455 const buffered = deflate_w.writer.buffered();
1500 // Required for repeating patterns and since writing from `buffered` is illegal1456 // Required for repeating patterns and since writing from `buffered` is illegal
1501 var copy_buf: [512]u8 = undefined;1457 var copy_buf: [512]u8 = undefined;
15021458
1503 if (data.is_rebase) {1459 const bytes = bytes: switch (smith.valueRangeAtMost(
1504 const usable_capacity = deflate_w.writer.buffer.len - rebase_reserved_capacity;1460 u2,
1505 const preserve = @min(data.params.rebase.preserve, usable_capacity);1461 @intFromBool(buffered.len == 0),
1506 const capacity = @min(data.params.rebase.capacity, usable_capacity -1462 2,
1507 @max(rebase_min_preserve, preserve));1463 )) {
1508 try deflate_w.writer.rebase(preserve, capacity);1464 0 => { // Copy
1509 continue;1465 const start = smith.valueRangeLessThan(u32, 0, @intCast(buffered.len));
1510 }1466 // Reuse the implementation's history; otherwise, our own would need maintained.
15111467 const from = buffered[start..];
1512 const max_bytes = max_size -| expected_size;1468 const len = smith.valueRangeAtMost(u16, 1, @min(copy_buf.len, max_bytes));
1513 const bytes = if (!data.is_bytes and buffered.len != 0) bytes: {1469
1514 const dist = @min(buffered.len, @as(u32, data.params.copy.dist) + 1);1470 const history_bytes = from[0..@min(from.len, len)];
1515 const len = @min(1471 @memcpy(copy_buf[0..history_bytes.len], history_bytes);
1516 @max(@shlExact(@as(u9, data.params.copy.len_hi), 5) | data.params.copy.len_lo, 1),1472 const repeat_len = len - history_bytes.len;
1517 max_bytes,1473 for (
1518 );1474 copy_buf[history_bytes.len..][0..repeat_len],
1519 // Reuse the implementation's history. Otherwise our own would need maintained.1475 copy_buf[0..repeat_len],
1520 const bytes_start = buffered[buffered.len - dist ..];1476 ) |*next, prev| {
1521 const history_bytes = bytes_start[0..@min(bytes_start.len, len)];1477 next.* = prev;
15221478 }
1523 @memcpy(copy_buf[0..history_bytes.len], history_bytes);1479 break :bytes copy_buf[0..len];
1524 const new_history = len - history_bytes.len;1480 },
1525 if (history_bytes.len != len) for ( // check needed for `- dist`1481 1 => { // Bytes
1526 copy_buf[history_bytes.len..][0..new_history],1482 const fbuf = &fbufs[
1527 copy_buf[history_bytes.len - dist ..][0..new_history],1483 smith.valueWeighted(u1, &.{
1528 ) |*next, prev| {1484 .value(FreqBufIndex, .gradient, 3),
1529 next.* = prev;1485 .value(FreqBufIndex, .random, 1),
1530 };1486 })
1531 break :bytes copy_buf[0..len];1487 ];
1532 } else bytes: {1488 const len = smith.valueRangeAtMost(u32, 1, @min(fbuf.len, max_bytes));
1533 const off = @shlExact(@as(u16, data.params.bytes.off_hi), 12) |1489 const off = smith.valueRangeAtMost(u32, 0, @intCast(fbuf.len - len));
1534 @shlExact(@as(u16, data.params.bytes.off_mi), 8) |1490 break :bytes fbuf[off..][0..len];
1535 data.params.bytes.off_lo;1491 },
1536 const len = @shlExact(@as(u16, data.params.bytes.len_hi), 10) |1492 2 => { // Rebase
1537 data.params.bytes.len_lo;1493 const rebaseable = bufsize - rebase_reserved_capacity;
1538 const fbuf = &fbufs[@intFromEnum(data.params.bytes.kind)];1494 const capacity = smith.valueRangeAtMost(u32, 1, rebaseable - rebase_min_preserve);
1539 break :bytes fbuf[off..][0..@min(len, fbuf.len - off, max_bytes)];1495 const preserve = smith.valueRangeAtMost(u32, 0, rebaseable - capacity);
1496 try deflate_w.writer.rebase(preserve, capacity);
1497 continue;
1498 },
1499 else => unreachable,
1540 };1500 };
1501
1541 assert(bytes.len <= max_bytes);1502 assert(bytes.len <= max_bytes);
1542 try deflate_w.writer.writeAll(bytes);1503 try deflate_w.writer.writeAll(bytes);
1543 expected_hash.update(bytes);1504 expected_hash.update(bytes);
...@@ -1780,7 +1741,8 @@ fn countVec(data: []const []const u8) usize {...@@ -1780,7 +1741,8 @@ fn countVec(data: []const []const u8) usize {
1780 return bytes;1741 return bytes;
1781}1742}
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();
1784 const HashedStoreWriter = struct {1746 const HashedStoreWriter = struct {
1785 writer: Writer,1747 writer: Writer,
1786 state: enum {1748 state: enum {
...@@ -1819,8 +1781,8 @@ fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, input: []const u8) !void {...@@ -1819,8 +1781,8 @@ fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, input: []const u8) !void {
18191781
1820 /// Note that this implementation is somewhat dependent on the implementation of1782 /// Note that this implementation is somewhat dependent on the implementation of
1821 /// `Raw` by expecting headers / footers to be continous in data elements. It1783 /// `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 not1784 /// also expects the header to be the same as `flate.Container.header` and for
1823 /// for multiple streams to be concatenated.1785 /// multiple streams to not be concatenated.
1824 fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {1786 fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
1825 errdefer w.* = .failing;1787 errdefer w.* = .failing;
1826 var h: *@This() = @fieldParentPtr("writer", w);1788 var h: *@This() = @fieldParentPtr("writer", w);
...@@ -1909,102 +1871,110 @@ fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, input: []const u8) !void {...@@ -1909,102 +1871,110 @@ fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, input: []const u8) !void {
1909 }1871 }
19101872
1911 fn flush(w: *Writer) Writer.Error!void {1873 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`
1913 _ = try @This().drain(w, &.{""}, 0);1875 _ = try @This().drain(w, &.{""}, 0);
1914 }1876 }
1915 };1877 };
19161878
1917 var in: Io.Reader = .fixed(input);1879 const container = smith.value(flate.Container);
1918 const opts: packed struct(u19) {1880 var output: HashedStoreWriter = .init(&.{}, container);
1919 container: PackedContainer,1881 var expected_hash: flate.Container.Hasher = .init(container);
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());
1932 var expected_size: u32 = 0;1882 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);
1933 var vecs: [32][]const u8 = undefined;1895 var vecs: [32][]const u8 = undefined;
1934 var vecs_n: usize = 0;1896 var vecs_n: usize = 0;
19351897
1936 while (in.seek != in.end) {1898 while (true) {
1937 const VecInfo = packed struct(u58) {1899 const Op = packed struct {
1938 output: bool,1900 drain: bool = false,
1939 /// If set, `data_len` and `splat` are reinterpreted as `capacity`1901 add_vec: bool = false,
1940 /// and `preserve_len` respectively and `output` is treated as set.1902 rebase: bool = false,
1941 rebase: bool,1903
1942 block_aligning_len: bool,1904 pub const drain_only: @This() = .{ .drain = true };
1943 block_aligning_splat: bool,1905 pub const add_vec_only: @This() = .{ .add_vec = true };
1944 data_len: u18,1906 pub const add_vec_and_drain: @This() = .{ .add_vec = true, .drain = true };
1945 splat: u18,1907 pub const drain_and_rebase: @This() = .{ .drain = true, .rebase = true };
1946 data_off: u18,
1947 };1908 };
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]);1910 const is_eos = expected_size == max_size or smith.eosWeightedSimple(7, 1);
1988 expected_size += @as(u32, @intCast(vecs[vecs_n].len)) * splat;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];
1989 vecs_n += 1;1933 vecs_n += 1;
1934 op.drain |= vecs_n == vecs.len;
1990 }1935 }
19911936
1992 const want_drain = vecs_n == vecs.len or vec_info.output or vec_info.rebase or1937 op.drain |= is_eos;
1993 in.seek == in.end;1938 op.drain &= vecs_n != 0;
1994 if (want_drain and vecs_n != 0) {1939 if (op.drain) {
1995 try r.writer.writeSplatAll(vecs[0..vecs_n], splat);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);
1996 vecs_n = 0;1965 vecs_n = 0;
1997 } else assert(splat == 1);1966 }
19981967
1999 if (vec_info.rebase) {1968 if (op.rebase) {
2000 try r.writer.rebase(vec_info.data_len, @min(1969 const capacity = smith.valueRangeAtMost(u32, 0, raw_buf_len);
2001 r.writer.buffer.len -| vec_info.data_len,1970 const preserve = smith.valueRangeAtMost(u32, 0, raw_buf_len - capacity);
2002 vec_info.splat,1971 try raw.writer.rebase(preserve, capacity);
2003 ));
2004 }1972 }
1973
1974 if (is_eos) break;
2005 }1975 }
20061976
2007 try r.writer.flush();1977 try raw.writer.flush();
2008 try output.writer.flush();1978 try output.writer.flush();
20091979
2010 try std.testing.expectEqual(.end, output.state);1980 try std.testing.expectEqual(.end, output.state);
...@@ -2432,120 +2402,146 @@ test Huffman {...@@ -2432,120 +2402,146 @@ test Huffman {
2432 try std.testing.fuzz(fbufs, testFuzzedHuffmanInput, .{});2402 try std.testing.fuzz(fbufs, testFuzzedHuffmanInput, .{});
2433}2403}
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
2435/// This function is derived from `testFuzzedRawInput` with a few changes for fuzzing `Huffman`.2413/// This function is derived from `testFuzzedRawInput` with a few changes for fuzzing `Huffman`.
2436fn testFuzzedHuffmanInput(fbufs: *const [2][65536]u8, input: []const u8) !void {2414fn testFuzzedHuffmanInput(fbufs: *const [2][65536]u8, smith: *std.testing.Smith) !void {
2437 var in: Io.Reader = .fixed(input);2415 @disableInstrumentation();
2438 const opts: packed struct(u19) {2416 const container = smith.value(flate.Container);
2439 container: PackedContainer,
2440 buf_len: u17,
2441 } = @bitCast(in.takeLeb128(u19) catch 0);
2442 var flate_buf: [2 * 65536]u8 = undefined;2417 var flate_buf: [2 * 65536]u8 = undefined;
2443 var flate_w: Writer = .fixed(&flate_buf);2418 var flate_w: Writer = .fixed(&flate_buf);
2444 var h_buf: [2 * 65536]u8 = undefined;2419 var expected_hash: flate.Container.Hasher = .init(container);
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());
2452 var expected_size: u32 = 0;2420 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
2453 var vecs: [32][]const u8 = undefined;2430 var vecs: [32][]const u8 = undefined;
2454 var vecs_n: usize = 0;2431 var vecs_n: usize = 0;
24552432
2456 while (in.seek != in.end) {2433 while (true) {
2457 const VecInfo = packed struct(u55) {2434 const Op = packed struct {
2458 output: bool,2435 drain: bool = false,
2459 /// If set, `data_len` and `splat` are reinterpreted as `capacity`2436 add_vec: bool = false,
2460 /// and `preserve_len` respectively and `output` is treated as set.2437 rebase: bool = false,
2461 rebase: bool,2438
2462 block_aligning_len: bool,2439 pub const drain_only: @This() = .{ .drain = true };
2463 block_aligning_splat: bool,2440 pub const add_vec_only: @This() = .{ .add_vec = true };
2464 data_off_hi: u8,2441 pub const add_vec_and_drain: @This() = .{ .add_vec = true, .drain = true };
2465 random_data: u1,2442 pub const drain_and_rebase: @This() = .{ .drain = true, .rebase = true };
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,
2470 };2443 };
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 {2445 const is_eos = expected_size == max_size or smith.eosWeightedSimple(7, 1);
2477 const buffered = h.writer.buffered().len + countVec(vecs[0..vecs_n]);2446 var op: Op = if (!is_eos) smith.valueWeighted(Op, &.{
2478 const to_align = mem.alignForwardAnyAlign(usize, buffered, Huffman.max_tokens) - buffered;2447 .value(Op, .add_vec_only, 6),
2479 assert((buffered + to_align) % Huffman.max_tokens == 0);2448 .value(Op, .add_vec_and_drain, 1),
24802449 .value(Op, .drain_and_rebase, 1),
2481 if (vec_info.block_aligning_len) {2450 }) else .drain_only;
2482 vec_info.data_len = @intCast(to_align);2451
2483 } else if (vec_info.block_aligning_splat and vec_info.data_len != 0 and2452 if (op.add_vec) {
2484 to_align % vec_info.data_len == 0)2453 const max_write = max_size - expected_size;
2485 {2454 const buffered: u32 = @intCast(h.writer.buffered().len + countVec(vecs[0..vecs_n]));
2486 vec_info.splat = @divExact(@as(u18, @intCast(to_align)), vec_info.data_len) -% 1;2455 const to_align = Huffman.max_tokens - buffered % Huffman.max_tokens;
2487 }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;
2488 }2478 }
24892479
2490 var splat = if (vec_info.output and !vec_info.rebase) vec_info.splat +% 1 else 1;2480 op.drain |= is_eos;
2491 add_vec: {2481 op.drain &= vecs_n != 0;
2492 if (vec_info.rebase) break :add_vec;2482 if (op.drain) {
2493 if (expected_size +| math.mulWide(u18, vec_info.data_len, splat) > 4 * (1 << 16)) {2483 const pattern_len: u32 = @intCast(vecs[vecs_n - 1].len);
2494 // Skip this vector to avoid this test taking too long.2484 const pattern_len_z = @max(pattern_len, 1);
2495 splat = 1;2485
2496 break :add_vec;2486 const max_write = max_size - (expected_size - pattern_len);
2497 }2487 const buffered: u32 = @intCast(h.writer.buffered().len + countVec(vecs[0 .. vecs_n - 1]));
24982488 const to_align = Huffman.max_tokens - buffered % Huffman.max_tokens;
2499 const data_buf = &fbufs[vec_info.random_data];2489 assert(to_align != 0); // otherwise, not helpful.
2500 vecs[vecs_n] = data_buf[@min(2490
2501 (@as(u16, vec_info.data_off_hi) << 8) | vec_info.data_off_lo,2491 const max_splat = max_write / pattern_len_z;
2502 data_buf.len - vec_info.data_len,2492 const weights: [3]std.testing.Smith.Weight = .{
2503 )..][0..vec_info.data_len];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]);2517 vecs_n = 0;
2506 expected_size += @as(u32, @intCast(vecs[vecs_n].len)) * splat;
2507 vecs_n += 1;
2508 }2518 }
25092519
2510 const want_drain = vecs_n == vecs.len or vec_info.output or vec_info.rebase or2520 if (op.rebase) {
2511 in.seek == in.end;2521 const capacity = smith.valueRangeAtMost(u32, 0, h_buf_len);
2512 if (want_drain and vecs_n != 0) {2522 const preserve = smith.valueRangeAtMost(u32, 0, h_buf_len - capacity);
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;
25202523
2521 vecs_n = 0;2524 const max_space = fuzzedHuffmanDrainSpaceLimit(
2522 } else assert(splat == 1);2525 h.writer.buffered().len,
25232526 flate_w.buffered().len,
2524 if (vec_info.rebase) {2527 false,
2525 const old_end = flate_w.end;2528 );
2526 var n = h.writer.buffered().len;2529 h.writer.rebase(preserve, capacity) catch
2527 const oos = h.writer.rebase(vec_info.data_len, @min(2530 return if (max_space <= flate_w.buffer.len) error.OverheadTooLarge else {};
2528 h.writer.buffer.len -| vec_info.data_len,2531 if (flate_w.buffered().len > max_space) return error.OverheadTooLarge;
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;
2536 }2532 }
2537 }
25382533
2539 {2534 if (is_eos) break;
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;
2548 }2535 }
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
2550 try testingCheckDecompressedMatches(flate_w.buffered(), expected_size, expected_hash);2546 try testingCheckDecompressedMatches(flate_w.buffered(), expected_size, expected_hash);
2551}2547}
lib/std/debug.zig+1
...@@ -417,6 +417,7 @@ pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *con...@@ -417,6 +417,7 @@ pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *con
417/// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert417/// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert
418/// function is the correct function to use.418/// function is the correct function to use.
419pub fn assert(ok: bool) void {419pub fn assert(ok: bool) void {
420 @disableInstrumentation();
420 if (!ok) unreachable; // assertion failure421 if (!ok) unreachable; // assertion failure
421}422}
422423
lib/std/deque.zig+120-34
...@@ -518,55 +518,139 @@ test "fuzz against ArrayList oracle" {...@@ -518,55 +518,139 @@ test "fuzz against ArrayList oracle" {
518 try std.testing.fuzz({}, fuzzAgainstArrayList, .{});518 try std.testing.fuzz({}, fuzzAgainstArrayList, .{});
519}519}
520520
521test "dumb fuzz against ArrayList oracle" {521const FuzzAllocator = struct {
522 const testing = std.testing;522 smith: *std.testing.Smith,
523 const gpa = testing.allocator;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);548 pub fn allocCount(f: *FuzzAllocator) u2 {
526 defer gpa.free(input);549 return @popCount(f.used_bitmap);
550 }
527551
528 var prng = std.Random.DefaultPrng.init(testing.random_seed);552 fn alloc(ctx: *anyopaque, len: usize, a: std.mem.Alignment, _: usize) ?[*]u8 {
529 prng.random().bytes(input);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);565 fn memSlot(f: *FuzzAllocator, mem: []u8) u1 {
532}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 {
535 const testing = std.testing;620 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
538 var q: Deque(u32) = .empty;628 var q: Deque(u32) = .empty;
539 defer q.deinit(gpa);
540 var l: std.ArrayList(u32) = .empty;629 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,
549 push_back,633 push_back,
550 push_front,634 push_front,
551 push_back_slice,635 push_back_slice,
552 push_front_slice,636 push_front_slice,
553 pop_back,637 pop_back,
554 pop_front,638 pop_front,
555 grow,
556 /// Sentinel to avoid hardcoding the cast below
557 max,
558 };639 };
559 for (input[1..]) |byte| {640
560 switch (@as(Action, @enumFromInt(byte % (@intFromEnum(Action.max))))) {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})) {
561 .push_back => {645 .push_back => {
562 const item = random.int(u8);646 const item = smith.value(u32);
563 try testing.expectEqual(647 try testing.expectEqual(
564 l.appendBounded(item),648 l.appendBounded(item),
565 q.pushBackBounded(item),649 q.pushBackBounded(item),
566 );650 );
567 },651 },
568 .push_front => {652 .push_front => {
569 const item = random.int(u8);653 const item = smith.value(u32);
570 try testing.expectEqual(654 try testing.expectEqual(
571 l.insertBounded(0, item),655 l.insertBounded(0, item),
572 q.pushFrontBounded(item),656 q.pushFrontBounded(item),
...@@ -574,9 +658,9 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {...@@ -574,9 +658,9 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {
574 },658 },
575 .push_back_slice => {659 .push_back_slice => {
576 var buffer: [std.math.maxInt(u3)]u32 = undefined;660 var buffer: [std.math.maxInt(u3)]u32 = undefined;
577 const items = buffer[0..random.int(u3)];661 const items = buffer[0..smith.value(u3)];
578 for (items) |*item| {662 for (items) |*item| {
579 item.* = random.int(u8);663 item.* = smith.value(u32);
580 }664 }
581 try testing.expectEqual(665 try testing.expectEqual(
582 l.appendSliceBounded(items),666 l.appendSliceBounded(items),
...@@ -585,9 +669,9 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {...@@ -585,9 +669,9 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {
585 },669 },
586 .push_front_slice => {670 .push_front_slice => {
587 var buffer: [std.math.maxInt(u3)]u32 = undefined;671 var buffer: [std.math.maxInt(u3)]u32 = undefined;
588 const items = buffer[0..random.int(u3)];672 const items = buffer[0..smith.value(u3)];
589 for (items) |*item| {673 for (items) |*item| {
590 item.* = random.int(u8);674 item.* = smith.value(u32);
591 }675 }
592 try testing.expectEqual(676 try testing.expectEqual(
593 l.insertSliceBounded(0, items),677 l.insertSliceBounded(0, items),
...@@ -607,11 +691,10 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {...@@ -607,11 +691,10 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {
607 // ensureTotalCapacityPrecise(), which is the most complex part691 // ensureTotalCapacityPrecise(), which is the most complex part
608 // of the Deque implementation.692 // of the Deque implementation.
609 .grow => {693 .grow => {
610 const growth = random.int(u3);694 const growth = smith.value(u3);
611 try l.ensureTotalCapacityPrecise(gpa, l.items.len + growth);695 try l.ensureTotalCapacityPrecise(l_gpa, l.items.len + growth);
612 try q.ensureTotalCapacityPrecise(gpa, q.len + growth);696 try q.ensureTotalCapacityPrecise(q_gpa, q.len + growth);
613 },697 },
614 .max => unreachable,
615 }698 }
616 try testing.expectEqual(l.getLastOrNull(), q.back());699 try testing.expectEqual(l.getLastOrNull(), q.back());
617 try testing.expectEqual(700 try testing.expectEqual(
...@@ -627,5 +710,8 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {...@@ -627,5 +710,8 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {
627 }710 }
628 try testing.expectEqual(null, it.next());711 try testing.expectEqual(null, it.next());
629 }712 }
713 try testing.expectEqual(@intFromBool(q.buffer.len != 0), q_gpa_inst.allocCount());
630 }714 }
715 q.deinit(q_gpa);
716 try testing.expectEqual(0, q_gpa_inst.allocCount());
631}717}
lib/std/json/scanner_test.zig-17
...@@ -490,20 +490,3 @@ test isNumberFormattedLikeAnInteger {...@@ -490,20 +490,3 @@ test isNumberFormattedLikeAnInteger {
490 try std.testing.expect(!isNumberFormattedLikeAnInteger("1e10"));490 try std.testing.expect(!isNumberFormattedLikeAnInteger("1e10"));
491 try std.testing.expect(!isNumberFormattedLikeAnInteger("1E10"));491 try std.testing.expect(!isNumberFormattedLikeAnInteger("1E10"));
492}492}
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 {...@@ -1203,6 +1203,8 @@ pub fn refAllDecls(comptime T: type) void {
1203 }1203 }
1204}1204}
12051205
1206pub const Smith = @import("testing/Smith.zig");
1207
1206pub const FuzzInputOptions = struct {1208pub const FuzzInputOptions = struct {
1207 corpus: []const []const u8 = &.{},1209 corpus: []const []const u8 = &.{},
1208};1210};
...@@ -1210,7 +1212,7 @@ pub const FuzzInputOptions = struct {...@@ -1210,7 +1212,7 @@ pub const FuzzInputOptions = struct {
1210/// Inline to avoid coverage instrumentation.1212/// Inline to avoid coverage instrumentation.
1211pub inline fn fuzz(1213pub inline fn fuzz(
1212 context: anytype,1214 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,
1214 options: FuzzInputOptions,1216 options: FuzzInputOptions,
1215) anyerror!void {1217) anyerror!void {
1216 return @import("root").fuzz(context, testOne, options);1218 return @import("root").fuzz(context, testOne, options);
...@@ -1317,3 +1319,7 @@ pub const ReaderIndirect = struct {...@@ -1317,3 +1319,7 @@ pub const ReaderIndirect = struct {
1317 };1319 };
1318 }1320 }
1319};1321};
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");...@@ -14,6 +14,7 @@ pub const Server = @import("zig/Server.zig");
14pub const Client = @import("zig/Client.zig");14pub const Client = @import("zig/Client.zig");
15pub const Token = tokenizer.Token;15pub const Token = tokenizer.Token;
16pub const Tokenizer = tokenizer.Tokenizer;16pub const Tokenizer = tokenizer.Tokenizer;
17pub const TokenSmith = @import("zig/TokenSmith.zig");
17pub const string_literal = @import("zig/string_literal.zig");18pub const string_literal = @import("zig/string_literal.zig");
18pub const number_literal = @import("zig/number_literal.zig");19pub const number_literal = @import("zig/number_literal.zig");
19pub const primitives = @import("zig/primitives.zig");20pub const primitives = @import("zig/primitives.zig");
...@@ -991,6 +992,7 @@ test {...@@ -991,6 +992,7 @@ test {
991 _ = LibCDirs;992 _ = LibCDirs;
992 _ = LibCInstallation;993 _ = LibCInstallation;
993 _ = Server;994 _ = Server;
995 _ = TokenSmith;
994 _ = WindowsSdk;996 _ = WindowsSdk;
995 _ = number_literal;997 _ = number_literal;
996 _ = primitives;998 _ = 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...@@ -160,10 +160,21 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A
160 if (token.tag == .eof) break;160 if (token.tag == .eof) break;
161 }161 }
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 {
163 var parser: Parse = .{174 var parser: Parse = .{
164 .source = source,175 .source = source,
165 .gpa = gpa,176 .gpa = gpa,
166 .tokens = tokens.slice(),177 .tokens = tokens,
167 .errors = .{},178 .errors = .{},
168 .nodes = .{},179 .nodes = .{},
169 .extra_data = .{},180 .extra_data = .{},
...@@ -194,7 +205,7 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A...@@ -194,7 +205,7 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A
194 return Ast{205 return Ast{
195 .source = source,206 .source = source,
196 .mode = mode,207 .mode = mode,
197 .tokens = tokens.toOwnedSlice(),208 .tokens = tokens,
198 .nodes = parser.nodes.toOwnedSlice(),209 .nodes = parser.nodes.toOwnedSlice(),
199 .extra_data = extra_data,210 .extra_data = extra_data,
200 .errors = errors,211 .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" {...@@ -6420,14 +6420,9 @@ test "fuzz ast parse" {
6420 try std.testing.fuzz({}, fuzzTestOneParse, .{});6420 try std.testing.fuzz({}, fuzzTestOneParse, .{});
6421}6421}
64226422
6423fn fuzzTestOneParse(_: void, input: []const u8) !void {6423fn fuzzTestOneParse(_: void, smith: *std.testing.Smith) !void {
6424 // The first byte holds if zig / zon6424 const mode = smith.value(std.zig.Ast.Mode);
6425 if (input.len == 0) return;6425 var tokens: std.zig.TokenSmith = .gen(smith);
6426 const mode: std.zig.Ast.Mode = if (input[0] & 1 == 0) .zig else .zon;
6427 const bytes = input[1..];
6428
6429 var fba: std.heap.FixedBufferAllocator = .init(&fixed_buffer_mem);6426 var fba: std.heap.FixedBufferAllocator = .init(&fixed_buffer_mem);
6430 const allocator = fba.allocator();6427 _ = std.zig.Ast.parseTokens(fba.allocator(), tokens.source(), tokens.list(), mode) catch return;
6431 const source = allocator.dupeZ(u8, bytes) catch return;
6432 _ = std.zig.Ast.parse(allocator, source, mode) catch return;
6433}6428}
lib/std/zig/tokenizer.zig+22-12
...@@ -713,6 +713,9 @@ pub const Tokenizer = struct {...@@ -713,6 +713,9 @@ pub const Tokenizer = struct {
713 self.index += 1;713 self.index += 1;
714 switch (self.buffer[self.index]) {714 switch (self.buffer[self.index]) {
715 0, '\n' => result.tag = .invalid,715 0, '\n' => result.tag = .invalid,
716 0x01...0x09, 0x0b...0x1f, 0x7f => {
717 continue :state .invalid;
718 },
716 else => continue :state .string_literal,719 else => continue :state .string_literal,
717 }720 }
718 },721 },
...@@ -1721,15 +1724,22 @@ fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !v...@@ -1721,15 +1724,22 @@ fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !v
1721 try std.testing.expectEqual(source.len, last_token.loc.end);1724 try std.testing.expectEqual(source.len, last_token.loc.end);
1722}1725}
17231726
1724fn testPropertiesUpheld(_: void, source: []const u8) !void {1727fn testPropertiesUpheld(_: void, smith: *std.testing.Smith) !void {
1725 var source0_buf: [512]u8 = undefined;1728 @disableInstrumentation();
1726 if (source.len + 1 > source0_buf.len)1729 var source_buf: [512]u8 = undefined;
1727 return;1730 const len = smith.sliceWeightedBytes(source_buf[0 .. source_buf.len - 1], &.{
1728 @memcpy(source0_buf[0..source.len], source);1731 .rangeAtMost(u8, 0x00, 0xff, 1),
1729 source0_buf[source.len] = 0;1732 .rangeAtMost(u8, 0x20, 0x7e, 4),
1730 const source0 = source0_buf[0..source.len :0];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);
1733 var tokenization_failed = false;1743 var tokenization_failed = false;
1734 while (true) {1744 while (true) {
1735 const token = tokenizer.next();1745 const token = tokenizer.next();
...@@ -1742,12 +1752,12 @@ fn testPropertiesUpheld(_: void, source: []const u8) !void {...@@ -1742,12 +1752,12 @@ fn testPropertiesUpheld(_: void, source: []const u8) !void {
1742 tokenization_failed = true;1752 tokenization_failed = true;
17431753
1744 // Property: invalid token always ends at newline or eof1754 // 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);
1746 },1756 },
1747 .eof => {1757 .eof => {
1748 // Property: EOF token is always 0-length at end of source.1758 // Property: EOF token is always 0-length at end of source.
1749 try std.testing.expectEqual(source0.len, token.loc.start);1759 try std.testing.expectEqual(source.len, token.loc.start);
1750 try std.testing.expectEqual(source0.len, token.loc.end);1760 try std.testing.expectEqual(source.len, token.loc.end);
1751 break;1761 break;
1752 },1762 },
1753 else => continue,1763 else => continue,
...@@ -1755,7 +1765,7 @@ fn testPropertiesUpheld(_: void, source: []const u8) !void {...@@ -1755,7 +1765,7 @@ fn testPropertiesUpheld(_: void, source: []const u8) !void {
1755 }1765 }
17561766
1757 if (tokenization_failed) return;1767 if (tokenization_failed) return;
1758 for (source0) |cur| {1768 for (source) |cur| {
1759 // Property: No null byte allowed except at end.1769 // Property: No null byte allowed except at end.
1760 if (cur == 0) {1770 if (cur == 0) {
1761 return error.TestUnexpectedResult;1771 return error.TestUnexpectedResult;
src/codegen/llvm.zig+1-1
...@@ -1116,7 +1116,7 @@ pub const Object = struct {...@@ -1116,7 +1116,7 @@ pub const Object = struct {
1116 // needs to for better fuzzing logic.1116 // needs to for better fuzzing logic.
1117 .IndirectCalls = false,1117 .IndirectCalls = false,
1118 .TraceBB = false,1118 .TraceBB = false,
1119 .TraceCmp = options.fuzz,1119 .TraceCmp = false,
1120 .TraceDiv = false,1120 .TraceDiv = false,
1121 .TraceGep = false,1121 .TraceGep = false,
1122 .Use8bitCounters = false,1122 .Use8bitCounters = false,
test/standalone/libfuzzer/main.zig+2-4
...@@ -2,9 +2,7 @@ const std = @import("std");...@@ -2,9 +2,7 @@ const std = @import("std");
2const abi = std.Build.abi.fuzz;2const abi = std.Build.abi.fuzz;
3const native_endian = @import("builtin").cpu.arch.endian();3const native_endian = @import("builtin").cpu.arch.endian();
44
5fn testOne(in: abi.Slice) callconv(.c) void {5fn testOne() callconv(.c) void {}
6 std.debug.assertReadable(in.toSlice());
7}
86
9pub fn main(init: std.process.Init) !void {7pub fn main(init: std.process.Init) !void {
10 const gpa = init.gpa;8 const gpa = init.gpa;
...@@ -19,7 +17,7 @@ pub fn main(init: std.process.Init) !void {...@@ -19,7 +17,7 @@ pub fn main(init: std.process.Init) !void {
19 defer cache_dir.close(io);17 defer cache_dir.close(io);
2018
21 abi.fuzzer_init(.fromSlice(cache_dir_path));19 abi.fuzzer_init(.fromSlice(cache_dir_path));
22 abi.fuzzer_init_test(testOne, .fromSlice("test"));20 abi.fuzzer_set_test(testOne, .fromSlice("test"));
23 abi.fuzzer_new_input(.fromSlice(""));21 abi.fuzzer_new_input(.fromSlice(""));
24 abi.fuzzer_new_input(.fromSlice("hello"));22 abi.fuzzer_new_input(.fromSlice("hello"));
2523