2
2
// Distributed under the MIT software license, see the accompanying
3
3
// file COPYING or https://opensource.org/license/mit/.
4
4
5
+ use std:: collections:: VecDeque ;
5
6
use std:: env;
6
- use std:: fs:: { read_dir, File } ;
7
+ use std:: fs:: { read_dir, DirEntry , File } ;
7
8
use std:: path:: { Path , PathBuf } ;
8
9
use std:: process:: { Command , ExitCode } ;
9
10
use std:: str;
11
+ use std:: thread;
10
12
11
13
/// A type for a complete and readable error message.
12
14
type AppError = String ;
@@ -16,12 +18,14 @@ const LLVM_PROFDATA: &str = "llvm-profdata";
16
18
const LLVM_COV : & str = "llvm-cov" ;
17
19
const GIT : & str = "git" ;
18
20
21
+ const DEFAULT_PAR : usize = 1 ;
22
+
19
23
fn exit_help ( err : & str ) -> AppError {
20
24
format ! (
21
25
r#"
22
26
Error: {err}
23
27
24
- Usage: program ./build_dir ./qa-assets/fuzz_corpora fuzz_target_name
28
+ Usage: program ./build_dir ./qa-assets/fuzz_corpora fuzz_target_name [parallelism={DEFAULT_PAR}]
25
29
26
30
Refer to the devtools/README.md for more details."#
27
31
)
@@ -63,7 +67,14 @@ fn app() -> AppResult {
63
67
// Require fuzz target for now. In the future it could be optional and the tool could
64
68
// iterate over all compiled fuzz targets
65
69
. ok_or ( exit_help ( "Must set fuzz target" ) ) ?;
66
- if args. get ( 4 ) . is_some ( ) {
70
+ let par = match args. get ( 4 ) {
71
+ Some ( s) => s
72
+ . parse :: < usize > ( )
73
+ . map_err ( |e| exit_help ( & format ! ( "Could not parse parallelism as usize ({s}): {e}" ) ) ) ?,
74
+ None => DEFAULT_PAR ,
75
+ }
76
+ . max ( 1 ) ;
77
+ if args. get ( 5 ) . is_some ( ) {
67
78
Err ( exit_help ( "Too many args" ) ) ?;
68
79
}
69
80
@@ -73,7 +84,7 @@ fn app() -> AppResult {
73
84
74
85
sanity_check ( corpora_dir, & fuzz_exe) ?;
75
86
76
- deterministic_coverage ( build_dir, corpora_dir, & fuzz_exe, fuzz_target)
87
+ deterministic_coverage ( build_dir, corpora_dir, & fuzz_exe, fuzz_target, par )
77
88
}
78
89
79
90
fn using_libfuzzer ( fuzz_exe : & Path ) -> Result < bool , AppError > {
@@ -94,10 +105,9 @@ fn deterministic_coverage(
94
105
corpora_dir : & Path ,
95
106
fuzz_exe : & Path ,
96
107
fuzz_target : & str ,
108
+ par : usize ,
97
109
) -> AppResult {
98
110
let using_libfuzzer = using_libfuzzer ( fuzz_exe) ?;
99
- let profraw_file = build_dir. join ( "fuzz_det_cov.profraw" ) ;
100
- let profdata_file = build_dir. join ( "fuzz_det_cov.profdata" ) ;
101
111
let corpus_dir = corpora_dir. join ( fuzz_target) ;
102
112
let mut entries = read_dir ( & corpus_dir)
103
113
. map_err ( |err| {
@@ -110,8 +120,10 @@ fn deterministic_coverage(
110
120
. map ( |entry| entry. expect ( "IO error" ) )
111
121
. collect :: < Vec < _ > > ( ) ;
112
122
entries. sort_by_key ( |entry| entry. file_name ( ) ) ;
113
- let run_single = |run_id : u8 , entry : & Path | -> Result < PathBuf , AppError > {
114
- let cov_txt_path = build_dir. join ( format ! ( "fuzz_det_cov.show.{run_id}.txt" ) ) ;
123
+ let run_single = |run_id : u8 , entry : & Path , thread_id : usize | -> Result < PathBuf , AppError > {
124
+ let cov_txt_path = build_dir. join ( format ! ( "fuzz_det_cov.show.t{thread_id}.r{run_id}.txt" ) ) ;
125
+ let profraw_file = build_dir. join ( format ! ( "fuzz_det_cov.t{thread_id}.r{run_id}.profraw" ) ) ;
126
+ let profdata_file = build_dir. join ( format ! ( "fuzz_det_cov.t{thread_id}.r{run_id}.profdata" ) ) ;
115
127
if !{
116
128
{
117
129
let mut cmd = Command :: new ( fuzz_exe) ;
@@ -187,20 +199,46 @@ The coverage was not deterministic between runs.
187
199
//
188
200
// Also, This can catch issues where several fuzz inputs are non-deterministic, but the sum of
189
201
// their overall coverage trace remains the same across runs and thus remains undetected.
190
- println ! ( "Check each fuzz input individually ..." ) ;
191
- for entry in entries {
202
+ println ! (
203
+ "Check each fuzz input individually ... ({} inputs with parallelism {par})" ,
204
+ entries. len( )
205
+ ) ;
206
+ let check_individual = |entry : & DirEntry , thread_id : usize | -> AppResult {
192
207
let entry = entry. path ( ) ;
193
208
if !entry. is_file ( ) {
194
209
Err ( format ! ( "{} should be a file" , entry. display( ) ) ) ?;
195
210
}
196
- let cov_txt_base = run_single ( 0 , & entry) ?;
197
- let cov_txt_repeat = run_single ( 1 , & entry) ?;
211
+ let cov_txt_base = run_single ( 0 , & entry, thread_id ) ?;
212
+ let cov_txt_repeat = run_single ( 1 , & entry, thread_id ) ?;
198
213
check_diff (
199
214
& cov_txt_base,
200
215
& cov_txt_repeat,
201
216
& format ! ( "The fuzz target input was {}." , entry. display( ) ) ,
202
217
) ?;
203
- }
218
+ Ok ( ( ) )
219
+ } ;
220
+ thread:: scope ( |s| -> AppResult {
221
+ let mut handles = VecDeque :: with_capacity ( par) ;
222
+ let mut res = Ok ( ( ) ) ;
223
+ for ( i, entry) in entries. iter ( ) . enumerate ( ) {
224
+ println ! ( "[{}/{}]" , i + 1 , entries. len( ) ) ;
225
+ handles. push_back ( s. spawn ( move || check_individual ( entry, i % par) ) ) ;
226
+ while handles. len ( ) >= par || i == ( entries. len ( ) - 1 ) || res. is_err ( ) {
227
+ if let Some ( th) = handles. pop_front ( ) {
228
+ let thread_result = match th. join ( ) {
229
+ Err ( _e) => Err ( "A scoped thread panicked" . to_string ( ) ) ,
230
+ Ok ( r) => r,
231
+ } ;
232
+ if thread_result. is_err ( ) {
233
+ res = thread_result;
234
+ }
235
+ } else {
236
+ return res;
237
+ }
238
+ }
239
+ }
240
+ res
241
+ } ) ?;
204
242
// Finally, check that running over all fuzz inputs in one process is deterministic as well.
205
243
// This can catch issues where mutable global state is leaked from one fuzz input execution to
206
244
// the next.
@@ -209,8 +247,8 @@ The coverage was not deterministic between runs.
209
247
if !corpus_dir. is_dir ( ) {
210
248
Err ( format ! ( "{} should be a folder" , corpus_dir. display( ) ) ) ?;
211
249
}
212
- let cov_txt_base = run_single ( 0 , & corpus_dir) ?;
213
- let cov_txt_repeat = run_single ( 1 , & corpus_dir) ?;
250
+ let cov_txt_base = run_single ( 0 , & corpus_dir, 0 ) ?;
251
+ let cov_txt_repeat = run_single ( 1 , & corpus_dir, 0 ) ?;
214
252
check_diff (
215
253
& cov_txt_base,
216
254
& cov_txt_repeat,
0 commit comments