Skip to content
zit Artifact [3f08bcf472]
Login

Artifact [3f08bcf472]

Artifact 3f08bcf472e80ae9f5eb4eff2c0d6ad79cccbd093800f026364c592e6fa22477:


//! Seeded fuzz-target runner.
//!
//! `zig build fuzz -- <target> [--seed N] [--rounds N]`
//!
//! One binary; targets are named-tuple fields dispatched through
//! `std.meta.FieldEnum`, so a module registers a new target by adding one
//! field to `targets` below. The seed is explicit (`--seed`, decimal or
//! `0x`-prefixed) or drawn from the process's csprng; either way it is
//! always printed as `reproduce with seed=0x...` before the target runs, so
//! a failure replays exactly. `--rounds` bounds the run explicitly (default
//! 10_000); an out-of-band runner sweeps higher counts.

const std = @import("std");
const delta_fuzz = @import("delta_fuzz.zig");
const artifact_fuzz = @import("artifact_fuzz.zig");

/// One field per fuzz target: `pub fn(allocator, seed: u64, rounds: usize) !void`.
/// The field name is the argv target name.
const targets = .{
    .delta_apply_garbage = delta_fuzz.applyGarbage,
    .delta_round_trip = delta_fuzz.roundTrip,
    .delta_decode_int_bounded = delta_fuzz.decodeIntBounded,
    .delta_target_size_garbage = delta_fuzz.targetSizeGarbage,
    .artifact_parse_garbage = artifact_fuzz.parseGarbage,
    .artifact_parse_structured = artifact_fuzz.parseStructured,
    .artifact_card_line = artifact_fuzz.cardLine,
    .artifact_escapes = artifact_fuzz.escapes,
    .artifact_timestamp = artifact_fuzz.timestamp,
    .artifact_framing = artifact_fuzz.framing,
    .artifact_predicates = artifact_fuzz.predicates,
};
const Target = std.meta.FieldEnum(@TypeOf(targets));

const default_rounds: usize = 10_000;

pub fn main(init: std.process.Init) !void {
    const gpa = init.gpa;
    const args = try init.minimal.args.toSlice(init.arena.allocator());

    var stderr_buffer: [1024]u8 = undefined;
    var stderr_writer: std.Io.File.Writer = .init(.stderr(), init.io, &stderr_buffer);
    const err = &stderr_writer.interface;

    if (args.len < 2) {
        try usage(err);
        try err.flush();
        std.process.exit(2);
    }

    const target = std.meta.stringToEnum(Target, args[1]) orelse {
        try err.print("fuzz: unknown target '{s}'\n\n", .{args[1]});
        try usage(err);
        try err.flush();
        std.process.exit(2);
    };

    var seed: u64 = undefined;
    init.io.random(std.mem.asBytes(&seed));
    var rounds: usize = default_rounds;
    try parseFlags(args[2..], &seed, &rounds);

    try err.print("reproduce with seed=0x{x}\n", .{seed});
    try err.flush();

    switch (target) {
        inline else => |tag| try @field(targets, @tagName(tag))(gpa, seed, rounds),
    }
}

/// Parse `[--seed N] [--rounds N]` in any order, overwriting the defaults
/// already in `seed`/`rounds`. Each flag consumes exactly one value; an
/// unknown flag or a missing value is an explicit error, never ignored.
fn parseFlags(args: []const []const u8, seed: *u64, rounds: *usize) !void {
    var i: usize = 0;
    while (i < args.len) : (i += 1) {
        if (std.mem.eql(u8, args[i], "--seed")) {
            i += 1;
            if (i >= args.len) return error.MissingSeedValue;
            seed.* = try std.fmt.parseInt(u64, args[i], 0);
        } else if (std.mem.eql(u8, args[i], "--rounds")) {
            i += 1;
            if (i >= args.len) return error.MissingRoundsValue;
            rounds.* = try std.fmt.parseInt(usize, args[i], 0);
        } else {
            return error.UnknownArgument;
        }
    }
}

fn usage(w: *std.Io.Writer) !void {
    try w.writeAll("usage: zig build fuzz -- <target> [--seed N] [--rounds N]\n\ntargets:\n");
    inline for (@typeInfo(Target).@"enum".fields) |f| {
        try w.print("  {s}\n", .{f.name});
    }
}