4
4
5
5
use std:: env;
6
6
use std:: fs:: { read_dir, File } ;
7
- use std:: path:: Path ;
8
- use std:: process:: { exit , Command } ;
7
+ use std:: path:: { Path , PathBuf } ;
8
+ use std:: process:: { Command , ExitCode } ;
9
9
use std:: str;
10
10
11
+ /// A type for a complete and readable error message.
12
+ type AppError = String ;
13
+ type AppResult = Result < ( ) , AppError > ;
14
+
11
15
const LLVM_PROFDATA : & str = "llvm-profdata" ;
12
16
const LLVM_COV : & str = "llvm-cov" ;
13
17
const GIT : & str = "git" ;
14
18
15
- fn exit_help ( err : & str ) -> ! {
16
- eprintln ! ( "Error: {}" , err) ;
17
- eprintln ! ( ) ;
18
- eprintln ! ( "Usage: program ./build_dir ./qa-assets/fuzz_corpora fuzz_target_name" ) ;
19
- eprintln ! ( ) ;
20
- eprintln ! ( "Refer to the devtools/README.md for more details." ) ;
21
- exit ( 1 )
19
+ fn exit_help ( err : & str ) -> AppError {
20
+ format ! (
21
+ r#"
22
+ Error: {err}
23
+
24
+ Usage: program ./build_dir ./qa-assets/fuzz_corpora fuzz_target_name
25
+
26
+ Refer to the devtools/README.md for more details."#
27
+ )
22
28
}
23
29
24
- fn sanity_check ( corpora_dir : & Path , fuzz_exe : & Path ) {
30
+ fn sanity_check ( corpora_dir : & Path , fuzz_exe : & Path ) -> AppResult {
25
31
for tool in [ LLVM_PROFDATA , LLVM_COV , GIT ] {
26
32
let output = Command :: new ( tool) . arg ( "--help" ) . output ( ) ;
27
33
match output {
28
34
Ok ( output) if output. status . success ( ) => { }
29
- _ => {
30
- exit_help ( & format ! ( "The tool {} is not installed" , tool) ) ;
31
- }
35
+ _ => Err ( exit_help ( & format ! ( "The tool {} is not installed" , tool) ) ) ?,
32
36
}
33
37
}
34
38
if !corpora_dir. is_dir ( ) {
35
- exit_help ( & format ! (
39
+ Err ( exit_help ( & format ! (
36
40
"Fuzz corpora path ({}) must be a directory" ,
37
41
corpora_dir. display( )
38
- ) ) ;
42
+ ) ) ) ? ;
39
43
}
40
44
if !fuzz_exe. exists ( ) {
41
- exit_help ( & format ! (
45
+ Err ( exit_help ( & format ! (
42
46
"Fuzz executable ({}) not found" ,
43
47
fuzz_exe. display( )
44
- ) ) ;
48
+ ) ) ) ? ;
45
49
}
50
+ Ok ( ( ) )
46
51
}
47
52
48
- fn main ( ) {
53
+ fn app ( ) -> AppResult {
49
54
// Parse args
50
55
let args = env:: args ( ) . collect :: < Vec < _ > > ( ) ;
51
- let build_dir = args
52
- . get ( 1 )
53
- . unwrap_or_else ( || exit_help ( "Must set build dir" ) ) ;
56
+ let build_dir = args. get ( 1 ) . ok_or ( exit_help ( "Must set build dir" ) ) ?;
54
57
if build_dir == "--help" {
55
- exit_help ( "--help requested" )
58
+ Err ( exit_help ( "--help requested" ) ) ? ;
56
59
}
57
- let corpora_dir = args
58
- . get ( 2 )
59
- . unwrap_or_else ( || exit_help ( "Must set fuzz corpora dir" ) ) ;
60
+ let corpora_dir = args. get ( 2 ) . ok_or ( exit_help ( "Must set fuzz corpora dir" ) ) ?;
60
61
let fuzz_target = args
61
62
. get ( 3 )
62
63
// Require fuzz target for now. In the future it could be optional and the tool could
63
64
// iterate over all compiled fuzz targets
64
- . unwrap_or_else ( || exit_help ( "Must set fuzz target" ) ) ;
65
+ . ok_or ( exit_help ( "Must set fuzz target" ) ) ? ;
65
66
if args. get ( 4 ) . is_some ( ) {
66
- exit_help ( "Too many args" )
67
+ Err ( exit_help ( "Too many args" ) ) ? ;
67
68
}
68
69
69
70
let build_dir = Path :: new ( build_dir) ;
70
71
let corpora_dir = Path :: new ( corpora_dir) ;
71
72
let fuzz_exe = build_dir. join ( "bin/fuzz" ) ;
72
73
73
- sanity_check ( corpora_dir, & fuzz_exe) ;
74
+ sanity_check ( corpora_dir, & fuzz_exe) ? ;
74
75
75
- deterministic_coverage ( build_dir, corpora_dir, & fuzz_exe, fuzz_target) ;
76
+ deterministic_coverage ( build_dir, corpora_dir, & fuzz_exe, fuzz_target)
76
77
}
77
78
78
- fn using_libfuzzer ( fuzz_exe : & Path ) -> bool {
79
+ fn using_libfuzzer ( fuzz_exe : & Path ) -> Result < bool , AppError > {
79
80
println ! ( "Check if using libFuzzer ..." ) ;
80
81
let stderr = Command :: new ( fuzz_exe)
81
82
. arg ( "-help=1" ) // Will be interpreted as option (libfuzzer) or as input file
82
83
. env ( "FUZZ" , "addition_overflow" ) // Any valid target
83
84
. output ( )
84
- . expect ( "fuzz failed" )
85
+ . map_err ( |e| format ! ( "fuzz failed with {e}" ) ) ?
85
86
. stderr ;
86
- let help_output = str:: from_utf8 ( & stderr) . expect ( "The -help=1 output must be valid text" ) ;
87
- help_output. contains ( "libFuzzer" )
87
+ let help_output = str:: from_utf8 ( & stderr)
88
+ . map_err ( |e| format ! ( "The libFuzzer -help=1 output must be valid text ({e})" ) ) ?;
89
+ Ok ( help_output. contains ( "libFuzzer" ) )
88
90
}
89
91
90
92
fn deterministic_coverage (
91
93
build_dir : & Path ,
92
94
corpora_dir : & Path ,
93
95
fuzz_exe : & Path ,
94
96
fuzz_target : & str ,
95
- ) {
96
- let using_libfuzzer = using_libfuzzer ( fuzz_exe) ;
97
+ ) -> AppResult {
98
+ let using_libfuzzer = using_libfuzzer ( fuzz_exe) ? ;
97
99
let profraw_file = build_dir. join ( "fuzz_det_cov.profraw" ) ;
98
100
let profdata_file = build_dir. join ( "fuzz_det_cov.profdata" ) ;
99
101
let corpus_dir = corpora_dir. join ( fuzz_target) ;
100
102
let mut entries = read_dir ( & corpus_dir)
101
- . unwrap_or_else ( |err| {
103
+ . map_err ( |err| {
102
104
exit_help ( & format ! (
103
105
"The fuzz target's input directory must exist! ({}; {})" ,
104
106
corpus_dir. display( ) ,
105
107
err
106
108
) )
107
- } )
109
+ } ) ?
108
110
. map ( |entry| entry. expect ( "IO error" ) )
109
111
. collect :: < Vec < _ > > ( ) ;
110
112
entries. sort_by_key ( |entry| entry. file_name ( ) ) ;
111
- let run_single = |run_id : u8 , entry : & Path | {
113
+ let run_single = |run_id : u8 , entry : & Path | -> Result < PathBuf , AppError > {
112
114
let cov_txt_path = build_dir. join ( format ! ( "fuzz_det_cov.show.{run_id}.txt" ) ) ;
113
- assert ! ( {
115
+ if ! {
114
116
{
115
117
let mut cmd = Command :: new ( fuzz_exe) ;
116
118
if using_libfuzzer {
@@ -122,20 +124,26 @@ fn deterministic_coverage(
122
124
. env ( "FUZZ" , fuzz_target)
123
125
. arg ( entry)
124
126
. status ( )
125
- . expect ( "fuzz failed" )
127
+ . map_err ( |e| format ! ( "fuzz failed with {e}" ) ) ?
126
128
. success ( )
127
- } ) ;
128
- assert ! ( Command :: new( LLVM_PROFDATA )
129
+ } {
130
+ Err ( "fuzz failed" . to_string ( ) ) ?;
131
+ }
132
+ if !Command :: new ( LLVM_PROFDATA )
129
133
. arg ( "merge" )
130
134
. arg ( "--sparse" )
131
135
. arg ( & profraw_file)
132
136
. arg ( "-o" )
133
137
. arg ( & profdata_file)
134
138
. status ( )
135
- . expect( "merge failed" )
136
- . success( ) ) ;
137
- let cov_file = File :: create ( & cov_txt_path) . expect ( "Failed to create coverage txt file" ) ;
138
- assert ! ( Command :: new( LLVM_COV )
139
+ . map_err ( |e| format ! ( "{LLVM_PROFDATA} merge failed with {e}" ) ) ?
140
+ . success ( )
141
+ {
142
+ Err ( format ! ( "{LLVM_PROFDATA} merge failed. This can be a sign of compiling without code coverage support." ) ) ?;
143
+ }
144
+ let cov_file = File :: create ( & cov_txt_path)
145
+ . map_err ( |e| format ! ( "Failed to create coverage txt file ({e})" ) ) ?;
146
+ if !Command :: new ( LLVM_COV )
139
147
. args ( [
140
148
"show" ,
141
149
"--show-line-counts-or-regions" ,
@@ -146,27 +154,31 @@ fn deterministic_coverage(
146
154
. arg ( fuzz_exe)
147
155
. stdout ( cov_file)
148
156
. spawn ( )
149
- . expect ( "Failed to execute llvm-cov" )
157
+ . map_err ( |e| format ! ( "{LLVM_COV} show failed with {e}" ) ) ?
150
158
. wait ( )
151
- . expect( "Failed to execute llvm-cov" )
152
- . success( ) ) ;
153
- cov_txt_path
159
+ . map_err ( |e| format ! ( "{LLVM_COV} show failed with {e}" ) ) ?
160
+ . success ( )
161
+ {
162
+ Err ( format ! ( "{LLVM_COV} show failed" ) ) ?;
163
+ } ;
164
+ Ok ( cov_txt_path)
154
165
} ;
155
- let check_diff = |a : & Path , b : & Path , err : & str | {
166
+ let check_diff = |a : & Path , b : & Path , err : & str | -> AppResult {
156
167
let same = Command :: new ( GIT )
157
168
. args ( [ "--no-pager" , "diff" , "--no-index" ] )
158
169
. arg ( a)
159
170
. arg ( b)
160
171
. status ( )
161
- . expect ( "Failed to execute git command" )
172
+ . map_err ( |e| format ! ( "{GIT} diff failed with {e}" ) ) ?
162
173
. success ( ) ;
163
174
if !same {
164
- eprintln ! ( ) ;
165
- eprintln ! ( "The coverage was not deterministic between runs." ) ;
166
- eprintln ! ( "{}" , err ) ;
167
- eprintln ! ( "Exiting." ) ;
168
- exit ( 1 ) ;
175
+ Err ( format ! (
176
+ r#"
177
+ The coverage was not deterministic between runs.
178
+ {err}"#
179
+ ) ) ? ;
169
180
}
181
+ Ok ( ( ) )
170
182
} ;
171
183
// First, check that each fuzz input is deterministic running by itself in a process.
172
184
//
@@ -175,29 +187,46 @@ fn deterministic_coverage(
175
187
//
176
188
// Also, This can catch issues where several fuzz inputs are non-deterministic, but the sum of
177
189
// their overall coverage trace remains the same across runs and thus remains undetected.
190
+ println ! ( "Check each fuzz input individually ..." ) ;
178
191
for entry in entries {
179
192
let entry = entry. path ( ) ;
180
- assert ! ( entry. is_file( ) ) ;
181
- let cov_txt_base = run_single ( 0 , & entry) ;
182
- let cov_txt_repeat = run_single ( 1 , & entry) ;
193
+ if !entry. is_file ( ) {
194
+ Err ( format ! ( "{} should be a file" , entry. display( ) ) ) ?;
195
+ }
196
+ let cov_txt_base = run_single ( 0 , & entry) ?;
197
+ let cov_txt_repeat = run_single ( 1 , & entry) ?;
183
198
check_diff (
184
199
& cov_txt_base,
185
200
& cov_txt_repeat,
186
201
& format ! ( "The fuzz target input was {}." , entry. display( ) ) ,
187
- ) ;
202
+ ) ? ;
188
203
}
189
204
// Finally, check that running over all fuzz inputs in one process is deterministic as well.
190
205
// This can catch issues where mutable global state is leaked from one fuzz input execution to
191
206
// the next.
207
+ println ! ( "Check all fuzz inputs in one go ..." ) ;
192
208
{
193
- assert ! ( corpus_dir. is_dir( ) ) ;
194
- let cov_txt_base = run_single ( 0 , & corpus_dir) ;
195
- let cov_txt_repeat = run_single ( 1 , & corpus_dir) ;
209
+ if !corpus_dir. is_dir ( ) {
210
+ Err ( format ! ( "{} should be a folder" , corpus_dir. display( ) ) ) ?;
211
+ }
212
+ let cov_txt_base = run_single ( 0 , & corpus_dir) ?;
213
+ let cov_txt_repeat = run_single ( 1 , & corpus_dir) ?;
196
214
check_diff (
197
215
& cov_txt_base,
198
216
& cov_txt_repeat,
199
217
& format ! ( "All fuzz inputs in {} were used." , corpus_dir. display( ) ) ,
200
- ) ;
218
+ ) ? ;
201
219
}
202
220
println ! ( "Coverage test passed for {fuzz_target}." ) ;
221
+ Ok ( ( ) )
222
+ }
223
+
224
+ fn main ( ) -> ExitCode {
225
+ match app ( ) {
226
+ Ok ( ( ) ) => ExitCode :: SUCCESS ,
227
+ Err ( err) => {
228
+ eprintln ! ( "{}" , err) ;
229
+ ExitCode :: FAILURE
230
+ }
231
+ }
203
232
}
0 commit comments