authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-25 20:23:36+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-25 20:23:36+01:00
loge0173c2ce01890ea71ab7660f2ecdeda29956f77
treeed802e0723b023577af49e242f9ce21791222762
parentbb304796f466b4fd15a4adac7ffbb81ace95d2a4
parent5d583061625c1c413e11a3d14e796602ce752687

Merge pull request 'rework fuzz testing to be smith based' (#31205) from gooncreeper/zig:integrated-smith into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31205 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

29 files changed, 3404 insertions(+), 1544 deletions(-)

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