@@ -10,7 +10,7 @@ use std::sync::Arc;
10
10
11
11
/// A visitor that walks the AST of a single contract and finds coverage items.
12
12
#[ derive( Clone , Debug ) ]
13
- pub struct ContractVisitor < ' a > {
13
+ struct ContractVisitor < ' a > {
14
14
/// The source ID of the contract.
15
15
source_id : u32 ,
16
16
/// The source code that contains the AST being walked.
@@ -25,11 +25,11 @@ pub struct ContractVisitor<'a> {
25
25
last_line : u32 ,
26
26
27
27
/// Coverage items
28
- pub items : Vec < CoverageItem > ,
28
+ items : Vec < CoverageItem > ,
29
29
}
30
30
31
31
impl < ' a > ContractVisitor < ' a > {
32
- pub fn new ( source_id : usize , source : & ' a str , contract_name : & ' a Arc < str > ) -> Self {
32
+ fn new ( source_id : usize , source : & ' a str , contract_name : & ' a Arc < str > ) -> Self {
33
33
Self {
34
34
source_id : source_id. try_into ( ) . expect ( "too many sources" ) ,
35
35
source,
@@ -40,7 +40,46 @@ impl<'a> ContractVisitor<'a> {
40
40
}
41
41
}
42
42
43
- pub fn visit_contract ( & mut self , node : & Node ) -> eyre:: Result < ( ) > {
43
+ /// Filter out all items if the contract has any test functions.
44
+ fn clear_if_test ( & mut self ) {
45
+ let has_tests = self . items . iter ( ) . any ( |item| {
46
+ if let CoverageItemKind :: Function { name } = & item. kind {
47
+ name. is_any_test ( )
48
+ } else {
49
+ false
50
+ }
51
+ } ) ;
52
+ if has_tests {
53
+ self . items = Vec :: new ( ) ;
54
+ }
55
+ }
56
+
57
+ /// Disambiguate functions with the same name in the same contract.
58
+ fn disambiguate_functions ( & mut self ) {
59
+ if self . items . is_empty ( ) {
60
+ return ;
61
+ }
62
+
63
+ let mut dups = HashMap :: < _ , Vec < usize > > :: default ( ) ;
64
+ for ( i, item) in self . items . iter ( ) . enumerate ( ) {
65
+ if let CoverageItemKind :: Function { name } = & item. kind {
66
+ dups. entry ( name. clone ( ) ) . or_default ( ) . push ( i) ;
67
+ }
68
+ }
69
+ for dups in dups. values ( ) {
70
+ if dups. len ( ) > 1 {
71
+ for ( i, & dup) in dups. iter ( ) . enumerate ( ) {
72
+ let item = & mut self . items [ dup] ;
73
+ if let CoverageItemKind :: Function { name } = & item. kind {
74
+ item. kind =
75
+ CoverageItemKind :: Function { name : format ! ( "{name}.{i}" ) . into ( ) } ;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ fn visit_contract ( & mut self , node : & Node ) -> eyre:: Result < ( ) > {
44
83
// Find all functions and walk their AST
45
84
for node in & node. nodes {
46
85
match node. node_type {
@@ -59,14 +98,14 @@ impl<'a> ContractVisitor<'a> {
59
98
fn visit_function_definition ( & mut self , node : & Node ) -> eyre:: Result < ( ) > {
60
99
let Some ( body) = & node. body else { return Ok ( ( ) ) } ;
61
100
62
- let name: String =
101
+ let name: Box < str > =
63
102
node. attribute ( "name" ) . ok_or_else ( || eyre:: eyre!( "Function has no name" ) ) ?;
64
- let kind: String =
103
+ let kind: Box < str > =
65
104
node. attribute ( "kind" ) . ok_or_else ( || eyre:: eyre!( "Function has no kind" ) ) ?;
66
105
67
106
// TODO: We currently can only detect empty bodies in normal functions, not any of the other
68
107
// kinds: https://github.com/foundry-rs/foundry/issues/9458
69
- if kind != "function" && !has_statements ( body) {
108
+ if & * kind != "function" && !has_statements ( body) {
70
109
return Ok ( ( ) ) ;
71
110
}
72
111
@@ -79,16 +118,12 @@ impl<'a> ContractVisitor<'a> {
79
118
}
80
119
81
120
fn visit_modifier_or_yul_fn_definition ( & mut self , node : & Node ) -> eyre:: Result < ( ) > {
82
- let name: String =
83
- node. attribute ( "name" ) . ok_or_else ( || eyre:: eyre!( "Modifier has no name" ) ) ?;
121
+ let Some ( body) = & node. body else { return Ok ( ( ) ) } ;
84
122
85
- match & node. body {
86
- Some ( body) => {
87
- self . push_item_kind ( CoverageItemKind :: Function { name } , & node. src ) ;
88
- self . visit_block ( body)
89
- }
90
- _ => Ok ( ( ) ) ,
91
- }
123
+ let name: Box < str > =
124
+ node. attribute ( "name" ) . ok_or_else ( || eyre:: eyre!( "Modifier has no name" ) ) ?;
125
+ self . push_item_kind ( CoverageItemKind :: Function { name } , & node. src ) ;
126
+ self . visit_block ( body)
92
127
}
93
128
94
129
fn visit_block ( & mut self , node : & Node ) -> eyre:: Result < ( ) > {
@@ -571,6 +606,7 @@ impl SourceAnalysis {
571
606
/// Note: Source IDs are only unique per compilation job; that is, a code base compiled with
572
607
/// two different solc versions will produce overlapping source IDs if the compiler version is
573
608
/// not taken into account.
609
+ #[ instrument( name = "SourceAnalysis::new" , skip_all) ]
574
610
pub fn new ( data : & SourceFiles < ' _ > ) -> eyre:: Result < Self > {
575
611
let mut sourced_items = data
576
612
. sources
@@ -592,23 +628,12 @@ impl SourceAnalysis {
592
628
let name = node
593
629
. attribute ( "name" )
594
630
. ok_or_else ( || eyre:: eyre!( "Contract has no name" ) ) ?;
595
-
631
+ let _guard = debug_span ! ( "visit_contract" , %name ) . entered ( ) ;
596
632
let mut visitor = ContractVisitor :: new ( source_id, & source. content , & name) ;
597
633
visitor. visit_contract ( node) ?;
598
- let mut items = visitor. items ;
599
-
600
- let is_test = items. iter ( ) . any ( |item| {
601
- if let CoverageItemKind :: Function { name } = & item. kind {
602
- name. is_any_test ( )
603
- } else {
604
- false
605
- }
606
- } ) ;
607
- if is_test {
608
- items. clear ( ) ;
609
- }
610
-
611
- Ok ( items)
634
+ visitor. clear_if_test ( ) ;
635
+ visitor. disambiguate_functions ( ) ;
636
+ Ok ( visitor. items )
612
637
} ) ;
613
638
items. map ( move |items| items. map ( |items| ( source_id, items) ) )
614
639
} )
0 commit comments