11#![ no_main]
22
33use libfuzzer_sys:: fuzz_target;
4+ use once_cell:: sync:: Lazy ;
5+ use std:: collections:: HashMap ;
6+ use std:: sync:: atomic:: AtomicU64 ;
7+ use std:: sync:: atomic:: Ordering ;
48
59use cranelift_codegen:: data_value:: DataValue ;
6- use cranelift_codegen:: ir:: LibCall ;
10+ use cranelift_codegen:: ir:: { LibCall , TrapCode } ;
711use cranelift_codegen:: settings;
812use cranelift_codegen:: settings:: Configurable ;
913use cranelift_filetests:: function_runner:: { TestFileCompiler , Trampoline } ;
@@ -19,6 +23,87 @@ use smallvec::smallvec;
1923
2024const INTERPRETER_FUEL : u64 = 4096 ;
2125
26+ /// Gather statistics about the fuzzer executions
27+ struct Statistics {
28+ /// Inputs that fuzzgen can build a function with
29+ /// This is also how many compiles we executed
30+ pub valid_inputs : AtomicU64 ,
31+
32+ /// Total amount of runs that we tried in the interpreter
33+ /// One fuzzer input can have many runs
34+ pub total_runs : AtomicU64 ,
35+ /// How many runs were successful?
36+ /// This is also how many runs were run in the backend
37+ pub run_result_success : AtomicU64 ,
38+ /// How many runs resulted in a timeout?
39+ pub run_result_timeout : AtomicU64 ,
40+ /// How many runs ended with a trap?
41+ pub run_result_trap : HashMap < CraneliftTrap , AtomicU64 > ,
42+ }
43+
44+ impl Statistics {
45+ pub fn print ( & self , valid_inputs : u64 ) {
46+ // We get valid_inputs as a param since we already loaded it previously.
47+ let total_runs = self . total_runs . load ( Ordering :: SeqCst ) ;
48+ let run_result_success = self . run_result_success . load ( Ordering :: SeqCst ) ;
49+ let run_result_timeout = self . run_result_timeout . load ( Ordering :: SeqCst ) ;
50+
51+ println ! ( "== FuzzGen Statistics ====================" ) ;
52+ println ! ( "Valid Inputs: {}" , valid_inputs) ;
53+ println ! ( "Total Runs: {}" , total_runs) ;
54+ println ! (
55+ "Successful Runs: {} ({:.1}% of Total Runs)" ,
56+ run_result_success,
57+ ( run_result_success as f64 / total_runs as f64 ) * 100.0
58+ ) ;
59+ println ! (
60+ "Timed out Runs: {} ({:.1}% of Total Runs)" ,
61+ run_result_timeout,
62+ ( run_result_timeout as f64 / total_runs as f64 ) * 100.0
63+ ) ;
64+ println ! ( "Traps:" ) ;
65+ // Load and filter out empty trap codes.
66+ let mut traps = self
67+ . run_result_trap
68+ . iter ( )
69+ . map ( |( trap, count) | ( trap, count. load ( Ordering :: SeqCst ) ) )
70+ . filter ( |( _, count) | * count != 0 )
71+ . collect :: < Vec < _ > > ( ) ;
72+
73+ // Sort traps by count in a descending order
74+ traps. sort_by_key ( |( _, count) | -( * count as i64 ) ) ;
75+
76+ for ( trap, count) in traps. into_iter ( ) {
77+ println ! (
78+ "\t {}: {} ({:.1}% of Total Runs)" ,
79+ trap,
80+ count,
81+ ( count as f64 / total_runs as f64 ) * 100.0
82+ ) ;
83+ }
84+ }
85+ }
86+
87+ impl Default for Statistics {
88+ fn default ( ) -> Self {
89+ // Pre-Register all trap codes since we can't modify this hashmap atomically.
90+ let mut run_result_trap = HashMap :: new ( ) ;
91+ run_result_trap. insert ( CraneliftTrap :: Debug , AtomicU64 :: new ( 0 ) ) ;
92+ run_result_trap. insert ( CraneliftTrap :: Resumable , AtomicU64 :: new ( 0 ) ) ;
93+ for trapcode in TrapCode :: non_user_traps ( ) {
94+ run_result_trap. insert ( CraneliftTrap :: User ( * trapcode) , AtomicU64 :: new ( 0 ) ) ;
95+ }
96+
97+ Self {
98+ valid_inputs : AtomicU64 :: new ( 0 ) ,
99+ total_runs : AtomicU64 :: new ( 0 ) ,
100+ run_result_success : AtomicU64 :: new ( 0 ) ,
101+ run_result_timeout : AtomicU64 :: new ( 0 ) ,
102+ run_result_trap,
103+ }
104+ }
105+ }
106+
22107#[ derive( Debug ) ]
23108enum RunResult {
24109 Success ( Vec < DataValue > ) ,
@@ -79,7 +164,15 @@ fn build_interpreter(testcase: &TestCase) -> Interpreter {
79164 interpreter
80165}
81166
167+ static STATISTICS : Lazy < Statistics > = Lazy :: new ( Statistics :: default) ;
168+
82169fuzz_target ! ( |testcase: TestCase | {
170+ // Periodically print statistics
171+ let valid_inputs = STATISTICS . valid_inputs. fetch_add( 1 , Ordering :: SeqCst ) ;
172+ if valid_inputs != 0 && valid_inputs % 10000 == 0 {
173+ STATISTICS . print( valid_inputs) ;
174+ }
175+
83176 // Native fn
84177 let flags = {
85178 let mut builder = settings:: builder( ) ;
@@ -101,13 +194,18 @@ fuzz_target!(|testcase: TestCase| {
101194 let trampoline = compiled. get_trampoline( & testcase. func) . unwrap( ) ;
102195
103196 for args in & testcase. inputs {
197+ STATISTICS . total_runs. fetch_add( 1 , Ordering :: SeqCst ) ;
198+
104199 // We rebuild the interpreter every run so that we don't accidentally carry over any state
105200 // between runs, such as fuel remaining.
106201 let mut interpreter = build_interpreter( & testcase) ;
107202 let int_res = run_in_interpreter( & mut interpreter, args) ;
108203 match int_res {
109- RunResult :: Success ( _) => { }
110- RunResult :: Trap ( _) => {
204+ RunResult :: Success ( _) => {
205+ STATISTICS . run_result_success. fetch_add( 1 , Ordering :: SeqCst ) ;
206+ }
207+ RunResult :: Trap ( trap) => {
208+ STATISTICS . run_result_trap[ & trap] . fetch_add( 1 , Ordering :: SeqCst ) ;
111209 // If this input traps, skip it and continue trying other inputs
112210 // for this function. We've already compiled it anyway.
113211 //
@@ -120,6 +218,7 @@ fuzz_target!(|testcase: TestCase| {
120218 RunResult :: Timeout => {
121219 // We probably generated an infinite loop, we should drop this entire input.
122220 // We could `continue` like we do on traps, but timeouts are *really* expensive.
221+ STATISTICS . run_result_timeout. fetch_add( 1 , Ordering :: SeqCst ) ;
123222 return ;
124223 }
125224 RunResult :: Error ( _) => panic!( "interpreter failed: {:?}" , int_res) ,
0 commit comments