1
- use rustc_middle:: mir;
2
- use rustc_middle:: mir:: coverage:: BranchSpan ;
1
+ use std:: assert_matches:: assert_matches;
2
+ use std:: collections:: hash_map:: Entry ;
3
+
4
+ use rustc_data_structures:: fx:: FxHashMap ;
5
+ use rustc_middle:: mir:: coverage:: { BlockMarkerId , BranchSpan , CoverageKind } ;
6
+ use rustc_middle:: mir:: { self , BasicBlock , UnOp } ;
7
+ use rustc_middle:: thir:: { ExprId , ExprKind , Thir } ;
3
8
use rustc_middle:: ty:: TyCtxt ;
4
9
use rustc_span:: def_id:: LocalDefId ;
5
10
11
+ use crate :: build:: Builder ;
12
+
6
13
pub ( crate ) struct HirBranchInfoBuilder {
14
+ /// Maps condition expressions to their enclosing `!`, for better instrumentation.
15
+ inversions : FxHashMap < ExprId , Inversion > ,
16
+
7
17
num_block_markers : usize ,
8
18
branch_spans : Vec < BranchSpan > ,
9
19
}
10
20
21
+ #[ derive( Clone , Copy ) ]
22
+ struct Inversion {
23
+ /// When visiting the associated expression as a branch condition, treat this
24
+ /// enclosing `!` as the branch condition instead.
25
+ enclosing_not : ExprId ,
26
+ /// True if the associated expression is nested within an odd number of `!`
27
+ /// expressions relative to `enclosing_not` (inclusive of `enclosing_not`).
28
+ is_inverted : bool ,
29
+ }
30
+
11
31
impl HirBranchInfoBuilder {
12
32
/// Creates a new branch info builder, but only if branch coverage instrumentation
13
33
/// is enabled and `def_id` represents a function that is eligible for coverage.
14
34
pub ( crate ) fn new_if_enabled ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> Option < Self > {
15
35
if tcx. sess . instrument_coverage_branch ( ) && tcx. is_eligible_for_coverage ( def_id) {
16
36
Some ( Self {
17
- // (placeholder)
37
+ inversions : FxHashMap :: default ( ) ,
18
38
num_block_markers : 0 ,
19
39
branch_spans : vec ! [ ] ,
20
40
} )
@@ -23,8 +43,54 @@ impl HirBranchInfoBuilder {
23
43
}
24
44
}
25
45
46
+ /// Unary `!` expressions inside an `if` condition are lowered by lowering
47
+ /// their argument instead, and then reversing the then/else arms of that `if`.
48
+ ///
49
+ /// That's awkward for branch coverage instrumentation, so to work around that
50
+ /// we pre-emptively visit any affected `!` expressions, and record extra
51
+ /// information that [`Builder::visit_coverage_branch_condition`] can use to
52
+ /// synthesize branch instrumentation for the enclosing `!`.
53
+ pub ( crate ) fn visit_unary_not ( & mut self , thir : & Thir < ' _ > , unary_not : ExprId ) {
54
+ assert_matches ! ( thir[ unary_not] . kind, ExprKind :: Unary { op: UnOp :: Not , .. } ) ;
55
+
56
+ self . visit_inverted (
57
+ thir,
58
+ unary_not,
59
+ // Set `is_inverted: false` for the `!` itself, so that its enclosed
60
+ // expression will have `is_inverted: true`.
61
+ Inversion { enclosing_not : unary_not, is_inverted : false } ,
62
+ ) ;
63
+ }
64
+
65
+ fn visit_inverted ( & mut self , thir : & Thir < ' _ > , expr_id : ExprId , inversion : Inversion ) {
66
+ match self . inversions . entry ( expr_id) {
67
+ // This expression has already been marked by an enclosing `!`.
68
+ Entry :: Occupied ( _) => return ,
69
+ Entry :: Vacant ( entry) => entry. insert ( inversion) ,
70
+ } ;
71
+
72
+ match thir[ expr_id] . kind {
73
+ ExprKind :: Unary { op : UnOp :: Not , arg } => {
74
+ // Flip the `is_inverted` flag for the contents of this `!`.
75
+ let inversion = Inversion { is_inverted : !inversion. is_inverted , ..inversion } ;
76
+ self . visit_inverted ( thir, arg, inversion) ;
77
+ }
78
+ ExprKind :: Scope { value, .. } => self . visit_inverted ( thir, value, inversion) ,
79
+ ExprKind :: Use { source } => self . visit_inverted ( thir, source, inversion) ,
80
+ // All other expressions (including `&&` and `||`) don't need any
81
+ // special handling of their contents, so stop visiting.
82
+ _ => { }
83
+ }
84
+ }
85
+
86
+ fn next_block_marker_id ( & mut self ) -> BlockMarkerId {
87
+ let id = BlockMarkerId :: from_usize ( self . num_block_markers ) ;
88
+ self . num_block_markers += 1 ;
89
+ id
90
+ }
91
+
26
92
pub ( crate ) fn into_done ( self ) -> Option < Box < mir:: coverage:: HirBranchInfo > > {
27
- let Self { num_block_markers, branch_spans } = self ;
93
+ let Self { inversions : _ , num_block_markers, branch_spans } = self ;
28
94
29
95
if num_block_markers == 0 {
30
96
assert ! ( branch_spans. is_empty( ) ) ;
@@ -34,3 +100,54 @@ impl HirBranchInfoBuilder {
34
100
Some ( Box :: new ( mir:: coverage:: HirBranchInfo { num_block_markers, branch_spans } ) )
35
101
}
36
102
}
103
+
104
+ impl Builder < ' _ , ' _ > {
105
+ /// If branch coverage is enabled, inject marker statements into `then_block`
106
+ /// and `else_block`, and record their IDs in the table of branch spans.
107
+ pub ( crate ) fn visit_coverage_branch_condition (
108
+ & mut self ,
109
+ expr_id : ExprId ,
110
+ then_block : BasicBlock ,
111
+ else_block : BasicBlock ,
112
+ ) {
113
+ // Bail out if branch coverage is not enabled for this function.
114
+ let Some ( branch_info) = self . coverage_branch_info . as_ref ( ) else { return } ;
115
+
116
+ // If this condition expression is nested within one or more `!` expressions,
117
+ // replace it with the enclosing `!` collected by `visit_unary_not`.
118
+ let ( expr_id, is_inverted) = match branch_info. inversions . get ( & expr_id) {
119
+ Some ( & Inversion { enclosing_not, is_inverted } ) => ( enclosing_not, is_inverted) ,
120
+ None => ( expr_id, false ) ,
121
+ } ;
122
+ let source_info = self . source_info ( self . thir [ expr_id] . span ) ;
123
+
124
+ // Now that we have `source_info`, we can upgrade to a &mut reference.
125
+ let branch_info = self . coverage_branch_info . as_mut ( ) . expect ( "upgrading & to &mut" ) ;
126
+
127
+ let mut inject_branch_marker = |block : BasicBlock | {
128
+ let id = branch_info. next_block_marker_id ( ) ;
129
+
130
+ let marker_statement = mir:: Statement {
131
+ source_info,
132
+ kind : mir:: StatementKind :: Coverage ( Box :: new ( mir:: Coverage {
133
+ kind : CoverageKind :: BlockMarker { id } ,
134
+ } ) ) ,
135
+ } ;
136
+ self . cfg . push ( block, marker_statement) ;
137
+
138
+ id
139
+ } ;
140
+
141
+ let mut true_marker = inject_branch_marker ( then_block) ;
142
+ let mut false_marker = inject_branch_marker ( else_block) ;
143
+ if is_inverted {
144
+ std:: mem:: swap ( & mut true_marker, & mut false_marker) ;
145
+ }
146
+
147
+ branch_info. branch_spans . push ( BranchSpan {
148
+ span : source_info. span ,
149
+ true_marker,
150
+ false_marker,
151
+ } ) ;
152
+ }
153
+ }
0 commit comments