@@ -13,13 +13,24 @@ use rustc_span::def_id::LocalDefId;
1313use rustc_span:: Span ;
1414
1515use crate :: build:: Builder ;
16+ use crate :: errors:: MCDCNestedDecision ;
1617
1718pub ( crate ) struct BranchInfoBuilder {
1819 /// Maps condition expressions to their enclosing `!`, for better instrumentation.
1920 nots : FxHashMap < ExprId , NotInfo > ,
2021
2122 num_block_markers : usize ,
2223 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
2334 decisions : IndexVec < DecisionMarkerId , Span > ,
2435}
2536
@@ -44,6 +55,8 @@ impl BranchInfoBuilder {
4455 nots : FxHashMap :: default ( ) ,
4556 num_block_markers : 0 ,
4657 branch_spans : vec ! [ ] ,
58+ current_decision_id : None ,
59+ nested_decision_level : 0 ,
4760 decisions : IndexVec :: new ( ) ,
4861 } )
4962 } else {
@@ -98,7 +111,7 @@ impl BranchInfoBuilder {
98111 }
99112
100113 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 ;
102115
103116 if num_block_markers == 0 {
104117 assert ! ( branch_spans. is_empty( ) ) ;
@@ -122,6 +135,32 @@ impl BranchInfoBuilder {
122135 decision_spans,
123136 } ) )
124137 }
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+ }
125164}
126165
127166impl Builder < ' _ , ' _ > {
@@ -132,7 +171,6 @@ impl Builder<'_, '_> {
132171 mut expr_id : ExprId ,
133172 mut then_block : BasicBlock ,
134173 mut else_block : BasicBlock ,
135- decision_id : Option < DecisionMarkerId > , // If MCDC is enabled
136174 ) {
137175 // Bail out if branch coverage is not enabled for this function.
138176 let Some ( branch_info) = self . coverage_branch_info . as_ref ( ) else { return } ;
@@ -153,7 +191,7 @@ impl Builder<'_, '_> {
153191 let mut inject_branch_marker = |block : BasicBlock | {
154192 let id = branch_info. next_block_marker_id ( ) ;
155193
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 ( ) {
157195 CoverageKind :: MCDCBlockMarker { id, decision_id }
158196 } else {
159197 CoverageKind :: BlockMarker { id }
@@ -171,30 +209,35 @@ impl Builder<'_, '_> {
171209
172210 branch_info. branch_spans . push ( BranchSpan {
173211 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 ) ) ,
176214 true_marker,
177215 false_marker,
178216 } ) ;
179217 }
180218
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 ) {
188222 // Early return if MCDC coverage is not enabled.
189223 if !self . tcx . sess . instrument_coverage_mcdc ( ) {
190- return None ;
224+ return ;
191225 }
192226 let Some ( branch_info) = self . coverage_branch_info . as_mut ( ) else {
193- return None ;
227+ return ;
194228 } ;
195229
196230 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." ) ;
198241
199242 // Inject a decision marker
200243 let source_info = self . source_info ( span) ;
@@ -205,7 +248,58 @@ impl Builder<'_, '_> {
205248 } ) ,
206249 } ;
207250 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) ;
208302
209- Some ( decision_id )
303+ new_bb
210304 }
211305}
0 commit comments