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}