1
1
use std:: ffi:: OsStr ;
2
2
use std:: path:: Path ;
3
3
4
+ mod reduce;
5
+
4
6
use crate :: utils:: run_command_with_output;
5
7
6
8
fn show_usage ( ) {
7
9
println ! (
8
10
r#"
9
11
`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"#
11
17
) ;
12
18
}
13
19
@@ -20,6 +26,16 @@ pub fn run() -> Result<(), String> {
20
26
std:: thread:: available_parallelism ( ) . map ( |threads| threads. get ( ) ) . unwrap_or ( 1 ) ;
21
27
while let Some ( arg) = args. next ( ) {
22
28
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
+ }
23
39
"--help" => {
24
40
show_usage ( ) ;
25
41
return Ok ( ( ) ) ;
@@ -75,16 +91,17 @@ fn fuzz_range(start: u64, end: u64, threads: usize) {
75
91
let start = Arc :: new ( AtomicU64 :: new ( start) ) ;
76
92
// Count time during fuzzing
77
93
let start_time = Instant :: now ( ) ;
94
+ let mut workers = Vec :: with_capacity ( threads) ;
78
95
// Spawn `threads`..
79
96
for _ in 0 ..threads {
80
97
let start = start. clone ( ) ;
81
98
// .. which each will ..
82
- std:: thread:: spawn ( move || {
99
+ workers . push ( std:: thread:: spawn ( move || {
83
100
// ... grab the next fuzz seed ...
84
101
while start. load ( Ordering :: Relaxed ) < end {
85
102
let next = start. fetch_add ( 1 , Ordering :: Relaxed ) ;
86
103
// .. test that seed .
87
- match test ( next) {
104
+ match test ( next, false ) {
88
105
Err ( err) => {
89
106
// If the test failed at compile-time...
90
107
println ! ( "test({}) failed because {err:?}" , next) ;
@@ -99,29 +116,38 @@ fn fuzz_range(start: u64, end: u64, threads: usize) {
99
116
Ok ( Err ( err) ) => {
100
117
// If the test failed at run-time...
101
118
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) ...
103
120
let mut out_path: std:: path:: PathBuf = "target/fuzz/runtime_error" . into ( ) ;
104
121
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`..
106
130
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 propierites of `rustlantis` to speed up the process.
133
+ reduce:: reduce ( & out_path) ;
108
134
}
109
135
// If the test passed, do nothing
110
136
Ok ( Ok ( ( ) ) ) => ( ) ,
111
137
}
112
138
}
113
- } ) ;
139
+ } ) ) ;
114
140
}
115
141
// 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 ( ) ) {
117
143
// Every 500 ms...
118
144
let five_hundred_millis = Duration :: from_millis ( 500 ) ;
119
145
std:: thread:: sleep ( five_hundred_millis) ;
120
146
// ... calculate the remaining fuzz iters ...
121
147
let remaining = end - start. load ( Ordering :: Relaxed ) ;
122
148
// ... fix the count(the start counter counts the cases that
123
149
// 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 ) ;
125
151
// ... and the fuzz speed ...
126
152
let iter_per_sec = fuzzed as f64 / start_time. elapsed ( ) . as_secs_f64 ( ) ;
127
153
// .. and use them to display fuzzing stats.
@@ -131,6 +157,7 @@ fn fuzz_range(start: u64, end: u64, threads: usize) {
131
157
( remaining as f64 ) / iter_per_sec
132
158
)
133
159
}
160
+ drop ( workers) ;
134
161
}
135
162
136
163
/// Builds & runs a file with LLVM.
@@ -200,35 +227,59 @@ fn release_gcc(path: &std::path::Path) -> Result<Vec<u8>, String> {
200
227
}
201
228
202
229
/// 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 > {
204
231
// Generate a Rust source...
205
- let source_file = generate ( seed) ?;
206
- // ... test it with debug LLVM ...
207
- let llvm_res = debug_llvm ( & source_file) ?;
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 Option < Vec < u8 > > ,
241
+ ) -> Result < Result < ( ) , std:: path:: PathBuf > , String > {
242
+ if let None = cache {
243
+ // Test `source_file` with debug LLVM ...
244
+ * cache = Some ( debug_llvm ( & source_file) ?) ;
245
+ }
246
+ let llvm_res = cache. as_ref ( ) . unwrap ( ) ;
208
247
// ... test it with release GCC ...
209
248
let gcc_res = release_gcc ( & source_file) ?;
210
249
// ... compare the results ...
211
- if llvm_res != gcc_res {
250
+ if * llvm_res != gcc_res {
212
251
// .. if they don't match, report an error.
213
- Ok ( Err ( source_file) )
252
+ Ok ( Err ( source_file. to_path_buf ( ) ) )
214
253
} 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
+ }
216
257
Ok ( Ok ( ( ) ) )
217
258
}
218
259
}
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
+ }
219
267
220
268
/// 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 > {
222
270
use std:: io:: Write ;
223
271
let mut out_path = std:: env:: temp_dir ( ) ;
224
272
out_path. push ( & format ! ( "fuzz{seed}.rs" ) ) ;
225
273
// 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
227
276
. args ( [ "run" , "--release" , "--bin" , "generate" ] )
228
277
. 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:?}" ) ) ?;
232
283
// Stuff the rustlantis output in a source file.
233
284
std:: fs:: File :: create ( & out_path)
234
285
. map_err ( |err| format ! ( "{err:?}" ) ) ?
0 commit comments