@@ -7,14 +7,15 @@ use slang_solidity::{
7
7
parser:: { ParseError , Parser as SolidityParser , ParserInitializationError } ,
8
8
} ;
9
9
10
- #[ derive( Serialize ) ]
10
+ #[ derive( Debug , Serialize ) ]
11
11
pub struct InstrumentationMetadata {
12
12
pub tag : B256 ,
13
13
pub kind : & ' static str ,
14
14
pub start_utf16 : usize ,
15
15
pub end_utf16 : usize ,
16
16
}
17
17
18
+ #[ derive( Debug ) ]
18
19
pub struct InstrumentationResult {
19
20
pub source : String ,
20
21
pub metadata : Vec < InstrumentationMetadata > ,
@@ -44,20 +45,37 @@ fn compute_deterministic_hash_for_statement(
44
45
pub enum InstrumentationError {
45
46
#[ error( transparent) ]
46
47
Initialization ( #[ from] ParserInitializationError ) ,
48
+ #[ error( "Invalid library path." ) ]
49
+ InvalidLibraryPath {
50
+ errors : Vec < ParseError > ,
51
+ import : String ,
52
+ } ,
47
53
#[ error( "Failed to parse Solidity file." ) ]
48
- ParseError { errors : Vec < ParseError > } ,
54
+ InvalidSourceCode { errors : Vec < ParseError > } ,
49
55
}
50
56
51
57
pub fn instrument_code (
52
58
source_code : & str ,
53
59
source_id : & str ,
54
60
solidity_version : Version ,
61
+ coverage_library_path : & str ,
55
62
) -> Result < InstrumentationResult , InstrumentationError > {
56
63
let parser = SolidityParser :: create ( solidity_version) ?;
57
- let parse_result = parser. parse ( SolidityParser :: ROOT_KIND , source_code) ;
58
- if !parse_result. is_valid ( ) {
59
- return Err ( InstrumentationError :: ParseError {
60
- errors : parse_result. errors ( ) . clone ( ) ,
64
+ let parsed_file = parser. parse_file_contents ( source_code) ;
65
+ if !parsed_file. is_valid ( ) {
66
+ return Err ( InstrumentationError :: InvalidSourceCode {
67
+ errors : parsed_file. errors ( ) . clone ( ) ,
68
+ } ) ;
69
+ }
70
+
71
+ let coverage_library_import = format ! ( "\n import \" {coverage_library_path}\" ;" ) ;
72
+ let parsed_library_import =
73
+ parser. parse_nonterminal ( NonterminalKind :: ImportDirective , & coverage_library_import) ;
74
+
75
+ if !parsed_library_import. is_valid ( ) {
76
+ return Err ( InstrumentationError :: InvalidLibraryPath {
77
+ errors : parsed_library_import. errors ( ) . clone ( ) ,
78
+ import : coverage_library_import,
61
79
} ) ;
62
80
}
63
81
@@ -68,7 +86,7 @@ pub fn instrument_code(
68
86
let mut metadata = Vec :: new ( ) ;
69
87
70
88
let mut statement_counter: u64 = 0 ;
71
- let mut cursor = parse_result . create_tree_cursor ( ) ;
89
+ let mut cursor = parsed_file . create_tree_cursor ( ) ;
72
90
while cursor. go_to_next ( ) {
73
91
match cursor. node ( ) {
74
92
Node :: Nonterminal ( node) if node. kind == NonterminalKind :: Statement => {
@@ -97,7 +115,7 @@ pub fn instrument_code(
97
115
}
98
116
}
99
117
100
- instrumented_source. push_str ( " \n import \" hardhat/coverage.sol \" ;" ) ;
118
+ instrumented_source. push_str ( & coverage_library_import ) ;
101
119
102
120
Ok ( InstrumentationResult {
103
121
source : instrumented_source,
@@ -112,6 +130,7 @@ mod tests {
112
130
use super :: * ;
113
131
114
132
const FIXTURE : & str = include_str ! ( "../../../data/contracts/instrumentation.sol" ) ;
133
+ const LIBRARY_PATH : & str = "__hardhat_coverage.sol" ;
115
134
116
135
fn assert_metadata (
117
136
metadata : & InstrumentationMetadata ,
@@ -141,8 +160,8 @@ mod tests {
141
160
#[ test]
142
161
fn determinism ( ) {
143
162
let version = Version :: parse ( "0.8.0" ) . expect ( "Failed to parse version" ) ;
144
- let result =
145
- instrument_code ( FIXTURE , "instrumentation.sol" , version ) . expect ( "Failed to instrument" ) ;
163
+ let result = instrument_code ( FIXTURE , "instrumentation.sol" , version , LIBRARY_PATH )
164
+ . expect ( "Failed to instrument" ) ;
146
165
147
166
let tags = result
148
167
. metadata
@@ -164,17 +183,38 @@ mod tests {
164
183
#[ test]
165
184
fn import ( ) {
166
185
let version = Version :: parse ( "0.8.0" ) . expect ( "Failed to parse version" ) ;
167
- let result =
168
- instrument_code ( FIXTURE , "instrumentation.sol" , version ) . expect ( "Failed to instrument" ) ;
186
+ let result = instrument_code ( FIXTURE , "instrumentation.sol" , version , LIBRARY_PATH )
187
+ . expect ( "Failed to instrument" ) ;
169
188
170
- assert ! ( result. source. contains( "import \" hardhat/coverage.sol\" ;" ) ) ;
189
+ assert ! (
190
+ result
191
+ . source
192
+ . contains( & format!( "import \" {LIBRARY_PATH}\" ;" ) )
193
+ ) ;
194
+ }
195
+
196
+ #[ test]
197
+ fn invalid_import ( ) {
198
+ let version = Version :: parse ( "0.8.0" ) . expect ( "Failed to parse version" ) ;
199
+ let result = instrument_code (
200
+ FIXTURE ,
201
+ "instrumentation.sol" ,
202
+ version,
203
+ "\" path/with/quotes.sol\" " ,
204
+ )
205
+ . expect_err ( "Expected an error for invalid import path" ) ;
206
+
207
+ assert ! ( matches!(
208
+ result,
209
+ InstrumentationError :: InvalidLibraryPath { .. }
210
+ ) ) ;
171
211
}
172
212
173
213
#[ test]
174
214
fn instrumentation ( ) {
175
215
let version = Version :: parse ( "0.8.0" ) . expect ( "Failed to parse version" ) ;
176
- let result =
177
- instrument_code ( FIXTURE , "instrumentation.sol" , version ) . expect ( "Failed to instrument" ) ;
216
+ let result = instrument_code ( FIXTURE , "instrumentation.sol" , version , LIBRARY_PATH )
217
+ . expect ( "Failed to instrument" ) ;
178
218
179
219
assert ! ( result. source. contains( "__HardhatCoverage.sendHit(" ) ) ;
180
220
assert ! ( result. source. contains( ");" ) ) ;
@@ -183,8 +223,8 @@ mod tests {
183
223
#[ test]
184
224
fn mapping ( ) {
185
225
let version = Version :: parse ( "0.8.0" ) . expect ( "Failed to parse version" ) ;
186
- let result =
187
- instrument_code ( FIXTURE , "instrumentation.sol" , version ) . expect ( "Failed to instrument" ) ;
226
+ let result = instrument_code ( FIXTURE , "instrumentation.sol" , version , LIBRARY_PATH )
227
+ . expect ( "Failed to instrument" ) ;
188
228
189
229
assert_eq ! ( result. metadata. len( ) , 3 ) ;
190
230
0 commit comments