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