@@ -13,13 +13,24 @@ use rustc_span::def_id::LocalDefId;
13
13
use rustc_span:: Span ;
14
14
15
15
use crate :: build:: Builder ;
16
+ use crate :: errors:: MCDCNestedDecision ;
16
17
17
18
pub ( crate ) struct BranchInfoBuilder {
18
19
/// Maps condition expressions to their enclosing `!`, for better instrumentation.
19
20
nots : FxHashMap < ExprId , NotInfo > ,
20
21
21
22
num_block_markers : usize ,
22
23
branch_spans : Vec < BranchSpan > ,
24
+
25
+ // MCDC decision stuff
26
+ /// ID of the current decision.
27
+ /// Do not use directly. Use the function instead, as it will hide
28
+ /// the decision in the scope of nested decisions.
29
+ current_decision_id : Option < DecisionMarkerId > ,
30
+ /// Track the nesting level of decision to avoid MCDC instrumentation of
31
+ /// nested decisions.
32
+ nested_decision_level : u32 ,
33
+ /// Vector for storing all the decisions with their span
23
34
decisions : IndexVec < DecisionMarkerId , Span > ,
24
35
}
25
36
@@ -44,6 +55,8 @@ impl BranchInfoBuilder {
44
55
nots : FxHashMap :: default ( ) ,
45
56
num_block_markers : 0 ,
46
57
branch_spans : vec ! [ ] ,
58
+ current_decision_id : None ,
59
+ nested_decision_level : 0 ,
47
60
decisions : IndexVec :: new ( ) ,
48
61
} )
49
62
} else {
@@ -98,7 +111,7 @@ impl BranchInfoBuilder {
98
111
}
99
112
100
113
pub ( crate ) fn into_done ( self ) -> Option < Box < mir:: coverage:: BranchInfo > > {
101
- let Self { nots : _, num_block_markers, branch_spans, decisions } = self ;
114
+ let Self { nots : _, num_block_markers, branch_spans, decisions, .. } = self ;
102
115
103
116
if num_block_markers == 0 {
104
117
assert ! ( branch_spans. is_empty( ) ) ;
@@ -122,6 +135,32 @@ impl BranchInfoBuilder {
122
135
decision_spans,
123
136
} ) )
124
137
}
138
+
139
+ /// Increase the nested decision level and return true if the
140
+ /// decision can be instrumented (not in a nested condition).
141
+ pub fn enter_decision ( & mut self , span : Span ) -> bool {
142
+ self . nested_decision_level += 1 ;
143
+ let can_mcdc = !self . in_nested_condition ( ) ;
144
+
145
+ if can_mcdc {
146
+ self . current_decision_id = Some ( self . decisions . push ( span) ) ;
147
+ }
148
+
149
+ can_mcdc
150
+ }
151
+
152
+ pub fn exit_decision ( & mut self ) {
153
+ self . nested_decision_level -= 1 ;
154
+ }
155
+
156
+ /// Return true if the current decision is located inside another decision.
157
+ pub fn in_nested_condition ( & self ) -> bool {
158
+ self . nested_decision_level > 1
159
+ }
160
+
161
+ pub fn current_decision_id ( & self ) -> Option < DecisionMarkerId > {
162
+ if self . in_nested_condition ( ) { None } else { self . current_decision_id }
163
+ }
125
164
}
126
165
127
166
impl Builder < ' _ , ' _ > {
@@ -132,7 +171,6 @@ impl Builder<'_, '_> {
132
171
mut expr_id : ExprId ,
133
172
mut then_block : BasicBlock ,
134
173
mut else_block : BasicBlock ,
135
- decision_id : Option < DecisionMarkerId > , // If MCDC is enabled
136
174
) {
137
175
// Bail out if branch coverage is not enabled for this function.
138
176
let Some ( branch_info) = self . coverage_branch_info . as_ref ( ) else { return } ;
@@ -153,7 +191,7 @@ impl Builder<'_, '_> {
153
191
let mut inject_branch_marker = |block : BasicBlock | {
154
192
let id = branch_info. next_block_marker_id ( ) ;
155
193
156
- let cov_kind = if let Some ( decision_id) = decision_id {
194
+ let cov_kind = if let Some ( decision_id) = branch_info . current_decision_id ( ) {
157
195
CoverageKind :: MCDCBlockMarker { id, decision_id }
158
196
} else {
159
197
CoverageKind :: BlockMarker { id }
@@ -171,30 +209,35 @@ impl Builder<'_, '_> {
171
209
172
210
branch_info. branch_spans . push ( BranchSpan {
173
211
span : source_info. span ,
174
- // FIXME: Handle case when MCDC is disabled better than just putting 0.
175
- decision_id : decision_id . unwrap_or ( DecisionMarkerId :: from_u32 ( 0 ) ) ,
212
+ // FIXME(dprn) : Handle case when MCDC is disabled better than just putting 0.
213
+ decision_id : branch_info . current_decision_id . unwrap_or ( DecisionMarkerId :: from_u32 ( 0 ) ) ,
176
214
true_marker,
177
215
false_marker,
178
216
} ) ;
179
217
}
180
218
181
- /// If MCDC coverage is enabled, inject a marker in all the decisions
182
- /// (boolean expressions)
183
- pub ( crate ) fn visit_coverage_decision (
184
- & mut self ,
185
- expr_id : ExprId ,
186
- block : BasicBlock ,
187
- ) -> Option < DecisionMarkerId > {
219
+ /// If MCDC coverage is enabled, inject a decision entry marker in the given decision.
220
+ /// return true
221
+ pub ( crate ) fn begin_mcdc_decision_coverage ( & mut self , expr_id : ExprId , block : BasicBlock ) {
188
222
// Early return if MCDC coverage is not enabled.
189
223
if !self . tcx . sess . instrument_coverage_mcdc ( ) {
190
- return None ;
224
+ return ;
191
225
}
192
226
let Some ( branch_info) = self . coverage_branch_info . as_mut ( ) else {
193
- return None ;
227
+ return ;
194
228
} ;
195
229
196
230
let span = self . thir [ expr_id] . span ;
197
- let decision_id = branch_info. decisions . push ( span) ;
231
+
232
+ // enter_decision returns false if it detects nested decisions.
233
+ if !branch_info. enter_decision ( span) {
234
+ // FIXME(dprn): do WARNING for nested decision.
235
+ debug ! ( "MCDC: Unsupported nested decision" ) ;
236
+ self . tcx . dcx ( ) . emit_warn ( MCDCNestedDecision { span } ) ;
237
+ return ;
238
+ }
239
+
240
+ let decision_id = branch_info. current_decision_id ( ) . expect ( "Should have returned." ) ;
198
241
199
242
// Inject a decision marker
200
243
let source_info = self . source_info ( span) ;
@@ -205,7 +248,58 @@ impl Builder<'_, '_> {
205
248
} ) ,
206
249
} ;
207
250
self . cfg . push ( block, marker_statement) ;
251
+ }
252
+
253
+ /// If MCDC is enabled, and function is instrumented,
254
+ pub ( crate ) fn end_mcdc_decision_coverage ( & mut self ) {
255
+ // Early return if MCDC coverage is not enabled.
256
+ if !self . tcx . sess . instrument_coverage_mcdc ( ) {
257
+ return ;
258
+ }
259
+ let Some ( branch_info) = self . coverage_branch_info . as_mut ( ) else {
260
+ return ;
261
+ } ;
262
+
263
+ // Exit decision now so we can drop &mut to branch info
264
+ branch_info. exit_decision ( ) ;
265
+ }
266
+
267
+ /// If MCDC is enabled and the current decision is being instrumented,
268
+ /// inject an `MCDCDecisionOutputMarker` to the given basic block.
269
+ /// `outcome` should be true for the then block and false for the else block.
270
+ pub ( crate ) fn mcdc_decision_outcome_block (
271
+ & mut self ,
272
+ bb : BasicBlock ,
273
+ outcome : bool ,
274
+ ) -> BasicBlock {
275
+ let Some ( branch_info) = self . coverage_branch_info . as_mut ( ) else {
276
+ // Coverage instrumentation is not enabled.
277
+ return bb;
278
+ } ;
279
+ let Some ( decision_id) = branch_info. current_decision_id ( ) else {
280
+ // Decision is not instrumented
281
+ return bb;
282
+ } ;
283
+
284
+ let span = branch_info. decisions [ decision_id] ;
285
+ let source_info = self . source_info ( span) ;
286
+ let marker_statement = mir:: Statement {
287
+ source_info,
288
+ kind : mir:: StatementKind :: Coverage ( CoverageKind :: MCDCDecisionOutputMarker {
289
+ id : decision_id,
290
+ outcome,
291
+ } ) ,
292
+ } ;
293
+
294
+ // Insert statements at the beginning of the following basic block
295
+ self . cfg . block_data_mut ( bb) . statements . insert ( 0 , marker_statement) ;
296
+
297
+ // Create a new block to return
298
+ let new_bb = self . cfg . start_new_block ( ) ;
299
+
300
+ // Set bb -> new_bb
301
+ self . cfg . goto ( bb, source_info, new_bb) ;
208
302
209
- Some ( decision_id )
303
+ new_bb
210
304
}
211
305
}
0 commit comments