|
| 1 | +use std::ffi::OsStr; |
| 2 | +use std::path::Path; |
| 3 | + |
| 4 | +use crate::utils::run_command_with_output; |
| 5 | + |
| 6 | +fn show_usage() { |
| 7 | + println!( |
| 8 | + r#" |
| 9 | +`fuzz` command help: |
| 10 | + --help : Show this help"# |
| 11 | + ); |
| 12 | +} |
| 13 | + |
| 14 | +pub fn run() -> Result<(), String> { |
| 15 | + // We skip binary name and the `fuzz` command. |
| 16 | + let mut args = std::env::args().skip(2); |
| 17 | + let mut start = 0; |
| 18 | + let mut count = 100; |
| 19 | + let mut threads = |
| 20 | + std::thread::available_parallelism().map(|threads| threads.get()).unwrap_or(1); |
| 21 | + while let Some(arg) = args.next() { |
| 22 | + match arg.as_str() { |
| 23 | + "--help" => { |
| 24 | + show_usage(); |
| 25 | + return Ok(()); |
| 26 | + } |
| 27 | + "--start" => { |
| 28 | + start = |
| 29 | + str::parse(&args.next().ok_or_else(|| "Fuzz start not provided!".to_string())?) |
| 30 | + .map_err(|err| (format!("Fuzz start not a number {err:?}!")))?; |
| 31 | + } |
| 32 | + "--count" => { |
| 33 | + count = |
| 34 | + str::parse(&args.next().ok_or_else(|| "Fuzz count not provided!".to_string())?) |
| 35 | + .map_err(|err| (format!("Fuzz count not a number {err:?}!")))?; |
| 36 | + } |
| 37 | + "-j" | "--jobs" => { |
| 38 | + threads = str::parse( |
| 39 | + &args.next().ok_or_else(|| "Fuzz thread count not provided!".to_string())?, |
| 40 | + ) |
| 41 | + .map_err(|err| (format!("Fuzz thread count not a number {err:?}!")))?; |
| 42 | + } |
| 43 | + _ => return Err(format!("Unknown option {}", arg)), |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + // Ensure that we have a cloned version of rustlantis on hand. |
| 48 | + crate::utils::git_clone( |
| 49 | + "https://github.com/cbeuw/rustlantis.git", |
| 50 | + Some("clones/rustlantis".as_ref()), |
| 51 | + true, |
| 52 | + ) |
| 53 | + .map_err(|err| (format!("Git clone failed with message: {err:?}!")))?; |
| 54 | + |
| 55 | + // Ensure that we are on the newest rustlantis commit. |
| 56 | + let cmd: &[&dyn AsRef<OsStr>] = &[&"git", &"pull", &"origin"]; |
| 57 | + run_command_with_output(cmd, Some(&Path::new("clones/rustlantis")))?; |
| 58 | + |
| 59 | + // Build the release version of rustlantis |
| 60 | + let cmd: &[&dyn AsRef<OsStr>] = &[&"cargo", &"build", &"--release"]; |
| 61 | + run_command_with_output(cmd, Some(&Path::new("clones/rustlantis")))?; |
| 62 | + // Fuzz a given range |
| 63 | + fuzz_range(start, start + count, threads); |
| 64 | + Ok(()) |
| 65 | +} |
| 66 | + |
| 67 | +/// Fuzzes a range `start..end` with `threads`. |
| 68 | +fn fuzz_range(start: u64, end: u64, threads: usize) { |
| 69 | + use std::sync::Arc; |
| 70 | + use std::sync::atomic::{AtomicU64, Ordering}; |
| 71 | + use std::time::{Duration, Instant}; |
| 72 | + // Total amount of files to fuzz |
| 73 | + let total = end - start; |
| 74 | + // Currently fuzzed element |
| 75 | + let start = Arc::new(AtomicU64::new(start)); |
| 76 | + // Count time during fuzzing |
| 77 | + let start_time = Instant::now(); |
| 78 | + // Spawn `threads`.. |
| 79 | + for _ in 0..threads { |
| 80 | + let start = start.clone(); |
| 81 | + // .. which each will .. |
| 82 | + std::thread::spawn(move || { |
| 83 | + // ... grab the next fuzz seed ... |
| 84 | + while start.load(Ordering::Relaxed) < end { |
| 85 | + let next = start.fetch_add(1, Ordering::Relaxed); |
| 86 | + // .. test that seed . |
| 87 | + match test(next) { |
| 88 | + Err(err) => { |
| 89 | + // If the test failed at compile-time... |
| 90 | + println!("test({}) failed because {err:?}", next); |
| 91 | + // ... copy that file to the directory `target/fuzz/compiletime_error`... |
| 92 | + let mut out_path: std::path::PathBuf = |
| 93 | + "target/fuzz/compiletime_error".into(); |
| 94 | + std::fs::create_dir_all(&out_path).unwrap(); |
| 95 | + // .. into a file named `fuzz{seed}.rs`. |
| 96 | + out_path.push(&format!("fuzz{next}.rs")); |
| 97 | + std::fs::copy(err, out_path).unwrap(); |
| 98 | + } |
| 99 | + Ok(Err(err)) => { |
| 100 | + // If the test failed at run-time... |
| 101 | + println!("The LLVM and GCC results don't match for {err:?}"); |
| 102 | + // ... copy that file to the directory `target/fuzz/runtime_error`... |
| 103 | + let mut out_path: std::path::PathBuf = "target/fuzz/runtime_error".into(); |
| 104 | + std::fs::create_dir_all(&out_path).unwrap(); |
| 105 | + // .. into a file named `fuzz{seed}.rs`. |
| 106 | + out_path.push(&format!("fuzz{next}.rs")); |
| 107 | + std::fs::copy(err, out_path).unwrap(); |
| 108 | + } |
| 109 | + // If the test passed, do nothing |
| 110 | + Ok(Ok(())) => (), |
| 111 | + } |
| 112 | + } |
| 113 | + }); |
| 114 | + } |
| 115 | + // The "manager" thread loop. |
| 116 | + while start.load(Ordering::Relaxed) < end { |
| 117 | + // Every 500 ms... |
| 118 | + let five_hundred_millis = Duration::from_millis(500); |
| 119 | + std::thread::sleep(five_hundred_millis); |
| 120 | + // ... calculate the remaining fuzz iters ... |
| 121 | + let remaining = end - start.load(Ordering::Relaxed); |
| 122 | + // ... fix the count(the start counter counts the cases that |
| 123 | + // begun fuzzing, and not only the ones that are done)... |
| 124 | + let fuzzed = (total - remaining) - threads as u64; |
| 125 | + // ... and the fuzz speed ... |
| 126 | + let iter_per_sec = fuzzed as f64 / start_time.elapsed().as_secs_f64(); |
| 127 | + // .. and use them to display fuzzing stats. |
| 128 | + println!( |
| 129 | + "fuzzed {fuzzed} cases({}%), at rate {iter_per_sec} iter/s, remaining ~{}s", |
| 130 | + (100 * fuzzed) as f64 / total as f64, |
| 131 | + (remaining as f64) / iter_per_sec |
| 132 | + ) |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | +/// Builds & runs a file with LLVM. |
| 137 | +fn debug_llvm(path: &std::path::Path) -> Result<Vec<u8>, String> { |
| 138 | + // Build a file named `llvm_elf`... |
| 139 | + let exe_path = path.with_extension("llvm_elf"); |
| 140 | + // ... using the LLVM backend ... |
| 141 | + let output = std::process::Command::new("rustc") |
| 142 | + .arg(path) |
| 143 | + .arg("-o") |
| 144 | + .arg(&exe_path) |
| 145 | + .output() |
| 146 | + .map_err(|err| format!("{err:?}"))?; |
| 147 | + // ... check that the compilation succeeded ... |
| 148 | + if !output.status.success() { |
| 149 | + return Err(format!("LLVM compilation failed:{output:?}")); |
| 150 | + } |
| 151 | + // ... run the resulting executable ... |
| 152 | + let output = |
| 153 | + std::process::Command::new(&exe_path).output().map_err(|err| format!("{err:?}"))?; |
| 154 | + // ... check it run normally ... |
| 155 | + if !output.status.success() { |
| 156 | + return Err(format!( |
| 157 | + "The program at {path:?}, compiled with LLVM, exited unsuccessfully:{output:?}" |
| 158 | + )); |
| 159 | + } |
| 160 | + // ... cleanup that executable ... |
| 161 | + std::fs::remove_file(exe_path).map_err(|err| format!("{err:?}"))?; |
| 162 | + // ... and return the output(stdout + stderr - this allows UB checks to fire). |
| 163 | + let mut res = output.stdout; |
| 164 | + res.extend(output.stderr); |
| 165 | + Ok(res) |
| 166 | +} |
| 167 | + |
| 168 | +/// Builds & runs a file with GCC. |
| 169 | +fn release_gcc(path: &std::path::Path) -> Result<Vec<u8>, String> { |
| 170 | + // Build a file named `gcc_elf`... |
| 171 | + let exe_path = path.with_extension("gcc_elf"); |
| 172 | + // ... using the GCC backend ... |
| 173 | + let output = std::process::Command::new("./y.sh") |
| 174 | + .arg("rustc") |
| 175 | + .arg(path) |
| 176 | + .arg("-O") |
| 177 | + .arg("-o") |
| 178 | + .arg(&exe_path) |
| 179 | + .output() |
| 180 | + .map_err(|err| format!("{err:?}"))?; |
| 181 | + // ... check that the compilation succeeded ... |
| 182 | + if !output.status.success() { |
| 183 | + return Err(format!("GCC compilation failed:{output:?}")); |
| 184 | + } |
| 185 | + // ... run the resulting executable .. |
| 186 | + let output = |
| 187 | + std::process::Command::new(&exe_path).output().map_err(|err| format!("{err:?}"))?; |
| 188 | + // ... check it run normally ... |
| 189 | + if !output.status.success() { |
| 190 | + return Err(format!( |
| 191 | + "The program at {path:?}, compiled with GCC, exited unsuccessfully:{output:?}" |
| 192 | + )); |
| 193 | + } |
| 194 | + // ... cleanup that executable ... |
| 195 | + std::fs::remove_file(exe_path).map_err(|err| format!("{err:?}"))?; |
| 196 | + // ... and return the output(stdout + stderr - this allows UB checks to fire). |
| 197 | + let mut res = output.stdout; |
| 198 | + res.extend(output.stderr); |
| 199 | + Ok(res) |
| 200 | +} |
| 201 | + |
| 202 | +/// Generates a new rustlantis file, & compares the result of running it with GCC and LLVM. |
| 203 | +fn test(seed: u64) -> Result<Result<(), std::path::PathBuf>, String> { |
| 204 | + // Generate a Rust source... |
| 205 | + let source_file = generate(seed)?; |
| 206 | + // ... test it with debug LLVM ... |
| 207 | + let llvm_res = debug_llvm(&source_file)?; |
| 208 | + // ... test it with release GCC ... |
| 209 | + let gcc_res = release_gcc(&source_file)?; |
| 210 | + // ... compare the results ... |
| 211 | + if llvm_res != gcc_res { |
| 212 | + // .. if they don't match, report an error. |
| 213 | + Ok(Err(source_file)) |
| 214 | + } else { |
| 215 | + std::fs::remove_file(source_file).map_err(|err| format!("{err:?}"))?; |
| 216 | + Ok(Ok(())) |
| 217 | + } |
| 218 | +} |
| 219 | + |
| 220 | +/// Generates a new rustlantis file for us to run tests on. |
| 221 | +fn generate(seed: u64) -> Result<std::path::PathBuf, String> { |
| 222 | + use std::io::Write; |
| 223 | + let mut out_path = std::env::temp_dir(); |
| 224 | + out_path.push(&format!("fuzz{seed}.rs")); |
| 225 | + // We need to get the command output here. |
| 226 | + let out = std::process::Command::new("cargo") |
| 227 | + .args(["run", "--release", "--bin", "generate"]) |
| 228 | + .arg(&format!("{seed}")) |
| 229 | + .current_dir("clones/rustlantis") |
| 230 | + .output() |
| 231 | + .map_err(|err| format!("{err:?}"))?; |
| 232 | + // Stuff the rustlantis output in a source file. |
| 233 | + std::fs::File::create(&out_path) |
| 234 | + .map_err(|err| format!("{err:?}"))? |
| 235 | + .write_all(&out.stdout) |
| 236 | + .map_err(|err| format!("{err:?}"))?; |
| 237 | + Ok(out_path) |
| 238 | +} |
0 commit comments