|
| 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 | + // Ensure that we have a cloned version of rustlantis on hand. |
| 47 | + if !std::fs::exists("rustlantis").unwrap_or_default() { |
| 48 | + let cmd: &[&dyn AsRef<OsStr>] = |
| 49 | + &[&"git", &"clone", &"https://github.com/cbeuw/rustlantis.git"]; |
| 50 | + run_command_with_output(cmd, Some(&Path::new(".")))?; |
| 51 | + } |
| 52 | + // Ensure that we are on the newest rustlantis commit. |
| 53 | + let cmd: &[&dyn AsRef<OsStr>] = &[&"git", &"pull", &"origin"]; |
| 54 | + run_command_with_output(cmd, Some(&Path::new("rustlantis")))?; |
| 55 | + // Build the release version of rustlantis |
| 56 | + let cmd: &[&dyn AsRef<OsStr>] = &[&"cargo", &"build", &"--release"]; |
| 57 | + run_command_with_output(cmd, Some(&Path::new("rustlantis")))?; |
| 58 | + fuzz_range(start, start + count, threads); |
| 59 | + Ok(()) |
| 60 | +} |
| 61 | +fn fuzz_range(start: u64, end: u64, threads: usize) { |
| 62 | + use std::sync::Arc; |
| 63 | + use std::sync::atomic::{AtomicU64, Ordering}; |
| 64 | + use std::time::{Duration, Instant}; |
| 65 | + |
| 66 | + let total = end - start; |
| 67 | + let start = Arc::new(AtomicU64::new(start)); |
| 68 | + let start_time = Instant::now(); |
| 69 | + for _ in 0..threads { |
| 70 | + let start = start.clone(); |
| 71 | + std::thread::spawn(move || { |
| 72 | + while start.load(Ordering::Relaxed) < end { |
| 73 | + let next = start.fetch_add(1, Ordering::Relaxed); |
| 74 | + |
| 75 | + match test(next) { |
| 76 | + Err(err) => { |
| 77 | + println!("test({}) failed because {err:?}", next); |
| 78 | + let mut out_path: std::path::PathBuf = |
| 79 | + "target/fuzz/compiletime_error".into(); |
| 80 | + std::fs::create_dir_all(&out_path).unwrap(); |
| 81 | + out_path.push(&format!("fuzz{next}.rs")); |
| 82 | + std::fs::copy(err, out_path).unwrap(); |
| 83 | + } |
| 84 | + Ok(Err(err)) => { |
| 85 | + println!("The LLVM and GCC results don't match for {err:?}"); |
| 86 | + let mut out_path: std::path::PathBuf = "target/fuzz/runtime_error".into(); |
| 87 | + std::fs::create_dir_all(&out_path).unwrap(); |
| 88 | + out_path.push(&format!("fuzz{next}.rs")); |
| 89 | + std::fs::copy(err, out_path).unwrap(); |
| 90 | + } |
| 91 | + Ok(Ok(())) => (), |
| 92 | + } |
| 93 | + } |
| 94 | + }); |
| 95 | + } |
| 96 | + while start.load(Ordering::Relaxed) < end { |
| 97 | + let hundred_millis = Duration::from_millis(500); |
| 98 | + std::thread::sleep(hundred_millis); |
| 99 | + let remaining = end - start.load(Ordering::Relaxed); |
| 100 | + let fuzzed = total - remaining; |
| 101 | + let iter_per_sec = fuzzed as f64 / start_time.elapsed().as_secs_f64(); |
| 102 | + println!( |
| 103 | + "fuzzed {fuzzed} cases({}%), at rate {iter_per_sec} iter/s, remaining ~{}s", |
| 104 | + (100 * fuzzed) as f64 / total as f64, |
| 105 | + (remaining as f64) / iter_per_sec |
| 106 | + ) |
| 107 | + } |
| 108 | +} |
| 109 | +/// Builds & runs a file with LLVM. |
| 110 | +fn debug_llvm(path: &std::path::Path) -> Result<Vec<u8>, String> { |
| 111 | + let exe_path = path.with_extension("llvm_elf"); |
| 112 | + let output = std::process::Command::new("rustc") |
| 113 | + .arg(path) |
| 114 | + .arg("-o") |
| 115 | + .arg(&exe_path) |
| 116 | + .output() |
| 117 | + .map_err(|err| format!("{err:?}"))?; |
| 118 | + if !output.status.success() { |
| 119 | + return Err(format!("LLVM compilation failed:{output:?}")); |
| 120 | + } |
| 121 | + let output = |
| 122 | + std::process::Command::new(&exe_path).output().map_err(|err| format!("{err:?}"))?; |
| 123 | + if !output.status.success() { |
| 124 | + return Err(format!( |
| 125 | + "The program at {path:?}, compiled with LLVM, exited unsuccessfully:{output:?}" |
| 126 | + )); |
| 127 | + } |
| 128 | + std::fs::remove_file(exe_path).map_err(|err| format!("{err:?}"))?; |
| 129 | + Ok(output.stdout) |
| 130 | +} |
| 131 | + |
| 132 | +/// Builds & runs a file with GCC. |
| 133 | +fn release_gcc(path: &std::path::Path) -> Result<Vec<u8>, String> { |
| 134 | + let exe_path = path.with_extension("gcc_elf"); |
| 135 | + let output = std::process::Command::new("./y.sh") |
| 136 | + .arg("rustc") |
| 137 | + .arg(path) |
| 138 | + .arg("-O") |
| 139 | + .arg("-o") |
| 140 | + .arg(&exe_path) |
| 141 | + .output() |
| 142 | + .map_err(|err| format!("{err:?}"))?; |
| 143 | + if !output.status.success() { |
| 144 | + return Err(format!("GCC compilation failed:{output:?}")); |
| 145 | + } |
| 146 | + let output = |
| 147 | + std::process::Command::new(&exe_path).output().map_err(|err| format!("{err:?}"))?; |
| 148 | + if !output.status.success() { |
| 149 | + return Err(format!( |
| 150 | + "The program at {path:?}, compiled with GCC, exited unsuccessfully:{output:?}" |
| 151 | + )); |
| 152 | + } |
| 153 | + std::fs::remove_file(exe_path).map_err(|err| format!("{err:?}"))?; |
| 154 | + Ok(output.stdout) |
| 155 | +} |
| 156 | +/// Generates a new rustlantis file, & compares the result of running it with GCC and LLVM. |
| 157 | +fn test(seed: u64) -> Result<Result<(), std::path::PathBuf>, String> { |
| 158 | + let source_file = generate(seed)?; |
| 159 | + let llvm_res = debug_llvm(&source_file)?; |
| 160 | + let gcc_res = release_gcc(&source_file)?; |
| 161 | + if llvm_res != gcc_res { |
| 162 | + Ok(Err(source_file)) |
| 163 | + } else { |
| 164 | + std::fs::remove_file(source_file).map_err(|err| format!("{err:?}"))?; |
| 165 | + Ok(Ok(())) |
| 166 | + } |
| 167 | +} |
| 168 | +/// Generates a new rustlantis file for us to run tests on. |
| 169 | +fn generate(seed: u64) -> Result<std::path::PathBuf, String> { |
| 170 | + use std::io::Write; |
| 171 | + let mut out_path = std::env::temp_dir(); |
| 172 | + out_path.push(&format!("fuzz{seed}.rs")); |
| 173 | + // We need to get the command output here. |
| 174 | + let out = std::process::Command::new("cargo") |
| 175 | + .args(["run", "--release", "--bin", "generate"]) |
| 176 | + .arg(&format!("{seed}")) |
| 177 | + .current_dir("rustlantis") |
| 178 | + .output() |
| 179 | + .map_err(|err| format!("{err:?}"))?; |
| 180 | + std::fs::File::create(&out_path) |
| 181 | + .map_err(|err| format!("{err:?}"))? |
| 182 | + .write_all(&out.stdout) |
| 183 | + .map_err(|err| format!("{err:?}"))?; |
| 184 | + Ok(out_path) |
| 185 | +} |
0 commit comments