| author | |
| committer | |
| log | e0173c2ce01890ea71ab7660f2ecdeda29956f77 |
| tree | ed802e0723b023577af49e242f9ce21791222762 |
| parent | bb304796f466b4fd15a4adac7ffbb81ace95d2a4 |
| parent | 5d583061625c1c413e11a3d14e796602ce752687 |
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); |
| 86 | 86 | ||
| 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]; |
| 419 | 420 | ||
| 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. |
| 2 | var start_fuzzing_timestamp: i64 = undefined; | 2 | var start_fuzzing_timestamp: i64 = undefined; |
| 3 | var start_fuzzing_n_runs: u64 = undefined; | ||
| 3 | 4 | ||
| 4 | const js = struct { | 5 | const 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])); |
| 37 | 38 | ||
| 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 { |
| 271 | 273 | ||
| 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 | }; |
| 277 | 279 |
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; |
| 379 | 379 | ||
| 380 | pub fn fuzz( | 380 | pub 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 code | 385 | // 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; |
| 408 | 408 | ||
| 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)); | ||
| 441 | 439 | ||
| 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 the | 447 | // 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 | } |
| 454 | 453 | ||
| 455 | // In case there is no provided corpus, also use an empty | 454 | // 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 @@ |
| 1 | const builtin = @import("builtin"); | 1 | const builtin = @import("builtin"); |
| 2 | const native_endian = builtin.cpu.arch.endian(); | ||
| 3 | 2 | ||
| 4 | const std = @import("std"); | 3 | const std = @import("std"); |
| 5 | const Io = std.Io; | 4 | const Io = std.Io; |
| 6 | const fatal = std.process.fatal; | ||
| 7 | const mem = std.mem; | 5 | const mem = std.mem; |
| 8 | const math = std.math; | 6 | const math = std.math; |
| 9 | const Allocator = std.mem.Allocator; | ||
| 10 | const assert = std.debug.assert; | 7 | const assert = std.debug.assert; |
| 11 | const panic = std.debug.panic; | 8 | const panic = std.debug.panic; |
| 12 | const abi = std.Build.abi.fuzz; | 9 | const abi = std.Build.abi.fuzz; |
| 10 | const Uid = abi.Uid; | ||
| 13 | 11 | ||
| 14 | pub const std_options = std.Options{ | 12 | pub 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 orelse | 24 | 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); |
| 30 | 27 | ||
| ... | @@ -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 | }; |
| 50 | 47 | ||
| 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. |
| 52 | var log_f: ?Io.File = null; | 49 | var log_f: ?Io.File = null; |
| 53 | var exec: Executable = .preinit; | 50 | var exec: Executable = undefined; |
| 54 | var inst: Instrumentation = .preinit; | ||
| 55 | var fuzzer: Fuzzer = undefined; | 51 | var fuzzer: Fuzzer = undefined; |
| 56 | var current_test_name: ?[]const u8 = null; | 52 | var current_test_name: ?[]const u8 = null; |
| 57 | 53 | ||
| ... | @@ -60,36 +56,28 @@ fn bitsetUsizes(elems: usize) usize { | ... | @@ -60,36 +56,28 @@ fn bitsetUsizes(elems: usize) usize { |
| 60 | } | 56 | } |
| 61 | 57 | ||
| 62 | const Executable = struct { | 58 | const 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, |
| 65 | 61 | ||
| 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 viewed | 63 | /// 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 file | 66 | /// Hash of pcs used to uniquely identify the shared coverage file |
| 71 | pc_digest: u64, | 67 | pc_digest: u64, |
| 72 | 68 | ||
| 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 behavior | 70 | 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 section | 73 | ) []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); | ||
| 88 | 75 | ||
| 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 it | 82 | // 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 | }; |
| 109 | 97 | ||
| 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); |
| 113 | 104 | ||
| 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 | } | ||
| 143 | 116 | ||
| 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 | ); | ||
| 152 | 120 | ||
| 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 | ); |
| 162 | 149 | 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 | } |
| 166 | 159 | ||
| 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); |
| 234 | 227 | ||
| 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, |
| 246 | 239 | ||
| 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 | }; |
| 264 | 264 | ||
| 265 | /// Data gathered from instrumentation functions. | 265 | const 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 |
| 268 | const 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 using | 270 | // 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, |
| 273 | 273 | ||
| 274 | /// Stores a fresh input's new pcs | 274 | seen_pcs: []usize, |
| 275 | fresh_pcs: []usize, | 275 | bests: struct { |
| 276 | 276 | len: u32, | |
| 277 | /// Pcs which __sanitizer_cov_trace_switch and __sanitizer_cov_trace_const_cmpx | 277 | quality_buf: []Input.Best, |
| 278 | /// have been called from and have had their already been added to const_x_vals | 278 | 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 is | 281 | 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 | |
| 287 | 287 | /// 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 behavior | 289 | 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 | }; |
| 295 | 335 | ||
| 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 | }; | ||
| 303 | 372 | ||
| 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(); | ||
| 310 | 378 | ||
| 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 | } | ||
| 318 | 384 | ||
| 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 | } | ||
| 328 | 395 | ||
| 329 | /// If false is returned, then the pc is marked as seen | 396 | 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 = &.{}, | ||
| 333 | 408 | ||
| 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 | } | ||
| 339 | 412 | ||
| 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 | }; | ||
| 342 | 421 | ||
| 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 | }, | ||
| 350 | 441 | ||
| 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 | } | ||
| 359 | 445 | ||
| 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 | ); | ||
| 366 | 450 | ||
| 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 | }; | ||
| 374 | 456 | ||
| 375 | const 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 the | 461 | order_i: u32, |
| 380 | /// current process crashes, this memory-mapped file is used to recover the | 462 | }), |
| 381 | /// input. | 463 | bytes: std.MultiArrayList(struct { |
| 382 | input: MemoryMappedList, | 464 | value: Data.Bytes.Entry, |
| 383 | 465 | order_i: u32, | |
| 384 | /// Minimized past inputs leading to new pc hits. | 466 | }), |
| 385 | /// These are randomly mutated in round-robin fashion | 467 | }, 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 the | 471 | 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 | }; | ||
| 392 | 485 | ||
| 393 | /// Filesystem directory containing found inputs for future runs | 486 | 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, | ||
| 396 | 613 | ||
| 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 | } | ||
| 407 | 631 | ||
| 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 | else | 677 | 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 | } |
| 435 | 680 | ||
| 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(); | ||
| 439 | 684 | ||
| 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 to | 686 | 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 | } | ||
| 444 | 693 | ||
| 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 created | 705 | 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 | } | ||
| 462 | 710 | ||
| 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 | } |
| 465 | 715 | ||
| 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 | } |
| 474 | 721 | ||
| 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 | } |
| 486 | 863 | ||
| 487 | /// Assumes `fresh_pcs` correspond to the input | 864 | 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 each | 866 | 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; | ||
| 495 | 911 | ||
| 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"); | ||
| 498 | 919 | ||
| 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 to | 929 | 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 | } |
| 508 | 998 | ||
| 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; | ||
| 511 | 1005 | ||
| 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 | } |
| 515 | 1009 | ||
| 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 | } |
| 522 | 1023 | ||
| 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 | |
| 528 | 1029 | 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 | }; |
| 544 | 1048 | ||
| 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 | }; | ||
| 546 | 1078 | ||
| 547 | if (inst.isFresh()) { | 1079 | f.run(data.uid_slices.entries.len); |
| 1080 | if (f.isFresh()) { | ||
| 548 | @branchHint(.unlikely); | 1081 | @branchHint(.unlikely); |
| 549 | 1082 | ||
| 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 | |
| 555 | 1088 | 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 | |
| 559 | 1092 | 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 the | 1093 | var contains: bool = false; |
| 561 | // test has to be non-deterministic. This has to be checked as duplicate empty | 1094 | 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 | } | ||
| 568 | 1487 | ||
| 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 | } | ||
| 571 | 1494 | ||
| 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 | } | ||
| 574 | 1503 | ||
| 575 | // Write new corpus to cache | 1504 | @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 | }; |
| 585 | 1638 | ||
| 586 | /// Instrumentation must not be triggered before this function is called | ||
| 587 | export fn fuzzer_init(cache_dir_path: abi.Slice) void { | 1639 | export 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 | } |
| 592 | 1643 | ||
| 593 | /// Invalid until `fuzzer_init` is called. | ||
| 594 | export fn fuzzer_coverage() abi.Coverage { | 1644 | export 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()); |
| 597 | 1647 | ||
| 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 | } |
| 610 | 1660 | ||
| 611 | /// fuzzer_init must be called beforehand | 1661 | export fn fuzzer_set_test(test_one: abi.TestOne, unit_test_name: abi.Slice) void { |
| 612 | export 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 | } |
| 616 | 1665 | ||
| 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. | ||
| 619 | export fn fuzzer_new_input(bytes: abi.Slice) void { | 1666 | export 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 | } |
| 624 | 1670 | ||
| 625 | /// fuzzer_init_test must be called first | ||
| 626 | export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void { | 1671 | export 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 | } |
| 632 | 1679 | ||
| 633 | export fn fuzzer_unslide_address(addr: usize) usize { | 1680 | export 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. | ||
| 643 | export threadlocal var __sancov_lowest_stack: usize = 0; | ||
| 644 | |||
| 645 | /// Inline since the return address of the callee is required | ||
| 646 | inline 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 | |||
| 653 | export fn __sanitizer_cov_trace_const_cmp1(const_arg: u8, arg: u8) void { | ||
| 654 | _ = const_arg; | ||
| 655 | _ = arg; | ||
| 656 | } | 1683 | } |
| 657 | 1684 | ||
| 658 | export fn __sanitizer_cov_trace_const_cmp2(const_arg: u16, arg: u16) void { | 1685 | export 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 | } |
| 662 | 1689 | ||
| 663 | export fn __sanitizer_cov_trace_const_cmp4(const_arg: u32, arg: u32) void { | 1690 | export 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 | } |
| 667 | 1694 | ||
| 668 | export fn __sanitizer_cov_trace_const_cmp8(const_arg: u64, arg: u64) void { | 1695 | export 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 | } |
| 672 | 1704 | ||
| 673 | export fn __sanitizer_cov_trace_switch(val: u64, cases: [*]const u64) void { | 1705 | export 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 generated | 1712 | 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 | |||
| 693 | export fn __sanitizer_cov_trace_cmp1(arg1: u8, arg2: u8) void { | ||
| 694 | _ = arg1; | ||
| 695 | _ = arg2; | ||
| 696 | } | ||
| 697 | |||
| 698 | export fn __sanitizer_cov_trace_cmp2(arg1: u16, arg2: u16) void { | ||
| 699 | _ = arg1; | ||
| 700 | _ = arg2; | ||
| 701 | } | ||
| 702 | |||
| 703 | export fn __sanitizer_cov_trace_cmp4(arg1: u32, arg2: u32) void { | ||
| 704 | _ = arg1; | ||
| 705 | _ = arg2; | ||
| 706 | } | 1713 | } |
| 707 | 1714 | ||
| 708 | export 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; | 1717 | export threadlocal var __sancov_lowest_stack: usize = 0; |
| 711 | } | ||
| 712 | 1718 | ||
| 713 | export fn __sanitizer_cov_trace_pc_indir(callee: usize) void { | 1719 | export 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 | } |
| 731 | 1737 | ||
| 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 | /// |
| 734 | fn 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 | } | 1742 | const MemoryMappedInput = struct { |
| 737 | 1743 | 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. | ||
| 740 | fn 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 | |||
| 748 | const 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. | ||
| 1306 | pub const MemoryMappedList = struct { | ||
| 1307 | /// Contents of the list. | ||
| 1308 | /// | 1745 | /// |
| 1309 | /// Pointers to elements in this slice are invalidated by various functions | 1746 | /// `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. In | 1747 | mmap: Io.File.MemoryMap, |
| 1311 | /// all cases, "invalidated" means that the memory has been passed to this | 1748 | |
| 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 | } |
| 1334 | 1757 | ||
| 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); |
| 1339 | 1762 | 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 | } |
| 1345 | 1765 | ||
| 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 | } |
| 1351 | 1772 | ||
| 1352 | /// If the current capacity is less than `new_capacity`, this function will | 1773 | /// 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) { |
| 1357 | 1779 | @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; | ||
| 1364 | 1780 | ||
| 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 | } |
| 1369 | 1800 | ||
| 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 | } |
| 1374 | 1805 | ||
| 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 | } |
| 1384 | 1810 | ||
| 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 | } |
| 1392 | 1814 | ||
| 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 | } |
| 1402 | 1820 | ||
| 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 will | 1823 | /// 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 | } |
| 1414 | 1830 | ||
| 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 | } |
| 1426 | 1841 | ||
| 1427 | /// Called when memory growth is necessary. Returns a capacity larger than | 1842 | /// 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 | } |
| 41 | 41 | ||
| 42 | test "fuzz example" { | 42 | test "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! | 46 | fn 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, | ||
| 67 | 68 | ||
| 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 | } |
| 137 | 138 | ||
| 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 below | 392 | .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); |
| 387 | 398 | ||
| ... | @@ -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 |
| 459 | 470 | ||
| 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; | ||
| 462 | 474 | ||
| 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 | } |
| 1704 | 1704 | ||
| 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 | } |
| 1716 | 1720 | ||
| 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; | ||
| 1720 | 1724 | ||
| 1721 | const hdr = try reader.takeStruct(elf.ar_hdr, .little); | 1725 | const hdr = try reader.takeStruct(elf.ar_hdr, .little); |
| 1722 | 1726 |
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 "."); |
| 1464 | 1464 | ||
| 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})); | ||
| 1466 | 1467 | ||
| 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); |
| 609 | 609 | ||
| 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 connection | 7 | //! * The build runner communicates with the web interface over a WebSocket connection |
| 8 | //! * The build runner communicates with `libfuzzer` over a shared memory-mapped file | 8 | //! * The build runner communicates with `libfuzzer` over a shared memory-mapped file |
| 9 | const std = @import("std"); | ||
| 9 | 10 | ||
| 10 | // Check that no WebSocket message type has implicit padding bits. This ensures we never send any | 11 | // 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, for | 12 | // 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 @@ |
| 13 | comptime { | 14 | comptime { |
| 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 { |
| 139 | 139 | ||
| 140 | /// ABI bits specifically relating to the fuzzer interface. | 140 | /// ABI bits specifically relating to the fuzzer interface. |
| 141 | pub const fuzz = struct { | 141 | pub 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; |
| 149 | 173 | ||
| 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 | }; |
| 162 | 196 | ||
| 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 }; |
| 164 | 292 | ||
| 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 | }; |
| 223 | 352 | ||
| 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); |
| 280 | 280 | ||
| 281 | // note that disallowing some of these simplifies matching logic | 281 | // note that disallowing some of these simplifies matching logic |
| 282 | assert(opts.chain != 0); // use `Huffman`, disallowing this simplies matching | 282 | assert(opts.chain != 0); // use `Huffman`; disallowing this simplies matching |
| 283 | assert(opts.good >= 3 and opts.nice >= 3); // a match will (usually) not be found | 283 | 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 found | 284 | 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 found | 285 | 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 | } |
| 560 | 560 | ||
| 561 | fn testFuzzedMatchLen(_: void, input: []const u8) !void { | 561 | fn 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); | ||
| 569 | 565 | ||
| 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), |
| 574 | 570 | 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; |
| 592 | 585 | 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)); |
| 600 | 590 | ||
| 601 | const diff_index = mem.findDiff(u8, prev, bytes).?; // unwrap since lengths are not same | 591 | 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 length | 1030 | // 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 | } |
| 1257 | 1247 | ||
| 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; |
| 1264 | 1253 | ||
| 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 | } |
| 1283 | 1267 | ||
| 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 | ); |
| 1293 | 1278 | ||
| 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 | } |
| 1304 | 1289 | ||
| 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 and | 1318 | 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 | } |
| 1341 | const FreqBufIndex = enum(u1) { gradient, random }; | ||
| 1356 | 1342 | ||
| 1357 | fn testingCheckDecompressedMatches( | 1343 | fn 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 | } |
| 1428 | 1414 | ||
| 1429 | fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, input: []const u8) !void { | 1415 | fn 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 performance | 1423 | // 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; |
| 1442 | 1429 | ||
| 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 | ); | ||
| 1457 | 1440 | ||
| 1458 | // It is ensured that more bytes are not written then this to ensure this run | 1441 | // 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); |
| 1467 | 1450 | ||
| 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 | }); | ||
| 1498 | 1454 | ||
| 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 illegal | 1456 | // 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; |
| 1502 | 1458 | ||
| 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. |
| 1511 | 1467 | 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; |
| 1522 | 1478 | } | |
| 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 | } |
| 1782 | 1743 | ||
| 1783 | fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, input: []const u8) !void { | 1744 | fn 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 { |
| 1819 | 1781 | ||
| 1820 | /// Note that this implementation is somewhat dependent on the implementation of | 1782 | /// Note that this implementation is somewhat dependent on the implementation of |
| 1821 | /// `Raw` by expecting headers / footers to be continous in data elements. It | 1783 | /// `Raw` by expecting headers / footers to be continous in data elements. It |
| 1822 | /// also expects the header to be the same as `flate.Container.header` and not | 1784 | /// 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 | } |
| 1910 | 1872 | ||
| 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 | }; |
| 1916 | 1878 | ||
| 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; |
| 1935 | 1897 | ||
| 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 | ||
| 1986 | 1909 | ||
| 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 | } |
| 1991 | 1936 | ||
| 1992 | const want_drain = vecs_n == vecs.len or vec_info.output or vec_info.rebase or | 1937 | 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 | } |
| 1998 | 1967 | ||
| 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 | } |
| 2006 | 1976 | ||
| 2007 | try r.writer.flush(); | 1977 | try raw.writer.flush(); |
| 2008 | try output.writer.flush(); | 1978 | try output.writer.flush(); |
| 2009 | 1979 | ||
| 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 | } |
| 2434 | 2404 | ||
| 2405 | fn 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`. |
| 2436 | fn testFuzzedHuffmanInput(fbufs: *const [2][65536]u8, input: []const u8) !void { | 2414 | fn 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; |
| 2455 | 2432 | ||
| 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 | }); | ||
| 2475 | 2444 | ||
| 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), |
| 2480 | 2449 | .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 and | 2452 | 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 | } |
| 2489 | 2479 | ||
| 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])); |
| 2498 | 2488 | 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; | ||
| 2504 | 2516 | ||
| 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 | } |
| 2509 | 2519 | ||
| 2510 | const want_drain = vecs_n == vecs.len or vec_info.output or vec_info.rebase or | 2520 | 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; | ||
| 2520 | 2523 | ||
| 2521 | vecs_n = 0; | 2524 | const max_space = fuzzedHuffmanDrainSpaceLimit( |
| 2522 | } else assert(splat == 1); | 2525 | h.writer.buffered().len, |
| 2523 | 2526 | 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 | } | ||
| 2538 | 2533 | ||
| 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 | } |
| 2549 | 2536 | ||
| 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 assert | 417 | /// 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. |
| 419 | pub fn assert(ok: bool) void { | 419 | pub fn assert(ok: bool) void { |
| 420 | @disableInstrumentation(); | ||
| 420 | if (!ok) unreachable; // assertion failure | 421 | if (!ok) unreachable; // assertion failure |
| 421 | } | 422 | } |
| 422 | 423 |
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 | } |
| 520 | 520 | ||
| 521 | test "dumb fuzz against ArrayList oracle" { | 521 | const 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 | } | ||
| 524 | 547 | ||
| 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 | } | ||
| 527 | 551 | ||
| 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 | } | ||
| 530 | 564 | ||
| 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); | ||
| 533 | 592 | ||
| 534 | fn 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 | |||
| 619 | fn 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(); | ||
| 537 | 627 | ||
| 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(); | ||
| 547 | 630 | ||
| 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 part | 691 | // 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 | |||
| 494 | test "fuzz" { | ||
| 495 | try std.testing.fuzz({}, fuzzTestOne, .{}); | ||
| 496 | } | ||
| 497 | |||
| 498 | fn 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 | } |
| 1205 | 1205 | ||
| 1206 | pub const Smith = @import("testing/Smith.zig"); | ||
| 1207 | |||
| 1206 | pub const FuzzInputOptions = struct { | 1208 | pub 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. |
| 1211 | pub inline fn fuzz( | 1213 | pub 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 | |||
| 1323 | test { | ||
| 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 | |||
| 3 | const builtin = @import("builtin"); | ||
| 4 | const std = @import("../std.zig"); | ||
| 5 | const assert = std.debug.assert; | ||
| 6 | const fuzz_abi = std.Build.abi.fuzz; | ||
| 7 | const 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. | ||
| 12 | in: ?[]const u8, | ||
| 13 | |||
| 14 | pub const Weight = fuzz_abi.Weight; | ||
| 15 | |||
| 16 | fn intUid(hash: u32) fuzz_abi.Uid { | ||
| 17 | @disableInstrumentation(); | ||
| 18 | return @bitCast(hash << 1); | ||
| 19 | } | ||
| 20 | |||
| 21 | fn bytesUid(hash: u32) fuzz_abi.Uid { | ||
| 22 | @disableInstrumentation(); | ||
| 23 | return @bitCast(hash | 1); | ||
| 24 | } | ||
| 25 | |||
| 26 | fn Backing(T: type) type { | ||
| 27 | return @Int(.unsigned, @bitSizeOf(T)); | ||
| 28 | } | ||
| 29 | |||
| 30 | fn toExcessK(T: type, x: T) Backing(T) { | ||
| 31 | return @bitCast(x -% std.math.minInt(T)); | ||
| 32 | } | ||
| 33 | |||
| 34 | fn fromExcessK(T: type, x: Backing(T)) T { | ||
| 35 | return @as(T, @bitCast(x)) +% std.math.minInt(T); | ||
| 36 | } | ||
| 37 | |||
| 38 | fn 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 | ||
| 45 | pub 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 | |||
| 103 | test 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 | |||
| 155 | fn 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 | |||
| 163 | fn 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 | ||
| 180 | inline fn firstHash() u32 { | ||
| 181 | return @truncate(std.hash.int(@returnAddress())); | ||
| 182 | } | ||
| 183 | |||
| 184 | // `noinline` to capture a unique return address | ||
| 185 | pub 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 | ||
| 191 | pub 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 | ||
| 197 | pub 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 | ||
| 203 | pub 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 | ||
| 212 | pub 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 | ||
| 223 | pub 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 | ||
| 234 | pub 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 | ||
| 240 | pub 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 | ||
| 246 | pub 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 | ||
| 255 | pub 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 | ||
| 265 | pub 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 | ||
| 275 | pub 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 | |||
| 285 | fn 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 | ||
| 297 | inline 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 | |||
| 308 | test 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 | |||
| 326 | fn 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 | |||
| 345 | pub 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 | |||
| 417 | pub 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 | |||
| 423 | fn 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 | |||
| 440 | pub 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 | |||
| 453 | pub 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. | ||
| 468 | pub 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. | ||
| 477 | pub 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 | ||
| 509 | pub 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 | |||
| 517 | pub fn bytesWithHash(s: *Smith, out: []u8, hash: u32) void { | ||
| 518 | @disableInstrumentation(); | ||
| 519 | return s.bytesWeightedWithHash(out, baselineWeights(u8), hash); | ||
| 520 | } | ||
| 521 | |||
| 522 | pub 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 | ||
| 548 | pub 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 | ||
| 556 | pub 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 | ||
| 574 | pub 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 | |||
| 625 | fn 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 | |||
| 661 | test 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 | |||
| 735 | test 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 | |||
| 753 | test 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 | |||
| 772 | test 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 | |||
| 787 | test 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 | |||
| 799 | test 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 | |||
| 804 | test 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 | |||
| 818 | test 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 | |||
| 833 | test 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 | |||
| 850 | test 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 | |||
| 865 | test 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"); |
| 14 | pub const Client = @import("zig/Client.zig"); | 14 | pub const Client = @import("zig/Client.zig"); |
| 15 | pub const Token = tokenizer.Token; | 15 | pub const Token = tokenizer.Token; |
| 16 | pub const Tokenizer = tokenizer.Tokenizer; | 16 | pub const Tokenizer = tokenizer.Tokenizer; |
| 17 | pub const TokenSmith = @import("zig/TokenSmith.zig"); | ||
| 17 | pub const string_literal = @import("zig/string_literal.zig"); | 18 | pub const string_literal = @import("zig/string_literal.zig"); |
| 18 | pub const number_literal = @import("zig/number_literal.zig"); | 19 | pub const number_literal = @import("zig/number_literal.zig"); |
| 19 | pub const primitives = @import("zig/primitives.zig"); | 20 | pub 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 | } |
| 162 | 162 | ||
| 163 | var tokens_slice = tokens.toOwnedSlice(); | ||
| 164 | errdefer tokens_slice.deinit(gpa); | ||
| 165 | return parseTokens(gpa, source, tokens_slice, mode); | ||
| 166 | } | ||
| 167 | |||
| 168 | pub 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 | |||
| 4 | const std = @import("../std.zig"); | ||
| 5 | const Smith = std.testing.Smith; | ||
| 6 | const Token = std.zig.Token; | ||
| 7 | const TokenList = std.zig.Ast.TokenList; | ||
| 8 | const TokenSmith = @This(); | ||
| 9 | |||
| 10 | source_buf: [4096]u8, | ||
| 11 | source_len: u32, | ||
| 12 | tag_buf: [512]Token.Tag, | ||
| 13 | start_buf: [512]std.zig.Ast.ByteOffset, | ||
| 14 | tags_len: u16, | ||
| 15 | |||
| 16 | fn 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 | |||
| 26 | pub 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 | |||
| 245 | pub 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. | ||
| 250 | pub 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 | |||
| 262 | test TokenSmith { | ||
| 263 | try std.testing.fuzz({}, checkSource, .{}); | ||
| 264 | } | ||
| 265 | |||
| 266 | fn 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 | } |
| 6422 | 6422 | ||
| 6423 | fn fuzzTestOneParse(_: void, input: []const u8) !void { | 6423 | fn fuzzTestOneParse(_: void, smith: *std.testing.Smith) !void { |
| 6424 | // The first byte holds if zig / zon | 6424 | 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 | } |
| 1723 | 1726 | ||
| 1724 | fn testPropertiesUpheld(_: void, source: []const u8) !void { | 1727 | fn 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]; | ||
| 1731 | 1741 | ||
| 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; |
| 1743 | 1753 | ||
| 1744 | // Property: invalid token always ends at newline or eof | 1754 | // 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 | } |
| 1756 | 1766 | ||
| 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, |
| 186 | link_depfile: ?[]const u8, | 186 | link_depfile: ?[]const u8, |
| 187 | disable_c_depfile: bool, | 187 | disable_c_depfile: bool, |
| 188 | stack_report: bool, | 188 | stack_report: bool, |
| 189 | debug_compiler_runtime_libs: bool, | 189 | debug_compiler_runtime_libs: ?std.builtin.OptimizeMode, |
| 190 | debug_compile_errors: bool, | 190 | debug_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. |
| 192 | debug_incremental: bool, | 192 | debug_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 build | 1755 | /// 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 the | 2206 | // 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, including | 8374 | /// 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. |
| 8375 | pub fn compilerRtOptMode(comp: Compilation) std.builtin.OptimizeMode { | 8376 | pub 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); | ||
| 98 | 99 | ||
| 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 | } |
| 103 | 104 | ||
| 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 | } |
| 115 | 116 | ||
| 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 { |
| 131 | 132 | ||
| 132 | // Write symtab | 133 | // 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); | ||
| 134 | 136 | ||
| 135 | // Write strtab | 137 | // 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 | } |
| 140 | 142 | ||
| 141 | // Write object files | 143 | // 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 | } |
| 146 | 148 | ||
| 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 scope | 708 | \\ --debug-log [scope] Enable printing debug/info log messages for scope |
| 709 | \\ --debug-compile-errors Crash with helpful diagnostics at the first compile error | 709 | \\ --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 format | 710 | \\ --debug-link-snapshot Enable dumping of the linker's state in JSON format |
| 711 | \\ --debug-rt Debug compiler runtime libraries | 711 | \\ --debug-rt[=mode] Build compiler runtime libraries with [mode] optimization |
| 712 | \\ (Debug if [=mode] is omitted) | ||
| 712 | \\ --debug-incremental Enable incremental compilation debug features | 713 | \\ --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"); |
| 2 | const abi = std.Build.abi.fuzz; | 2 | const abi = std.Build.abi.fuzz; |
| 3 | const native_endian = @import("builtin").cpu.arch.endian(); | 3 | const native_endian = @import("builtin").cpu.arch.endian(); |
| 4 | 4 | ||
| 5 | fn testOne(in: abi.Slice) callconv(.c) void { | 5 | fn testOne() callconv(.c) void {} |
| 6 | std.debug.assertReadable(in.toSlice()); | ||
| 7 | } | ||
| 8 | 6 | ||
| 9 | pub fn main(init: std.process.Init) !void { | 7 | pub 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); |
| 20 | 18 | ||
| 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")); |
| 25 | 23 |
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 | } |
| 2312 | 2312 | ||
| 2313 | const ModuleTestOptions = struct { | 2313 | pub 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 | }; |
| 2339 | 2344 | ||
| 2340 | pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { | 2345 | pub 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); |
| 2342 | 2347 | ||
| 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 | } |
| 2350 | 2361 | ||
| 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 { |
| 2425 | fn addOneModuleTest( | 2436 | fn 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, |