11use crate :: { HitMap , HitMaps } ;
22use alloy_primitives:: B256 ;
33use revm:: { interpreter:: Interpreter , Database , EvmContext , Inspector } ;
4+ use std:: ptr:: NonNull ;
45
56/// Inspector implementation for collecting coverage information.
6- #[ derive( Clone , Debug , Default ) ]
7+ #[ derive( Clone , Debug ) ]
78pub struct CoverageCollector {
9+ current_map : NonNull < HitMap > ,
10+ current_hash : B256 ,
811 maps : HitMaps ,
912}
1013
14+ // SAFETY: `current_map` is always valid and points into an allocation managed by self.
15+ unsafe impl Send for CoverageCollector { }
16+ unsafe impl Sync for CoverageCollector { }
17+
18+ impl Default for CoverageCollector {
19+ fn default ( ) -> Self {
20+ Self {
21+ current_map : NonNull :: dangling ( ) ,
22+ current_hash : B256 :: ZERO ,
23+ maps : Default :: default ( ) ,
24+ }
25+ }
26+ }
27+
1128impl < DB : Database > Inspector < DB > for CoverageCollector {
1229 fn initialize_interp ( & mut self , interpreter : & mut Interpreter , _context : & mut EvmContext < DB > ) {
13- self . maps
14- . entry ( * get_contract_hash ( interpreter) )
15- . or_insert_with ( || HitMap :: new ( interpreter. contract . bytecode . original_bytes ( ) ) ) ;
30+ get_or_insert_contract_hash ( interpreter) ;
31+ self . insert_map ( interpreter) ;
1632 }
1733
1834 #[ inline]
1935 fn step ( & mut self , interpreter : & mut Interpreter , _context : & mut EvmContext < DB > ) {
20- if let Some ( map) = self . maps . get_mut ( get_contract_hash ( interpreter) ) {
21- map. hit ( interpreter. program_counter ( ) ) ;
22- }
36+ let map = self . get_or_insert_map ( interpreter) ;
37+ map. hit ( interpreter. program_counter ( ) ) ;
2338 }
2439}
2540
@@ -28,15 +43,50 @@ impl CoverageCollector {
2843 pub fn finish ( self ) -> HitMaps {
2944 self . maps
3045 }
46+
47+ #[ inline]
48+ fn get_or_insert_map ( & mut self , interpreter : & mut Interpreter ) -> & mut HitMap {
49+ let hash = get_or_insert_contract_hash ( interpreter) ;
50+ if self . current_hash != * hash {
51+ self . insert_map ( interpreter) ;
52+ }
53+ unsafe { self . current_map . as_mut ( ) }
54+ }
55+
56+ #[ cold]
57+ #[ inline( never) ]
58+ fn insert_map ( & mut self , interpreter : & Interpreter ) {
59+ let Some ( hash) = interpreter. contract . hash else { eof_panic ( ) } ;
60+ self . current_hash = hash;
61+ self . current_map = self
62+ . maps
63+ . entry ( hash)
64+ . or_insert_with ( || HitMap :: new ( interpreter. contract . bytecode . original_bytes ( ) ) )
65+ . into ( ) ;
66+ }
3167}
3268
3369/// Helper function for extracting contract hash used to record coverage hit map.
34- /// If contract hash available in interpreter contract is zero (contract not yet created but going
35- /// to be created in current tx) then it hash is calculated from contract bytecode.
36- fn get_contract_hash ( interpreter : & mut Interpreter ) -> & B256 {
37- let hash = interpreter. contract . hash . as_mut ( ) . expect ( "coverage does not support EOF" ) ;
38- if * hash == B256 :: ZERO {
39- * hash = interpreter. contract . bytecode . hash_slow ( ) ;
70+ ///
71+ /// If the contract hash is zero (contract not yet created but it's going to be created in current
72+ /// tx) then the hash is calculated from the bytecode.
73+ #[ inline]
74+ fn get_or_insert_contract_hash ( interpreter : & mut Interpreter ) -> & B256 {
75+ let Some ( hash) = interpreter. contract . hash . as_mut ( ) else { eof_panic ( ) } ;
76+ if hash. is_zero ( ) {
77+ set_contract_hash ( hash, & interpreter. contract . bytecode ) ;
4078 }
4179 hash
4280}
81+
82+ #[ cold]
83+ #[ inline( never) ]
84+ fn set_contract_hash ( hash : & mut B256 , bytecode : & revm:: primitives:: Bytecode ) {
85+ * hash = bytecode. hash_slow ( ) ;
86+ }
87+
88+ #[ cold]
89+ #[ inline( never) ]
90+ fn eof_panic ( ) -> ! {
91+ panic ! ( "coverage does not support EOF" ) ;
92+ }
0 commit comments