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