use rustc_version::Channel;
use std::env;
use std::fs;
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
use std::process::{self, Command, Stdio};
const VERSION: &str = env!("CARGO_PKG_VERSION");
const HONGGFUZZ_TARGET: &str = "hfuzz_target";
const HONGGFUZZ_WORKSPACE: &str = "hfuzz_workspace";
#[cfg(target_family = "windows")]
compile_error!("honggfuzz-rs does not currently support Windows but works well under WSL (Windows Subsystem for Linux)");
#[derive(PartialEq)]
enum BuildType {
ReleaseInstrumented,
ReleaseNotInstrumented,
ProfileWithGrcov,
Debug,
}
fn target_triple() -> String {
let output = Command::new("rustc").args(&["-v", "-V"]).output().unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
let triple = stdout
.lines()
.filter(|l| l.starts_with("host: "))
.next()
.unwrap()
.get(6..)
.unwrap();
triple.into()
}
fn find_crate_root() -> Option<PathBuf> {
let mut path = env::current_dir().unwrap();
while !path.join("Cargo.toml").is_file() {
path = match path.parent() {
Some(parent) => parent.into(),
None => return None, };
}
Some(path)
}
fn debugger_command(target: &str) -> Command {
let debugger = env::var("HFUZZ_DEBUGGER").unwrap_or_else(|_| "rust-lldb".into());
let honggfuzz_target = env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| HONGGFUZZ_TARGET.into());
let mut cmd = Command::new(&debugger);
match Path::new(&debugger)
.file_name()
.map(|f| f.to_string_lossy().contains("lldb"))
{
Some(true) => {
cmd.args(&[
"-o",
"b rust_panic",
"-o",
"r",
"-o",
"bt",
"-f",
&format!("{}/{}/debug/{}", &honggfuzz_target, target_triple(), target),
"--",
]);
}
_ => {
cmd.args(&[
"-ex",
"b rust_panic",
"-ex",
"r",
"-ex",
"bt",
"--args",
&format!("{}/{}/debug/{}", &honggfuzz_target, target_triple(), target),
]);
}
};
cmd
}
fn hfuzz_version() {
println!("cargo-hfuzz {}", VERSION);
}
fn hfuzz_run<T>(mut args: T, crate_root: &Path, build_type: &BuildType)
where
T: std::iter::Iterator<Item = String>,
{
let target = args.next().unwrap_or_else(||{
eprintln!("please specify the name of the target like this \"cargo hfuzz run[-debug|-no-instr] TARGET [ ARGS ... ]\"");
process::exit(1);
});
let honggfuzz_target = env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| HONGGFUZZ_TARGET.into());
let honggfuzz_workspace =
env::var("HFUZZ_WORKSPACE").unwrap_or_else(|_| HONGGFUZZ_WORKSPACE.into());
let honggfuzz_input = env::var("HFUZZ_INPUT")
.unwrap_or_else(|_| format!("{}/{}/input", honggfuzz_workspace, target));
hfuzz_build(
vec!["--bin".to_string(), target.clone()].into_iter(),
crate_root,
build_type,
);
match *build_type {
BuildType::Debug => {
let crash_filename = args.next().unwrap_or_else(||{
eprintln!("please specify the crash filename like this \"cargo hfuzz run-debug TARGET CRASH_FILENAME [ ARGS ... ]\"");
process::exit(1);
});
let status = debugger_command(&target)
.args(args)
.env("CARGO_HONGGFUZZ_CRASH_FILENAME", crash_filename)
.env(
"RUST_BACKTRACE",
env::var("RUST_BACKTRACE").unwrap_or_else(|_| "1".into()),
)
.status()
.unwrap();
if !status.success() {
process::exit(status.code().unwrap_or(1));
}
}
_ => {
let asan_options = env::var("ASAN_OPTIONS").unwrap_or_default();
let asan_options = format!("detect_odr_violation=0:{}", asan_options);
let tsan_options = env::var("TSAN_OPTIONS").unwrap_or_default();
let tsan_options = format!("report_signal_unsafe=0:{}", tsan_options);
let hfuzz_run_args = env::var("HFUZZ_RUN_ARGS").unwrap_or_default();
let hfuzz_run_args = hfuzz_run_args.split_whitespace();
let hfuzz_build_args = env::var("HFUZZ_BUILD_ARGS").unwrap_or_default();
let hfuzz_build_args: Vec<_> = hfuzz_build_args.split_whitespace().collect();
let hfuzz_build_profile =
if let Some(arg) = hfuzz_build_args.iter().find(|f| &f[..9] == "--profile") {
arg.split("=")
.collect::<Vec<_>>()
.get(1)
.expect("--profile not in correct format (eg. --profile=<label>)")
} else {
"release"
};
fs::create_dir_all(&format!("{}/{}/input", &honggfuzz_workspace, target))
.unwrap_or_else(|_| {
println!(
"error: failed to create \"{}/{}/input\"",
&honggfuzz_workspace, target
);
});
let command = format!("{}/honggfuzz", &honggfuzz_target);
let err = Command::new(&command) .args(&[
"-W",
&format!("{}/{}", &honggfuzz_workspace, target),
"-f",
&honggfuzz_input,
"-P",
])
.args(hfuzz_run_args) .args(&[
"--",
&format!(
"{}/{}/{}/{}",
&honggfuzz_target,
target_triple(),
hfuzz_build_profile,
target
),
])
.args(args)
.env("ASAN_OPTIONS", asan_options)
.env("TSAN_OPTIONS", tsan_options)
.exec();
eprintln!("cannot execute {}, try to execute \"cargo hfuzz build\" from fuzzed project directory", &command);
eprintln!("{:?}", err);
process::exit(1);
}
}
}
fn hfuzz_build<T>(args: T, crate_root: &Path, build_type: &BuildType)
where
T: std::iter::Iterator<Item = String>,
{
let honggfuzz_target = env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| HONGGFUZZ_TARGET.into());
let use_gold_linker: bool = match rustc_version::version_meta() {
Ok(version_meta) => match version_meta.channel {
Channel::Nightly | Channel::Dev => {
version_meta
.commit_date
.map_or(false, |date| *date < *"2025-03-08")
}
Channel::Stable | Channel::Beta => {
version_meta.semver < semver::Version::new(1, 87, 0)
}
},
Err(_) => false,
} && match Command::new("which") .args(&["ld.gold"])
.stdout(Stdio::null())
.stderr(Stdio::null())
.stdin(Stdio::null())
.status()
{
Err(_) => false,
Ok(status) => match status.code() {
Some(0) => true,
_ => false,
},
};
let mut rustflags = String::new();
rustflags.push_str("--cfg fuzzing ");
rustflags.push_str("-C debug-assertions=y ");
rustflags.push_str("-C overflow-checks=y ");
rustflags.push_str("-C force-frame-pointers=y ");
let mut cargo_incremental = "1";
match *build_type {
BuildType::Debug => {
rustflags.push_str("--cfg fuzzing_debug ");
rustflags.push_str("-C opt-level=0 ");
rustflags.push_str("-C debuginfo=2 ");
}
BuildType::ProfileWithGrcov => {
rustflags.push_str("--cfg fuzzing_debug ");
rustflags.push_str("-Zprofile ");
rustflags.push_str("-C panic=abort ");
rustflags.push_str("-C opt-level=0 ");
rustflags.push_str("-C debuginfo=2 ");
rustflags.push_str("-C codegen-units=1 ");
rustflags.push_str("-C link-dead-code ");
cargo_incremental = "0";
}
_ => {
rustflags.push_str("-C opt-level=3 ");
rustflags.push_str("-C target-cpu=native ");
rustflags.push_str("-C debuginfo=0 ");
if *build_type == BuildType::ReleaseInstrumented {
let version_meta = rustc_version::version_meta().unwrap();
if version_meta.llvm_version.map_or(true, |v| v.major >= 13) {
rustflags.push_str("-C passes=sancov-module ");
} else {
rustflags.push_str("-C passes=sancov ");
};
rustflags.push_str("-C llvm-args=-sanitizer-coverage-level=4 "); rustflags.push_str("-C llvm-args=-sanitizer-coverage-trace-pc-guard ");
rustflags.push_str("-C llvm-args=-sanitizer-coverage-trace-divs ");
rustflags.push_str("-C llvm-args=-sanitizer-coverage-trace-geps ");
rustflags.push_str("-C llvm-args=-sanitizer-coverage-stack-depth ");
if cfg!(not(target_os = "macos")) {
rustflags.push_str("-C llvm-args=-sanitizer-coverage-trace-compares ");
}
if use_gold_linker {
rustflags.push_str("-Clink-arg=-fuse-ld=gold ");
}
}
}
}
rustflags.push_str(&env::var("RUSTFLAGS").unwrap_or_default());
let hfuzz_build_args = env::var("HFUZZ_BUILD_ARGS").unwrap_or_default();
let mut hfuzz_build_args = hfuzz_build_args.split_whitespace();
let cargo_bin = env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let mut command = Command::new(cargo_bin);
command
.args(&["build", "--target", &target_triple()]) .args(args)
.args(hfuzz_build_args.clone()) .env("RUSTFLAGS", rustflags)
.env("CARGO_INCREMENTAL", cargo_incremental)
.env("CARGO_TARGET_DIR", &honggfuzz_target) .env("CRATE_ROOT", &crate_root);
if *build_type == BuildType::ProfileWithGrcov {
command
.env("CARGO_HONGGFUZZ_BUILD_VERSION", VERSION) .env("CARGO_HONGGFUZZ_TARGET_DIR", &honggfuzz_target); }
else if *build_type != BuildType::Debug {
if !hfuzz_build_args.any(|f| f.starts_with("--profile")) {
command.arg("--release");
}
command
.env("CARGO_HONGGFUZZ_BUILD_VERSION", VERSION) .env("CARGO_HONGGFUZZ_TARGET_DIR", &honggfuzz_target); }
let status = command.status().unwrap();
if !status.success() {
process::exit(status.code().unwrap_or(1));
}
}
fn hfuzz_clean<T>(args: T)
where
T: std::iter::Iterator<Item = String>,
{
let honggfuzz_target = env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| HONGGFUZZ_TARGET.into());
let cargo_bin = env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let status = Command::new(cargo_bin)
.args(&["clean"])
.args(args)
.env("CARGO_TARGET_DIR", &honggfuzz_target) .status()
.unwrap();
if !status.success() {
process::exit(status.code().unwrap_or(1));
}
}
fn main() {
let mut args = env::args().skip(1);
if args.next() != Some("hfuzz".to_string()) {
eprintln!("please launch as a cargo subcommand: \"cargo hfuzz ...\"");
process::exit(1);
}
let crate_root = find_crate_root().unwrap_or_else(|| {
eprintln!(
"error: could not find `Cargo.toml` in current directory or any parent directory"
);
process::exit(1);
});
env::set_current_dir(&crate_root).unwrap();
match args.next() {
Some(ref s) if s == "build" => {
hfuzz_build(args, &crate_root, &BuildType::ReleaseInstrumented);
}
Some(ref s) if s == "build-no-instr" => {
hfuzz_build(args, &crate_root, &BuildType::ReleaseNotInstrumented);
}
Some(ref s) if s == "build-debug" => {
hfuzz_build(args, &crate_root, &BuildType::Debug);
}
Some(ref s) if s == "build-grcov" => {
hfuzz_build(args, &crate_root, &BuildType::ProfileWithGrcov);
}
Some(ref s) if s == "run" => {
hfuzz_run(args, &crate_root, &BuildType::ReleaseInstrumented);
}
Some(ref s) if s == "run-no-instr" => {
hfuzz_run(args, &crate_root, &BuildType::ReleaseNotInstrumented);
}
Some(ref s) if s == "run-debug" => {
hfuzz_run(args, &crate_root, &BuildType::Debug);
}
Some(ref s) if s == "clean" => {
hfuzz_clean(args);
}
Some(ref s) if s == "version" => {
hfuzz_version();
}
_ => {
eprintln!("possible commands are: run, run-no-instr, run-debug, build, build-no-instr, build-grcov, build-debug, clean, version");
process::exit(1);
}
}
}