Skip to content

Commit 967fc29

Browse files
authored
Merge pull request #688 from FractalFir/fuzz_support
Add support for easily fuzzing `cg_gcc` with rustlantis
2 parents d80802b + 22ca124 commit 967fc29

File tree

3 files changed

+246
-2
lines changed

3 files changed

+246
-2
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ tools/llvmint-2
1919
llvm
2020
build_system/target
2121
config.toml
22-
build
22+
build
23+
rustlantis

build_system/src/fuzz.rs

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
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+
}

build_system/src/main.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod clean;
55
mod clone_gcc;
66
mod config;
77
mod fmt;
8+
mod fuzz;
89
mod info;
910
mod prepare;
1011
mod rust_tools;
@@ -42,7 +43,8 @@ Commands:
4243
test : Runs tests for the project.
4344
info : Displays information about the build environment and project configuration.
4445
clone-gcc : Clones the GCC compiler from a specified source.
45-
fmt : Runs rustfmt"
46+
fmt : Runs rustfmt
47+
fuzz : Fuzzes `cg_gcc` using rustlantis"
4648
);
4749
}
4850

@@ -56,6 +58,7 @@ pub enum Command {
5658
Test,
5759
Info,
5860
Fmt,
61+
Fuzz,
5962
}
6063

6164
fn main() {
@@ -75,6 +78,7 @@ fn main() {
7578
Some("info") => Command::Info,
7679
Some("clone-gcc") => Command::CloneGcc,
7780
Some("fmt") => Command::Fmt,
81+
Some("fuzz") => Command::Fuzz,
7882
Some("--help") => {
7983
usage();
8084
process::exit(0);
@@ -97,6 +101,7 @@ fn main() {
97101
Command::Info => info::run(),
98102
Command::CloneGcc => clone_gcc::run(),
99103
Command::Fmt => fmt::run(),
104+
Command::Fuzz => fuzz::run(),
100105
} {
101106
eprintln!("Command failed to run: {e}");
102107
process::exit(1);

0 commit comments

Comments
 (0)