cargo_fuzzcheck/
lib.rs

1#![allow(clippy::collapsible_if)]
2#![allow(clippy::too_many_arguments)]
3#![allow(clippy::format_push_string)]
4
5use std::cmp::Ordering;
6use std::path::{Path, PathBuf};
7use std::process;
8use std::process::{Command, Stdio};
9
10use fuzzcheck_common::arg::*;
11const TARGET: &str = env!("TARGET");
12const BUILD_FOLDER: &str = "target/fuzzcheck";
13
14pub enum CompiledTarget {
15    Lib,
16    Bin(String),
17    Test(String),
18}
19impl CompiledTarget {
20    fn to_args(&self) -> Vec<String> {
21        match self {
22            CompiledTarget::Lib => vec!["--lib".to_owned()],
23            CompiledTarget::Bin(name) => vec!["--bin".to_owned(), name.clone()],
24            CompiledTarget::Test(name) => vec!["--test".to_owned(), name.clone()],
25        }
26    }
27}
28
29pub fn launch_executable(
30    target_name: &str,
31    args: &Arguments,
32    compiled_target: &CompiledTarget,
33    cargo_args: &[String],
34    address_sanitizer: bool,
35    profile: &str,
36    instrument_coverage: bool,
37    stdio: impl Fn() -> Stdio,
38) -> std::io::Result<process::Child> {
39    let args = string_from_args(args);
40    let mut rustflags = std::env::var("RUSTFLAGS").unwrap_or_else(|_| "".to_owned());
41    if instrument_coverage {
42        rustflags.push_str(" -C instrument-coverage");
43    }
44    rustflags.push_str(" --cfg fuzzing");
45
46    if address_sanitizer {
47        rustflags.push_str(" -Zsanitizer=address");
48    }
49    let child = Command::new("cargo")
50        .env("FUZZCHECK_ARGS", args)
51        .env("RUSTFLAGS", &rustflags)
52        .arg("test")
53        .args(compiled_target.to_args())
54        .args(cargo_args)
55        .args(["--target", TARGET])
56        .arg("--profile")
57        .arg(profile)
58        .args(["--target-dir", BUILD_FOLDER])
59        .arg("--")
60        .arg("--nocapture")
61        .arg("--exact")
62        .arg(target_name)
63        .args(["--test-threads", "1"])
64        .stdout(stdio())
65        .stderr(stdio())
66        .spawn()?;
67
68    Ok(child)
69}
70
71pub fn input_minify_command(
72    target_name: &str,
73    args: &Arguments,
74    compiled_target: &CompiledTarget,
75    cargo_args: &[String],
76    address_sanitizer: bool,
77    profile: &str,
78    instrument_coverage: bool,
79    stdio: &impl Fn() -> Stdio,
80) -> std::io::Result<()> {
81    let mut config = args.clone();
82    let file_to_minify = if let FuzzerCommand::MinifyInput { input_file } = config.command {
83        input_file
84    } else {
85        panic!()
86    };
87
88    let artifacts_folder = {
89        let mut x = file_to_minify.parent().unwrap().to_path_buf();
90        x.push(file_to_minify.file_stem().unwrap());
91        x = x.with_extension("minified");
92        x
93    };
94
95    let _ = std::fs::create_dir(&artifacts_folder);
96    config.artifacts_folder = Some(artifacts_folder.clone());
97    config.stop_after_first_failure = true;
98
99    fn simplest_input_file(folder: &Path) -> Option<PathBuf> {
100        let files_with_complexity = std::fs::read_dir(folder)
101            .ok()?
102            .filter_map(|path| -> Option<(PathBuf, f64)> {
103                let path = path.ok()?.path();
104                let name_components: Vec<&str> = path.file_stem()?.to_str()?.splitn(2, "--").collect();
105                if name_components.len() == 2 {
106                    let cplx = name_components[0].parse::<f64>().ok()?;
107                    Some((path.to_path_buf(), cplx))
108                } else {
109                    None
110                }
111            });
112
113        files_with_complexity
114            .min_by(|x, y| std::cmp::PartialOrd::partial_cmp(&x.1, &y.1).unwrap_or(Ordering::Equal))
115            .map(|x| x.0)
116    }
117
118    let mut simplest = simplest_input_file(artifacts_folder.as_path()).unwrap_or(file_to_minify);
119    config.command = FuzzerCommand::Read {
120        input_file: simplest.clone(),
121    };
122
123    println!("launch with config: {:?}", string_from_args(&config));
124
125    let child = launch_executable(
126        target_name,
127        &config,
128        compiled_target,
129        cargo_args,
130        address_sanitizer,
131        profile,
132        instrument_coverage,
133        stdio,
134    )?;
135    let o = child.wait_with_output()?;
136
137    assert!(
138        !o.status.success(),
139        "The test case in the given input file didn't appear to trigger a test failure."
140    );
141
142    loop {
143        simplest = simplest_input_file(&artifacts_folder).unwrap_or_else(|| simplest.clone());
144        config.command = FuzzerCommand::MinifyInput {
145            input_file: simplest.clone(),
146        };
147        println!("launch with config: {:?}", string_from_args(&config));
148        let mut c = launch_executable(
149            target_name,
150            &config,
151            compiled_target,
152            cargo_args,
153            address_sanitizer,
154            profile,
155            instrument_coverage,
156            Stdio::inherit,
157        )?;
158        c.wait()?;
159    }
160}
161
162pub fn string_from_args(args: &Arguments) -> String {
163    let mut s = String::new();
164
165    let input_file = match &args.command {
166        FuzzerCommand::Fuzz => {
167            s.push_str("--command ");
168            s.push_str(COMMAND_FUZZ);
169            s.push(' ');
170            None
171        }
172        FuzzerCommand::Read { input_file } => {
173            s.push_str("--command ");
174            s.push_str(COMMAND_READ);
175            s.push(' ');
176            Some(input_file.clone())
177        }
178        FuzzerCommand::MinifyInput { input_file } => {
179            s.push_str("--command ");
180            s.push_str(COMMAND_MINIFY_INPUT);
181            s.push(' ');
182            Some(input_file.clone())
183        }
184    };
185    if let Some(input_file) = input_file {
186        s.push_str(&format!("--{} {} ", INPUT_FILE_FLAG, input_file.display()));
187    }
188
189    let corpus_in_args = args
190        .corpus_in
191        .as_ref()
192        .map(|f| format!("--{} {} ", IN_CORPUS_FLAG, f.display()))
193        .unwrap_or_else(|| format!("--{} ", NO_IN_CORPUS_FLAG));
194
195    s.push_str(&corpus_in_args);
196    s.push(' ');
197
198    let corpus_out_args = args
199        .corpus_out
200        .as_ref()
201        .map(|f| format!("--{} {} ", OUT_CORPUS_FLAG, f.display()))
202        .unwrap_or_else(|| format!("--{} ", NO_OUT_CORPUS_FLAG));
203
204    s.push_str(&corpus_out_args);
205    s.push(' ');
206
207    let artifacts_args = args
208        .artifacts_folder
209        .as_ref()
210        .map(|f| format!("--{} {} ", ARTIFACTS_FLAG, f.display()))
211        .unwrap_or_else(|| format!("--{} ", NO_ARTIFACTS_FLAG));
212    s.push_str(&artifacts_args);
213    s.push(' ');
214
215    let stats_args = args
216        .stats_folder
217        .as_ref()
218        .map(|f| format!("--{} {} ", STATS_FLAG, f.display()))
219        .unwrap_or_else(|| format!("--{} ", NO_STATS_FLAG));
220    s.push_str(&stats_args);
221    s.push(' ');
222
223    s.push_str(&format!("--{} {} ", MAX_INPUT_CPLX_FLAG, args.max_input_cplx as usize));
224    s.push_str(&format!("--{} {} ", MAX_DURATION_FLAG, args.maximum_duration.as_secs()));
225    s.push_str(&format!("--{} {} ", MAX_ITERATIONS_FLAG, args.maximum_iterations));
226    if args.stop_after_first_failure {
227        s.push_str(&format!("--{} ", STOP_AFTER_FIRST_FAILURE_FLAG));
228    }
229    if args.detect_infinite_loop {
230        s.push_str(&format!("--{} ", DETECT_INFINITE_LOOP_FLAG));
231    }
232    s
233}