╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ fuzzer.zig ───────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
101 102 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize); 103 comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr); 104 const pc_bitset_usizes = bitsetUsizes(pcs.len); 105 const coverage_file_len = @sizeOf(abi.SeenPcsHeader) + 106 pc_bitset_usizes * @sizeOf(usize) + 107 pcs.len * @sizeOf(usize); 108 109 var populate: bool = false; 110 const size = coverage_file.length(io) catch |e| 111 panic("failed to stat coverage file '{s}': {t}", .{ &file_name, e }); 112 if (size == 0 and maybe_populate) { 113 coverage_file.setLength(io, coverage_file_len) catch |e| 114 panic("failed to resize new coverage file '{s}': {t}", .{ &file_name, e }); 115 populate = true; 116 } else if (size != coverage_file_len) { 117 panic( 118 "incompatible existing coverage file '{s}' (differing lengths: {} != {})", 119 .{ &file_name, size, coverage_file_len }, 120 ); 121 } else if (maybe_populate) { 122 coverage_file.lock(io, .shared) catch |e| 123 panic("failed to demote lock for coverage file '{s}': {t}", .{ &file_name, e }); 124 } 125 126 var io_map = coverage_file.createMemoryMap(io, .{ .len = coverage_file_len }) catch |e| 127 panic("failed to memmap coverage file '{s}': {t}", .{ &file_name, e }); 128 const map = io_map.memory; 129 130 const header: *abi.SeenPcsHeader = @ptrCast(map[0..@sizeOf(abi.SeenPcsHeader)]); 131 const trailing = map[@sizeOf(abi.SeenPcsHeader)..]; 132 const trailing_bitset_end = pc_bitset_usizes * @sizeOf(usize); 133 const trailing_bitset: []usize = @ptrCast(@alignCast(trailing[0..trailing_bitset_end])); 134 const trailing_addresses: []usize = @ptrCast(@alignCast(trailing[trailing_bitset_end..])); 135 136 if (populate) { 137 header.* = .{ 138 .n_runs = 0, 139 .unique_runs = 0, 140 .pcs_len = pcs.len, 141 }; 142 @memset(trailing_bitset, 0); 143 for (trailing_addresses, pcs) |*cov_pc, slided_pc| { 144 cov_pc.* = fuzzer_unslide_address(slided_pc); 145 } 146 io_map.write(io) catch |e| 147 panic("failed to write memory map of '{s}': {t}", .{ &file_name, e }); 148 149 coverage_file.lock(io, .shared) catch |e| panic( 150 "failed to demote lock for coverage file '{s}': {t}", 151 .{ &file_name, e }, 152 ); 153 } else { // Check expected contents 154 if (header.pcs_len != pcs.len) panic( 155 "incompatible existing coverage file '{s}' (differing pcs length: {} != {})", 156 .{ &file_name, header.pcs_len, pcs.len }, 157 ); 158 for (0.., header.pcAddrs(), pcs) |i, cov_pc, slided_pc| { 159 const pc = fuzzer_unslide_address(slided_pc); 160 if (cov_pc != pc) panic( 161 "incompatible existing coverage file '{s}' (differing pc at index {d}: {x} != {x})", 162 .{ &file_name, i, cov_pc, pc }, 163 ); 164 } 165 } 166 return map; 167 } 168 169 pub fn init(cache_dir_path: []const u8) Executable { 170 var self: Executable = undefined; 171 172 const cache_dir = Io.Dir.cwd().createDirPathOpen(io, cache_dir_path, .{}) catch |e| 173 panic("failed to open directory '{s}': {t}", .{ cache_dir_path, e }); 174 cache_dir.createDirPath(io, "tmp") catch |e| 175 panic("failed to create directory 'tmp': {t}", .{e}); 176 log_f = cache_dir.createFile(io, "tmp/libfuzzer.log", .{ .truncate = false }) catch |e| 177 panic("failed to create file 'tmp/libfuzzer.log': {t}", .{e}); 178 self.cache_f = cache_dir.createDirPathOpen(io, "f", .{}) catch |e| 179 panic("failed to open directory 'f': {t}", .{e}); 180 181 // Linkers are expected to automatically add symbols prefixed with these for the start and 182 // end of sections whose names are valid C identifiers. 183 const ofmt = builtin.object_format; 184 const section_start_prefix, const section_end_prefix = switch (ofmt) { 185 .elf => .{ "__start_", "__stop_" }, 186 .macho => .{ "\x01section$start$__DATA$", "\x01section$end$__DATA$" }, 187 else => @compileError("unsupported fuzzing object format '" ++ @tagName(ofmt) ++ "'"), 188 }; 189 190 self.pc_counters = blk: { 191 const pc_counters_start_name = section_start_prefix ++ "__sancov_cntrs"; 192 const pc_counters_start = @extern([*]u8, .{ 193 .name = pc_counters_start_name, 194 .linkage = .weak, 195 }) orelse panic("missing {s} symbol", .{pc_counters_start_name}); 196 197 const pc_counters_end_name = section_end_prefix ++ "__sancov_cntrs"; 198 const pc_counters_end = @extern([*]u8, .{ 199 .name = pc_counters_end_name, 200 .linkage = .weak, 201 }) orelse panic("missing {s} symbol", .{pc_counters_end_name}); 202 203 break :blk pc_counters_start[0 .. pc_counters_end - pc_counters_start]; 204 }; 205 206 const pcs = blk: { 207 const pcs_start_name = section_start_prefix ++ "__sancov_pcs1"; 208 const pcs_start = @extern([*]usize, .{ 209 .name = pcs_start_name, 210 .linkage = .weak, 211 }) orelse panic("missing {s} symbol", .{pcs_start_name}); 212 213 const pcs_end_name = section_end_prefix ++ "__sancov_pcs1"; 214 const pcs_end = @extern([*]usize, .{ 215 .name = pcs_end_name, 216 .linkage = .weak, 217 }) orelse panic("missing {s} symbol", .{pcs_end_name}); 218 219 break :blk pcs_start[0 .. pcs_end - pcs_start]; 220 }; 221 222 if (self.pc_counters.len != pcs.len) panic( 223 "pc counters length and pcs length do not match ({} != {})", 224 .{ self.pc_counters.len, pcs.len }, 225 ); 226 227 self.pc_digest = digest: { 228 // Relocations have been applied to `pcs` so it contains runtime addresses (with slide 229 // applied). We need to translate these to the virtual addresses as on disk. 230 var h: std.hash.Wyhash = .init(0); 231 for (pcs) |pc| { 232 const pc_vaddr = fuzzer_unslide_address(pc); 233 h.update(@ptrCast(&pc_vaddr)); 234 } 235 break :digest h.final(); 236 }; 237 self.shared_seen_pcs = getCoverageMap(cache_dir, pcs, self.pc_digest); 238 239 return self; 240 } 241 242 /// Asserts `buf[0..2]` is "in" 243 fn inputFileName(buf: *[10]u8, i: u32) []u8 { 244 assert(buf[0..2].* == "in".*); 245 const hex = std.mem.print(buf[2..], "{x}", .{i}) catch unreachable; 246 return buf[0 .. 2 + hex.len]; 247 } 248 249 pub fn pcBitsetIterator(self: Executable) PcBitsetIterator { 250 return .{ .pc_counters = self.pc_counters }; 251 } 252 253 /// Iterates over pc_counters returning a bitset for if each of them have been hit 254 pub const PcBitsetIterator = struct { 255 index: usize = 0, 256 pc_counters: []u8, 257 258 pub fn next(i: *PcBitsetIterator) usize { 259 const rest = i.pc_counters[i.index..]; 260 if (rest.len >= @bitSizeOf(usize)) { 261 defer i.index += @bitSizeOf(usize); 262 const V = @Vector(@bitSizeOf(usize), u8); 263 return @as(usize, @bitCast(@as(V, @splat(0)) != rest[0..@bitSizeOf(usize)].*)); 264 } else if (rest.len != 0) { 265 defer i.index += rest.len; 266 var res: usize = 0; 267 for (0.., rest) |bit_index, byte| { 268 res |= @shlExact(@as(usize, @intFromBool(byte != 0)), @intCast(bit_index)); 269 } 270 return res; 271 } else unreachable; 272 } 273 }; 274 275 pub fn seenPcsHeader(e: Executable) *align(std.heap.page_size_min) volatile abi.SeenPcsHeader { 276 return mem.bytesAsValue( 277 abi.SeenPcsHeader, 278 e.shared_seen_pcs[0..@sizeOf(abi.SeenPcsHeader)], 279 ); 280 } 281 }; 282 283 const Fuzzer = struct { 284 tests: []Test, 285 test_i: u32, 286 test_one: abi.TestOne, 287 288 // The default PRNG is not used here since going through `Random` can be very expensive 289 // since LLVM often fails to devirtualize and inline `fill`. Additionally, optimization 290 // is simpler since integers are not serialized then deserialized in the random stream. 291 // 292 // This acounts for a 30% performance improvement with LLVM 21. 293 xoshiro: std.Random.Xoshiro256, 294 bytes_input: std.testing.Smith, 295 input_builder: Input.Builder, 296 /// Number of data calls the current run has made. 297 req_values: u32, 298 /// Number of bytes provided to the current run. 299 req_bytes: u32, 300 /// Index into the uid slices the current run is at. 301 /// `uid_data_i[i]` corresponds to `corpus[corpus_pos].data.uid_slices.values()[i]`. 302 uid_data_i: std.ArrayList(u32), 303 mut_data: struct { 304 /// Untyped indexes of `corpus[corpus_pos].data` that should be mutated. 305 /// 306 /// If an index appears multiple times, the first should be prioritized. 307 i: [4]u32, 308 /// For mutations which are a sequential mutation, the state is stored here. 309 seq: [4]struct { 310 kind: packed struct { 311 class: enum(u1) { replace, insert }, 312 copy: bool, 313 /// If set then `.copy = true` and `.class = .replace` 314 ordered_mutate: bool, 315 /// If set then all other bits are undefined 316 none: bool, 317 }, 318 len: u32, 319 copy: SeqCopy, 320 }, 321 }, 322 323 /// As values are provided to the Smith, they are appended to this. If the test 324 /// crashes, this can be recovered and used to obtain the crashing values. It is 325 /// also used to rerun fresh inputs. 326 mmap_input: MemoryMappedInput, 327 /// The instance is responsible for updating the filesystem corpus. 328 /// 329 /// Since different fuzzer instances can be out of sync due to finding inputs before recieving 330 /// others and nondeterministic tests, the filesystem is only based off the first instance. 331 main_instance: bool, 332 333 const Test = struct { 334 const NameHash = u64; 335 const dirname_len = @sizeOf(NameHash) * 2; 336 337 seen_pcs: []usize, 338 bests: struct { 339 len: u32, 340 quality_buf: []Input.Best, 341 input_buf: []Input.Best.Map, 342 }, 343 seen_uids: std.array_hash_map.Custom(Uid, struct { 344 slices: union { 345 ints: std.ArrayList([]u64), 346 bytes: std.ArrayList(Input.Data.Bytes), 347 }, 348 }, Uid.hashmap_ctx, false), 349 350 /// Past inputs leading to new pc or uid hits. 351 /// These are randomly mutated in round-robin fashion. 352 corpus: std.MultiArrayList(Input), 353 corpus_pos: Input.Index, 354 /// If this is `math.maxInt(u32)` (reserved), it means the corpus has not been loaded from 355 /// the filesystem. 356 /// 357 /// If `main_instance` is set, the values in `corpus` after this are mirrored to the 358 /// filesystem. 359 start_mut_corpus: u32, 360 dirname: [dirname_len]u8, 361 /// Ensures only one fuzzer writes to the corpus. 362 /// 363 /// Undefined if this is not the main instance. 364 lock_file: Io.File, 365 received: Received, 366 367 limit: ?u64, 368 /// A batch is the amount of cycles approximently for one second of runtime. 369 /// 370 /// This value is set to the previous batch's runs per second or run limit. 371 batch_cycles: u32, 372 batches: u64, 373 batches_since_find: u64, 374 seen_pc_count: u32, 375 }; 376 377 const Received = struct { 378 state: State, 379 /// Stream of inputs with each prefixed with a u32 length 380 inputs: std.ArrayList(u8), 381 382 pub const empty: Received = .{ 383 .state = .{ 384 .pending = false, 385 .read_lock = false, 386 .write_lock = false, 387 }, 388 .inputs = .empty, 389 }; 390 391 pub const State = packed struct(u32) { 392 pending: bool, 393 read_lock: bool, 394 /// If set in conjucation with `read_lock`, then there is a waiter on state. 395 write_lock: bool, 396 _: u29 = 0, 397 398 pub fn hasPending(s: *State) bool { 399 return @atomicLoad(State, s, .monotonic).pending; 400 } 401 402 pub fn startReadIfPending(s: *State) bool { 403 return @cmpxchgWeak( 404 State, 405 s, 406 .{ .pending = true, .read_lock = false, .write_lock = false }, 407 .{ .pending = true, .read_lock = true, .write_lock = false }, 408 .acquire, 409 .monotonic, 410 ) == null; 411 } 412 413 pub fn finishRead(s: *State) void { 414 const prev = @atomicRmw(State, s, .And, .{ 415 .pending = false, 416 .read_lock = false, 417 .write_lock = true, 418 }, .release); 419 assert(prev.read_lock); 420 if (prev.write_lock) { 421 abi.runner_futex_wake(@ptrCast(s), 1); 422 } 423 } 424 425 /// Returns if cancelation is requested. 426 pub fn startWrite(s: *State) bool { 427 var prev = @atomicRmw(State, s, .Or, .{ 428 .pending = false, 429 .read_lock = false, 430 .write_lock = true, 431 }, .acquire); 432 assert(!prev.write_lock); 433 while (prev.read_lock) { 434 if (abi.runner_futex_wait(@ptrCast(s), @bitCast(prev))) { 435 s.* = undefined; // fuzzer is exiting 436 return true; 437 } 438 // Still need `.acquire` ordering so @atomicRmw is necessary 439 prev = @atomicRmw(State, s, .Or, .{ 440 .pending = false, 441 .read_lock = false, 442 .write_lock = false, 443 }, .acquire); 444 assert(prev.write_lock); 445 } 446 return false; 447 } 448 449 pub fn finishWrite(s: *State) void { 450 @atomicStore(State, s, .{ 451 .pending = true, 452 .read_lock = false, 453 .write_lock = false, 454 }, .release); 455 } 456 }; 457 }; 458 459 const SeqCopy = union { 460 order_i: u32, 461 ints: []u64, 462 bytes: Input.Data.Bytes, 463 }; 464 465 const Input = struct { 466 /// Untyped indexes into this are formed as follows: If the index is less than `ints.len` 467 /// it indexes into `ints`, otherwise it indexes into `bytes` subtracted by `ints.len`. 468 /// `math.maxInt(u32)` is reserved and impossible normally. 469 data: Data, 470 /// Corresponds with `data.uid_slices`. 471 /// Values are the indexes of `seen_uids` with the same uid. 472 seen_uid_i: []u32, 473 /// Used to select a random uid to mutate from. 474 /// 475 /// The number of times a uid is present in this array is logarithmic 476 /// to its data length in order to avoid long inputs from only being 477 /// selected while still having some bias towards longer ones. 478 weighted_uid_slice_i: []u32, 479 480 ref: struct { 481 /// Values are indexes of `Fuzzer.bests`. 482 best_i_buf: []u32, 483 best_i_len: u32, 484 }, 485 486 pub const Data = struct { 487 uid_slices: Data.UidSlices, 488 ints: []u64, 489 bytes: Bytes, 490 /// Contains untyped indexes in the order they were requested. 491 order: []u32, 492 493 pub const Bytes = struct { 494 entries: []Entry, 495 table: []u8, 496 497 pub const Entry = struct { 498 off: u32, 499 len: u32, 500 }; 501 502 pub fn deinit(b: Bytes) void { 503 gpa.free(b.entries); 504 gpa.free(b.table); 505 } 506 }; 507 508 pub const UidSlices = std.array_hash_map.Custom(Uid, struct { 509 base: u32, 510 len: u32, 511 }, Uid.hashmap_ctx, false); 512 }; 513 514 pub fn deinit(i: *Input) void { 515 i.data.uid_slices.deinit(gpa); 516 gpa.free(i.data.ints); 517 i.data.bytes.deinit(); 518 gpa.free(i.data.order); 519 gpa.free(i.seen_uid_i); 520 gpa.free(i.weighted_uid_slice_i); 521 gpa.free(i.ref.best_i_buf); 522 i.* = undefined; 523 } 524 525 pub const none: Input = .{ 526 .data = .{ 527 .uid_slices = .empty, 528 .ints = &.{}, 529 .bytes = .{ 530 .entries = &.{}, 531 .table = undefined, 532 }, 533 .order = &.{}, 534 }, 535 .seen_uid_i = &.{}, 536 .weighted_uid_slice_i = &.{}, 537 538 // Empty input is not referenced by `Fuzzer` 539 .ref = undefined, 540 }; 541 542 pub const Index = enum(u32) { 543 pub const reserved_start: Index = .bytes_dry; 544 /// Only touches `Fuzzer.smith`. 545 bytes_dry = math.maxInt(u32) - 1, 546 /// Only touches `Fuzzer.smith` and `Fuzzer.input_builder`. 547 bytes_fresh = math.maxInt(u32), 548 _, 549 }; 550 551 pub const Best = struct { 552 pc: u32, 553 min: Quality, 554 max: Quality, 555 556 /// Order of significance: 557 /// * n_pcs 558 /// * req.values 559 /// * req.bytes 560 pub const Quality = struct { 561 n_pcs: u32, 562 req: packed struct(u64) { 563 bytes: u32, 564 values: u32, 565 566 pub fn int(r: @This()) u64 { 567 return @bitCast(r); 568 } 569 }, 570 571 pub fn betterLess(a: Quality, b: Quality) bool { 572 return (a.n_pcs < b.n_pcs) | ((a.n_pcs == b.n_pcs) & (a.req.int() < b.req.int())); 573 } 574 575 pub fn betterMore(a: Quality, b: Quality) bool { 576 return (a.n_pcs > b.n_pcs) | ((a.n_pcs == b.n_pcs) & (a.req.int() < b.req.int())); 577 } 578 }; 579 580 pub const Map = struct { 581 min: Input.Index, 582 max: Input.Index, 583 }; 584 }; 585 586 pub const Builder = struct { 587 uid_slices: std.array_hash_map.Custom(Uid, union { 588 ints: std.MultiArrayList(struct { 589 value: u64, 590 order_i: u32, 591 }), 592 bytes: std.MultiArrayList(struct { 593 value: Data.Bytes.Entry, 594 order_i: u32, 595 }), 596 }, Uid.hashmap_ctx, false), 597 bytes_table: std.ArrayList(u8), 598 // These will not overflow due to the 32-bit constraint on `MemoryMappedInput` 599 total_ints: u32, 600 total_bytes: u32, 601 weighted_len: u32, 602 /// Used to ensure that the 32-bit constraint in 603 /// `MemoryMappedInput` applies to this run. 604 smithed_len: u32, 605 606 pub const init: Builder = .{ 607 .uid_slices = .empty, 608 .bytes_table = .empty, 609 .total_ints = 0, 610 .total_bytes = 0, 611 .weighted_len = 0, 612 // The - 1 is because we check that `smithed_len` does not overflow a u32; 613 // however, `MemoryMappedInput` allows up to `1 << 32`. 614 .smithed_len = @sizeOf(abi.MmapInputHeader) - 1, 615 }; 616 617 pub fn addInt(b: *Builder, uid: Uid, int: u64) void { 618 const u = &b.uid_slices; 619 const gop = u.getOrPutValue(gpa, uid, .{ .ints = .empty }) catch @panic("OOM"); 620 gop.value_ptr.ints.append(gpa, .{ 621 .value = int, 622 .order_i = b.total_ints + b.total_bytes, 623 }) catch @panic("OOM"); 624 b.total_ints += 1; 625 b.weighted_len += @intFromBool(math.isPowerOfTwo(gop.value_ptr.ints.len)); 626 } 627 628 pub fn addBytes(b: *Builder, uid: Uid, bytes: []const u8) void { 629 const u = &b.uid_slices; 630 const gop = u.getOrPutValue(gpa, uid, .{ .bytes = .empty }) catch @panic("OOM"); 631 gop.value_ptr.bytes.append(gpa, .{ 632 .value = .{ 633 .off = @intCast(b.bytes_table.items.len), 634 .len = @intCast(bytes.len), 635 }, 636 .order_i = b.total_ints + b.total_bytes, 637 }) catch @panic("OOM"); 638 b.bytes_table.appendSlice(gpa, bytes) catch @panic("OOM"); 639 b.total_bytes += 1; 640 b.weighted_len += @intFromBool(math.isPowerOfTwo(gop.value_ptr.bytes.len)); 641 } 642 643 pub fn checkSmithedLen(b: *Builder, n: usize) void { 644 const n32 = @min(n, math.maxInt(u32)); // second will overflow 645 b.smithed_len, const ov = @addWithOverflow(b.smithed_len, n32); 646 if (ov == 1) @panic("too much smith data requested (non-deterministic)"); 647 } 648 649 /// Additionally resets the state of this structure. 650 /// 651 /// The callee must populate 652 /// * `.seen_uid_i` 653 /// * `.ref` 654 pub fn build(b: *Builder) Input { 655 const uid_slices = b.uid_slices.entries.slice(); 656 var input: Input = .{ 657 .data = .{ 658 .uid_slices = Data.UidSlices.init(gpa, uid_slices.items(.key), &.{}) catch 659 @panic("OOM"), 660 .ints = gpa.alloc(u64, b.total_ints) catch @panic("OOM"), 661 .bytes = .{ 662 .entries = gpa.alloc(Data.Bytes.Entry, b.total_bytes) catch @panic("OOM"), 663 .table = b.bytes_table.toOwnedSlice(gpa) catch @panic("OOM"), 664 }, 665 .order = gpa.alloc(u32, b.total_ints + b.total_bytes) catch @panic("OOM"), 666 }, 667 .seen_uid_i = gpa.alloc(u32, uid_slices.len) catch @panic("OOM"), 668 .weighted_uid_slice_i = gpa.alloc(u32, b.weighted_len) catch @panic("OOM"), 669 .ref = undefined, 670 }; 671 var ints_pos: u32 = 0; 672 var bytes_pos: u32 = 0; 673 var weighted_pos: u32 = 0; 674 675 assert(mem.eql(Uid, uid_slices.items(.key), input.data.uid_slices.keys())); 676 for ( 677 0.., 678 uid_slices.items(.key), 679 uid_slices.items(.value), 680 input.data.uid_slices.values(), 681 ) |uid_i, uid, *uid_data, *slice| { 682 const weighted_len = 1 + math.log2_int(u32, len: switch (uid.kind) { 683 .int => { 684 const ints = uid_data.ints.slice(); 685 @memcpy(input.data.ints[ints_pos..][0..ints.len], ints.items(.value)); 686 for (ints.items(.order_i), ints_pos..) |order_i, data_i| { 687 input.data.order[order_i] = @intCast(data_i); 688 } 689 uid_data.ints.deinit(gpa); 690 slice.* = .{ .base = ints_pos, .len = @intCast(ints.len) }; 691 ints_pos += @intCast(ints.len); 692 break :len @intCast(ints.len); 693 }, 694 .bytes => { 695 const bytes = uid_data.bytes.slice(); 696 @memcpy( 697 input.data.bytes.entries[bytes_pos..][0..bytes.len], 698 bytes.items(.value), 699 ); 700 for ( 701 bytes.items(.order_i), 702 b.total_ints + bytes_pos.., 703 ) |order_i, data_i| { 704 input.data.order[order_i] = @intCast(data_i); 705 } 706 uid_data.bytes.deinit(gpa); 707 slice.* = .{ .base = bytes_pos, .len = @intCast(bytes.len) }; 708 bytes_pos += @intCast(bytes.len); 709 break :len @intCast(bytes.len); 710 }, 711 }); 712 const weighted = input.weighted_uid_slice_i[weighted_pos..][0..weighted_len]; 713 @memset(weighted, @intCast(uid_i)); 714 weighted_pos += weighted_len; 715 } 716 717 assert(ints_pos == b.total_ints); 718 assert(bytes_pos == b.total_bytes); 719 assert(weighted_pos == b.weighted_len); 720 721 b.uid_slices.clearRetainingCapacity(); 722 b.total_ints = 0; 723 b.total_bytes = 0; 724 b.weighted_len = 0; 725 b.smithed_len = Builder.init.smithed_len; 726 return input; 727 } 728 729 pub fn reset(b: *Builder) void { 730 const uid_slices = b.uid_slices.entries.slice(); 731 for (uid_slices.items(.key), uid_slices.items(.value)) |uid, *uid_data| { 732 switch (uid.kind) { 733 .int => uid_data.ints.deinit(gpa), 734 .bytes => uid_data.bytes.deinit(gpa), 735 } 736 } 737 b.uid_slices.clearRetainingCapacity(); 738 b.bytes_table.clearRetainingCapacity(); 739 b.total_ints = 0; 740 b.total_bytes = 0; 741 b.weighted_len = 0; 742 b.smithed_len = Builder.init.smithed_len; 743 } 744 745 /// Asserts the structure is reset 746 pub fn deinit(b: *Builder) void { 747 assert(b.uid_slices.entries.len == 0); 748 b.uid_slices.deinit(gpa); 749 b.bytes_table.deinit(gpa); 750 b.* = undefined; 751 } 752 }; 753 }; 754 755 pub fn init(n_tests: u32, seed: u64, instance_id: u32, limit: ?u64) Fuzzer { 756 const pcs = exec.pc_counters.len; 757 if (pcs > math.maxInt(u32)) @panic("too many pcs"); 758 759 const mmap_input = map: { 760 // Find a free input file. `instance_id` should give one that is not in use; 761 // however, this may not be the case if there are multiple libfuzzers running. 762 var input_i = instance_id; 763 const input_f = while (true) { 764 var name_buf: [10]u8 = undefined; 765 name_buf[0..2].* = "in".*; 766 const hex = std.mem.print(name_buf[2..], "{x}", .{input_i}) catch unreachable; 767 const name = name_buf[0 .. 2 + hex.len]; 768 769 if (exec.cache_f.createFile(io, name, .{ 770 .read = true, 771 .truncate = false, 772 .lock = .exclusive, 773 .lock_nonblocking = true, 774 })) |f| { 775 break f; 776 } else |e| switch (e) { 777 // To ensure no input file is unused to avoid the number of input files 778 // growing indefinitely across runs, they are linearly searched through. 779 // 780 // This could be avoided by creating a shared file holding the current number 781 // of input files in use; however, using multiple libfuzzers is uncommon and 782 // there should not be that many input files to search through anyways. 783 error.WouldBlock => input_i += 1, 784 else => panic("failed to create file '{s}': {t}", .{ name, e }), 785 } 786 }; 787 break :map MemoryMappedInput.init(input_f, instance_id, input_i); 788 }; 789 790 const tests = gpa.alloc(Test, n_tests) catch @panic("OOM"); 791 const seen_pcs_len = bitsetUsizes(pcs); 792 var seen_pcs_bufs = gpa.alloc(usize, seen_pcs_len * n_tests) catch @panic("OOM"); 793 var best_quality_bufs = gpa.alloc(Input.Best, pcs * n_tests) catch @panic("OOM"); 794 var best_input_bufs = gpa.alloc(Input.Best.Map, pcs * n_tests) catch @panic("OOM"); 795 @memset(seen_pcs_bufs, 0); 796 for (0.., tests) |i, *t| { 797 const name = abi.runner_test_name(@intCast(i)).toSlice(); 798 // A hash is used as the dirname instead of the actual test name since the test name 799 // may be not allowed by the filesystem or have a special meaning (e.g. absolute / 800 // relative paths). 801 const dirname = std.fmt.hex(std.hash.Wyhash.hash(0, name)); 802 803 const lock_file = file: { 804 if (instance_id != 0) break :file undefined; 805 806 exec.cache_f.createDir(io, &dirname, .default_dir) catch |e| switch (e) { 807 error.PathAlreadyExists => {}, 808 else => panic("failed to create directory '{s}': {t}", .{ &dirname, e }), 809 }; 810 811 var cname: CorpusFileName = .fromTest(dirname); 812 const lock_name = cname.syncLockName(); 813 break :file exec.cache_f.createFile(io, lock_name, .{ 814 .truncate = false, 815 .lock = .exclusive, 816 .lock_nonblocking = true, 817 }) catch |e| switch (e) { 818 error.WouldBlock => panic("corpus of '{s}' is in use by another fuzzer", .{name}), 819 else => panic("failed to create file '{s}': {t}", .{ lock_name, e }), 820 }; 821 }; 822 823 t.* = .{ 824 .seen_pcs = seen_pcs_bufs[0..seen_pcs_len], 825 .bests = .{ 826 .len = 0, 827 .quality_buf = best_quality_bufs[0..pcs], 828 .input_buf = best_input_bufs[0..pcs], 829 }, 830 .seen_uids = .empty, 831 832 .corpus = .empty, 833 .corpus_pos = @fromBackingInt(@intCast(0)), 834 .start_mut_corpus = math.maxInt(u32), 835 .dirname = dirname, 836 .lock_file = lock_file, 837 .received = .empty, 838 839 .limit = limit, 840 .batch_cycles = 1, 841 .batches = 0, 842 .batches_since_find = 0, 843 .seen_pc_count = 0, 844 }; 845 t.corpus.append(gpa, .none) catch @panic("OOM"); // Also ensures the corpus is not empty 846 seen_pcs_bufs = seen_pcs_bufs[seen_pcs_len..]; 847 best_quality_bufs = best_quality_bufs[pcs..]; 848 best_input_bufs = best_input_bufs[pcs..]; 849 } 850 assert(seen_pcs_bufs.len == 0); 851 assert(best_quality_bufs.len == 0); 852 assert(best_input_bufs.len == 0); 853 854 return .{ 855 .tests = tests, 856 .test_i = undefined, 857 .test_one = undefined, 858 859 .xoshiro = .init(seed), 860 .bytes_input = undefined, 861 .input_builder = .init, 862 .req_values = undefined, 863 .req_bytes = undefined, 864 .uid_data_i = .empty, 865 .mut_data = undefined, 866 867 .mmap_input = mmap_input, 868 .main_instance = instance_id == 0, 869 }; 870 } 871 872 pub fn deinit(f: *Fuzzer) void { 873 const pcs = exec.pc_counters.len; 874 const n_tests = f.tests.len; 875 gpa.free(f.tests[0].seen_pcs.ptr[0 .. bitsetUsizes(pcs) * n_tests]); 876 gpa.free(f.tests[0].bests.quality_buf.ptr[0 .. pcs * n_tests]); 877 gpa.free(f.tests[0].bests.input_buf.ptr[0 .. pcs * n_tests]); 878 for (f.tests) |*t| { 879 const seen_uids = t.seen_uids.entries.slice(); 880 for (seen_uids.items(.key), seen_uids.items(.value)) |uid, *data| { 881 switch (uid.kind) { 882 .int => data.slices.ints.deinit(gpa), 883 .bytes => data.slices.bytes.deinit(gpa), 884 } 885 } 886 t.seen_uids.deinit(gpa); 887 const corpus = t.corpus.slice(); 888 // The first input is `Input.none` and so is skipped as `deinit` is illegal. 889 for (1..corpus.len) |i| { 890 var in = corpus.get(i); 891 in.deinit(); 892 } 893 if (f.main_instance) { 894 t.lock_file.close(io); 895 } 896 t.received.inputs.deinit(gpa); 897 } 898 gpa.free(f.tests); 899 f.input_builder.deinit(); 900 f.mmap_input.deinit(); 901 f.* = undefined; 902 } 903 904 pub fn ensureCorpusLoaded(f: *Fuzzer) void { 905 const t = &f.tests[f.test_i]; 906 if (t.start_mut_corpus != math.maxInt(u32)) return; 907 908 const start_mut: u32 = @intCast(t.corpus.len); 909 if (!f.main_instance) { 910 // Inputs can be culled as added since filesystem synchronacy is not required 911 t.start_mut_corpus = start_mut; 912 } 913 914 read_corpus: { 915 var cname: CorpusFileName = .fromTest(t.dirname); 916 917 const readlock_name = cname.readLockName(); 918 const readlock_file = exec.cache_f.createFile(io, readlock_name, .{ 919 .truncate = false, 920 .lock = .shared, 921 }) catch |e| switch (e) { 922 // FileNotFound means the corpus directory does not exist, which means it is empty 923 error.FileNotFound => break :read_corpus, 924 else => panic("failed to open '{s}': {t}", .{ readlock_name, e }), 925 }; 926 defer readlock_file.close(io); 927 928 var input_buf: std.ArrayList(u8) = .empty; 929 defer input_buf.deinit(gpa); 930 var i: u32 = 0; 931 while (true) { 932 const name = cname.inputName(i); 933 const input_file = exec.cache_f.openFile(io, name, .{}) catch |e| switch (e) { 934 error.FileNotFound => break, 935 else => panic("failed to open input file '{s}': {t}", .{ name, e }), 936 }; 937 938 const len = input_file.length(io) catch |e| 939 panic("failed to get length of '{s}': {t}", .{ name, e }); 940 const ulen = math.cast(usize, len) orelse @panic("OOM"); 941 input_buf.resize(gpa, ulen) catch @panic("OOM"); 942 943 var r = input_file.readerStreaming(io, &.{}); 944 r.interface.readSliceAll(input_buf.items) catch |e| switch (e) { 945 error.ReadFailed => panic( 946 "failed to read from input file '{s}': {t}", 947 .{ name, r.err.? }, 948 ), 949 error.EndOfStream => panic( 950 "input file '{s}' ended before its reported length", 951 .{name}, 952 ), 953 }; 954 f.newInputExternal(input_buf.items); 955 956 i += 1; // Cannot overflow due to corpus 32-bit size limit 957 } 958 } 959 960 if (f.main_instance) { 961 t.start_mut_corpus = start_mut; 962 963 // Cull old inputs 964 const ref = t.corpus.items(.ref); 965 var i: usize = t.start_mut_corpus; 966 while (i < t.corpus.len) { 967 if (ref[i].best_i_len == 0) { 968 f.removeInput(@fromBackingInt(@intCast(i))); 969 } else { 970 i += 1; 971 } 972 } 973 } 974 975 t.corpus_pos = @fromBackingInt(@intCast(0)); 976 } 977 978 const CorpusFileName = struct { 979 buf: [Test.dirname_len + 9]u8, 980 981 pub fn fromTest(dirname: [Test.dirname_len]u8) CorpusFileName { 982 var n: CorpusFileName = undefined; 983 n.buf[0..dirname.len].* = dirname; 984 n.buf[dirname.len] = Io.Dir.path.sep; 985 return n; 986 } 987 988 pub fn readLockName(n: *CorpusFileName) []u8 { 989 const basename = "readlock"; 990 n.buf[Test.dirname_len + 1 ..][0..basename.len].* = basename.*; 991 return n.buf[0 .. Test.dirname_len + 1 + basename.len]; 992 } 993 994 pub fn syncLockName(n: *CorpusFileName) []u8 { 995 const basename = "synclock"; 996 n.buf[Test.dirname_len + 1 ..][0..basename.len].* = basename.*; 997 return n.buf[0 .. Test.dirname_len + 1 + basename.len]; 998 } 999 1000 pub fn inputName(n: *CorpusFileName, i: u32) []u8 { 1001 const hex = std.mem.print(n.buf[Test.dirname_len + 1 ..][0..8], "{x}", .{i}) catch unreachable; 1002 return n.buf[0 .. Test.dirname_len + 1 + hex.len]; 1003 } 1004 }; 1005 1006 fn rngInt(f: *Fuzzer, T: type) T { 1007 comptime assert(@bitSizeOf(T) <= 64); 1008 const Unsigned = @Int(.unsigned, @bitSizeOf(T)); 1009 return @bitCast(@as(Unsigned, @truncate(f.xoshiro.next()))); 1010 } 1011 1012 fn rngLessThan(f: *Fuzzer, T: type, limit: T) T { 1013 return std.Random.limitRangeBiased(T, f.rngInt(T), limit); 1014 } 1015 1016 /// Used for generating small values rather than making many calls into the prng. 1017 const SmallEntronopy = struct { 1018 bits: u64, 1019 1020 pub fn take(e: *SmallEntronopy, T: type) T { 1021 defer e.bits >>= @bitSizeOf(T); 1022 return @truncate(e.bits); 1023 } 1024 }; 1025 1026 fn isFresh(f: *Fuzzer) bool { 1027 const t = &f.tests[f.test_i]; 1028 // Store as a bool instead of returning immediately to aid optimizations 1029 // by reducing branching since a fresh input is the unlikely case. 1030 var fresh: bool = false; 1031 1032 var n_pcs: u32 = 0; 1033 var hit_pcs = exec.pcBitsetIterator(); 1034 for (t.seen_pcs) |seen| { 1035 const hits = hit_pcs.next(); 1036 fresh |= hits & ~seen != 0; 1037 n_pcs += @popCount(hits); 1038 } 1039 1040 const quality: Input.Best.Quality = .{ 1041 .n_pcs = n_pcs, 1042 .req = .{ 1043 .values = f.req_values, 1044 .bytes = f.req_bytes, 1045 }, 1046 }; 1047 for (t.bests.quality_buf[0..t.bests.len]) |best| { 1048 if (exec.pc_counters[best.pc] == 0) continue; 1049 fresh |= quality.betterLess(best.min) | quality.betterMore(best.max); 1050 } 1051 1052 return fresh; 1053 } 1054 1055 /// It is the callee's responsibility to reset the corpus pos 1056 /// 1057 /// Returns if `error.SkipZigTest` was indicated 1058 fn runBytes(f: *Fuzzer, bytes: []const u8, mode: Input.Index) bool { 1059 assert(mode == .bytes_dry or mode == .bytes_fresh); 1060 1061 f.bytes_input = .{ .in = bytes }; 1062 f.tests[f.test_i].corpus_pos = mode; 1063 defer f.tests[f.test_i].corpus_pos = undefined; 1064 return f.run(0); // 0 since `f.uid_data` is unused 1065 } 1066 1067 fn updateSeenPcs(f: *Fuzzer) void { 1068 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize); 1069 const shared_seen_pcs: [*]volatile usize = @ptrCast( 1070 exec.shared_seen_pcs[@sizeOf(abi.SeenPcsHeader)..].ptr, 1071 ); 1072 1073 const t = &f.tests[f.test_i]; 1074 var hit_pcs = exec.pcBitsetIterator(); 1075 for (t.seen_pcs, shared_seen_pcs) |*seen, *shared_seen| { 1076 const new = hit_pcs.next() & ~seen.*; 1077 if (new != 0) { 1078 seen.* |= new; 1079 _ = @atomicRmw(usize, shared_seen, .Or, new, .monotonic); 1080 t.seen_pc_count += @popCount(new); 1081 } 1082 } 1083 } 1084 1085 fn removeBest(f: *Fuzzer, i: Input.Index, best_i: u32) void { 1086 const t = &f.tests[f.test_i]; 1087 const ref = &t.corpus.items(.ref)[@backingInt(i)]; 1088 const list_i = mem.findScalar(u32, ref.best_i_buf[0..ref.best_i_len], best_i).?; 1089 ref.best_i_len -= 1; 1090 ref.best_i_buf[list_i] = ref.best_i_buf[ref.best_i_len]; 1091 1092 if (ref.best_i_len == 0 and @backingInt(i) >= t.start_mut_corpus) { 1093 // The input is no longer valuable, so remove it. 1094 f.removeInput(i); 1095 } 1096 } 1097 1098 fn removeInput(f: *Fuzzer, i: Input.Index) void { 1099 const t = &f.tests[f.test_i]; 1100 const ref = &t.corpus.items(.ref)[@backingInt(i)]; 1101 assert(ref.best_i_len == 0 and @backingInt(i) >= t.start_mut_corpus); 1102 1103 var removed_input = t.corpus.get(@backingInt(i)); 1104 for ( 1105 removed_input.data.uid_slices.keys(), 1106 removed_input.data.uid_slices.values(), 1107 removed_input.seen_uid_i, 1108 ) |uid, slice, seen_uid_i| { 1109 switch (uid.kind) { 1110 .int => { 1111 const seen_ints = &t.seen_uids.values()[seen_uid_i].slices.ints; 1112 const removed_ints = removed_input.data.ints[slice.base..][0..slice.len]; 1113 _ = seen_ints.swapRemove(for (0.., seen_ints.items) |idx, ints| { 1114 if (removed_ints.ptr == ints.ptr) { 1115 assert(removed_ints.len == ints.len); 1116 break idx; 1117 } 1118 } else unreachable); 1119 }, 1120 .bytes => { 1121 const seen_bytes = &t.seen_uids.values()[seen_uid_i].slices.bytes; 1122 const removed_bytes: Input.Data.Bytes = .{ 1123 .entries = removed_input.data.bytes.entries[slice.base..][0..slice.len], 1124 .table = removed_input.data.bytes.table, 1125 }; 1126 _ = seen_bytes.swapRemove(for (0.., seen_bytes.items) |idx, bytes| { 1127 if (removed_bytes.entries.ptr == bytes.entries.ptr) { 1128 assert(removed_bytes.entries.len == bytes.entries.len); 1129 assert(removed_bytes.table.ptr == bytes.table.ptr); 1130 assert(removed_bytes.table.len == bytes.table.len); 1131 break idx; 1132 } 1133 } else unreachable); 1134 }, 1135 } 1136 } 1137 removed_input.deinit(); 1138 t.corpus.swapRemove(@backingInt(i)); 1139 1140 if (@backingInt(i) != t.corpus.len) { 1141 // The last item was moved so its refs need updated. 1142 // `ref` can be reused since it was a swap remove. 1143 for (ref.best_i_buf[0..ref.best_i_len]) |update_pc_i| { 1144 const best = &t.bests.input_buf[update_pc_i]; 1145 assert(@backingInt(best.min) == t.corpus.len or 1146 @backingInt(best.max) == t.corpus.len); 1147 1148 if (@backingInt(best.min) == t.corpus.len) best.min = i; 1149 if (@backingInt(best.max) == t.corpus.len) best.max = i; 1150 } 1151 } 1152 1153 if (!f.main_instance) return; 1154 1155 var removed_cname: CorpusFileName = .fromTest(t.dirname); 1156 // Temporarily use removed_name to construct the path to the lock 1157 const readlock_name = removed_cname.readLockName(); 1158 const readlock_file = exec.cache_f.createFile(io, readlock_name, .{ 1159 .truncate = false, 1160 .lock = .exclusive, 1161 }) catch |e| panic("failed to open '{s}': {t}", .{ readlock_name, e }); 1162 defer readlock_file.close(io); 1163 1164 const removed_name = removed_cname.inputName(@backingInt(i) - t.start_mut_corpus); 1165 if (@backingInt(i) == t.corpus.len) { 1166 exec.cache_f.deleteFile(io, removed_name) catch |e| panic( 1167 "failed to remove corpus file '{s}': {t}", 1168 .{ removed_name, e }, 1169 ); 1170 } else { 1171 var swapped_cname: CorpusFileName = .fromTest(t.dirname); 1172 const swapped_i: u32 = @intCast(t.corpus.len); 1173 const swapped_name = swapped_cname.inputName(swapped_i - t.start_mut_corpus); 1174 1175 exec.cache_f.rename(swapped_name, exec.cache_f, removed_name, io) catch |e| panic( 1176 "failed to rename corpus file '{s}' to '{s}': {t}", 1177 .{ swapped_name, removed_name, e }, 1178 ); 1179 } 1180 } 1181 1182 pub fn newInputExternal(f: *Fuzzer, bytes: []const u8) void { 1183 // All inputs including the corpus are required to go through the memory 1184 // mapped input in case they cause a crash so they can be identified. 1185 f.mmap_input.appendSlice(bytes); 1186 f.newInput(); 1187 f.mmap_input.clearRetainingCapacity(); 1188 } 1189 1190 fn newInput(f: *Fuzzer) void { 1191 const t = &f.tests[f.test_i]; 1192 const new_is_mut = t.start_mut_corpus != math.maxInt(u32); 1193 assert(new_is_mut == (t.corpus.len >= t.start_mut_corpus)); 1194 const bytes = f.mmap_input.inputSlice(); 1195 // `error.SkipZigTest` here can be from one of these causes: 1196 // * A previous corpus input after the test has changed 1197 // * An input provided by the test 1198 // * The test is non-deterministic 1199 if (f.runBytes(bytes, .bytes_fresh) and 1200 new_is_mut // The corpus must be mutable at this point for the input to be 1201 // omitted (i.e. test corpus inputs and filesystem inputs cannot be dropped) 1202 ) { 1203 f.input_builder.reset(); 1204 t.corpus_pos = @fromBackingInt(@intCast(0)); 1205 return; 1206 } 1207 1208 f.req_values = f.input_builder.total_ints + f.input_builder.total_bytes; 1209 f.req_bytes = @intCast(f.input_builder.bytes_table.items.len); 1210 const quality: Input.Best.Quality = .{ 1211 .n_pcs = n_pcs: { 1212 @setRuntimeSafety(builtin.mode == .debug); // Necessary for vectorization 1213 var n: u32 = 0; 1214 for (exec.pc_counters) |c| { 1215 n += @intFromBool(c != 0); 1216 } 1217 break :n_pcs n; 1218 }, 1219 .req = .{ 1220 .values = f.req_values, 1221 .bytes = f.req_bytes, 1222 }, 1223 }; 1224 1225 var best_i_list: std.ArrayList(u32) = .empty; 1226 for (0.., t.bests.quality_buf[0..t.bests.len]) |best_i, best| { 1227 if (exec.pc_counters[best.pc] == 0) continue; 1228 1229 const better_min = quality.betterLess(best.min); 1230 const better_max = quality.betterMore(best.max); 1231 if (!better_min and !better_max) { 1232 @branchHint(.likely); 1233 continue; 1234 } 1235 best_i_list.append(gpa, @intCast(best_i)) catch @panic("OOM"); 1236 1237 const map = &t.bests.input_buf[best_i]; 1238 if (map.min != map.max) { 1239 if (better_min) { 1240 f.removeBest(map.min, @intCast(best_i)); 1241 } 1242 if (better_max) { 1243 f.removeBest(map.max, @intCast(best_i)); 1244 } 1245 } else { 1246 if (better_min and better_max) { 1247 f.removeBest(map.min, @intCast(best_i)); 1248 } 1249 } 1250 } 1251 1252 // Must come after the above since some inputs may be removed 1253 const input_i: Input.Index = @fromBackingInt(@intCast(t.corpus.len)); 1254 if (input_i == Input.Index.reserved_start) { 1255 @panic("corpus size limit exceeded"); 1256 } 1257 1258 for (best_i_list.items) |i| { 1259 const best_qual = &t.bests.quality_buf[i]; 1260 const best_map = &t.bests.input_buf[i]; 1261 1262 if (quality.betterLess(best_qual.min)) { 1263 best_qual.min = quality; 1264 best_map.min = input_i; 1265 } 1266 if (quality.betterMore(best_qual.max)) { 1267 best_qual.max = quality; 1268 best_map.max = input_i; 1269 } 1270 } 1271 1272 for (0.., exec.pc_counters) |i, hits| { 1273 if (hits == 0) { 1274 @branchHint(.likely); 1275 continue; 1276 } 1277 1278 if ((t.seen_pcs[i / @bitSizeOf(usize)] >> @intCast(i % @bitSizeOf(usize))) & 1 == 0) { 1279 @branchHint(.unlikely); 1280 best_i_list.append(gpa, t.bests.len) catch @panic("OOM"); 1281 t.bests.quality_buf[t.bests.len] = .{ 1282 .pc = @intCast(i), 1283 .min = quality, 1284 .max = quality, 1285 }; 1286 t.bests.input_buf[t.bests.len] = .{ .min = input_i, .max = input_i }; 1287 t.bests.len += 1; 1288 } 1289 } 1290 1291 // Having no best qualities could be from one of these causes: 1292 // * A previous corpus input after the test has changed 1293 // * An input provided by the test 1294 // * The test is non-deterministic 1295 if (best_i_list.items.len == 0 and new_is_mut) { 1296 assert(best_i_list.capacity == 0); 1297 f.input_builder.reset(); 1298 t.corpus_pos = @fromBackingInt(@intCast(0)); 1299 return; 1300 } 1301 1302 var input = f.input_builder.build(); 1303 f.uid_data_i.ensureTotalCapacity(gpa, input.data.uid_slices.entries.len) catch @panic("OOM"); 1304 for ( 1305 input.seen_uid_i, 1306 input.data.uid_slices.keys(), 1307 input.data.uid_slices.values(), 1308 ) |*i, uid, slice| { 1309 const gop = t.seen_uids.getOrPutValue(gpa, uid, switch (uid.kind) { 1310 .int => .{ .slices = .{ .ints = .empty } }, 1311 .bytes => .{ .slices = .{ .bytes = .empty } }, 1312 }) catch @panic("OOM"); 1313 switch (uid.kind) { 1314 .int => t.seen_uids.values()[gop.index].slices.ints.append( 1315 gpa, 1316 input.data.ints[slice.base..][0..slice.len], 1317 ) catch @panic("OOM"), 1318 .bytes => t.seen_uids.values()[gop.index].slices.bytes.append(gpa, .{ 1319 .entries = input.data.bytes.entries[slice.base..][0..slice.len], 1320 .table = input.data.bytes.table, 1321 }) catch @panic("OOM"), 1322 } 1323 i.* = @intCast(gop.index); 1324 } 1325 1326 input.ref.best_i_buf = best_i_list.toOwnedSlice(gpa) catch @panic("OOM"); 1327 input.ref.best_i_len = @intCast(input.ref.best_i_buf.len); 1328 t.corpus.append(gpa, input) catch @panic("OOM"); 1329 t.corpus_pos = input_i; 1330 1331 // Must come after the above since `seen_pcs` is used 1332 f.updateSeenPcs(); 1333 1334 t.batches_since_find = 0; 1335 if (f.main_instance and new_is_mut) { 1336 // Only the main instance increments the number of unique runs since it is likely 1337 // multiple instances find the same new input at the same time. 1338 _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic); 1339 // Write new input to the cache 1340 var cname: CorpusFileName = .fromTest(t.dirname); 1341 const name = cname.inputName(@backingInt(input_i) - t.start_mut_corpus); 1342 exec.cache_f.writeFile(io, .{ .sub_path = name, .data = bytes, .flags = .{ 1343 .exclusive = true, 1344 } }) catch |e| panic("failed to write corpus file '{s}': {t}", .{ name, e }); 1345 } 1346 } 1347 1348 /// Returns if `error.SkipZigTest` was indicated 1349 fn run(f: *Fuzzer, input_uids: usize) bool { 1350 @memset(exec.pc_counters, 0); 1351 f.uid_data_i.items.len = input_uids; 1352 @memset(f.uid_data_i.items, 0); 1353 f.req_values = 0; 1354 f.req_bytes = 0; 1355 1356 const skip = f.test_one(); 1357 _ = @atomicRmw(usize, &exec.seenPcsHeader().n_runs, .Add, 1, .monotonic); 1358 return skip; 1359 } 1360 1361 /// Returns a number of mutations to perform from 1-4 1362 /// with smaller values exponentially more likely. 1363 pub fn mutCount(rng: u16) u8 { 1364 // The below provides the following distribution 1365 // @clz(@clz( range mapped percentage ratio 1366 // 0 -> 0 -> 4 1 = 93.750% (15 / 16 ) 1367 // 1 -> 1 - 255 -> 3 2 = 5.859% (15 / 256 ) 1368 // 2 -> 256 - 4095 -> 2 3 = .391% (<1 / 256 ) 1369 // 3 -> 4096 - 16383 -> 1 4 = .002% ( 1 / 65536) 1370 // 4 -> 16384 - 32767 -> 1 1371 // 5 -> 32768 - 65535 -> 1 1372 return @as(u8, 4) - @min(@clz(@clz(rng)), 3); 1373 } 1374 1375 pub fn cycle(f: *Fuzzer) void { 1376 assert(f.mmap_input.len == 0); 1377 1378 const t = &f.tests[f.test_i]; 1379 const corpus = t.corpus.slice(); 1380 const corpus_i = @backingInt(t.corpus_pos); 1381 1382 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) }; 1383 var n_mutate = mutCount(small_entronopy.take(u16)); 1384 const data = &corpus.items(.data)[corpus_i]; 1385 const weighted_uid_slice_i = corpus.items(.weighted_uid_slice_i)[corpus_i]; 1386 n_mutate *= @intFromBool(weighted_uid_slice_i.len != 0); // No static mutations on empty 1387 1388 f.mut_data = .{ 1389 .i = @splat(math.maxInt(u32)), 1390 .seq = @splat(.{ 1391 .kind = .{ 1392 .class = undefined, 1393 .copy = undefined, 1394 .ordered_mutate = undefined, 1395 .none = true, 1396 }, 1397 .len = undefined, 1398 .copy = undefined, 1399 }), 1400 }; 1401 1402 const uid_slices = data.uid_slices.entries.slice(); 1403 for ( 1404 f.mut_data.i[0..n_mutate], 1405 f.mut_data.seq[0..n_mutate], 1406 ) |*i, *s| if ((data.order.len < 2) | (small_entronopy.take(u3) != 0)) { 1407 // Mutation on uid 1408 const uid_slice_wi = f.rngLessThan(u32, @intCast(weighted_uid_slice_i.len)); 1409 const uid_slice_i = weighted_uid_slice_i[uid_slice_wi]; 1410 1411 const is_bytes = uid_slices.items(.key)[uid_slice_i].kind == .bytes; 1412 const data_slice = uid_slices.items(.value)[uid_slice_i]; 1413 i.* = @as(u32, @intCast(data.ints.len)) * @intFromBool(is_bytes) + 1414 data_slice.base + f.rngLessThan(u32, data_slice.len); 1415 } else { 1416 // Sequence mutation on order 1417 const order_len: u32 = @intCast(data.order.len); 1418 const order_i = f.rngLessThan(u32, order_len - 1); 1419 s.* = .{ 1420 .kind = .{ 1421 .class = .replace, 1422 .copy = true, 1423 .ordered_mutate = true, 1424 .none = false, 1425 }, 1426 .len = @min(@clz(f.rngInt(u16)) + 1, order_len - order_i), 1427 .copy = .{ .order_i = order_i }, 1428 }; 1429 i.* = data.order[order_i]; 1430 }; 1431 1432 const skip = f.run(data.uid_slices.entries.len); 1433 if (!skip and f.isFresh()) { 1434 @branchHint(.unlikely); 1435 1436 abi.runner_broadcast_input(f.test_i, .fromSlice(f.mmap_input.inputSlice())); 1437 f.newInput(); 1438 } else { 1439 assert(@backingInt(t.corpus_pos) < t.corpus.len); 1440 t.corpus_pos = @fromBackingInt(@intCast((@backingInt(t.corpus_pos) + 1) % t.corpus.len)); 1441 } 1442 f.mmap_input.clearRetainingCapacity(); 1443 } 1444 1445 fn takeReceived(f: *Fuzzer) void { 1446 const t = &f.tests[f.test_i]; 1447 if (t.received.state.startReadIfPending()) { 1448 defer t.received.state.finishRead(); 1449 const inputs = &t.received.inputs; 1450 var rem = inputs.items; 1451 1452 while (true) { 1453 const len: u32 = @bitCast(rem[0..4].*); 1454 rem = rem[4..]; 1455 const bytes = rem[0..len]; 1456 rem = rem[len..]; 1457 1458 f.mmap_input.appendSlice(bytes); 1459 f.newInput(); 1460 f.mmap_input.clearRetainingCapacity(); 1461 1462 if (rem.len == 0) break; 1463 } 1464 1465 inputs.clearRetainingCapacity(); 1466 } 1467 } 1468 1469 pub fn batch(f: *Fuzzer) void { 1470 const t = &f.tests[f.test_i]; 1471 assert(t.limit != 0); 1472 t.batches += 1; 1473 t.batches_since_find += 1; 1474 if (f.tests.len != 1) { 1475 // Use cpu_process since some fuzz tests may spawn 1476 // other threads and give all the work to them. 1477 const start: Io.Timestamp = .now(io, .cpu_process); 1478 var completed_cycles: u32 = 0; 1479 var total_cycles: u32 = t.batch_cycles; 1480 1481 while (true) { 1482 assert(completed_cycles != total_cycles); 1483 while (completed_cycles < total_cycles) { 1484 f.takeReceived(); 1485 f.cycle(); 1486 completed_cycles += 1; 1487 } 1488 1489 const duration = start.untilNow(io, .cpu_process); 1490 const ns = @min(@max(1, duration.nanoseconds), math.maxInt(u64)); 1491 const speed = @as(u64, t.batch_cycles) * std.time.ns_per_s / ns; 1492 // @min avoids large increases in batch_cycles due to just a few cycles running 1493 // fast. For example, if batch_cycles is only 2, and both run very fast due to 1494 // unlucky rng, this avoids a large runtime on the next batch. This also avoids 1495 // timer inprecision giving large values. 1496 t.batch_cycles = @max(1, @min(speed, t.batch_cycles *| 2)); 1497 1498 if (ns < std.time.ns_per_s * 7 / 8) { 1499 // Keep running the test to get closer to a second. This will almost always 1500 // be the case for the first batch as the default batch_cycles is 1. 1501 if (t.limit == total_cycles) break; 1502 1503 const rem_ns: u64 = @as(u32, std.time.ns_per_s) - ns; 1504 const extra: u32 = @intCast(rem_ns * t.batch_cycles / std.time.ns_per_s); 1505 if (extra == 0) break; // No better approximation of a second possible 1506 total_cycles += extra; 1507 if (t.limit) |limit| total_cycles = @min(total_cycles, limit); 1508 continue; 1509 } 1510 1511 break; 1512 } 1513 1514 assert(completed_cycles == total_cycles); 1515 if (t.limit) |prev| { 1516 t.limit = prev - total_cycles; 1517 t.batch_cycles = @min(t.batch_cycles, t.limit.?); 1518 } 1519 } else { 1520 while (true) { 1521 if (t.limit) |limit| { 1522 if (limit == 0) break; 1523 t.limit = limit - 1; 1524 } 1525 f.takeReceived(); 1526 f.cycle(); 1527 } 1528 } 1529 } 1530 1531 pub fn select(f: *Fuzzer) ?u32 { 1532 assert(f.tests.len > 1); // More efficiently handled by the callee 1533 1534 // The algorithm for selecting tests is such that: 1535 // - 1/4 are from the number of pcs as they give an indication of test complexity. 1536 // - 3/4 are from the recency of the last find as it gives an indication of the 1537 // effectiveness of fuzzing for the test. 1538 // - Tests finding fresh inputs are run 8x other tests. 1539 // - Since new tests are considered to have just found a fresh input, this means they 1540 // are also prioritized which allows their characteristics to be learnt. 1541 // When a test has a new input pending, it is treated as if it had just found a fresh 1542 // input instead of immediately being run. This avoids a test which is finding many new 1543 // inputs from being exclusively run. 1544 const new_batches = 16; 1545 1546 var n_with_new: u32 = 0; 1547 var n_seen_pcs: u64 = 0; 1548 var n_latest_find: u64 = 0; 1549 1550 for (f.tests) |*t| { 1551 const has_pending = t.received.state.hasPending(); 1552 if (has_pending) { 1553 assert(t.limit == null); // If multiprocess limited fuzzing was to be added, then 1554 // `t.received.inputs.clearRetainingCapacity()` would need to be added after 1555 // `t.received.state.startReadIfPending()` when the limit has been reached. 1556 } 1557 if (t.limit == 0) continue; 1558 1559 const latest_find = t.batches - t.batches_since_find; 1560 n_with_new += @intFromBool(t.batches_since_find < new_batches or has_pending); 1561 n_seen_pcs += @max(t.seen_pc_count, 1); 1562 n_latest_find += @max(latest_find, 1); 1563 } 1564 1565 if (n_seen_pcs == 0) { 1566 assert(n_with_new == 0); 1567 assert(n_latest_find == 0); 1568 return null; // All fuzz tests have used up their limit 1569 } 1570 1571 const rng: packed struct(u64) { 1572 idx_rng: u32, 1573 from_new: u3, 1574 from_latest_find: u2, 1575 _: u27, 1576 } = @bitCast(f.rngInt(u64)); 1577 1578 if (n_with_new != 0 and rng.from_new != 0) { 1579 var n = std.Random.limitRangeBiased(u32, rng.idx_rng, n_with_new); 1580 for (0.., f.tests) |i, *t| { 1581 if (t.limit == 0) continue; 1582 if (t.batches_since_find < new_batches or t.received.state.hasPending()) { 1583 if (n == 0) return @intCast(i); 1584 n -= 1; 1585 } 1586 } 1587 unreachable; 1588 } 1589 1590 if (rng.from_latest_find != 0) { 1591 const total_weight = n_latest_find; 1592 var n = f.rngLessThan(u64, total_weight); 1593 for (0.., f.tests) |i, *t| { 1594 if (t.limit == 0) continue; 1595 const latest_find = @max(t.batches - t.batches_since_find, 1); 1596 if (n < latest_find) return @intCast(i); 1597 n -= latest_find; 1598 } 1599 unreachable; 1600 } else { 1601 const total_weight = n_seen_pcs; 1602 var n = f.rngLessThan(u64, total_weight); 1603 for (0.., f.tests) |i, *t| { 1604 if (t.limit == 0) continue; 1605 const seen_pc_count = @max(t.seen_pc_count, 1); 1606 if (n < seen_pc_count) return @intCast(i); 1607 n -= seen_pc_count; 1608 } 1609 unreachable; 1610 } 1611 } 1612 1613 fn weightsContain(int: u64, weights: []const abi.Weight) bool { 1614 var contains: bool = false; 1615 for (weights) |w| { 1616 contains |= w.min <= int and int <= w.max; 1617 } 1618 return contains; 1619 } 1620 1621 fn weightsContainBytes(bytes: []const u8, weights: []const abi.Weight) bool { 1622 if (weights[0].min == 0 and weights[0].max == 0xff) { 1623 // Fast path: all bytes are valid 1624 return true; 1625 } 1626 1627 var contains: bool = true; 1628 for (bytes) |b| { 1629 contains &= weightsContain(b, weights); 1630 } 1631 return contains; 1632 } 1633 1634 fn sumWeightsInclusive(weights: []const abi.Weight) u64 { 1635 var sum: u64 = math.maxInt(u64); 1636 for (weights) |w| { 1637 sum +%= (w.max - w.min +% 1) *% w.weight; 1638 } 1639 return sum; 1640 } 1641 1642 fn weightedValue(f: *Fuzzer, weights: []const abi.Weight, incl_sum: u64) u64 { 1643 var incl_n: u64 = f.rngInt(u64); 1644 const limit = incl_sum +% 1; 1645 if (limit != 0) incl_n = std.Random.limitRangeBiased(u64, incl_n, limit); 1646 1647 for (weights) |w| { 1648 // (w.max - w.min + 1) * w.weight - 1 1649 const incl_vals = (w.max - w.min) * w.weight + (w.weight - 1); 1650 if (incl_n > incl_vals) { 1651 incl_n -= incl_vals + 1; 1652 } else { 1653 const val = w.min + incl_n / w.weight; 1654 assert(val <= w.max); 1655 return val; 1656 } 1657 } else unreachable; 1658 } 1659 1660 const Untyped = union { 1661 int: u64, 1662 bytes: []u8, 1663 }; 1664 1665 fn nextUntyped(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) union(enum) { 1666 copy: Untyped, 1667 mutate: Untyped, 1668 fresh: void, 1669 } { 1670 const t = &f.tests[f.test_i]; 1671 const corpus = t.corpus.slice(); 1672 const corpus_i = @backingInt(t.corpus_pos); 1673 const data = &corpus.items(.data)[corpus_i]; 1674 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) }; 1675 1676 const uid_i = data.uid_slices.getIndex(uid) orelse { 1677 @branchHint(.unlikely); 1678 return .fresh; 1679 }; 1680 const data_slice = data.uid_slices.values()[uid_i]; 1681 var slice_i = f.uid_data_i.items[uid_i]; 1682 var data_i = data_slice.base + slice_i; 1683 1684 new_data: while (true) { 1685 assert(slice_i == f.uid_data_i.items[uid_i] and data_i == data_slice.base + slice_i); 1686 if (slice_i == data_slice.len) break :new_data; 1687 assert(slice_i < data_slice.len); 1688 1689 f.uid_data_i.items[uid_i] += 1; 1690 const mut_i = std.simd.firstIndexOfValue( 1691 @as(@Vector(4, u32), f.mut_data.i), 1692 data_i + @as(u32, @intCast(data.ints.len)) * @backingInt(uid.kind), 1693 ) orelse { 1694 @branchHint(.likely); 1695 switch (uid.kind) { 1696 .int => { 1697 const int = data.ints[data_i]; 1698 if (weightsContain(int, weights)) { 1699 @branchHint(.likely); 1700 return .{ .copy = .{ .int = int } }; 1701 } 1702 }, 1703 .bytes => { 1704 const entry = data.bytes.entries[data_i]; 1705 const bytes = data.bytes.table[entry.off..][0..entry.len]; 1706 if (weightsContainBytes(bytes, weights)) { 1707 @branchHint(.likely); 1708 return .{ .copy = .{ .bytes = bytes } }; 1709 } 1710 }, 1711 } 1712 break :new_data; 1713 }; 1714 1715 const seq = &f.mut_data.seq[mut_i]; 1716 new_seq: { 1717 if (!seq.kind.none) break :new_seq; 1718 1719 var opts: packed struct(u6) { 1720 // Matches layout as `mut_data.seq.kind` 1721 insert: bool, 1722 copy: bool, 1723 1724 seq: u2, 1725 delete: bool, 1726 splice: bool, 1727 } = @bitCast(small_entronopy.take(u6)); 1728 if (opts.seq != 0) break :new_data; 1729 1730 const max_consume = data_slice.len - slice_i; // inclusive 1731 if (opts.delete) { 1732 f.uid_data_i.items[uid_i] += f.rngLessThan(u32, max_consume); 1733 slice_i = f.uid_data_i.items[uid_i]; 1734 data_i = data_slice.base + slice_i; 1735 continue; 1736 } 1737 opts.insert |= max_consume == 0; 1738 seq.kind = .{ 1739 .class = if (opts.insert) .replace else .insert, 1740 .copy = opts.copy, 1741 .ordered_mutate = false, 1742 .none = false, 1743 }; 1744 1745 if (!seq.kind.copy) { 1746 seq.len = switch (seq.kind.class) { 1747 .replace => f.rngLessThan(u32, max_consume) + 1, 1748 .insert => @clz(f.rngInt(u16)) + 1, 1749 }; 1750 seq.copy = undefined; 1751 } else { 1752 const src: SeqCopy, const src_len: u32 = if (!opts.splice) .{ 1753 switch (uid.kind) { 1754 .int => .{ .ints = data.ints[data_slice.base..][0..data_slice.len] }, 1755 .bytes => .{ .bytes = .{ 1756 .entries = data.bytes.entries[data_slice.base..][0..data_slice.len], 1757 .table = data.bytes.table, 1758 } }, 1759 }, 1760 data_slice.len, 1761 } else src: { 1762 const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i]; 1763 const untyped_slices = t.seen_uids.values()[seen_uid_i].slices; 1764 switch (uid.kind) { 1765 .int => { 1766 const slices = untyped_slices.ints.items; 1767 const i = f.rngLessThan(u32, @intCast(slices.len)); 1768 break :src .{ 1769 .{ .ints = slices[i] }, 1770 @intCast(slices[i].len), 1771 }; 1772 }, 1773 .bytes => { 1774 const slices = untyped_slices.bytes.items; 1775 const i = f.rngLessThan(u32, @intCast(slices.len)); 1776 break :src .{ 1777 .{ .bytes = slices[i] }, 1778 @intCast(slices[i].entries.len), 1779 }; 1780 }, 1781 } 1782 }; 1783 1784 const off = f.rngLessThan(u32, src_len); 1785 seq.len = f.rngLessThan(u32, src_len - off) + 1; 1786 if (seq.kind.class == .replace) seq.len = @min(seq.len, max_consume); 1787 seq.copy = switch (uid.kind) { 1788 .int => .{ .ints = src.ints[off..][0..seq.len] }, 1789 .bytes => .{ .bytes = .{ 1790 .entries = src.bytes.entries[off..][0..seq.len], 1791 .table = src.bytes.table, 1792 } }, 1793 }; 1794 } 1795 } 1796 1797 assert(!seq.kind.none); 1798 f.uid_data_i.items[uid_i] -= @intFromBool(seq.kind.class == .insert); 1799 seq.len -= 1; 1800 seq.kind.none |= seq.len == 0; 1801 f.mut_data.i[mut_i] += @intFromBool(seq.kind.class == .replace and seq.len != 0); 1802 1803 if (!seq.kind.copy) { 1804 assert(!seq.kind.ordered_mutate); 1805 break :new_data; 1806 } 1807 if (seq.kind.ordered_mutate) { 1808 assert(seq.kind.class == .replace); 1809 seq.copy.order_i += @intFromBool(seq.len != 0); 1810 f.mut_data.i[mut_i] = data.order[seq.copy.order_i]; 1811 break :new_data; 1812 } 1813 switch (uid.kind) { 1814 .int => { 1815 const int = seq.copy.ints[0]; 1816 seq.copy.ints = seq.copy.ints[1..]; 1817 if (weightsContain(int, weights)) { 1818 @branchHint(.likely); 1819 return .{ .copy = .{ .int = int } }; 1820 } 1821 }, 1822 .bytes => { 1823 const entry = seq.copy.bytes.entries[0]; 1824 const bytes = seq.copy.bytes.table[entry.off..][0..entry.len]; 1825 seq.copy.bytes.entries = seq.copy.bytes.entries[1..]; 1826 if (weightsContainBytes(bytes, weights)) { 1827 @branchHint(.likely); 1828 return .{ .copy = .{ .bytes = bytes } }; 1829 } 1830 }, 1831 } 1832 break; 1833 } 1834 1835 const opts: packed struct(u10) { 1836 copy: u2, 1837 fresh: u2, 1838 splice: bool, 1839 local_far: bool, 1840 local_off: i4, 1841 } = @bitCast(small_entronopy.take(u10)); 1842 1843 if (opts.copy != 0) { 1844 if (opts.fresh == 0 or slice_i == data_slice.len) return .fresh; 1845 switch (uid.kind) { 1846 .int => { 1847 const int = data.ints[data_i]; 1848 if (weightsContain(int, weights)) { 1849 @branchHint(.likely); 1850 return .{ .mutate = .{ .int = int } }; 1851 } 1852 }, 1853 .bytes => { 1854 const entry = data.bytes.entries[data_i]; 1855 const bytes = data.bytes.table[entry.off..][0..entry.len]; 1856 if (weightsContainBytes(bytes, weights)) { 1857 @branchHint(.likely); 1858 return .{ .mutate = .{ .bytes = bytes } }; 1859 } 1860 }, 1861 } 1862 } 1863 1864 if (!opts.splice) { 1865 const src_data_i = data_slice.base + if (!opts.local_far) i: { 1866 const off = opts.local_off; 1867 break :i if (off >= 0) @min( 1868 f.uid_data_i.items[uid_i] +| @as(u4, @intCast(off)), 1869 data_slice.len - 1, 1870 ) else f.uid_data_i.items[uid_i] -| @abs(off); 1871 } else f.rngLessThan(u32, data_slice.len); 1872 switch (uid.kind) { 1873 .int => { 1874 const int = data.ints[src_data_i]; 1875 if (weightsContain(int, weights)) { 1876 @branchHint(.likely); 1877 return .{ .copy = .{ .int = int } }; 1878 } 1879 }, 1880 .bytes => { 1881 const entry = data.bytes.entries[src_data_i]; 1882 const bytes = data.bytes.table[entry.off..][0..entry.len]; 1883 if (weightsContainBytes(bytes, weights)) { 1884 @branchHint(.likely); 1885 return .{ .copy = .{ .bytes = bytes } }; 1886 } 1887 }, 1888 } 1889 } else { 1890 const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i]; 1891 const untyped_slices = t.seen_uids.values()[seen_uid_i].slices; 1892 switch (uid.kind) { 1893 .int => { 1894 const slices = untyped_slices.ints.items; 1895 const from = slices[f.rngLessThan(u32, @intCast(slices.len))]; 1896 const int = from[f.rngLessThan(u32, @intCast(from.len))]; 1897 if (weightsContain(int, weights)) { 1898 @branchHint(.likely); 1899 return .{ .copy = .{ .int = int } }; 1900 } 1901 }, 1902 .bytes => { 1903 const slices = untyped_slices.bytes.items; 1904 const from = slices[f.rngLessThan(u32, @intCast(slices.len))]; 1905 const entry_i = f.rngLessThan(u32, @intCast(from.entries.len)); 1906 const entry = from.entries[entry_i]; 1907 const bytes = from.table[entry.off..][0..entry.len]; 1908 if (weightsContainBytes(bytes, weights)) { 1909 @branchHint(.likely); 1910 return .{ .copy = .{ .bytes = bytes } }; 1911 } 1912 }, 1913 } 1914 } 1915 return .fresh; 1916 } 1917 1918 pub fn nextInt(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) u64 { 1919 const t = &f.tests[f.test_i]; 1920 f.req_values += 1; 1921 if (@backingInt(t.corpus_pos) >= @backingInt(Input.Index.reserved_start)) { 1922 @branchHint(.unlikely); 1923 const int = f.bytes_input.valueWeightedWithHash(u64, weights, undefined); 1924 if (t.corpus_pos == .bytes_fresh) { 1925 f.input_builder.checkSmithedLen(8); 1926 f.input_builder.addInt(uid, int); 1927 } 1928 return int; 1929 } 1930 const int = f.nextIntInner(uid, weights); 1931 f.mmap_input.appendLittleInt(u64, int); 1932 return int; 1933 } 1934 1935 fn nextIntInner(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) u64 { 1936 return switch (f.nextUntyped(uid, weights)) { 1937 .copy => |u| u.int, 1938 .mutate, .fresh => f.weightedValue(weights, sumWeightsInclusive(weights)), 1939 }; 1940 } 1941 1942 pub fn nextEos(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) bool { 1943 const t = &f.tests[f.test_i]; 1944 f.req_values += 1; 1945 if (@backingInt(t.corpus_pos) >= @backingInt(Input.Index.reserved_start)) { 1946 @branchHint(.unlikely); 1947 const eos = f.bytes_input.eosWeightedWithHash(weights, undefined); 1948 if (t.corpus_pos == .bytes_fresh) { 1949 f.input_builder.checkSmithedLen(1); 1950 f.input_builder.addInt(uid, @intFromBool(eos)); 1951 } 1952 return eos; 1953 } 1954 // `nextIntInner` is already gauraunteed to eventually return `1` 1955 const eos = @as(u1, @intCast(f.nextIntInner(uid, weights))) != 0; 1956 f.mmap_input.appendLittleInt(u8, @intFromBool(eos)); 1957 return eos; 1958 } 1959 1960 fn mutateBytes(f: *Fuzzer, in: []u8, out: []u8, weights: []const abi.Weight) void { 1961 assert(in.len != 0); 1962 const weights_incl_sum = sumWeightsInclusive(weights); 1963 1964 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) }; 1965 var muts = mutCount(small_entronopy.take(u16)); 1966 var rem_out = out; 1967 var rem_copy = in; 1968 while (rem_out.len != 0 and muts != 0) { 1969 muts -= 1; 1970 const opts: packed struct(u4) { 1971 kind: enum(u2) { 1972 random, 1973 stream_copy, 1974 stream_discard, 1975 absolute_copy, 1976 }, 1977 small: u2, 1978 1979 pub fn limitSmall(o: @This(), n: usize) u32 { 1980 return @min( 1981 @as(u32, @intCast(n)), 1982 @as(u32, if (o.small != 0) 8 else math.maxInt(u32)), 1983 ); 1984 } 1985 } = @bitCast(small_entronopy.take(u4)); 1986 s: switch (opts.kind) { 1987 .random => { 1988 const n = f.rngLessThan(u32, opts.limitSmall(rem_out.len)) + 1; 1989 for (rem_out[0..n]) |*o| { 1990 o.* = @intCast(f.weightedValue(weights, weights_incl_sum)); 1991 } 1992 rem_out = rem_out[n..]; 1993 }, 1994 .stream_copy => { 1995 if (rem_copy.len == 0) continue :s .random; 1996 const n = @min( 1997 f.rngLessThan(u32, opts.limitSmall(rem_copy.len)) + 1, 1998 rem_out.len, 1999 ); 2000 @memcpy(rem_out[0..n], rem_copy[0..n]); 2001 rem_out = rem_out[n..]; 2002 rem_copy = rem_copy[n..]; 2003 }, 2004 .stream_discard => { 2005 if (rem_copy.len == 0) continue :s .random; 2006 const n = f.rngLessThan(u32, opts.limitSmall(rem_copy.len)) + 1; 2007 rem_copy = rem_copy[n..]; 2008 }, 2009 .absolute_copy => { 2010 const in_len: u32 = @intCast(in.len); 2011 const off = f.rngLessThan(u32, in_len); 2012 const len = @min( 2013 f.rngLessThan(u32, in_len - off) + 1, 2014 opts.limitSmall(rem_out.len), 2015 ); 2016 @memcpy(rem_out[0..len], in[off..][0..len]); 2017 rem_out = rem_out[len..]; 2018 }, 2019 } 2020 } 2021 2022 const copy = @min(rem_out.len, rem_copy.len); 2023 @memcpy(rem_out[0..copy], rem_copy[0..copy]); 2024 for (rem_out[copy..]) |*o| { 2025 o.* = @intCast(f.weightedValue(weights, weights_incl_sum)); 2026 } 2027 } 2028 2029 fn nextBytesInner(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void { 2030 so: switch (f.nextUntyped(uid, weights)) { 2031 .copy => |u| { 2032 if (u.bytes.len >= out.len) { 2033 @branchHint(.likely); 2034 @memcpy(out, u.bytes[0..out.len]); 2035 return; 2036 } 2037 2038 @memcpy(out[0..u.bytes.len], u.bytes); 2039 const weights_incl_sum = sumWeightsInclusive(weights); 2040 for (out[u.bytes.len..]) |*o| { 2041 o.* = @intCast(f.weightedValue(weights, weights_incl_sum)); 2042 } 2043 }, 2044 .mutate => |u| { 2045 if (u.bytes.len == 0) continue :so .fresh; 2046 f.mutateBytes(u.bytes, out, weights); 2047 }, 2048 .fresh => { 2049 const weights_incl_sum = sumWeightsInclusive(weights); 2050 for (out) |*o| { 2051 o.* = @intCast(f.weightedValue(weights, weights_incl_sum)); 2052 } 2053 }, 2054 } 2055 } 2056 2057 pub fn nextBytes(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void { 2058 const t = &f.tests[f.test_i]; 2059 f.req_values += 1; 2060 f.req_bytes +%= @truncate(out.len); // This function should panic since the 32-bit 2061 // data limit is exceeded, so wrapping is fine. 2062 if (@backingInt(t.corpus_pos) >= @backingInt(Input.Index.reserved_start)) { 2063 @branchHint(.unlikely); 2064 f.bytes_input.bytesWeightedWithHash(out, weights, undefined); 2065 if (t.corpus_pos == .bytes_fresh) { 2066 f.input_builder.checkSmithedLen(out.len); 2067 f.input_builder.addBytes(uid, out); 2068 } 2069 return; 2070 } 2071 2072 f.nextBytesInner(uid, out, weights); 2073 f.mmap_input.appendSlice(out); 2074 } 2075 2076 fn nextSliceInner( 2077 f: *Fuzzer, 2078 uid: Uid, 2079 buf: []u8, 2080 len_weights: []const abi.Weight, 2081 byte_weights: []const abi.Weight, 2082 ) u32 { 2083 so: switch (f.nextUntyped(uid, byte_weights)) { 2084 .copy => |u| { 2085 var len: u32 = @intCast(u.bytes.len); 2086 if (!weightsContain(len, len_weights)) { 2087 @branchHint(.unlikely); 2088 len = @intCast(f.weightedValue(len_weights, sumWeightsInclusive(len_weights))); 2089 } 2090 2091 if (u.bytes.len >= len) { 2092 @branchHint(.likely); 2093 @memcpy(buf[0..len], u.bytes[0..len]); 2094 return len; 2095 } 2096 2097 @memcpy(buf[0..u.bytes.len], u.bytes); 2098 const weights_incl_sum = sumWeightsInclusive(byte_weights); 2099 for (buf[u.bytes.len..len]) |*o| { 2100 o.* = @intCast(f.weightedValue(byte_weights, weights_incl_sum)); 2101 } 2102 return len; 2103 }, 2104 .mutate => |u| { 2105 if (u.bytes.len == 0) continue :so .fresh; 2106 const len: u32 = len: { 2107 const offseted: packed struct { 2108 is: u3, 2109 sub: bool, 2110 by: u3, 2111 } = @bitCast(f.rngInt(u7)); 2112 if (offseted.is != 0) { 2113 const len = if (offseted.sub) 2114 @as(u32, @intCast(u.bytes.len)) -| offseted.by 2115 else 2116 @min(u.bytes.len + offseted.by, @as(u32, @intCast(buf.len))); 2117 if (weightsContain(len, len_weights)) { 2118 break :len len; 2119 } 2120 } 2121 break :len @intCast(f.weightedValue( 2122 len_weights, 2123 sumWeightsInclusive(len_weights), 2124 )); 2125 }; 2126 f.mutateBytes(u.bytes, buf[0..len], byte_weights); 2127 return len; 2128 }, 2129 .fresh => { 2130 const len: u32 = @intCast(f.weightedValue( 2131 len_weights, 2132 sumWeightsInclusive(len_weights), 2133 )); 2134 const weights_incl_sum = sumWeightsInclusive(byte_weights); 2135 for (buf[0..len]) |*o| { 2136 o.* = @intCast(f.weightedValue(byte_weights, weights_incl_sum)); 2137 } 2138 return len; 2139 }, 2140 } 2141 } 2142 2143 pub fn nextSlice( 2144 f: *Fuzzer, 2145 uid: Uid, 2146 buf: []u8, 2147 len_weights: []const abi.Weight, 2148 byte_weights: []const abi.Weight, 2149 ) u32 { 2150 const t = &f.tests[f.test_i]; 2151 f.req_values += 1; 2152 if (@backingInt(t.corpus_pos) >= @backingInt(Input.Index.reserved_start)) { 2153 @branchHint(.unlikely); 2154 const n = f.bytes_input.sliceWeightedWithHash( 2155 buf, 2156 len_weights, 2157 byte_weights, 2158 undefined, 2159 ); 2160 if (t.corpus_pos == .bytes_fresh) { 2161 f.input_builder.checkSmithedLen(@as(usize, 4) + n); 2162 f.input_builder.addBytes(uid, buf[0..n]); 2163 } 2164 return n; 2165 } 2166 2167 const n = f.nextSliceInner(uid, buf, len_weights, byte_weights); 2168 f.mmap_input.appendLittleInt(u32, n); 2169 f.mmap_input.appendSlice(buf[0..n]); 2170 f.req_bytes += n; 2171 return n; 2172 } 2173 }; 2174 2175 export fn fuzzer_init(cache_dir_path: abi.Slice) void { 2176 exec = .init(cache_dir_path.toSlice()); 2177 } 2178 2179 export fn fuzzer_coverage() abi.Coverage { 2180 const coverage_id = exec.pc_digest; 2181 const header = @volatileCast(exec.seenPcsHeader()); 2182 2183 var seen_count: usize = 0; 2184 for (header.seenBits()) |chunk| { 2185 seen_count += @popCount(chunk); 2186 } 2187 2188 return .{ 2189 .id = coverage_id, 2190 .runs = header.n_runs, 2191 .unique = header.unique_runs, 2192 .seen = seen_count, 2193 }; 2194 } 2195 2196 export fn fuzzer_main( 2197 n_tests: u32, 2198 seed: u32, 2199 limit_kind: abi.LimitKind, 2200 amount_or_instance: u64, 2201 ) void { 2202 fuzzer = .init( 2203 n_tests, 2204 seed ^ amount_or_instance, // seed is otherwise the same for all instances 2205 if (limit_kind == .forever) @as(u32, @intCast(amount_or_instance)) else 0, 2206 if (limit_kind == .forever) null else amount_or_instance, 2207 ); 2208 defer fuzzer.deinit(); 2209 abi.runner_start_input_poller(); 2210 defer abi.runner_stop_input_poller(); 2211 2212 if (n_tests == 1) { 2213 // no swapping between fuzz tests 2214 runTest(0); 2215 } else { 2216 while (fuzzer.select()) |i| { 2217 runTest(i); 2218 } 2219 } 2220 } 2221 2222 export fn fuzzer_receive_input(test_i: u32, bytes_slice: abi.Slice) bool { 2223 const recv = &fuzzer.tests[test_i].received; 2224 if (recv.state.startWrite()) return true; 2225 defer recv.state.finishWrite(); 2226 2227 const bytes = bytes_slice.toSlice(); 2228 const len: u32 = @intCast(bytes.len); 2229 recv.inputs.ensureUnusedCapacity(gpa, 4 + bytes.len) catch @panic("OOM"); 2230 recv.inputs.appendSliceAssumeCapacity(@ptrCast(&len)); 2231 recv.inputs.appendSliceAssumeCapacity(bytes); 2232 2233 return false; 2234 } 2235 2236 fn runTest(i: u32) void { 2237 fuzzer.test_i = i; 2238 fuzzer.mmap_input.setTest(i); 2239 current_test_name = abi.runner_test_name(i).toSlice(); 2240 abi.runner_test_run(i); 2241 } 2242 2243 export fn fuzzer_set_test(test_one: abi.TestOne) void { 2244 fuzzer.test_one = test_one; 2245 } 2246 2247 export fn fuzzer_new_input(bytes: abi.Slice) void { 2248 if (bytes.len == 0) return; // An entry of length zero is always present 2249 if (fuzzer.tests[fuzzer.test_i].start_mut_corpus != math.maxInt(u32)) return; // Test ran previously 2250 fuzzer.newInputExternal(bytes.toSlice()); 2251 } 2252 2253 export fn fuzzer_start_test() void { 2254 fuzzer.ensureCorpusLoaded(); 2255 fuzzer.batch(); 2256 } 2257 2258 export fn fuzzer_int(uid: Uid, weights: abi.Weights) u64 { 2259 assert(uid.kind == .int); 2260 return fuzzer.nextInt(uid, weights.toSlice()); 2261 } 2262 2263 export fn fuzzer_eos(uid: Uid, weights: abi.Weights) bool { 2264 assert(uid.kind == .int); 2265 return fuzzer.nextEos(uid, weights.toSlice()); 2266 } 2267 2268 export fn fuzzer_bytes(uid: Uid, out: abi.MutSlice, weights: abi.Weights) void { 2269 assert(uid.kind == .bytes); 2270 return fuzzer.nextBytes(uid, out.toSlice(), weights.toSlice()); 2271 } 2272 2273 export fn fuzzer_slice( 2274 uid: Uid, 2275 buf: abi.MutSlice, 2276 len_weights: abi.Weights, 2277 byte_weights: abi.Weights, 2278 ) u32 { 2279 assert(uid.kind == .bytes); 2280 return fuzzer.nextSlice(uid, buf.toSlice(), len_weights.toSlice(), byte_weights.toSlice()); 2281 } 2282 2283 export fn fuzzer_unslide_address(addr: usize) usize { 2284 const si = std.debug.getSelfDebugInfo() catch @compileError("unsupported"); 2285 const slide = si.getModuleSlide(io, addr) catch |err| { 2286 // The LLVM backend seems to insert placeholder values of `1` in __sancov_pcs1 2287 if (addr == 1) return 1; 2288 panic("failed to find virtual address slide for address 0x{x}: {t}", .{ addr, err }); 2289 }; 2290 return addr - slide; 2291 } 2292 2293 /// Helps determine run uniqueness in the face of recursion. 2294 /// Currently not used by the fuzzer. 2295 export threadlocal var __sancov_lowest_stack: usize = 0; 2296 2297 export fn __sanitizer_cov_trace_pc_indir(callee: usize) void { 2298 // Not valuable because we already have pc tracing via 8bit counters. 2299 _ = callee; 2300 } 2301 export fn __sanitizer_cov_8bit_counters_init(start: usize, end: usize) void { 2302 // clang will emit a call to this function when compiling with code coverage instrumentation. 2303 // however, fuzzer_init() does not need this information since it directly reads from the 2304 // symbol table. 2305 _ = start; 2306 _ = end; 2307 } 2308 export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void { 2309 // clang will emit a call to this function when compiling with code coverage instrumentation. 2310 // however, fuzzer_init() does not need this information since it directly reads from the 2311 // symbol table. 2312 _ = start; 2313 _ = end; 2314 } 2315 2316 /// Reusable and recoverable input. 2317 /// 2318 /// Has a 32-bit limit on the input length. This has the nice side effect that `u32` 2319 /// can be used in most placed in `fuzzer` with the last `@sizeOf(abi.MmapInputHeader)` 2320 /// values reserved. 2321 const MemoryMappedInput = struct { 2322 const Header = abi.MmapInputHeader; 2323 2324 len: u32, 2325 /// Directly accessing `memory` is unsafe, use either `inputSlice` or `writeSlice`. 2326 mmap: Io.File.MemoryMap, 2327 in_i: u32, 2328 2329 /// `file` becomes owned by the returned `MemoryMappedInput` 2330 pub fn init(file: Io.File, instance_id: u32, in_i: u32) MemoryMappedInput { 2331 var size = file.length(io) catch |e| 2332 panic("failed to get length of 'in{x}': {t}", .{ in_i, e }); 2333 if (size < std.heap.page_size_max) { 2334 size = std.heap.page_size_max; 2335 file.setLength(io, size) catch |e| 2336 panic("failed to resize 'in{x}': {t}", .{ in_i, e }); 2337 } 2338 const map = file.createMemoryMap(io, .{ .len = size }) catch |e| 2339 panic("failed to memmap input file 'in{x}': {t}", .{ in_i, e }); 2340 @as(*volatile Header, @ptrCast(map.memory)).* = .{ 2341 .pc_digest = mem.nativeToLittle(u64, exec.pc_digest), 2342 .instance_id = mem.nativeToLittle(u32, instance_id), 2343 .test_i = 0, 2344 .len = 0, 2345 }; 2346 return .{ 2347 .len = 0, 2348 .mmap = map, 2349 .in_i = in_i, 2350 }; 2351 } 2352 2353 pub fn deinit(l: *MemoryMappedInput) void { 2354 const f = l.mmap.file; 2355 l.mmap.write(io) catch |e| panic("failed to write memory map of 'in{x}': {t}", .{ l.in_i, e }); 2356 l.mmap.destroy(io); 2357 f.close(io); 2358 l.* = undefined; 2359 } 2360 2361 /// Modify the array so that it can hold at least `additional_count` **more** items. 2362 /// 2363 /// Invalidates element pointers if additional memory is needed. 2364 pub fn ensureUnusedCapacity(l: *MemoryMappedInput, additional_count: usize) void { 2365 return l.ensureSize(@sizeOf(Header) + l.len + additional_count); 2366 } 2367 2368 fn ensureSize(l: *MemoryMappedInput, min_capacity: usize) void { 2369 if (l.mmap.memory.len < min_capacity) { 2370 @branchHint(.unlikely); 2371 2372 const max_capacity = 1 << 32; // The size of the header is not added 2373 // in order to keep the capacity page aligned and to allow those values to 2374 // reserved for other places. 2375 if (min_capacity > max_capacity) @panic("too much smith data requested"); 2376 2377 const new_capacity = @min(growCapacity(min_capacity), max_capacity); 2378 l.mmap.file.setLength(io, new_capacity) catch |e| 2379 panic("failed to resize 'in{x}': {t}", .{ l.in_i, e }); 2380 l.mmap.setLength(io, new_capacity) catch |se| switch (se) { 2381 error.OperationUnsupported => { 2382 const f = l.mmap.file; 2383 l.mmap.destroy(io); 2384 l.mmap = f.createMemoryMap(io, .{ .len = new_capacity }) catch |e| 2385 panic("failed to memory map 'in{x}': {t}", .{ l.in_i, e }); 2386 }, 2387 else => panic("failed to resize memory map of 'in{x}': {t}", .{ l.in_i, se }), 2388 }; 2389 } 2390 } 2391 2392 // Only writing has side effects, so volatile is not needed 2393 pub fn inputSlice(l: *MemoryMappedInput) []const u8 { 2394 return l.mmap.memory[@sizeOf(Header)..][0..l.len]; 2395 } 2396 2397 // Writing has side effectsd, so volatile is necessary 2398 pub fn writeSlice(l: *MemoryMappedInput) []volatile u8 { 2399 return l.mmap.memory; 2400 } 2401 2402 fn writeLen(l: *MemoryMappedInput) void { 2403 l.writeSlice()[@offsetOf(Header, "len")..][0..4].* = 2404 @bitCast(mem.nativeToLittle(u32, l.len)); 2405 } 2406 2407 pub fn setTest(l: *MemoryMappedInput, i: u32) void { 2408 l.writeSlice()[@offsetOf(Header, "test_i")..][0..4].* = 2409 @bitCast(mem.nativeToLittle(u32, i)); 2410 } 2411 2412 /// Invalidates all element pointers. 2413 pub fn clearRetainingCapacity(l: *MemoryMappedInput) void { 2414 l.len = 0; 2415 l.writeLen(); 2416 } 2417 2418 /// Append the slice of items to the list. 2419 /// 2420 /// Invalidates item pointers if more space is required. 2421 pub fn appendSlice(l: *MemoryMappedInput, items: []const u8) void { 2422 l.ensureUnusedCapacity(items.len); 2423 @memcpy(l.writeSlice()[@sizeOf(Header) + l.len ..][0..items.len], items); 2424 l.len += @as(u32, @intCast(items.len)); 2425 l.writeLen(); 2426 } 2427 2428 /// Append the little-endian integer to the list. 2429 /// 2430 /// Invalidates item pointers if more space is required. 2431 pub fn appendLittleInt(l: *MemoryMappedInput, T: type, x: T) void { 2432 l.ensureUnusedCapacity(@sizeOf(T)); 2433 l.writeSlice()[@sizeOf(Header) + l.len ..][0..@sizeOf(T)].* = 2434 @bitCast(mem.nativeToLittle(T, x)); 2435 l.len += @sizeOf(T); 2436 l.writeLen(); 2437 } 2438 2439 /// Called when memory growth is necessary. Returns a capacity larger than 2440 /// minimum that grows super-linearly. 2441 fn growCapacity(minimum: usize) usize { 2442 return mem.alignForward( 2443 usize, 2444 minimum +| (minimum / 2 + std.heap.page_size_max), 2445 std.heap.page_size_max, 2446 ); 2447 } 2448 }; 2449