11use std:: ffi:: OsStr ;
22use std:: path:: Path ;
33
4+ mod reduce;
5+
46use crate :: utils:: run_command_with_output;
57
68fn show_usage ( ) {
79 println ! (
810 r#"
911`fuzz` command help:
10- --help : Show this help"#
12+ --reduce : Reduces a file generated by rustlantis
13+ --help : Show this help
14+ --start : Start of the fuzzed range
15+ --count : The number of cases to fuzz
16+ -j --jobs : The number of threads to use during fuzzing"#
1117 ) ;
1218}
1319
@@ -20,6 +26,16 @@ pub fn run() -> Result<(), String> {
2026 std:: thread:: available_parallelism ( ) . map ( |threads| threads. get ( ) ) . unwrap_or ( 1 ) ;
2127 while let Some ( arg) = args. next ( ) {
2228 match arg. as_str ( ) {
29+ "--reduce" => {
30+ let Some ( path) = args. next ( ) else {
31+ return Err ( "--reduce must be provided with a path" . into ( ) ) ;
32+ } ;
33+ if !std:: fs:: exists ( & path) . unwrap_or ( false ) {
34+ return Err ( "--reduce must be provided with a valid path" . into ( ) ) ;
35+ }
36+ reduce:: reduce ( & path) ;
37+ return Ok ( ( ) ) ;
38+ }
2339 "--help" => {
2440 show_usage ( ) ;
2541 return Ok ( ( ) ) ;
@@ -75,16 +91,17 @@ fn fuzz_range(start: u64, end: u64, threads: usize) {
7591 let start = Arc :: new ( AtomicU64 :: new ( start) ) ;
7692 // Count time during fuzzing
7793 let start_time = Instant :: now ( ) ;
94+ let mut workers = Vec :: with_capacity ( threads) ;
7895 // Spawn `threads`..
7996 for _ in 0 ..threads {
8097 let start = start. clone ( ) ;
8198 // .. which each will ..
82- std:: thread:: spawn ( move || {
99+ workers . push ( std:: thread:: spawn ( move || {
83100 // ... grab the next fuzz seed ...
84101 while start. load ( Ordering :: Relaxed ) < end {
85102 let next = start. fetch_add ( 1 , Ordering :: Relaxed ) ;
86103 // .. test that seed .
87- match test ( next) {
104+ match test ( next, false ) {
88105 Err ( err) => {
89106 // If the test failed at compile-time...
90107 println ! ( "test({}) failed because {err:?}" , next) ;
@@ -99,29 +116,38 @@ fn fuzz_range(start: u64, end: u64, threads: usize) {
99116 Ok ( Err ( err) ) => {
100117 // If the test failed at run-time...
101118 println ! ( "The LLVM and GCC results don't match for {err:?}" ) ;
102- // ... copy that file to the directory `target/fuzz/runtime_error` ...
119+ // ... generate a new file, which prints temporaries(instead of hashing them) ...
103120 let mut out_path: std:: path:: PathBuf = "target/fuzz/runtime_error" . into ( ) ;
104121 std:: fs:: create_dir_all ( & out_path) . unwrap ( ) ;
105- // .. into a file named `fuzz{seed}.rs`.
122+ let Ok ( Err ( tmp_print_err) ) = test ( next, true ) else {
123+ // ... if that file does not reproduce the issue...
124+ // ... save the original sample in a file named `fuzz{seed}.rs`...
125+ out_path. push ( & format ! ( "fuzz{next}.rs" ) ) ;
126+ std:: fs:: copy ( err, & out_path) . unwrap ( ) ;
127+ continue ;
128+ } ;
129+ // ... if that new file still produces the issue, copy it to `fuzz{seed}.rs`..
106130 out_path. push ( & format ! ( "fuzz{next}.rs" ) ) ;
107- std:: fs:: copy ( err, out_path) . unwrap ( ) ;
131+ std:: fs:: copy ( tmp_print_err, & out_path) . unwrap ( ) ;
132+ // ... and start reducing it, using some properties of `rustlantis` to speed up the process.
133+ reduce:: reduce ( & out_path) ;
108134 }
109135 // If the test passed, do nothing
110136 Ok ( Ok ( ( ) ) ) => ( ) ,
111137 }
112138 }
113- } ) ;
139+ } ) ) ;
114140 }
115141 // The "manager" thread loop.
116- while start. load ( Ordering :: Relaxed ) < end {
142+ while start. load ( Ordering :: Relaxed ) < end || !workers . iter ( ) . all ( |t| t . is_finished ( ) ) {
117143 // Every 500 ms...
118144 let five_hundred_millis = Duration :: from_millis ( 500 ) ;
119145 std:: thread:: sleep ( five_hundred_millis) ;
120146 // ... calculate the remaining fuzz iters ...
121147 let remaining = end - start. load ( Ordering :: Relaxed ) ;
122148 // ... fix the count(the start counter counts the cases that
123149 // begun fuzzing, and not only the ones that are done)...
124- let fuzzed = ( total - remaining) - threads as u64 ;
150+ let fuzzed = ( total - remaining) . saturating_sub ( threads as u64 ) ;
125151 // ... and the fuzz speed ...
126152 let iter_per_sec = fuzzed as f64 / start_time. elapsed ( ) . as_secs_f64 ( ) ;
127153 // .. and use them to display fuzzing stats.
@@ -131,6 +157,7 @@ fn fuzz_range(start: u64, end: u64, threads: usize) {
131157 ( remaining as f64 ) / iter_per_sec
132158 )
133159 }
160+ drop ( workers) ;
134161}
135162
136163/// Builds & runs a file with LLVM.
@@ -198,37 +225,61 @@ fn release_gcc(path: &std::path::Path) -> Result<Vec<u8>, String> {
198225 res. extend ( output. stderr ) ;
199226 Ok ( res)
200227}
201-
228+ type ResultCache = Option < ( Vec < u8 > , Vec < u8 > ) > ;
202229/// 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 > {
230+ fn test ( seed : u64 , print_tmp_vars : bool ) -> Result < Result < ( ) , std:: path:: PathBuf > , String > {
204231 // 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 ...
232+ let source_file = generate ( seed, print_tmp_vars) ?;
233+ test_file ( & source_file, true )
234+ }
235+ /// Tests a file with a cached LLVM result. Used for reduction, when it is known
236+ /// that a given transformation should not change the execution result.
237+ fn test_cached (
238+ source_file : & Path ,
239+ remove_tmps : bool ,
240+ cache : & mut ResultCache ,
241+ ) -> Result < Result < ( ) , std:: path:: PathBuf > , String > {
242+ // Test `source_file` with release GCC ...
209243 let gcc_res = release_gcc ( & source_file) ?;
244+ if cache. is_none ( ) {
245+ // ...test `source_file` with debug LLVM ...
246+ * cache = Some ( ( debug_llvm ( & source_file) ?, gcc_res. clone ( ) ) ) ;
247+ }
248+ let ( llvm_res, old_gcc) = cache. as_ref ( ) . unwrap ( ) ;
210249 // ... compare the results ...
211- if llvm_res != gcc_res {
250+ if * llvm_res != gcc_res && gcc_res == * old_gcc {
212251 // .. if they don't match, report an error.
213- Ok ( Err ( source_file) )
252+ Ok ( Err ( source_file. to_path_buf ( ) ) )
214253 } else {
215- std:: fs:: remove_file ( source_file) . map_err ( |err| format ! ( "{err:?}" ) ) ?;
254+ if remove_tmps {
255+ std:: fs:: remove_file ( source_file) . map_err ( |err| format ! ( "{err:?}" ) ) ?;
256+ }
216257 Ok ( Ok ( ( ) ) )
217258 }
218259}
260+ fn test_file (
261+ source_file : & Path ,
262+ remove_tmps : bool ,
263+ ) -> Result < Result < ( ) , std:: path:: PathBuf > , String > {
264+ let mut uncached = None ;
265+ test_cached ( source_file, remove_tmps, & mut uncached)
266+ }
219267
220268/// Generates a new rustlantis file for us to run tests on.
221- fn generate ( seed : u64 ) -> Result < std:: path:: PathBuf , String > {
269+ fn generate ( seed : u64 , print_tmp_vars : bool ) -> Result < std:: path:: PathBuf , String > {
222270 use std:: io:: Write ;
223271 let mut out_path = std:: env:: temp_dir ( ) ;
224272 out_path. push ( & format ! ( "fuzz{seed}.rs" ) ) ;
225273 // We need to get the command output here.
226- let out = std:: process:: Command :: new ( "cargo" )
274+ let mut generate = std:: process:: Command :: new ( "cargo" ) ;
275+ generate
227276 . args ( [ "run" , "--release" , "--bin" , "generate" ] )
228277 . arg ( & format ! ( "{seed}" ) )
229- . current_dir ( "clones/rustlantis" )
230- . output ( )
231- . map_err ( |err| format ! ( "{err:?}" ) ) ?;
278+ . current_dir ( "clones/rustlantis" ) ;
279+ if print_tmp_vars {
280+ generate. arg ( "--debug" ) ;
281+ }
282+ let out = generate. output ( ) . map_err ( |err| format ! ( "{err:?}" ) ) ?;
232283 // Stuff the rustlantis output in a source file.
233284 std:: fs:: File :: create ( & out_path)
234285 . map_err ( |err| format ! ( "{err:?}" ) ) ?
0 commit comments