@@ -211,59 +211,64 @@ impl<'a> ContractVisitor<'a> {
211
211
// branch ID as we do.
212
212
self . branch_id += 1 ;
213
213
214
- // The relevant source range for the true branch is the `if(...)` statement itself
215
- // and the true body of the if statement. The false body of the
216
- // statement (if any) is processed as its own thing. If this source
217
- // range is not processed like this, it is virtually impossible to
218
- // correctly map instructions back to branches that include more
219
- // complex logic like conditional logic.
220
- let true_branch_loc = & ast:: LowFidelitySourceLocation {
221
- start : node. src . start ,
222
- length : true_body
223
- . src
224
- . length
225
- . map ( |length| true_body. src . start - node. src . start + length) ,
226
- index : node. src . index ,
227
- } ;
228
-
229
- // Add the coverage item for branch 0 (true body).
230
- self . push_item ( CoverageItem {
231
- kind : CoverageItemKind :: Branch { branch_id, path_id : 0 } ,
232
- loc : self . source_location_for ( true_branch_loc) ,
233
- hits : 0 ,
234
- } ) ;
235
-
236
214
match node. attribute :: < Node > ( "falseBody" ) {
237
215
// Both if/else statements.
238
216
Some ( false_body) => {
239
- // Add the coverage item for branch 1 (false body).
240
- // The relevant source range for the false branch is the `else` statement
241
- // itself and the false body of the else statement.
242
- self . push_item ( CoverageItem {
243
- kind : CoverageItemKind :: Branch { branch_id, path_id : 1 } ,
244
- loc : self . source_location_for ( & ast:: LowFidelitySourceLocation {
245
- start : node. src . start ,
246
- length : false_body. src . length . map ( |length| {
247
- false_body. src . start - true_body. src . start + length
217
+ // Add branch coverage items only if one of true/branch bodies contains
218
+ // statements.
219
+ if has_statements ( & true_body) || has_statements ( & false_body) {
220
+ // Add the coverage item for branch 0 (true body).
221
+ // The relevant source range for the true branch is the `if(...)`
222
+ // statement itself and the true body of the if statement.
223
+ //
224
+ // The false body of the statement is processed as its own thing.
225
+ // If this source range is not processed like this, it is virtually
226
+ // impossible to correctly map instructions back to branches that
227
+ // include more complex logic like conditional logic.
228
+ self . push_item ( CoverageItem {
229
+ kind : CoverageItemKind :: Branch { branch_id, path_id : 0 } ,
230
+ loc : self . source_location_for ( & ast:: LowFidelitySourceLocation {
231
+ start : node. src . start ,
232
+ length : true_body. src . length . map ( |length| {
233
+ true_body. src . start - node. src . start + length
234
+ } ) ,
235
+ index : node. src . index ,
248
236
} ) ,
249
- index : node. src . index ,
250
- } ) ,
251
- hits : 0 ,
252
- } ) ;
253
- // Process the true body.
254
- self . visit_block_or_statement ( & true_body) ?;
255
- // Process the false body.
256
- self . visit_block_or_statement ( & false_body) ?;
237
+ hits : 0 ,
238
+ } ) ;
239
+ // Add the coverage item for branch 1 (false body).
240
+ // The relevant source range for the false branch is the `else`
241
+ // statement itself and the false body of the else statement.
242
+ self . push_item ( CoverageItem {
243
+ kind : CoverageItemKind :: Branch { branch_id, path_id : 1 } ,
244
+ loc : self . source_location_for ( & ast:: LowFidelitySourceLocation {
245
+ start : node. src . start ,
246
+ length : false_body. src . length . map ( |length| {
247
+ false_body. src . start - true_body. src . start + length
248
+ } ) ,
249
+ index : node. src . index ,
250
+ } ) ,
251
+ hits : 0 ,
252
+ } ) ;
253
+
254
+ // Process the true body.
255
+ self . visit_block_or_statement ( & true_body) ?;
256
+ // Process the false body.
257
+ self . visit_block_or_statement ( & false_body) ?;
258
+ }
257
259
}
258
260
None => {
259
- // Add the coverage item for branch 1 (same true body).
260
- self . push_item ( CoverageItem {
261
- kind : CoverageItemKind :: Branch { branch_id, path_id : 1 } ,
262
- loc : self . source_location_for ( true_branch_loc) ,
263
- hits : 0 ,
264
- } ) ;
265
- // Process the true body.
266
- self . visit_block_or_statement ( & true_body) ?;
261
+ // Add single branch coverage only if it contains statements.
262
+ if has_statements ( & true_body) {
263
+ // Add the coverage item for branch 0 (true body).
264
+ self . push_item ( CoverageItem {
265
+ kind : CoverageItemKind :: SinglePathBranch { branch_id } ,
266
+ loc : self . source_location_for ( & true_body. src ) ,
267
+ hits : 0 ,
268
+ } ) ;
269
+ // Process the true body.
270
+ self . visit_block_or_statement ( & true_body) ?;
271
+ }
267
272
}
268
273
}
269
274
@@ -321,9 +326,7 @@ impl<'a> ContractVisitor<'a> {
321
326
322
327
// Add coverage for clause body only if it is not empty.
323
328
if let Some ( block) = clause. attribute :: < Node > ( "block" ) {
324
- let statements: Vec < Node > =
325
- block. attribute ( "statements" ) . unwrap_or_default ( ) ;
326
- if !statements. is_empty ( ) {
329
+ if has_statements ( & block) {
327
330
self . push_item ( CoverageItem {
328
331
kind : CoverageItemKind :: Statement ,
329
332
loc : self . source_location_for ( & block. src ) ,
@@ -502,8 +505,12 @@ impl<'a> ContractVisitor<'a> {
502
505
let source_location = & item. loc ;
503
506
504
507
// Push a line item if we haven't already
505
- if matches ! ( item. kind, CoverageItemKind :: Statement | CoverageItemKind :: Branch { .. } ) &&
506
- self . last_line < source_location. line
508
+ if matches ! (
509
+ item. kind,
510
+ CoverageItemKind :: Statement |
511
+ CoverageItemKind :: Branch { .. } |
512
+ CoverageItemKind :: SinglePathBranch { .. }
513
+ ) && self . last_line < source_location. line
507
514
{
508
515
self . items . push ( CoverageItem {
509
516
kind : CoverageItemKind :: Line ,
@@ -527,6 +534,12 @@ impl<'a> ContractVisitor<'a> {
527
534
}
528
535
}
529
536
537
+ /// Helper function to check if a given node contains any statement.
538
+ fn has_statements ( node : & Node ) -> bool {
539
+ let statements: Vec < Node > = node. attribute ( "statements" ) . unwrap_or_default ( ) ;
540
+ !statements. is_empty ( )
541
+ }
542
+
530
543
/// [`SourceAnalyzer`] result type.
531
544
#[ derive( Debug ) ]
532
545
pub struct SourceAnalysis {
0 commit comments