44
55use std:: env;
66use 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 } ;
99use std:: str;
1010
11+ /// A type for a complete and readable error message.
12+ type AppError = String ;
13+ type AppResult = Result < ( ) , AppError > ;
14+
1115const LLVM_PROFDATA : & str = "llvm-profdata" ;
1216const LLVM_COV : & str = "llvm-cov" ;
1317const GIT : & str = "git" ;
1418
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+ )
2228}
2329
24- fn sanity_check ( corpora_dir : & Path , fuzz_exe : & Path ) {
30+ fn sanity_check ( corpora_dir : & Path , fuzz_exe : & Path ) -> AppResult {
2531 for tool in [ LLVM_PROFDATA , LLVM_COV , GIT ] {
2632 let output = Command :: new ( tool) . arg ( "--help" ) . output ( ) ;
2733 match output {
2834 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) ) ) ?,
3236 }
3337 }
3438 if !corpora_dir. is_dir ( ) {
35- exit_help ( & format ! (
39+ Err ( exit_help ( & format ! (
3640 "Fuzz corpora path ({}) must be a directory" ,
3741 corpora_dir. display( )
38- ) ) ;
42+ ) ) ) ? ;
3943 }
4044 if !fuzz_exe. exists ( ) {
41- exit_help ( & format ! (
45+ Err ( exit_help ( & format ! (
4246 "Fuzz executable ({}) not found" ,
4347 fuzz_exe. display( )
44- ) ) ;
48+ ) ) ) ? ;
4549 }
50+ Ok ( ( ) )
4651}
4752
48- fn main ( ) {
53+ fn app ( ) -> AppResult {
4954 // Parse args
5055 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" ) ) ?;
5457 if build_dir == "--help" {
55- exit_help ( "--help requested" )
58+ Err ( exit_help ( "--help requested" ) ) ? ;
5659 }
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" ) ) ?;
6061 let fuzz_target = args
6162 . get ( 3 )
6263 // Require fuzz target for now. In the future it could be optional and the tool could
6364 // 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" ) ) ? ;
6566 if args. get ( 4 ) . is_some ( ) {
66- exit_help ( "Too many args" )
67+ Err ( exit_help ( "Too many args" ) ) ? ;
6768 }
6869
6970 let build_dir = Path :: new ( build_dir) ;
7071 let corpora_dir = Path :: new ( corpora_dir) ;
7172 let fuzz_exe = build_dir. join ( "bin/fuzz" ) ;
7273
73- sanity_check ( corpora_dir, & fuzz_exe) ;
74+ sanity_check ( corpora_dir, & fuzz_exe) ? ;
7475
75- deterministic_coverage ( build_dir, corpora_dir, & fuzz_exe, fuzz_target) ;
76+ deterministic_coverage ( build_dir, corpora_dir, & fuzz_exe, fuzz_target)
7677}
7778
78- fn using_libfuzzer ( fuzz_exe : & Path ) -> bool {
79+ fn using_libfuzzer ( fuzz_exe : & Path ) -> Result < bool , AppError > {
7980 println ! ( "Check if using libFuzzer ..." ) ;
8081 let stderr = Command :: new ( fuzz_exe)
8182 . arg ( "-help=1" ) // Will be interpreted as option (libfuzzer) or as input file
8283 . env ( "FUZZ" , "addition_overflow" ) // Any valid target
8384 . output ( )
84- . expect ( "fuzz failed" )
85+ . map_err ( |e| format ! ( "fuzz failed with {e}" ) ) ?
8586 . 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" ) )
8890}
8991
9092fn deterministic_coverage (
9193 build_dir : & Path ,
9294 corpora_dir : & Path ,
9395 fuzz_exe : & Path ,
9496 fuzz_target : & str ,
95- ) {
96- let using_libfuzzer = using_libfuzzer ( fuzz_exe) ;
97+ ) -> AppResult {
98+ let using_libfuzzer = using_libfuzzer ( fuzz_exe) ? ;
9799 let profraw_file = build_dir. join ( "fuzz_det_cov.profraw" ) ;
98100 let profdata_file = build_dir. join ( "fuzz_det_cov.profdata" ) ;
99101 let corpus_dir = corpora_dir. join ( fuzz_target) ;
100102 let mut entries = read_dir ( & corpus_dir)
101- . unwrap_or_else ( |err| {
103+ . map_err ( |err| {
102104 exit_help ( & format ! (
103105 "The fuzz target's input directory must exist! ({}; {})" ,
104106 corpus_dir. display( ) ,
105107 err
106108 ) )
107- } )
109+ } ) ?
108110 . map ( |entry| entry. expect ( "IO error" ) )
109111 . collect :: < Vec < _ > > ( ) ;
110112 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 > {
112114 let cov_txt_path = build_dir. join ( format ! ( "fuzz_det_cov.show.{run_id}.txt" ) ) ;
113- assert ! ( {
115+ if ! {
114116 {
115117 let mut cmd = Command :: new ( fuzz_exe) ;
116118 if using_libfuzzer {
@@ -122,20 +124,26 @@ fn deterministic_coverage(
122124 . env ( "FUZZ" , fuzz_target)
123125 . arg ( entry)
124126 . status ( )
125- . expect ( "fuzz failed" )
127+ . map_err ( |e| format ! ( "fuzz failed with {e}" ) ) ?
126128 . success ( )
127- } ) ;
128- assert ! ( Command :: new( LLVM_PROFDATA )
129+ } {
130+ Err ( "fuzz failed" . to_string ( ) ) ?;
131+ }
132+ if !Command :: new ( LLVM_PROFDATA )
129133 . arg ( "merge" )
130134 . arg ( "--sparse" )
131135 . arg ( & profraw_file)
132136 . arg ( "-o" )
133137 . arg ( & profdata_file)
134138 . 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 )
139147 . args ( [
140148 "show" ,
141149 "--show-line-counts-or-regions" ,
@@ -146,27 +154,31 @@ fn deterministic_coverage(
146154 . arg ( fuzz_exe)
147155 . stdout ( cov_file)
148156 . spawn ( )
149- . expect ( "Failed to execute llvm-cov" )
157+ . map_err ( |e| format ! ( "{LLVM_COV} show failed with {e}" ) ) ?
150158 . 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)
154165 } ;
155- let check_diff = |a : & Path , b : & Path , err : & str | {
166+ let check_diff = |a : & Path , b : & Path , err : & str | -> AppResult {
156167 let same = Command :: new ( GIT )
157168 . args ( [ "--no-pager" , "diff" , "--no-index" ] )
158169 . arg ( a)
159170 . arg ( b)
160171 . status ( )
161- . expect ( "Failed to execute git command" )
172+ . map_err ( |e| format ! ( "{GIT} diff failed with {e}" ) ) ?
162173 . success ( ) ;
163174 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+ ) ) ? ;
169180 }
181+ Ok ( ( ) )
170182 } ;
171183 // First, check that each fuzz input is deterministic running by itself in a process.
172184 //
@@ -177,27 +189,42 @@ fn deterministic_coverage(
177189 // their overall coverage trace remains the same across runs and thus remains undetected.
178190 for entry in entries {
179191 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) ;
192+ if !entry. is_file ( ) {
193+ Err ( format ! ( "{} should be a file" , entry. display( ) ) ) ?;
194+ }
195+ let cov_txt_base = run_single ( 0 , & entry) ?;
196+ let cov_txt_repeat = run_single ( 1 , & entry) ?;
183197 check_diff (
184198 & cov_txt_base,
185199 & cov_txt_repeat,
186200 & format ! ( "The fuzz target input was {}." , entry. display( ) ) ,
187- ) ;
201+ ) ? ;
188202 }
189203 // Finally, check that running over all fuzz inputs in one process is deterministic as well.
190204 // This can catch issues where mutable global state is leaked from one fuzz input execution to
191205 // the next.
192206 {
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) ;
207+ if !corpus_dir. is_dir ( ) {
208+ Err ( format ! ( "{} should be a folder" , corpus_dir. display( ) ) ) ?;
209+ }
210+ let cov_txt_base = run_single ( 0 , & corpus_dir) ?;
211+ let cov_txt_repeat = run_single ( 1 , & corpus_dir) ?;
196212 check_diff (
197213 & cov_txt_base,
198214 & cov_txt_repeat,
199215 & format ! ( "All fuzz inputs in {} were used." , corpus_dir. display( ) ) ,
200- ) ;
216+ ) ? ;
201217 }
202218 println ! ( "Coverage test passed for {fuzz_target}." ) ;
219+ Ok ( ( ) )
220+ }
221+
222+ fn main ( ) -> ExitCode {
223+ match app ( ) {
224+ Ok ( ( ) ) => ExitCode :: SUCCESS ,
225+ Err ( err) => {
226+ eprintln ! ( "{}" , err) ;
227+ ExitCode :: FAILURE
228+ }
229+ }
203230}
0 commit comments