mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-17 23:27:15 +00:00

zig build repro-canonicalize -- -b MApwPXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sw -v
129 lines
4.5 KiB
Zig
129 lines
4.5 KiB
Zig
//! Fuzzing for the Roc canonicalization phase
|
|
//!
|
|
//! This fuzzer tests the canonicalization phase of the Roc compiler, which transforms
|
|
//! the parsed AST into a canonical intermediate representation (CIR). The fuzzer is
|
|
//! designed to bias toward inputs that successfully parse and pass through canonicalization
|
|
//! without errors, as these provide better code coverage for finding edge cases.
|
|
//!
|
|
//! ## Building
|
|
//!
|
|
//! Note: Compiling the fuzz tests requires llvm and does not currently work in our nix shell on all systems.
|
|
//!
|
|
//! To build with fuzzing support:
|
|
//! zig build -Dfuzz
|
|
//!
|
|
//! To build just the repro executable (no AFL++ required):
|
|
//! zig build repro-canonicalize
|
|
//!
|
|
//! ## Running
|
|
//!
|
|
//! To run the fuzzer:
|
|
//! 1. zig build -Dfuzz
|
|
//! 2. zig build snapshot -- --fuzz-corpus /tmp/corpus # Optional: generate corpus from snapshots
|
|
//! 3. ./zig-out/AFLplusplus/bin/afl-fuzz -i src/fuzz-corpus/canonicalize -o /tmp/canonicalize-out/ zig-out/bin/fuzz-canonicalize
|
|
//!
|
|
//! To reproduce a crash:
|
|
//! ./zig-out/bin/repro-canonicalize /tmp/canonicalize-out/default/crashes/id:000000...
|
|
//! # Or with verbose output:
|
|
//! ./zig-out/bin/repro-canonicalize --verbose /tmp/canonicalize-out/default/crashes/id:000000...
|
|
//!
|
|
//! ## Corpus
|
|
//!
|
|
//! The initial corpus in src/fuzz-corpus/canonicalize/ contains simple valid Roc programs
|
|
//! that pass parsing and canonicalization. This helps the fuzzer start with good examples
|
|
//! that explore more of the canonicalization code paths rather than getting stuck on
|
|
//! parse errors.
|
|
//!
|
|
//! Other afl commands also available in `./zig-out/AFLplusplus/bin`
|
|
|
|
const std = @import("std");
|
|
const compile = @import("compile");
|
|
const reporting = @import("reporting");
|
|
|
|
const BuildEnv = compile.BuildEnv;
|
|
|
|
/// Hook for AFL++ to initialize the fuzz test environment.
|
|
pub export fn zig_fuzz_init() void {}
|
|
|
|
/// Hook for AFL++ to run the fuzz test.
|
|
pub export fn zig_fuzz_test(buf: [*]u8, len: isize) void {
|
|
zig_fuzz_test_inner(buf, len, false);
|
|
}
|
|
|
|
/// Inner implementation that can be called with debug flag for repro executable
|
|
pub fn zig_fuzz_test_inner(buf: [*]u8, len: isize, debug: bool) void {
|
|
// We reinitialize the gpa on every loop of the fuzzer.
|
|
// This enables the gpa to do leak checking on each iteration.
|
|
var gpa_impl = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer {
|
|
_ = gpa_impl.deinit();
|
|
}
|
|
const gpa = gpa_impl.allocator();
|
|
|
|
const input = buf[0..@intCast(len)];
|
|
|
|
if (debug) {
|
|
std.debug.print("Input:\n==========\n{s}\n==========\n\n", .{input});
|
|
}
|
|
|
|
// Write input to a temporary file
|
|
var tmp_dir = std.testing.tmpDir(.{});
|
|
defer tmp_dir.cleanup();
|
|
|
|
const tmp_file_path = "fuzz_input.roc";
|
|
tmp_dir.dir.writeFile(.{ .sub_path = tmp_file_path, .data = input }) catch return;
|
|
|
|
// Get absolute path
|
|
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
const abs_path = tmp_dir.dir.realpath(tmp_file_path, &path_buf) catch return;
|
|
|
|
// Process the input through BuildEnv
|
|
var build_env = BuildEnv.init(gpa, .single_threaded, 1);
|
|
defer build_env.deinit();
|
|
|
|
build_env.build(abs_path) catch |err| {
|
|
switch (err) {
|
|
error.OutOfMemory => @panic("OOM"),
|
|
error.NoSpaceLeft => @panic("No Space Left"),
|
|
error.TooNested => {
|
|
// This comes from the parser, so don't treat it as a canonicalize error
|
|
return;
|
|
},
|
|
else => {},
|
|
}
|
|
};
|
|
|
|
// Drain reports
|
|
const drained = build_env.drainReports() catch return;
|
|
defer {
|
|
for (drained) |mod| {
|
|
gpa.free(mod.abs_path);
|
|
for (mod.reports) |*report| {
|
|
var mut_report = report;
|
|
mut_report.deinit();
|
|
}
|
|
gpa.free(mod.reports);
|
|
}
|
|
gpa.free(drained);
|
|
}
|
|
|
|
if (debug) {
|
|
var total_reports: usize = 0;
|
|
for (drained) |mod| {
|
|
total_reports += mod.reports.len;
|
|
}
|
|
std.debug.print("Processing completed with {} reports\n", .{total_reports});
|
|
if (total_reports > 0) {
|
|
std.debug.print("Reports:\n", .{});
|
|
for (drained) |mod| {
|
|
for (mod.reports) |report| {
|
|
std.debug.print(" - {s}\n", .{report.title});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Success! The input passed through the full compiler pipeline
|
|
// The fuzzer will be biased toward inputs that reach this point
|
|
// since they provide more code coverage
|
|
}
|