@@ -3,7 +3,7 @@ use std::collections::VecDeque;
3
3
use rustc_middle:: mir:: coverage:: {
4
4
BlockMarkerId , ConditionId , ConditionInfo , MCDCBranchSpan , MCDCDecisionSpan ,
5
5
} ;
6
- use rustc_middle:: mir:: { BasicBlock , SourceInfo } ;
6
+ use rustc_middle:: mir:: BasicBlock ;
7
7
use rustc_middle:: thir:: LogicalOp ;
8
8
use rustc_middle:: ty:: TyCtxt ;
9
9
use rustc_span:: Span ;
@@ -16,34 +16,24 @@ use crate::errors::MCDCExceedsConditionNumLimit;
16
16
/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
17
17
const MAX_CONDITIONS_NUM_IN_DECISION : usize = 6 ;
18
18
19
- #[ derive( Default ) ]
20
19
struct MCDCDecisionCtx {
21
20
/// To construct condition evaluation tree.
21
+ decision_info : MCDCDecisionSpan ,
22
22
decision_stack : VecDeque < ConditionInfo > ,
23
- processing_decision : Option < MCDCDecisionSpan > ,
23
+ conditions : Vec < MCDCBranchSpan > ,
24
24
}
25
25
26
- struct MCDCState {
27
- decision_ctx_stack : Vec < MCDCDecisionCtx > ,
28
- }
29
-
30
- impl MCDCState {
31
- fn new ( ) -> Self {
32
- Self { decision_ctx_stack : vec ! [ MCDCDecisionCtx :: default ( ) ] }
33
- }
34
-
35
- /// Decision depth is given as a u16 to reduce the size of the `CoverageKind`,
36
- /// as it is very unlikely that the depth ever reaches 2^16.
37
- #[ inline]
38
- fn decision_depth ( & self ) -> u16 {
39
- match u16:: try_from ( self . decision_ctx_stack . len ( ) )
40
- . expect (
41
- "decision depth did not fit in u16, this is likely to be an instrumentation error" ,
42
- )
43
- . checked_sub ( 1 )
44
- {
45
- Some ( d) => d,
46
- None => bug ! ( "Unexpected empty decision stack" ) ,
26
+ impl MCDCDecisionCtx {
27
+ fn new ( decision_depth : u16 ) -> Self {
28
+ Self {
29
+ decision_stack : VecDeque :: new ( ) ,
30
+ decision_info : MCDCDecisionSpan {
31
+ span : Span :: default ( ) ,
32
+ conditions_num : 0 ,
33
+ end_markers : vec ! [ ] ,
34
+ decision_depth,
35
+ } ,
36
+ conditions : vec ! [ ] ,
47
37
}
48
38
}
49
39
@@ -87,34 +77,17 @@ impl MCDCState {
87
77
// As the compiler tracks expression in pre-order, we can ensure that condition info of parents are always properly assigned when their children are visited.
88
78
// - If the op is AND, the "false_next" of LHS and RHS should be the parent's "false_next". While "true_next" of the LHS is the RHS, the "true next" of RHS is the parent's "true_next".
89
79
// - If the op is OR, the "true_next" of LHS and RHS should be the parent's "true_next". While "false_next" of the LHS is the RHS, the "false next" of RHS is the parent's "false_next".
90
- fn record_conditions ( & mut self , op : LogicalOp , span : Span ) {
91
- let decision_depth = self . decision_depth ( ) ;
92
- let Some ( decision_ctx) = self . decision_ctx_stack . last_mut ( ) else {
93
- bug ! ( "Unexpected empty decision_ctx_stack" )
94
- } ;
95
- let decision = match decision_ctx. processing_decision . as_mut ( ) {
96
- Some ( decision) => {
97
- decision. span = decision. span . to ( span) ;
98
- decision
99
- }
100
- None => decision_ctx. processing_decision . insert ( MCDCDecisionSpan {
101
- span,
102
- conditions_num : 0 ,
103
- end_markers : vec ! [ ] ,
104
- decision_depth,
105
- } ) ,
106
- } ;
107
-
108
- let parent_condition = decision_ctx. decision_stack . pop_back ( ) . unwrap_or_default ( ) ;
80
+ fn record_conditions ( & mut self , op : LogicalOp ) {
81
+ let parent_condition = self . decision_stack . pop_back ( ) . unwrap_or_default ( ) ;
109
82
let lhs_id = if parent_condition. condition_id == ConditionId :: NONE {
110
- decision . conditions_num += 1 ;
111
- ConditionId :: from ( decision . conditions_num )
83
+ self . decision_info . conditions_num += 1 ;
84
+ ConditionId :: from ( self . decision_info . conditions_num )
112
85
} else {
113
86
parent_condition. condition_id
114
87
} ;
115
88
116
- decision . conditions_num += 1 ;
117
- let rhs_condition_id = ConditionId :: from ( decision . conditions_num ) ;
89
+ self . decision_info . conditions_num += 1 ;
90
+ let rhs_condition_id = ConditionId :: from ( self . decision_info . conditions_num ) ;
118
91
119
92
let ( lhs, rhs) = match op {
120
93
LogicalOp :: And => {
@@ -145,85 +118,118 @@ impl MCDCState {
145
118
}
146
119
} ;
147
120
// We visit expressions tree in pre-order, so place the left-hand side on the top.
148
- decision_ctx . decision_stack . push_back ( rhs) ;
149
- decision_ctx . decision_stack . push_back ( lhs) ;
121
+ self . decision_stack . push_back ( rhs) ;
122
+ self . decision_stack . push_back ( lhs) ;
150
123
}
151
124
152
- fn take_condition (
125
+ fn finish_two_way_branch (
153
126
& mut self ,
127
+ span : Span ,
128
+ test_marker : BlockMarkerId ,
154
129
true_marker : BlockMarkerId ,
155
130
false_marker : BlockMarkerId ,
156
- ) -> ( Option < ConditionInfo > , Option < MCDCDecisionSpan > ) {
157
- let Some ( decision_ctx) = self . decision_ctx_stack . last_mut ( ) else {
158
- bug ! ( "Unexpected empty decision_ctx_stack" )
159
- } ;
160
- let Some ( condition_info) = decision_ctx. decision_stack . pop_back ( ) else {
161
- return ( None , None ) ;
162
- } ;
163
- let Some ( decision) = decision_ctx. processing_decision . as_mut ( ) else {
164
- bug ! ( "Processing decision should have been created before any conditions are taken" ) ;
165
- } ;
131
+ ) {
132
+ let condition_info = self . decision_stack . pop_back ( ) . unwrap_or_default ( ) ;
166
133
if condition_info. true_next_id == ConditionId :: NONE {
167
- decision . end_markers . push ( true_marker) ;
134
+ self . decision_info . end_markers . push ( true_marker) ;
168
135
}
169
136
if condition_info. false_next_id == ConditionId :: NONE {
170
- decision . end_markers . push ( false_marker) ;
137
+ self . decision_info . end_markers . push ( false_marker) ;
171
138
}
172
139
173
- if decision_ctx. decision_stack . is_empty ( ) {
174
- ( Some ( condition_info) , decision_ctx. processing_decision . take ( ) )
175
- } else {
176
- ( Some ( condition_info) , None )
177
- }
140
+ self . conditions . push ( MCDCBranchSpan {
141
+ span,
142
+ condition_info : Some ( condition_info) ,
143
+ test_markers : vec ! [ test_marker] ,
144
+ true_markers : vec ! [ true_marker] ,
145
+ decision_depth : self . decision_info . decision_depth ,
146
+ } ) ;
147
+ // In case this decision had only one condition
148
+ self . decision_info . conditions_num = self . decision_info . conditions_num . max ( 1 ) ;
149
+ }
150
+
151
+ fn finished ( & self ) -> bool {
152
+ self . decision_stack . is_empty ( )
178
153
}
179
154
}
180
155
181
156
pub struct MCDCInfoBuilder {
182
157
branch_spans : Vec < MCDCBranchSpan > ,
183
158
decision_spans : Vec < MCDCDecisionSpan > ,
184
- state : MCDCState ,
159
+ decision_ctx_stack : Vec < MCDCDecisionCtx > ,
160
+ base_depth : u16 ,
185
161
}
186
162
187
163
impl MCDCInfoBuilder {
188
164
pub fn new ( ) -> Self {
189
- Self { branch_spans : vec ! [ ] , decision_spans : vec ! [ ] , state : MCDCState :: new ( ) }
165
+ Self {
166
+ branch_spans : vec ! [ ] ,
167
+ decision_spans : vec ! [ ] ,
168
+ decision_ctx_stack : vec ! [ ] ,
169
+ base_depth : 0 ,
170
+ }
190
171
}
191
172
192
- pub fn visit_evaluated_condition (
173
+ fn next_decision_depth ( & self ) -> u16 {
174
+ u16:: try_from ( self . decision_ctx_stack . len ( ) ) . expect (
175
+ "decision depth did not fit in u16, this is likely to be an instrumentation error" ,
176
+ )
177
+ }
178
+
179
+ fn ensure_decision ( & mut self , span : Span ) -> & mut MCDCDecisionCtx {
180
+ if self . base_depth == self . next_decision_depth ( ) {
181
+ let depth = self . next_decision_depth ( ) ;
182
+ self . decision_ctx_stack . push ( MCDCDecisionCtx :: new ( depth) ) ;
183
+ } else {
184
+ assert ! (
185
+ self . base_depth <= self . next_decision_depth( ) ,
186
+ "expected depth shall not skip next decision depth"
187
+ ) ;
188
+ }
189
+ let ctx = self . decision_ctx_stack . last_mut ( ) . expect ( "ensured above" ) ;
190
+
191
+ if ctx. decision_info . span == Span :: default ( ) {
192
+ ctx. decision_info . span = span;
193
+ } else {
194
+ ctx. decision_info . span = ctx. decision_info . span . to ( span) ;
195
+ }
196
+ ctx
197
+ }
198
+
199
+ fn try_finish_decision ( & mut self , tcx : TyCtxt < ' _ > ) {
200
+ if !self . decision_ctx_stack . last ( ) . is_some_and ( |decision| decision. finished ( ) ) {
201
+ return ;
202
+ }
203
+ let MCDCDecisionCtx { decision_info, decision_stack : _, conditions } =
204
+ self . decision_ctx_stack . pop ( ) . expect ( "has checked above" ) ;
205
+
206
+ self . append_mcdc_info ( tcx, decision_info, conditions) ;
207
+ }
208
+
209
+ fn append_mcdc_info (
193
210
& mut self ,
194
211
tcx : TyCtxt < ' _ > ,
195
- source_info : SourceInfo ,
196
- true_block : BasicBlock ,
197
- false_block : BasicBlock ,
198
- mut inject_block_marker : impl FnMut ( SourceInfo , BasicBlock ) -> BlockMarkerId ,
212
+ decision : MCDCDecisionSpan ,
213
+ mut conditions : Vec < MCDCBranchSpan > ,
199
214
) {
200
- let true_marker = inject_block_marker ( source_info, true_block) ;
201
- let false_marker = inject_block_marker ( source_info, false_block) ;
202
-
203
- let decision_depth = self . state . decision_depth ( ) ;
204
- let ( mut condition_info, decision_result) =
205
- self . state . take_condition ( true_marker, false_marker) ;
206
215
// take_condition() returns Some for decision_result when the decision stack
207
216
// is empty, i.e. when all the conditions of the decision were instrumented,
208
217
// and the decision is "complete".
209
- if let Some ( decision) = decision_result {
210
- match decision. conditions_num {
211
- 0 => {
212
- unreachable ! ( "Decision with no condition is not expected" ) ;
213
- }
214
- 1 ..=MAX_CONDITIONS_NUM_IN_DECISION => {
215
- self . decision_spans . push ( decision) ;
216
- }
217
- _ => {
218
- // Do not generate mcdc mappings and statements for decisions with too many conditions.
219
- let rebase_idx = self . branch_spans . len ( ) - decision. conditions_num + 1 ;
220
- for branch in & mut self . branch_spans [ rebase_idx..] {
221
- branch. condition_info = None ;
222
- }
223
-
224
- // ConditionInfo of this branch shall also be reset.
225
- condition_info = None ;
226
-
218
+ match decision. conditions_num {
219
+ 0 => {
220
+ unreachable ! ( "Decision with no condition is not expected" ) ;
221
+ }
222
+ 2 ..=MAX_CONDITIONS_NUM_IN_DECISION => {
223
+ self . decision_spans . push ( decision) ;
224
+ self . branch_spans . extend ( conditions) ;
225
+ }
226
+ // MCDC is equivalent to normal branch coverage if number of conditions is less than 1, so ignore these decisions.
227
+ // See comment of `MAX_CONDITIONS_NUM_IN_DECISION` for why decisions with oversized conditions are ignored.
228
+ _ => {
229
+ // Generate normal branch coverage mappings in such cases.
230
+ conditions. iter_mut ( ) . for_each ( |branch| branch. condition_info = None ) ;
231
+ self . branch_spans . extend ( conditions) ;
232
+ if decision. conditions_num > MAX_CONDITIONS_NUM_IN_DECISION {
227
233
tcx. dcx ( ) . emit_warn ( MCDCExceedsConditionNumLimit {
228
234
span : decision. span ,
229
235
conditions_num : decision. conditions_num ,
@@ -232,13 +238,23 @@ impl MCDCInfoBuilder {
232
238
}
233
239
}
234
240
}
235
- self . branch_spans . push ( MCDCBranchSpan {
236
- span : source_info. span ,
237
- condition_info,
238
- true_marker,
239
- false_marker,
240
- decision_depth,
241
- } ) ;
241
+ }
242
+
243
+ pub fn visit_evaluated_condition (
244
+ & mut self ,
245
+ tcx : TyCtxt < ' _ > ,
246
+ span : Span ,
247
+ test_block : BasicBlock ,
248
+ true_block : BasicBlock ,
249
+ false_block : BasicBlock ,
250
+ mut inject_block_marker : impl FnMut ( BasicBlock ) -> BlockMarkerId ,
251
+ ) {
252
+ let test_marker = inject_block_marker ( test_block) ;
253
+ let true_marker = inject_block_marker ( true_block) ;
254
+ let false_marker = inject_block_marker ( false_block) ;
255
+ let decision = self . ensure_decision ( span) ;
256
+ decision. finish_two_way_branch ( span, test_marker, true_marker, false_marker) ;
257
+ self . try_finish_decision ( tcx) ;
242
258
}
243
259
244
260
pub fn into_done ( self ) -> ( Vec < MCDCDecisionSpan > , Vec < MCDCBranchSpan > ) {
@@ -251,25 +267,24 @@ impl Builder<'_, '_> {
251
267
if let Some ( branch_info) = self . coverage_branch_info . as_mut ( )
252
268
&& let Some ( mcdc_info) = branch_info. mcdc_info . as_mut ( )
253
269
{
254
- mcdc_info. state . record_conditions ( logical_op, span) ;
270
+ let decision = mcdc_info. ensure_decision ( span) ;
271
+ decision. record_conditions ( logical_op) ;
255
272
}
256
273
}
257
274
258
275
pub ( crate ) fn mcdc_increment_depth_if_enabled ( & mut self ) {
259
276
if let Some ( branch_info) = self . coverage_branch_info . as_mut ( )
260
277
&& let Some ( mcdc_info) = branch_info. mcdc_info . as_mut ( )
261
278
{
262
- mcdc_info. state . decision_ctx_stack . push ( MCDCDecisionCtx :: default ( ) ) ;
279
+ mcdc_info. base_depth = mcdc_info . next_decision_depth ( ) . max ( mcdc_info . base_depth + 1 ) ;
263
280
} ;
264
281
}
265
282
266
283
pub ( crate ) fn mcdc_decrement_depth_if_enabled ( & mut self ) {
267
284
if let Some ( branch_info) = self . coverage_branch_info . as_mut ( )
268
285
&& let Some ( mcdc_info) = branch_info. mcdc_info . as_mut ( )
269
286
{
270
- if mcdc_info. state . decision_ctx_stack . pop ( ) . is_none ( ) {
271
- bug ! ( "Unexpected empty decision stack" ) ;
272
- }
287
+ mcdc_info. base_depth -= 1 ;
273
288
} ;
274
289
}
275
290
}
0 commit comments