| 
 | 1 | +mod mcdc;  | 
1 | 2 | use std::assert_matches::assert_matches;  | 
2 | 3 | use std::collections::hash_map::Entry;  | 
3 |  | -use std::collections::VecDeque;  | 
4 | 4 | 
 
  | 
5 | 5 | use rustc_data_structures::fx::FxHashMap;  | 
6 |  | -use rustc_middle::mir::coverage::{  | 
7 |  | -    BlockMarkerId, BranchSpan, ConditionId, ConditionInfo, CoverageKind, MCDCBranchSpan,  | 
8 |  | -    MCDCDecisionSpan,  | 
9 |  | -};  | 
 | 6 | +use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind};  | 
10 | 7 | use rustc_middle::mir::{self, BasicBlock, SourceInfo, UnOp};  | 
11 |  | -use rustc_middle::thir::{ExprId, ExprKind, LogicalOp, Thir};  | 
 | 8 | +use rustc_middle::thir::{ExprId, ExprKind, Thir};  | 
12 | 9 | use rustc_middle::ty::TyCtxt;  | 
13 | 10 | use rustc_span::def_id::LocalDefId;  | 
14 |  | -use rustc_span::Span;  | 
15 | 11 | 
 
  | 
 | 12 | +use crate::build::coverageinfo::mcdc::MCDCInfoBuilder;  | 
16 | 13 | use crate::build::{Builder, CFG};  | 
17 |  | -use crate::errors::MCDCExceedsConditionNumLimit;  | 
18 | 14 | 
 
  | 
19 | 15 | pub(crate) struct BranchInfoBuilder {  | 
20 | 16 |     /// Maps condition expressions to their enclosing `!`, for better instrumentation.  | 
@@ -159,241 +155,6 @@ impl BranchInfoBuilder {  | 
159 | 155 |     }  | 
160 | 156 | }  | 
161 | 157 | 
 
  | 
162 |  | -/// The MCDC bitmap scales exponentially (2^n) based on the number of conditions seen,  | 
163 |  | -/// So llvm sets a maximum value prevents the bitmap footprint from growing too large without the user's knowledge.  | 
164 |  | -/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.  | 
165 |  | -const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6;  | 
166 |  | - | 
167 |  | -#[derive(Default)]  | 
168 |  | -struct MCDCDecisionCtx {  | 
169 |  | -    /// To construct condition evaluation tree.  | 
170 |  | -    decision_stack: VecDeque<ConditionInfo>,  | 
171 |  | -    processing_decision: Option<MCDCDecisionSpan>,  | 
172 |  | -}  | 
173 |  | - | 
174 |  | -struct MCDCState {  | 
175 |  | -    decision_ctx_stack: Vec<MCDCDecisionCtx>,  | 
176 |  | -}  | 
177 |  | - | 
178 |  | -impl MCDCState {  | 
179 |  | -    fn new() -> Self {  | 
180 |  | -        Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] }  | 
181 |  | -    }  | 
182 |  | - | 
183 |  | -    /// Decision depth is given as a u16 to reduce the size of the `CoverageKind`,  | 
184 |  | -    /// as it is very unlikely that the depth ever reaches 2^16.  | 
185 |  | -    #[inline]  | 
186 |  | -    fn decision_depth(&self) -> u16 {  | 
187 |  | -        match u16::try_from(self.decision_ctx_stack.len())  | 
188 |  | -            .expect(  | 
189 |  | -                "decision depth did not fit in u16, this is likely to be an instrumentation error",  | 
190 |  | -            )  | 
191 |  | -            .checked_sub(1)  | 
192 |  | -        {  | 
193 |  | -            Some(d) => d,  | 
194 |  | -            None => bug!("Unexpected empty decision stack"),  | 
195 |  | -        }  | 
196 |  | -    }  | 
197 |  | - | 
198 |  | -    // At first we assign ConditionIds for each sub expression.  | 
199 |  | -    // If the sub expression is composite, re-assign its ConditionId to its LHS and generate a new ConditionId for its RHS.  | 
200 |  | -    //  | 
201 |  | -    // Example: "x = (A && B) || (C && D) || (D && F)"  | 
202 |  | -    //  | 
203 |  | -    //      Visit Depth1:  | 
204 |  | -    //              (A && B) || (C && D) || (D && F)  | 
205 |  | -    //              ^-------LHS--------^    ^-RHS--^  | 
206 |  | -    //                      ID=1              ID=2  | 
207 |  | -    //  | 
208 |  | -    //      Visit LHS-Depth2:  | 
209 |  | -    //              (A && B) || (C && D)  | 
210 |  | -    //              ^-LHS--^    ^-RHS--^  | 
211 |  | -    //                ID=1        ID=3  | 
212 |  | -    //  | 
213 |  | -    //      Visit LHS-Depth3:  | 
214 |  | -    //               (A && B)  | 
215 |  | -    //               LHS   RHS  | 
216 |  | -    //               ID=1  ID=4  | 
217 |  | -    //  | 
218 |  | -    //      Visit RHS-Depth3:  | 
219 |  | -    //                         (C && D)  | 
220 |  | -    //                         LHS   RHS  | 
221 |  | -    //                         ID=3  ID=5  | 
222 |  | -    //  | 
223 |  | -    //      Visit RHS-Depth2:              (D && F)  | 
224 |  | -    //                                     LHS   RHS  | 
225 |  | -    //                                     ID=2  ID=6  | 
226 |  | -    //  | 
227 |  | -    //      Visit Depth1:  | 
228 |  | -    //              (A && B)  || (C && D)  || (D && F)  | 
229 |  | -    //              ID=1  ID=4   ID=3  ID=5   ID=2  ID=6  | 
230 |  | -    //  | 
231 |  | -    // A node ID of '0' always means MC/DC isn't being tracked.  | 
232 |  | -    //  | 
233 |  | -    // If a "next" node ID is '0', it means it's the end of the test vector.  | 
234 |  | -    //  | 
235 |  | -    // 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.  | 
236 |  | -    // - 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".  | 
237 |  | -    // - 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".  | 
238 |  | -    fn record_conditions(&mut self, op: LogicalOp, span: Span) {  | 
239 |  | -        let decision_depth = self.decision_depth();  | 
240 |  | -        let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else {  | 
241 |  | -            bug!("Unexpected empty decision_ctx_stack")  | 
242 |  | -        };  | 
243 |  | -        let decision = match decision_ctx.processing_decision.as_mut() {  | 
244 |  | -            Some(decision) => {  | 
245 |  | -                decision.span = decision.span.to(span);  | 
246 |  | -                decision  | 
247 |  | -            }  | 
248 |  | -            None => decision_ctx.processing_decision.insert(MCDCDecisionSpan {  | 
249 |  | -                span,  | 
250 |  | -                conditions_num: 0,  | 
251 |  | -                end_markers: vec![],  | 
252 |  | -                decision_depth,  | 
253 |  | -            }),  | 
254 |  | -        };  | 
255 |  | - | 
256 |  | -        let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_default();  | 
257 |  | -        let lhs_id = if parent_condition.condition_id == ConditionId::NONE {  | 
258 |  | -            decision.conditions_num += 1;  | 
259 |  | -            ConditionId::from(decision.conditions_num)  | 
260 |  | -        } else {  | 
261 |  | -            parent_condition.condition_id  | 
262 |  | -        };  | 
263 |  | - | 
264 |  | -        decision.conditions_num += 1;  | 
265 |  | -        let rhs_condition_id = ConditionId::from(decision.conditions_num);  | 
266 |  | - | 
267 |  | -        let (lhs, rhs) = match op {  | 
268 |  | -            LogicalOp::And => {  | 
269 |  | -                let lhs = ConditionInfo {  | 
270 |  | -                    condition_id: lhs_id,  | 
271 |  | -                    true_next_id: rhs_condition_id,  | 
272 |  | -                    false_next_id: parent_condition.false_next_id,  | 
273 |  | -                };  | 
274 |  | -                let rhs = ConditionInfo {  | 
275 |  | -                    condition_id: rhs_condition_id,  | 
276 |  | -                    true_next_id: parent_condition.true_next_id,  | 
277 |  | -                    false_next_id: parent_condition.false_next_id,  | 
278 |  | -                };  | 
279 |  | -                (lhs, rhs)  | 
280 |  | -            }  | 
281 |  | -            LogicalOp::Or => {  | 
282 |  | -                let lhs = ConditionInfo {  | 
283 |  | -                    condition_id: lhs_id,  | 
284 |  | -                    true_next_id: parent_condition.true_next_id,  | 
285 |  | -                    false_next_id: rhs_condition_id,  | 
286 |  | -                };  | 
287 |  | -                let rhs = ConditionInfo {  | 
288 |  | -                    condition_id: rhs_condition_id,  | 
289 |  | -                    true_next_id: parent_condition.true_next_id,  | 
290 |  | -                    false_next_id: parent_condition.false_next_id,  | 
291 |  | -                };  | 
292 |  | -                (lhs, rhs)  | 
293 |  | -            }  | 
294 |  | -        };  | 
295 |  | -        // We visit expressions tree in pre-order, so place the left-hand side on the top.  | 
296 |  | -        decision_ctx.decision_stack.push_back(rhs);  | 
297 |  | -        decision_ctx.decision_stack.push_back(lhs);  | 
298 |  | -    }  | 
299 |  | - | 
300 |  | -    fn take_condition(  | 
301 |  | -        &mut self,  | 
302 |  | -        true_marker: BlockMarkerId,  | 
303 |  | -        false_marker: BlockMarkerId,  | 
304 |  | -    ) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) {  | 
305 |  | -        let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else {  | 
306 |  | -            bug!("Unexpected empty decision_ctx_stack")  | 
307 |  | -        };  | 
308 |  | -        let Some(condition_info) = decision_ctx.decision_stack.pop_back() else {  | 
309 |  | -            return (None, None);  | 
310 |  | -        };  | 
311 |  | -        let Some(decision) = decision_ctx.processing_decision.as_mut() else {  | 
312 |  | -            bug!("Processing decision should have been created before any conditions are taken");  | 
313 |  | -        };  | 
314 |  | -        if condition_info.true_next_id == ConditionId::NONE {  | 
315 |  | -            decision.end_markers.push(true_marker);  | 
316 |  | -        }  | 
317 |  | -        if condition_info.false_next_id == ConditionId::NONE {  | 
318 |  | -            decision.end_markers.push(false_marker);  | 
319 |  | -        }  | 
320 |  | - | 
321 |  | -        if decision_ctx.decision_stack.is_empty() {  | 
322 |  | -            (Some(condition_info), decision_ctx.processing_decision.take())  | 
323 |  | -        } else {  | 
324 |  | -            (Some(condition_info), None)  | 
325 |  | -        }  | 
326 |  | -    }  | 
327 |  | -}  | 
328 |  | - | 
329 |  | -struct MCDCInfoBuilder {  | 
330 |  | -    branch_spans: Vec<MCDCBranchSpan>,  | 
331 |  | -    decision_spans: Vec<MCDCDecisionSpan>,  | 
332 |  | -    state: MCDCState,  | 
333 |  | -}  | 
334 |  | - | 
335 |  | -impl MCDCInfoBuilder {  | 
336 |  | -    fn new() -> Self {  | 
337 |  | -        Self { branch_spans: vec![], decision_spans: vec![], state: MCDCState::new() }  | 
338 |  | -    }  | 
339 |  | - | 
340 |  | -    fn visit_evaluated_condition(  | 
341 |  | -        &mut self,  | 
342 |  | -        tcx: TyCtxt<'_>,  | 
343 |  | -        source_info: SourceInfo,  | 
344 |  | -        true_block: BasicBlock,  | 
345 |  | -        false_block: BasicBlock,  | 
346 |  | -        mut inject_block_marker: impl FnMut(SourceInfo, BasicBlock) -> BlockMarkerId,  | 
347 |  | -    ) {  | 
348 |  | -        let true_marker = inject_block_marker(source_info, true_block);  | 
349 |  | -        let false_marker = inject_block_marker(source_info, false_block);  | 
350 |  | - | 
351 |  | -        let decision_depth = self.state.decision_depth();  | 
352 |  | -        let (mut condition_info, decision_result) =  | 
353 |  | -            self.state.take_condition(true_marker, false_marker);  | 
354 |  | -        // take_condition() returns Some for decision_result when the decision stack  | 
355 |  | -        // is empty, i.e. when all the conditions of the decision were instrumented,  | 
356 |  | -        // and the decision is "complete".  | 
357 |  | -        if let Some(decision) = decision_result {  | 
358 |  | -            match decision.conditions_num {  | 
359 |  | -                0 => {  | 
360 |  | -                    unreachable!("Decision with no condition is not expected");  | 
361 |  | -                }  | 
362 |  | -                1..=MAX_CONDITIONS_NUM_IN_DECISION => {  | 
363 |  | -                    self.decision_spans.push(decision);  | 
364 |  | -                }  | 
365 |  | -                _ => {  | 
366 |  | -                    // Do not generate mcdc mappings and statements for decisions with too many conditions.  | 
367 |  | -                    let rebase_idx = self.branch_spans.len() - decision.conditions_num + 1;  | 
368 |  | -                    for branch in &mut self.branch_spans[rebase_idx..] {  | 
369 |  | -                        branch.condition_info = None;  | 
370 |  | -                    }  | 
371 |  | - | 
372 |  | -                    // ConditionInfo of this branch shall also be reset.  | 
373 |  | -                    condition_info = None;  | 
374 |  | - | 
375 |  | -                    tcx.dcx().emit_warn(MCDCExceedsConditionNumLimit {  | 
376 |  | -                        span: decision.span,  | 
377 |  | -                        conditions_num: decision.conditions_num,  | 
378 |  | -                        max_conditions_num: MAX_CONDITIONS_NUM_IN_DECISION,  | 
379 |  | -                    });  | 
380 |  | -                }  | 
381 |  | -            }  | 
382 |  | -        }  | 
383 |  | -        self.branch_spans.push(MCDCBranchSpan {  | 
384 |  | -            span: source_info.span,  | 
385 |  | -            condition_info,  | 
386 |  | -            true_marker,  | 
387 |  | -            false_marker,  | 
388 |  | -            decision_depth,  | 
389 |  | -        });  | 
390 |  | -    }  | 
391 |  | - | 
392 |  | -    fn into_done(self) -> (Vec<MCDCDecisionSpan>, Vec<MCDCBranchSpan>) {  | 
393 |  | -        (self.decision_spans, self.branch_spans)  | 
394 |  | -    }  | 
395 |  | -}  | 
396 |  | - | 
397 | 158 | impl Builder<'_, '_> {  | 
398 | 159 |     /// If branch coverage is enabled, inject marker statements into `then_block`  | 
399 | 160 |     /// and `else_block`, and record their IDs in the table of branch spans.  | 
@@ -434,30 +195,4 @@ impl Builder<'_, '_> {  | 
434 | 195 | 
 
  | 
435 | 196 |         branch_info.add_two_way_branch(&mut self.cfg, source_info, then_block, else_block);  | 
436 | 197 |     }  | 
437 |  | - | 
438 |  | -    pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) {  | 
439 |  | -        if let Some(branch_info) = self.coverage_branch_info.as_mut()  | 
440 |  | -            && let Some(mcdc_info) = branch_info.mcdc_info.as_mut()  | 
441 |  | -        {  | 
442 |  | -            mcdc_info.state.record_conditions(logical_op, span);  | 
443 |  | -        }  | 
444 |  | -    }  | 
445 |  | - | 
446 |  | -    pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) {  | 
447 |  | -        if let Some(branch_info) = self.coverage_branch_info.as_mut()  | 
448 |  | -            && let Some(mcdc_info) = branch_info.mcdc_info.as_mut()  | 
449 |  | -        {  | 
450 |  | -            mcdc_info.state.decision_ctx_stack.push(MCDCDecisionCtx::default());  | 
451 |  | -        };  | 
452 |  | -    }  | 
453 |  | - | 
454 |  | -    pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) {  | 
455 |  | -        if let Some(branch_info) = self.coverage_branch_info.as_mut()  | 
456 |  | -            && let Some(mcdc_info) = branch_info.mcdc_info.as_mut()  | 
457 |  | -        {  | 
458 |  | -            if mcdc_info.state.decision_ctx_stack.pop().is_none() {  | 
459 |  | -                bug!("Unexpected empty decision stack");  | 
460 |  | -            }  | 
461 |  | -        };  | 
462 |  | -    }  | 
463 | 198 | }  | 
0 commit comments