From 0b26d8a8f7b81321b1829f7593ab4f2041d5303c Mon Sep 17 00:00:00 2001 From: zhuyunxing Date: Tue, 21 May 2024 15:51:54 +0800 Subject: [PATCH 1/5] coverage. Warn about too much decision depth --- compiler/rustc_mir_build/messages.ftl | 2 ++ .../src/builder/coverageinfo/mcdc.rs | 31 +++++++++++++------ compiler/rustc_mir_build/src/errors.rs | 8 +++++ 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl index fae159103e70d..c0a2f2f5c8d28 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -108,6 +108,8 @@ mir_build_deref_raw_pointer_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = mir_build_exceeds_mcdc_condition_limit = number of conditions in decision ({$num_conditions}) exceeds limit ({$max_conditions}), so MC/DC analysis will not count this expression +mir_build_exceeds_mcdc_decision_depth = number of decisions evaluated simultaneously exceeds limit ({$max_decision_depth}). MCDC analysis will not count this expression + mir_build_extern_static_requires_unsafe = use of extern static is unsafe and requires unsafe block .note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior diff --git a/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs b/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs index 6b4871dc1fcc8..b92937749c3b0 100644 --- a/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs +++ b/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs @@ -10,12 +10,16 @@ use rustc_middle::ty::TyCtxt; use rustc_span::Span; use crate::builder::Builder; -use crate::errors::MCDCExceedsConditionLimit; +use crate::errors::{MCDCExceedsConditionLimit, MCDCExceedsDecisionDepth}; /// LLVM uses `i16` to represent condition id. Hence `i16::MAX` is the hard limit for number of /// conditions in a decision. const MAX_CONDITIONS_IN_DECISION: usize = i16::MAX as usize; +/// MCDC allocates an i32 variable on stack for each depth. Ignore decisions nested too much to prevent it +/// consuming excessive memory. +const MAX_DECISION_DEPTH: u16 = 0x3FFF; + #[derive(Default)] struct MCDCDecisionCtx { /// To construct condition evaluation tree. @@ -236,25 +240,34 @@ impl MCDCInfoBuilder { &mut self.degraded_spans, ) { let num_conditions = conditions.len(); + let depth = decision.decision_depth; assert_eq!( num_conditions, decision.num_conditions, "final number of conditions is not correct" ); - match num_conditions { - 0 => { + match (num_conditions, depth) { + (0, _) => { unreachable!("Decision with no condition is not expected"); } - 1..=MAX_CONDITIONS_IN_DECISION => { + (1..=MAX_CONDITIONS_IN_DECISION, 0..=MAX_DECISION_DEPTH) => { self.mcdc_spans.push((decision, conditions)); } _ => { self.degraded_spans.extend(conditions); - tcx.dcx().emit_warn(MCDCExceedsConditionLimit { - span: decision.span, - num_conditions, - max_conditions: MAX_CONDITIONS_IN_DECISION, - }); + if num_conditions > MAX_CONDITIONS_IN_DECISION { + tcx.dcx().emit_warn(MCDCExceedsConditionLimit { + span: decision.span, + num_conditions, + max_conditions: MAX_CONDITIONS_IN_DECISION, + }); + } + if depth > MAX_DECISION_DEPTH { + tcx.dcx().emit_warn(MCDCExceedsDecisionDepth { + span: decision.span, + max_decision_depth: MAX_DECISION_DEPTH as usize, + }); + } } } } diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index 07bdc59756aa7..da505e9c2f7f7 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -989,6 +989,14 @@ pub(crate) struct MCDCExceedsConditionLimit { pub(crate) max_conditions: usize, } +#[derive(Diagnostic)] +#[diag(mir_build_exceeds_mcdc_decision_depth)] +pub(crate) struct MCDCExceedsDecisionDepth { + #[primary_span] + pub span: Span, + pub max_decision_depth: usize, +} + #[derive(Diagnostic)] #[diag(mir_build_pattern_not_covered, code = E0005)] pub(crate) struct PatternNotCovered<'s, 'tcx> { From 07058ed2f9c543973aa1f8c06ea490f20d81ed9f Mon Sep 17 00:00:00 2001 From: zhuyunxing Date: Tue, 9 Jul 2024 15:07:09 +0800 Subject: [PATCH 2/5] coverage. Refactor MCDCInfoBuilder for pattern matching implementation and change the way to calculate decision depth --- .../src/coverageinfo/mod.rs | 6 + compiler/rustc_middle/src/mir/coverage.rs | 33 +- compiler/rustc_middle/src/mir/pretty.rs | 17 +- .../src/builder/coverageinfo.rs | 8 +- .../src/builder/coverageinfo/mcdc.rs | 508 ++++++++++++------ .../rustc_mir_build/src/builder/expr/into.rs | 4 + .../src/builder/matches/mod.rs | 2 - .../src/coverage/counters.rs | 8 +- .../src/coverage/mappings.rs | 75 +-- .../rustc_mir_transform/src/coverage/mod.rs | 57 +- .../src/coverage/spans/from_mir.rs | 3 +- .../mcdc/nested_in_boolean_exprs.cov-map | 148 +++++ .../mcdc/nested_in_boolean_exprs.coverage | 195 +++++++ .../coverage/mcdc/nested_in_boolean_exprs.rs | 49 ++ 14 files changed, 876 insertions(+), 237 deletions(-) create mode 100644 tests/coverage/mcdc/nested_in_boolean_exprs.cov-map create mode 100644 tests/coverage/mcdc/nested_in_boolean_exprs.coverage create mode 100644 tests/coverage/mcdc/nested_in_boolean_exprs.rs diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs index 021108cd51caf..abfbf1155541e 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs @@ -210,6 +210,12 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> { bx.mcdc_tvbitmap_update(fn_name, hash, bitmap_index, cond_bitmap); bx.mcdc_condbitmap_reset(cond_bitmap); } + CoverageKind::CondBitmapReset { decision_depth } => { + let cond_bitmap = bx.coverage_cx() + .try_get_mcdc_condition_bitmap(&instance, decision_depth) + .expect("mcdc cond bitmap should have been allocated for merging into the global bitmap"); + bx.mcdc_condbitmap_reset(cond_bitmap); + } } } } diff --git a/compiler/rustc_middle/src/mir/coverage.rs b/compiler/rustc_middle/src/mir/coverage.rs index 46534697e1d60..1411892212af1 100644 --- a/compiler/rustc_middle/src/mir/coverage.rs +++ b/compiler/rustc_middle/src/mir/coverage.rs @@ -50,6 +50,15 @@ rustc_index::newtype_index! { pub struct ExpressionId {} } +rustc_index::newtype_index! { + /// ID of a mcdc decision. Used to identify decision in a function. + #[derive(HashStable)] + #[encodable] + #[orderable] + #[debug_format = "DecisionId({})"] + pub struct DecisionId {} +} + rustc_index::newtype_index! { /// ID of a mcdc condition. Used by llvm to check mcdc coverage. /// @@ -126,6 +135,11 @@ pub enum CoverageKind { /// This is eventually lowered to instruments updating mcdc temp variables. CondBitmapUpdate { index: u32, decision_depth: u16 }, + /// Marks the point in MIR control flow where a condition bitmap is reset. + /// + /// This is eventually lowered to instruments set the mcdc temp variable to zero. + CondBitmapReset { decision_depth: u16 }, + /// Marks the point in MIR control flow represented by a evaluated decision. /// /// This is eventually lowered to `llvm.instrprof.mcdc.tvbitmap.update` in LLVM IR. @@ -143,6 +157,9 @@ impl Debug for CoverageKind { CondBitmapUpdate { index, decision_depth } => { write!(fmt, "CondBitmapUpdate(index={:?}, depth={:?})", index, decision_depth) } + CondBitmapReset { decision_depth } => { + write!(fmt, "CondBitmapReset(depth={:?})", decision_depth) + } TestVectorBitmapUpdate { bitmap_idx, decision_depth } => { write!(fmt, "TestVectorUpdate({:?}, depth={:?})", bitmap_idx, decision_depth) } @@ -247,7 +264,7 @@ pub struct CoverageInfoHi { pub branch_spans: Vec, /// Branch spans generated by mcdc. Because of some limits mcdc builder give up generating /// decisions including them so that they are handled as normal branch spans. - pub mcdc_degraded_branch_spans: Vec, + pub mcdc_degraded_spans: Vec, pub mcdc_spans: Vec<(MCDCDecisionSpan, Vec)>, } @@ -272,8 +289,11 @@ pub struct ConditionInfo { pub struct MCDCBranchSpan { pub span: Span, pub condition_info: ConditionInfo, - pub true_marker: BlockMarkerId, - pub false_marker: BlockMarkerId, + // For boolean decisions and most pattern matching decisions there is only + // one true marker and one false marker in each branch. But for matching decisions + // with `|` there can be several. + pub true_markers: Vec, + pub false_markers: Vec, } #[derive(Copy, Clone, Debug)] @@ -289,7 +309,12 @@ pub struct MCDCDecisionSpan { pub span: Span, pub end_markers: Vec, pub decision_depth: u16, - pub num_conditions: usize, +} + +impl MCDCDecisionSpan { + pub fn new(span: Span) -> Self { + Self { span, end_markers: Vec::new(), decision_depth: 0 } + } } /// Summarizes coverage IDs inserted by the `InstrumentCoverage` MIR pass diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 3007b78749683..fcfaed8180fbf 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -561,7 +561,7 @@ fn write_coverage_info_hi( let coverage::CoverageInfoHi { num_block_markers: _, branch_spans, - mcdc_degraded_branch_spans, + mcdc_degraded_spans, mcdc_spans, } = coverage_info_hi; @@ -576,32 +576,27 @@ fn write_coverage_info_hi( did_print = true; } - for coverage::MCDCBranchSpan { span, true_marker, false_marker, .. } in - mcdc_degraded_branch_spans - { + for coverage::MCDCBranchSpan { span, true_markers, false_markers, .. } in mcdc_degraded_spans { writeln!( w, - "{INDENT}coverage branch {{ true: {true_marker:?}, false: {false_marker:?} }} => {span:?}", + "{INDENT}coverage branch {{ true: {true_markers:?}, false: {false_markers:?} }} => {span:?}", )?; did_print = true; } - for ( - coverage::MCDCDecisionSpan { span, end_markers, decision_depth, num_conditions: _ }, - conditions, - ) in mcdc_spans + for (coverage::MCDCDecisionSpan { span, end_markers, decision_depth }, conditions) in mcdc_spans { let num_conditions = conditions.len(); writeln!( w, "{INDENT}coverage mcdc decision {{ num_conditions: {num_conditions:?}, end: {end_markers:?}, depth: {decision_depth:?} }} => {span:?}" )?; - for coverage::MCDCBranchSpan { span, condition_info, true_marker, false_marker } in + for coverage::MCDCBranchSpan { span, condition_info, true_markers, false_markers } in conditions { writeln!( w, - "{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?} }} => {span:?}", + "{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_markers:?}, false: {false_markers:?} }} => {span:?}", condition_info.condition_id )?; } diff --git a/compiler/rustc_mir_build/src/builder/coverageinfo.rs b/compiler/rustc_mir_build/src/builder/coverageinfo.rs index a80bd4f3c8009..49037004284ac 100644 --- a/compiler/rustc_mir_build/src/builder/coverageinfo.rs +++ b/compiler/rustc_mir_build/src/builder/coverageinfo.rs @@ -144,10 +144,10 @@ impl CoverageInfoBuilder { // Separate path for handling branches when MC/DC is enabled. if let Some(mcdc_info) = self.mcdc_info.as_mut() { let inject_block_marker = - |source_info, block| self.markers.inject_block_marker(cfg, source_info, block); + |block| self.markers.inject_block_marker(cfg, source_info, block); mcdc_info.visit_evaluated_condition( tcx, - source_info, + source_info.span, true_block, false_block, inject_block_marker, @@ -175,7 +175,7 @@ impl CoverageInfoBuilder { let branch_spans = branch_info.map(|branch_info| branch_info.branch_spans).unwrap_or_default(); - let (mcdc_spans, mcdc_degraded_branch_spans) = + let (mcdc_degraded_spans, mcdc_spans) = mcdc_info.map(MCDCInfoBuilder::into_done).unwrap_or_default(); // For simplicity, always return an info struct (without Option), even @@ -183,7 +183,7 @@ impl CoverageInfoBuilder { Box::new(CoverageInfoHi { num_block_markers, branch_spans, - mcdc_degraded_branch_spans, + mcdc_degraded_spans, mcdc_spans, }) } diff --git a/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs b/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs index b92937749c3b0..46896fa566aeb 100644 --- a/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs +++ b/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs @@ -1,11 +1,14 @@ +use std::cell::Cell; use std::collections::VecDeque; +use std::rc::Rc; +use rustc_data_structures::fx::FxIndexMap; use rustc_middle::bug; +use rustc_middle::mir::BasicBlock; use rustc_middle::mir::coverage::{ - BlockMarkerId, ConditionId, ConditionInfo, MCDCBranchSpan, MCDCDecisionSpan, + BlockMarkerId, ConditionId, ConditionInfo, DecisionId, MCDCBranchSpan, MCDCDecisionSpan, }; -use rustc_middle::mir::{BasicBlock, SourceInfo}; -use rustc_middle::thir::LogicalOp; +use rustc_middle::thir::{ExprKind, LogicalOp}; use rustc_middle::ty::TyCtxt; use rustc_span::Span; @@ -20,36 +23,31 @@ const MAX_CONDITIONS_IN_DECISION: usize = i16::MAX as usize; /// consuming excessive memory. const MAX_DECISION_DEPTH: u16 = 0x3FFF; -#[derive(Default)] -struct MCDCDecisionCtx { +#[derive(Debug)] +struct BooleanDecisionCtx { + id: DecisionId, + decision_info: MCDCDecisionSpan, /// To construct condition evaluation tree. decision_stack: VecDeque, - processing_decision: Option, conditions: Vec, + condition_id_counter: usize, } -struct MCDCState { - decision_ctx_stack: Vec, -} - -impl MCDCState { - fn new() -> Self { - Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] } +impl BooleanDecisionCtx { + fn new(id: DecisionId) -> Self { + Self { + id, + decision_info: MCDCDecisionSpan::new(Span::default()), + decision_stack: VecDeque::new(), + conditions: vec![], + condition_id_counter: 0, + } } - /// Decision depth is given as a u16 to reduce the size of the `CoverageKind`, - /// as it is very unlikely that the depth ever reaches 2^16. - #[inline] - fn decision_depth(&self) -> u16 { - match u16::try_from(self.decision_ctx_stack.len()) - .expect( - "decision depth did not fit in u16, this is likely to be an instrumentation error", - ) - .checked_sub(1) - { - Some(d) => d, - None => bug!("Unexpected empty decision stack"), - } + fn next_condition_id(&mut self) -> ConditionId { + let id = ConditionId::from_usize(self.condition_id_counter); + self.condition_id_counter += 1; + id } // At first we assign ConditionIds for each sub expression. @@ -92,40 +90,19 @@ impl MCDCState { // 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. // - 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". // - 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". - fn record_conditions(&mut self, op: LogicalOp, span: Span) { - let decision_depth = self.decision_depth(); - let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else { - bug!("Unexpected empty decision_ctx_stack") - }; - let decision = match decision_ctx.processing_decision.as_mut() { - Some(decision) => { - decision.span = decision.span.to(span); - decision - } - None => decision_ctx.processing_decision.insert(MCDCDecisionSpan { - span, - num_conditions: 0, - end_markers: vec![], - decision_depth, - }), - }; - - let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_else(|| { - assert_eq!( - decision.num_conditions, 0, - "decision stack must be empty only for empty decision" - ); - decision.num_conditions += 1; - ConditionInfo { - condition_id: ConditionId::START, + fn record_conditions(&mut self, op: LogicalOp) { + let parent_condition = match self.decision_stack.pop_back() { + Some(info) => info, + None => ConditionInfo { + condition_id: self.next_condition_id(), true_next_id: None, false_next_id: None, - } - }); + }, + }; let lhs_id = parent_condition.condition_id; - let rhs_condition_id = ConditionId::from(decision.num_conditions); - decision.num_conditions += 1; + let rhs_condition_id = self.next_condition_id(); + let (lhs, rhs) = match op { LogicalOp::And => { let lhs = ConditionInfo { @@ -155,128 +132,341 @@ impl MCDCState { } }; // We visit expressions tree in pre-order, so place the left-hand side on the top. - decision_ctx.decision_stack.push_back(rhs); - decision_ctx.decision_stack.push_back(lhs); + self.decision_stack.push_back(rhs); + self.decision_stack.push_back(lhs); } - fn try_finish_decision( + fn finish_two_way_branch( &mut self, span: Span, true_marker: BlockMarkerId, false_marker: BlockMarkerId, - degraded_branches: &mut Vec, - ) -> Option<(MCDCDecisionSpan, Vec)> { - let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else { - bug!("Unexpected empty decision_ctx_stack") - }; - let Some(condition_info) = decision_ctx.decision_stack.pop_back() else { - let branch = MCDCBranchSpan { - span, - condition_info: ConditionInfo { - condition_id: ConditionId::START, - true_next_id: None, - false_next_id: None, - }, - true_marker, - false_marker, - }; - degraded_branches.push(branch); - return None; - }; - let Some(decision) = decision_ctx.processing_decision.as_mut() else { - bug!("Processing decision should have been created before any conditions are taken"); - }; + ) { + let condition_info = self.decision_stack.pop_back().unwrap_or(ConditionInfo { + condition_id: ConditionId::START, + true_next_id: None, + false_next_id: None, + }); if condition_info.true_next_id.is_none() { - decision.end_markers.push(true_marker); + self.decision_info.end_markers.push(true_marker); } if condition_info.false_next_id.is_none() { - decision.end_markers.push(false_marker); + self.decision_info.end_markers.push(false_marker); } - decision_ctx.conditions.push(MCDCBranchSpan { + + self.conditions.push(MCDCBranchSpan { span, condition_info, - true_marker, - false_marker, + true_markers: vec![true_marker], + false_markers: vec![false_marker], }); + } - if decision_ctx.decision_stack.is_empty() { - let conditions = std::mem::take(&mut decision_ctx.conditions); - decision_ctx.processing_decision.take().map(|decision| (decision, conditions)) - } else { - None + fn is_finished(&self) -> bool { + self.decision_stack.is_empty() + } + + fn into_done(self) -> (DecisionId, MCDCDecisionSpan, Vec) { + (self.id, self.decision_info, self.conditions) + } +} + +#[derive(Debug)] +enum DecisionCtx { + Boolean(BooleanDecisionCtx), + #[allow(unused)] + Matching, +} + +impl DecisionCtx { + fn new_boolean(id: DecisionId) -> Self { + Self::Boolean(BooleanDecisionCtx::new(id)) + } +} + +pub(crate) struct MCDCStateGuard { + state_stashed_ref: Option>>, +} + +impl Drop for MCDCStateGuard { + fn drop(&mut self) { + if let Some(stashed) = self.state_stashed_ref.take() { + stashed.set(false); } } } +/// `MCDCState` represents a layer to hold decisions. Decisions produced +/// by same state are nested in same decision. +#[derive(Debug)] +struct MCDCState { + current_ctx: Option, + nested_decision_records: Vec, + // `Stashed` means we are processing a decision nested in decision of this state. + stashed: Rc>, +} + +impl MCDCState { + fn new() -> Self { + Self { + current_ctx: None, + nested_decision_records: vec![], + stashed: Rc::new(Cell::new(false)), + } + } + + fn is_stashed(&self) -> bool { + self.stashed.get() + } + + fn take_ctx(&mut self) -> Option<(DecisionCtx, Vec)> { + let ctx = self.current_ctx.take()?; + let nested_decisions_id = std::mem::take(&mut self.nested_decision_records); + Some((ctx, nested_decisions_id)) + } + + // Return `true` if there is no decision being processed currently. + fn is_empty(&self) -> bool { + self.current_ctx.is_none() + } + + fn record_nested_decision(&mut self, id: DecisionId) { + self.nested_decision_records.push(id); + } + + fn inherit_nested_decisions(&mut self, nested_decisions_id: Vec) { + self.nested_decision_records.extend(nested_decisions_id); + } + + fn take_current_nested_decisions(&mut self) -> Vec { + std::mem::take(&mut self.nested_decision_records) + } +} + +#[derive(Debug)] +struct MCDCTargetInfo { + decision: MCDCDecisionSpan, + conditions: Vec, + nested_decisions_id: Vec, +} + +impl MCDCTargetInfo { + fn set_depth(&mut self, depth: u16) { + self.decision.decision_depth = depth; + } +} + +#[derive(Default)] +struct DecisionIdGen(usize); +impl DecisionIdGen { + fn next_decision_id(&mut self) -> DecisionId { + let id = DecisionId::from_usize(self.0); + self.0 += 1; + id + } +} + pub(crate) struct MCDCInfoBuilder { - degraded_spans: Vec, - mcdc_spans: Vec<(MCDCDecisionSpan, Vec)>, - state: MCDCState, + normal_branch_spans: Vec, + mcdc_targets: FxIndexMap, + state_stack: Vec, + decision_id_gen: DecisionIdGen, } impl MCDCInfoBuilder { pub(crate) fn new() -> Self { - Self { degraded_spans: vec![], mcdc_spans: vec![], state: MCDCState::new() } + Self { + normal_branch_spans: vec![], + mcdc_targets: FxIndexMap::default(), + state_stack: vec![], + decision_id_gen: DecisionIdGen::default(), + } + } + + fn has_processing_decision(&self) -> bool { + // Check from top to get working states a bit quicker. + !self.state_stack.iter().rev().all(|state| state.is_empty() && !state.is_stashed()) + } + + fn current_state_mut(&mut self) -> &mut MCDCState { + let current_idx = self.state_stack.len() - 1; + &mut self.state_stack[current_idx] + } + + fn ensure_active_state(&mut self) { + let mut active_state_idx = None; + // Down to the first non-stashed state or non-empty state, which can be ensured to be + // processed currently. + for (idx, state) in self.state_stack.iter().enumerate().rev() { + if state.is_stashed() { + active_state_idx = Some(idx + 1); + break; + } else if !state.is_empty() { + active_state_idx = Some(idx); + break; + } + } + match active_state_idx { + // There are some states were created for nested decisions but now + // since the lower state has been unstashed they should be removed. + Some(idx) if idx + 1 < self.state_stack.len() => { + let expected_len = idx + 1; + let nested_decisions_id = self + .state_stack + .iter_mut() + .skip(expected_len) + .map(|state| state.take_current_nested_decisions().into_iter()) + .flatten() + .collect(); + self.state_stack.truncate(expected_len); + self.state_stack[idx].inherit_nested_decisions(nested_decisions_id); + } + // The top state is just wanted. + Some(idx) if idx + 1 == self.state_stack.len() => {} + // Otherwise no available state yet, create a new one. + _ => self.state_stack.push(MCDCState::new()), + } + } + + fn ensure_boolean_decision(&mut self, condition_span: Span) -> &mut BooleanDecisionCtx { + self.ensure_active_state(); + let state = self.state_stack.last_mut().expect("ensured just now"); + let DecisionCtx::Boolean(ctx) = state.current_ctx.get_or_insert_with(|| { + DecisionCtx::new_boolean(self.decision_id_gen.next_decision_id()) + }) else { + unreachable!("ensured above"); + }; + + if ctx.decision_info.span == Span::default() { + ctx.decision_info.span = condition_span; + } else { + ctx.decision_info.span = ctx.decision_info.span.to(condition_span); + } + ctx + } + + fn append_normal_branches(&mut self, branches: Vec) { + self.normal_branch_spans.extend(branches); + } + + fn append_mcdc_info(&mut self, tcx: TyCtxt<'_>, id: DecisionId, info: MCDCTargetInfo) -> bool { + let num_conditions = info.conditions.len(); + match num_conditions { + 0 => { + unreachable!("Decision with no condition is not expected"); + } + // Ignore decisions with only one condition given that mcdc for them is completely equivalent to branch coverage. + 2..=MAX_CONDITIONS_IN_DECISION => { + self.mcdc_targets.insert(id, info); + true + } + _ => { + self.append_normal_branches(info.conditions); + self.current_state_mut().inherit_nested_decisions(info.nested_decisions_id); + if num_conditions > MAX_CONDITIONS_IN_DECISION { + tcx.dcx().emit_warn(MCDCExceedsConditionLimit { + span: info.decision.span, + num_conditions, + max_conditions: MAX_CONDITIONS_IN_DECISION, + }); + } + false + } + } + } + + fn normalize_depth_from(&mut self, tcx: TyCtxt<'_>, id: DecisionId) { + let Some(entry_decision) = self.mcdc_targets.get_mut(&id) else { + bug!("unknown mcdc decision"); + }; + let mut next_nested_records = entry_decision.nested_decisions_id.clone(); + let mut depth = 0; + while !next_nested_records.is_empty() { + depth += 1; + for id in std::mem::take(&mut next_nested_records) { + let Some(nested_target) = self.mcdc_targets.get_mut(&id) else { + continue; + }; + nested_target.set_depth(depth); + next_nested_records.extend(nested_target.nested_decisions_id.iter().copied()); + if depth > MAX_DECISION_DEPTH { + tcx.dcx().emit_warn(MCDCExceedsDecisionDepth { + span: nested_target.decision.span, + max_decision_depth: MAX_DECISION_DEPTH.into(), + }); + let branches = std::mem::take(&mut nested_target.conditions); + self.append_normal_branches(branches); + self.mcdc_targets.swap_remove(&id); + } + } + } + } + + // If `entry_decision_id` is some, there must be at least one mcdc decision being produced. + fn on_ctx_finished(&mut self, tcx: TyCtxt<'_>, entry_decision_id: Option) { + match (self.has_processing_decision(), entry_decision_id) { + // Can not be nested in other decisions, depth is accumulated starting from this decision. + (false, Some(id)) => self.normalize_depth_from(tcx, id), + // May be nested in other decisions, record it. + (true, Some(id)) => self.current_state_mut().record_nested_decision(id), + // No decision is produced this time and no other parent decision to be processing. + // All "nested decisions" now get zero depth and then calculate depth of their children. + (false, None) => { + for root_decision in self.current_state_mut().take_current_nested_decisions() { + self.normalize_depth_from(tcx, root_decision); + } + } + (true, None) => {} + } } pub(crate) fn visit_evaluated_condition( &mut self, tcx: TyCtxt<'_>, - source_info: SourceInfo, + span: Span, true_block: BasicBlock, false_block: BasicBlock, - mut inject_block_marker: impl FnMut(SourceInfo, BasicBlock) -> BlockMarkerId, + mut inject_block_marker: impl FnMut(BasicBlock) -> BlockMarkerId, ) { - let true_marker = inject_block_marker(source_info, true_block); - let false_marker = inject_block_marker(source_info, false_block); - - // take_condition() returns Some for decision_result when the decision stack - // is empty, i.e. when all the conditions of the decision were instrumented, - // and the decision is "complete". - if let Some((decision, conditions)) = self.state.try_finish_decision( - source_info.span, - true_marker, - false_marker, - &mut self.degraded_spans, - ) { - let num_conditions = conditions.len(); - let depth = decision.decision_depth; - assert_eq!( - num_conditions, decision.num_conditions, - "final number of conditions is not correct" - ); - match (num_conditions, depth) { - (0, _) => { - unreachable!("Decision with no condition is not expected"); - } - (1..=MAX_CONDITIONS_IN_DECISION, 0..=MAX_DECISION_DEPTH) => { - self.mcdc_spans.push((decision, conditions)); - } - _ => { - self.degraded_spans.extend(conditions); - - if num_conditions > MAX_CONDITIONS_IN_DECISION { - tcx.dcx().emit_warn(MCDCExceedsConditionLimit { - span: decision.span, - num_conditions, - max_conditions: MAX_CONDITIONS_IN_DECISION, - }); - } - if depth > MAX_DECISION_DEPTH { - tcx.dcx().emit_warn(MCDCExceedsDecisionDepth { - span: decision.span, - max_decision_depth: MAX_DECISION_DEPTH as usize, - }); - } - } - } + let true_marker = inject_block_marker(true_block); + let false_marker = inject_block_marker(false_block); + let decision = self.ensure_boolean_decision(span); + decision.finish_two_way_branch(span, true_marker, false_marker); + + if !decision.is_finished() { + return; } + + let Some((DecisionCtx::Boolean(ctx), nested_decisions_id)) = + self.current_state_mut().take_ctx() + else { + unreachable!("ensured boolean ctx above"); + }; + + let (id, decision, conditions) = ctx.into_done(); + let info = MCDCTargetInfo { decision, conditions, nested_decisions_id }; + let entry_decision_id = self.append_mcdc_info(tcx, id, info).then_some(id); + self.on_ctx_finished(tcx, entry_decision_id); } pub(crate) fn into_done( self, - ) -> (Vec<(MCDCDecisionSpan, Vec)>, Vec) { - (self.mcdc_spans, self.degraded_spans) + ) -> (Vec, Vec<(MCDCDecisionSpan, Vec)>) { + let MCDCInfoBuilder { + normal_branch_spans, + mcdc_targets, + state_stack: _, + decision_id_gen: _, + } = self; + + let mcdc_spans = mcdc_targets + .into_values() + .map(|MCDCTargetInfo { decision, conditions, nested_decisions_id: _ }| { + (decision, conditions) + }) + .collect(); + + (normal_branch_spans, mcdc_spans) } } @@ -285,24 +475,34 @@ impl Builder<'_, '_> { if let Some(coverage_info) = self.coverage_info.as_mut() && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut() { - mcdc_info.state.record_conditions(logical_op, span); + let decision = mcdc_info.ensure_boolean_decision(span); + decision.record_conditions(logical_op); } } - pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) { - if let Some(coverage_info) = self.coverage_info.as_mut() - && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut() - { - mcdc_info.state.decision_ctx_stack.push(MCDCDecisionCtx::default()); - }; - } - - pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) { + pub(crate) fn mcdc_prepare_ctx_for(&mut self, expr_kind: &ExprKind<'_>) -> MCDCStateGuard { if let Some(coverage_info) = self.coverage_info.as_mut() && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut() - && mcdc_info.state.decision_ctx_stack.pop().is_none() { - bug!("Unexpected empty decision stack"); + match expr_kind { + ExprKind::Unary { .. } | ExprKind::Scope { .. } => {} + ExprKind::LogicalOp { .. } => { + // By here a decision is going to be produced + mcdc_info.ensure_active_state(); + } + _ => { + // Non-logical expressions leads to nested decisions only when a decision is being processed. + // In such cases just mark the state `stashed`. If a nested decision following, a new active state will be + // created at the previous arm. The current top state will be unstashed when the guard is dropped. + if mcdc_info.has_processing_decision() { + let stashed = &mcdc_info.current_state_mut().stashed; + if !stashed.replace(true) { + return MCDCStateGuard { state_stashed_ref: Some(Rc::clone(stashed)) }; + } + } + } + } }; + MCDCStateGuard { state_stashed_ref: None } } } diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index b25cd0f4426bd..4bfde37fffe11 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -33,6 +33,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let expr_span = expr.span; let source_info = this.source_info(expr_span); + // Prepare mcdc context so that the nested decision get proper depth if any. + // The guard automatically marks the context processing parent decisions restored on dropping. + let _mcdc_guard = this.mcdc_prepare_ctx_for(&expr.kind); + let expr_is_block_or_scope = matches!(expr.kind, ExprKind::Block { .. } | ExprKind::Scope { .. }); diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index a2f92a93ec544..feab47785346f 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -201,7 +201,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Increment the decision depth, in case we encounter boolean expressions // further down. - this.mcdc_increment_depth_if_enabled(); let place = unpack!( block = this.as_temp( block, @@ -213,7 +212,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { mutability ) ); - this.mcdc_decrement_depth_if_enabled(); let operand = Operand::Move(Place::from(place)); diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs index adb99a75a9e47..aa8a04bf4e735 100644 --- a/compiler/rustc_mir_transform/src/coverage/counters.rs +++ b/compiler/rustc_mir_transform/src/coverage/counters.rs @@ -207,7 +207,13 @@ impl CoverageCounters { self.node_counters[bcb] } - /// Returns an iterator over all the nodes in the coverage graph that + pub(super) fn term_for_sum_of_bcbs(&mut self, bcbs: &[BasicCoverageBlock]) -> Option { + let counters = + bcbs.iter().copied().filter_map(|bcb| self.node_counters[bcb]).collect::>(); + self.make_sum(&counters) + } + + /// Returns an iterator over all the nodes/edges in the coverage graph that /// should have a counter-increment statement injected into MIR, along with /// each site's corresponding counter ID. pub(super) fn counter_increment_sites( diff --git a/compiler/rustc_mir_transform/src/coverage/mappings.rs b/compiler/rustc_mir_transform/src/coverage/mappings.rs index 8d0d92dc36772..5b457708d6dec 100644 --- a/compiler/rustc_mir_transform/src/coverage/mappings.rs +++ b/compiler/rustc_mir_transform/src/coverage/mappings.rs @@ -38,8 +38,8 @@ pub(super) struct BranchPair { #[derive(Debug)] pub(super) struct MCDCBranch { pub(super) span: Span, - pub(super) true_bcb: BasicCoverageBlock, - pub(super) false_bcb: BasicCoverageBlock, + pub(super) true_bcbs: Vec, + pub(super) false_bcbs: Vec, pub(super) condition_info: ConditionInfo, // Offset added to test vector idx if this branch is evaluated to true. pub(super) true_index: usize, @@ -152,12 +152,11 @@ impl ExtractedMappings { insert(true_bcb); insert(false_bcb); } - for &MCDCBranch { true_bcb, false_bcb, .. } in mcdc_degraded_branches + for MCDCBranch { true_bcbs, false_bcbs, .. } in mcdc_degraded_branches .iter() .chain(mcdc_mappings.iter().map(|(_, branches)| branches.into_iter()).flatten()) { - insert(true_bcb); - insert(false_bcb); + true_bcbs.into_iter().chain(false_bcbs.into_iter()).copied().for_each(&mut insert); } // MC/DC decisions refer to BCBs, but don't require those BCBs to have counters. @@ -253,35 +252,22 @@ pub(super) fn extract_mcdc_mappings( let bcb_from_marker = |marker: BlockMarkerId| graph.bcb_from_bb(block_markers[marker]?); - let check_branch_bcb = - |raw_span: Span, true_marker: BlockMarkerId, false_marker: BlockMarkerId| { - // For now, ignore any branch span that was introduced by - // expansion. This makes things like assert macros less noisy. - if !raw_span.ctxt().outer_expn_data().is_root() { - return None; - } - let span = unexpand_into_body_span(raw_span, body_span)?; + let check_branch_bcb = |raw_span: Span, + true_markers: &[BlockMarkerId], + false_markers: &[BlockMarkerId]| { + // For now, ignore any branch span that was introduced by + // expansion. This makes things like assert macros less noisy. + if !raw_span.ctxt().outer_expn_data().is_root() { + return None; + } + let span = unexpand_into_body_span(raw_span, body_span)?; - let true_bcb = bcb_from_marker(true_marker)?; - let false_bcb = bcb_from_marker(false_marker)?; - Some((span, true_bcb, false_bcb)) - }; + let true_bcbs = + true_markers.into_iter().copied().map(&bcb_from_marker).collect::>>()?; + let false_bcbs = + false_markers.into_iter().copied().map(&bcb_from_marker).collect::>>()?; - let to_mcdc_branch = |&mir::coverage::MCDCBranchSpan { - span: raw_span, - condition_info, - true_marker, - false_marker, - }| { - let (span, true_bcb, false_bcb) = check_branch_bcb(raw_span, true_marker, false_marker)?; - Some(MCDCBranch { - span, - true_bcb, - false_bcb, - condition_info, - true_index: usize::MAX, - false_index: usize::MAX, - }) + Some((span, true_bcbs, false_bcbs)) }; let mut get_bitmap_idx = |num_test_vectors: usize| -> Option { @@ -292,8 +278,27 @@ pub(super) fn extract_mcdc_mappings( bitmap_idx }) }; + + let extract_condition_mapping = |&mir::coverage::MCDCBranchSpan { + span: raw_span, + condition_info, + ref true_markers, + ref false_markers, + }| { + let (span, true_bcbs, false_bcbs) = + check_branch_bcb(raw_span, true_markers, false_markers)?; + Some(MCDCBranch { + span, + true_bcbs, + false_bcbs, + condition_info, + true_index: usize::MAX, + false_index: usize::MAX, + }) + }; + mcdc_degraded_branches - .extend(coverage_info_hi.mcdc_degraded_branch_spans.iter().filter_map(to_mcdc_branch)); + .extend(coverage_info_hi.mcdc_degraded_spans.iter().filter_map(extract_condition_mapping)); mcdc_mappings.extend(coverage_info_hi.mcdc_spans.iter().filter_map(|(decision, branches)| { if branches.len() == 0 { @@ -306,7 +311,9 @@ pub(super) fn extract_mcdc_mappings( .iter() .map(|&marker| bcb_from_marker(marker)) .collect::>()?; - let mut branch_mappings: Vec<_> = branches.into_iter().filter_map(to_mcdc_branch).collect(); + + let mut branch_mappings: Vec<_> = + branches.into_iter().filter_map(extract_condition_mapping).collect(); if branch_mappings.len() != branches.len() { mcdc_degraded_branches.extend(branch_mappings); return None; diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 15487d05a301d..023a711d403af 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -93,9 +93,9 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir: return; } - let coverage_counters = counters::make_bcb_counters(&graph, &bcbs_with_counter_mappings); + let mut coverage_counters = counters::make_bcb_counters(&graph, &bcbs_with_counter_mappings); - let mappings = create_mappings(&extracted_mappings, &coverage_counters); + let mappings = create_mappings(&extracted_mappings, &mut coverage_counters); if mappings.is_empty() { // No spans could be converted into valid mappings, so skip this function. debug!("no spans could be converted into valid mappings; skipping"); @@ -131,7 +131,7 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir: /// coverage counters. fn create_mappings( extracted_mappings: &ExtractedMappings, - coverage_counters: &CoverageCounters, + coverage_counters: &mut CoverageCounters, ) -> Vec { let term_for_bcb = |bcb| coverage_counters.term_for_bcb(bcb).expect("all BCBs with spans were given counters"); @@ -164,21 +164,24 @@ fn create_mappings( }, )); - let term_for_bcb = - |bcb| coverage_counters.term_for_bcb(bcb).expect("all BCBs with spans were given counters"); + let mut term_for_sum_of_bcbs = |bcbs| { + coverage_counters + .term_for_sum_of_bcbs(bcbs) + .expect("all BCBs with spans were given counters") + }; // MCDC branch mappings are appended with their decisions in case decisions were ignored. mappings.extend(mcdc_degraded_branches.iter().map( |&mappings::MCDCBranch { span, - true_bcb, - false_bcb, + ref true_bcbs, + ref false_bcbs, condition_info: _, true_index: _, false_index: _, }| { - let true_term = term_for_bcb(true_bcb); - let false_term = term_for_bcb(false_bcb); + let true_term = term_for_sum_of_bcbs(true_bcbs); + let false_term = term_for_sum_of_bcbs(false_bcbs); Mapping { kind: MappingKind::Branch { true_term, false_term }, span } }, )); @@ -195,14 +198,14 @@ fn create_mappings( .map( |&mappings::MCDCBranch { span, - true_bcb, - false_bcb, + ref true_bcbs, + ref false_bcbs, condition_info, true_index: _, false_index: _, }| { - let true_term = term_for_bcb(true_bcb); - let false_term = term_for_bcb(false_bcb); + let true_term = term_for_sum_of_bcbs(true_bcbs); + let false_term = term_for_sum_of_bcbs(false_bcbs); Mapping { kind: MappingKind::MCDCBranch { true_term, @@ -286,25 +289,27 @@ fn inject_mcdc_statements<'tcx>( ); } - for &mappings::MCDCBranch { + for mappings::MCDCBranch { span: _, - true_bcb, - false_bcb, + true_bcbs, + false_bcbs, condition_info: _, true_index, false_index, } in conditions { - for (index, bcb) in [(false_index, false_bcb), (true_index, true_bcb)] { - let bb = graph[bcb].leader_bb(); - inject_statement( - mir_body, - CoverageKind::CondBitmapUpdate { - index: index as u32, - decision_depth: decision.decision_depth, - }, - bb, - ); + for (&index, bcbs) in [(false_index, false_bcbs), (true_index, true_bcbs)] { + for &bcb in bcbs { + let bb = graph[bcb].leader_bb(); + inject_statement( + mir_body, + CoverageKind::CondBitmapUpdate { + index: index as u32, + decision_depth: decision.decision_depth, + }, + bb, + ); + } } } } diff --git a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs index 26ce743be3613..fde7c43a62420 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs @@ -140,7 +140,8 @@ fn filtered_statement_span(statement: &Statement<'_>) -> Option { CoverageKind::CounterIncrement { .. } | CoverageKind::ExpressionUsed { .. } | CoverageKind::CondBitmapUpdate { .. } - | CoverageKind::TestVectorBitmapUpdate { .. }, + | CoverageKind::TestVectorBitmapUpdate { .. } + | CoverageKind::CondBitmapReset { .. }, ) => bug!( "Unexpected coverage statement found during coverage instrumentation: {statement:?}" ), diff --git a/tests/coverage/mcdc/nested_in_boolean_exprs.cov-map b/tests/coverage/mcdc/nested_in_boolean_exprs.cov-map new file mode 100644 index 0000000000000..8779a5b9a9034 --- /dev/null +++ b/tests/coverage/mcdc/nested_in_boolean_exprs.cov-map @@ -0,0 +1,148 @@ +Function name: nested_in_boolean_exprs::assign_nested_func_call +Raw bytes (100): 0x[01, 01, 06, 01, 05, 01, 0b, 05, 11, 01, 13, 05, 09, 09, 0d, 0c, 01, 12, 01, 00, 37, 01, 01, 09, 00, 0a, 01, 00, 0d, 00, 0e, 28, 06, 02, 00, 0d, 00, 1d, 30, 05, 02, 01, 00, 02, 00, 0d, 00, 0e, 30, 11, 06, 02, 00, 00, 00, 12, 00, 1d, 02, 00, 16, 00, 17, 28, 03, 02, 00, 16, 00, 1c, 30, 09, 0e, 01, 02, 00, 00, 16, 00, 17, 09, 00, 1b, 00, 1c, 30, 0d, 16, 02, 00, 00, 00, 1b, 00, 1c, 01, 01, 05, 01, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 6 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Expression(2, Add) +- expression 2 operands: lhs = Counter(1), rhs = Counter(4) +- expression 3 operands: lhs = Counter(0), rhs = Expression(4, Add) +- expression 4 operands: lhs = Counter(1), rhs = Counter(2) +- expression 5 operands: lhs = Counter(2), rhs = Counter(3) +Number of file 0 mappings: 12 +- Code(Counter(0)) at (prev + 18, 1) to (start + 0, 55) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 10) +- Code(Counter(0)) at (prev + 0, 13) to (start + 0, 14) +- MCDCDecision { bitmap_idx: 6, conditions_num: 2 } at (prev + 0, 13) to (start + 0, 29) +- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 13) to (start + 0, 14) + true = c1 + false = (c0 - c1) +- MCDCBranch { true: Counter(4), false: Expression(1, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 18) to (start + 0, 29) + true = c4 + false = (c0 - (c1 + c4)) +- Code(Expression(0, Sub)) at (prev + 0, 22) to (start + 0, 23) + = (c0 - c1) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 0, 22) to (start + 0, 28) +- MCDCBranch { true: Counter(2), false: Expression(3, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 22) to (start + 0, 23) + true = c2 + false = (c0 - (c1 + c2)) +- Code(Counter(2)) at (prev + 0, 27) to (start + 0, 28) +- MCDCBranch { true: Counter(3), false: Expression(5, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 27) to (start + 0, 28) + true = c3 + false = (c2 - c3) +- Code(Counter(0)) at (prev + 1, 5) to (start + 1, 2) +Highest counter ID seen: c4 + +Function name: nested_in_boolean_exprs::assign_nested_if +Raw bytes (114): 0x[01, 01, 08, 01, 05, 01, 0b, 05, 11, 01, 13, 05, 09, 09, 0d, 01, 1f, 05, 0d, 0e, 01, 09, 01, 00, 30, 01, 01, 09, 00, 0a, 01, 00, 0d, 00, 0e, 28, 06, 02, 00, 0d, 00, 33, 30, 05, 02, 01, 00, 02, 00, 0d, 00, 0e, 30, 11, 06, 02, 00, 00, 00, 12, 00, 33, 02, 00, 15, 00, 16, 28, 03, 02, 00, 15, 00, 1b, 30, 09, 0e, 01, 02, 00, 00, 15, 00, 16, 09, 00, 1a, 00, 1b, 30, 0d, 16, 02, 00, 00, 00, 1a, 00, 1b, 0d, 00, 1e, 00, 23, 1a, 00, 2d, 00, 31, 01, 01, 05, 01, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 8 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Expression(2, Add) +- expression 2 operands: lhs = Counter(1), rhs = Counter(4) +- expression 3 operands: lhs = Counter(0), rhs = Expression(4, Add) +- expression 4 operands: lhs = Counter(1), rhs = Counter(2) +- expression 5 operands: lhs = Counter(2), rhs = Counter(3) +- expression 6 operands: lhs = Counter(0), rhs = Expression(7, Add) +- expression 7 operands: lhs = Counter(1), rhs = Counter(3) +Number of file 0 mappings: 14 +- Code(Counter(0)) at (prev + 9, 1) to (start + 0, 48) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 10) +- Code(Counter(0)) at (prev + 0, 13) to (start + 0, 14) +- MCDCDecision { bitmap_idx: 6, conditions_num: 2 } at (prev + 0, 13) to (start + 0, 51) +- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 13) to (start + 0, 14) + true = c1 + false = (c0 - c1) +- MCDCBranch { true: Counter(4), false: Expression(1, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 18) to (start + 0, 51) + true = c4 + false = (c0 - (c1 + c4)) +- Code(Expression(0, Sub)) at (prev + 0, 21) to (start + 0, 22) + = (c0 - c1) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 0, 21) to (start + 0, 27) +- MCDCBranch { true: Counter(2), false: Expression(3, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 22) + true = c2 + false = (c0 - (c1 + c2)) +- Code(Counter(2)) at (prev + 0, 26) to (start + 0, 27) +- MCDCBranch { true: Counter(3), false: Expression(5, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 26) to (start + 0, 27) + true = c3 + false = (c2 - c3) +- Code(Counter(3)) at (prev + 0, 30) to (start + 0, 35) +- Code(Expression(6, Sub)) at (prev + 0, 45) to (start + 0, 49) + = (c0 - (c1 + c3)) +- Code(Counter(0)) at (prev + 1, 5) to (start + 1, 2) +Highest counter ID seen: c4 + +Function name: nested_in_boolean_exprs::foo +Raw bytes (9): 0x[01, 01, 00, 01, 01, 0e, 01, 02, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 14, 1) to (start + 2, 2) +Highest counter ID seen: c0 + +Function name: nested_in_boolean_exprs::func_call_nested_if +Raw bytes (114): 0x[01, 01, 08, 01, 05, 01, 0b, 05, 11, 01, 13, 05, 09, 09, 0d, 01, 1f, 05, 0d, 0e, 01, 17, 01, 00, 33, 01, 01, 09, 00, 0a, 01, 00, 11, 00, 12, 28, 06, 02, 00, 11, 00, 37, 30, 05, 02, 01, 00, 02, 00, 11, 00, 12, 30, 11, 06, 02, 00, 00, 00, 16, 00, 37, 02, 00, 19, 00, 1a, 28, 03, 02, 00, 19, 00, 1f, 30, 09, 0e, 01, 02, 00, 00, 19, 00, 1a, 09, 00, 1e, 00, 1f, 30, 0d, 16, 02, 00, 00, 00, 1e, 00, 1f, 0d, 00, 22, 00, 27, 1a, 00, 31, 00, 35, 01, 01, 05, 01, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 8 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Expression(2, Add) +- expression 2 operands: lhs = Counter(1), rhs = Counter(4) +- expression 3 operands: lhs = Counter(0), rhs = Expression(4, Add) +- expression 4 operands: lhs = Counter(1), rhs = Counter(2) +- expression 5 operands: lhs = Counter(2), rhs = Counter(3) +- expression 6 operands: lhs = Counter(0), rhs = Expression(7, Add) +- expression 7 operands: lhs = Counter(1), rhs = Counter(3) +Number of file 0 mappings: 14 +- Code(Counter(0)) at (prev + 23, 1) to (start + 0, 51) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 10) +- Code(Counter(0)) at (prev + 0, 17) to (start + 0, 18) +- MCDCDecision { bitmap_idx: 6, conditions_num: 2 } at (prev + 0, 17) to (start + 0, 55) +- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 17) to (start + 0, 18) + true = c1 + false = (c0 - c1) +- MCDCBranch { true: Counter(4), false: Expression(1, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 22) to (start + 0, 55) + true = c4 + false = (c0 - (c1 + c4)) +- Code(Expression(0, Sub)) at (prev + 0, 25) to (start + 0, 26) + = (c0 - c1) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 0, 25) to (start + 0, 31) +- MCDCBranch { true: Counter(2), false: Expression(3, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 25) to (start + 0, 26) + true = c2 + false = (c0 - (c1 + c2)) +- Code(Counter(2)) at (prev + 0, 30) to (start + 0, 31) +- MCDCBranch { true: Counter(3), false: Expression(5, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 30) to (start + 0, 31) + true = c3 + false = (c2 - c3) +- Code(Counter(3)) at (prev + 0, 34) to (start + 0, 39) +- Code(Expression(6, Sub)) at (prev + 0, 49) to (start + 0, 53) + = (c0 - (c1 + c3)) +- Code(Counter(0)) at (prev + 1, 5) to (start + 1, 2) +Highest counter ID seen: c4 + +Function name: nested_in_boolean_exprs::func_call_with_unary_not +Raw bytes (62): 0x[01, 01, 03, 01, 05, 01, 0b, 05, 09, 08, 01, 1c, 01, 00, 2f, 01, 01, 09, 00, 0a, 01, 00, 0d, 00, 0e, 28, 03, 02, 00, 0d, 00, 19, 30, 05, 02, 01, 00, 02, 00, 0d, 00, 0e, 02, 00, 12, 00, 19, 30, 09, 06, 02, 00, 00, 00, 12, 00, 19, 01, 01, 05, 01, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 3 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Expression(2, Add) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 8 +- Code(Counter(0)) at (prev + 28, 1) to (start + 0, 47) +- Code(Counter(0)) at (prev + 1, 9) to (start + 0, 10) +- Code(Counter(0)) at (prev + 0, 13) to (start + 0, 14) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 0, 13) to (start + 0, 25) +- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 13) to (start + 0, 14) + true = c1 + false = (c0 - c1) +- Code(Expression(0, Sub)) at (prev + 0, 18) to (start + 0, 25) + = (c0 - c1) +- MCDCBranch { true: Counter(2), false: Expression(1, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 18) to (start + 0, 25) + true = c2 + false = (c0 - (c1 + c2)) +- Code(Counter(0)) at (prev + 1, 5) to (start + 1, 2) +Highest counter ID seen: c2 + diff --git a/tests/coverage/mcdc/nested_in_boolean_exprs.coverage b/tests/coverage/mcdc/nested_in_boolean_exprs.coverage new file mode 100644 index 0000000000000..26cb7a67b554b --- /dev/null +++ b/tests/coverage/mcdc/nested_in_boolean_exprs.coverage @@ -0,0 +1,195 @@ + LL| |#![feature(coverage_attribute)] + LL| |//@ edition: 2021 + LL| |//@ min-llvm-version: 19 + LL| |//@ compile-flags: -Zcoverage-options=mcdc + LL| |//@ llvm-cov-flags: --show-branches=count --show-mcdc + LL| | + LL| |use core::hint::black_box; + LL| | + LL| 3|fn assign_nested_if(a: bool, b: bool, c: bool) { + LL| 3| let x = a || if b && c { false } else { true }; + ^2 ^1 ^1 ^1 + ------------------ + | Branch (LL:13): [True: 1, False: 2] + | Branch (LL:18): [True: 1, False: 1] + | Branch (LL:21): [True: 1, False: 1] + | Branch (LL:26): [True: 1, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:13) to (LL:51) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:13) + | Condition C2 --> (LL:18) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, F = F } + | 2 { F, T = T } + | 3 { T, - = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: covered: (1,2) + | MC/DC Coverage for Decision: 100.00% + | + |---> MC/DC Decision Region (LL:21) to (LL:27) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:21) + | Condition C2 --> (LL:26) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, T = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 3| black_box(x); + LL| 3|} + LL| | + LL| 6|fn foo(a: bool) -> bool { + LL| 6| black_box(a) + LL| 6|} + LL| | + LL| 3|fn assign_nested_func_call(a: bool, b: bool, c: bool) { + LL| 3| let x = a || foo(b && c); + ^2 ^1 + ------------------ + | Branch (LL:13): [True: 1, False: 2] + | Branch (LL:18): [True: 1, False: 1] + | Branch (LL:22): [True: 1, False: 1] + | Branch (LL:27): [True: 1, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:13) to (LL:29) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:13) + | Condition C2 --> (LL:18) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, F = F } + | 2 { F, T = T } + | 3 { T, - = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: covered: (1,2) + | MC/DC Coverage for Decision: 100.00% + | + |---> MC/DC Decision Region (LL:22) to (LL:28) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:22) + | Condition C2 --> (LL:27) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, T = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 3| black_box(x); + LL| 3|} + LL| | + LL| 3|fn func_call_nested_if(a: bool, b: bool, c: bool) { + LL| 3| let x = foo(a || if b && c { false } else { true }); + ^2 ^1 ^1 ^1 + ------------------ + | Branch (LL:17): [True: 1, False: 2] + | Branch (LL:22): [True: 1, False: 1] + | Branch (LL:25): [True: 1, False: 1] + | Branch (LL:30): [True: 1, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:17) to (LL:55) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:17) + | Condition C2 --> (LL:22) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, F = F } + | 2 { F, T = T } + | 3 { T, - = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: covered: (1,2) + | MC/DC Coverage for Decision: 100.00% + | + |---> MC/DC Decision Region (LL:25) to (LL:31) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:25) + | Condition C2 --> (LL:30) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, T = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 3| black_box(x); + LL| 3|} + LL| | + LL| 2|fn func_call_with_unary_not(a: bool, b: bool) { + LL| 2| let x = a || foo(!b); + ^1 + ------------------ + | Branch (LL:13): [True: 1, False: 1] + | Branch (LL:18): [True: 0, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:13) to (LL:25) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:13) + | Condition C2 --> (LL:18) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, F = F } + | 2 { T, - = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 2| black_box(x); + LL| 2|} + LL| | + LL| |#[coverage(off)] + LL| |fn main() { + LL| | assign_nested_if(true, false, true); + LL| | assign_nested_if(false, false, true); + LL| | assign_nested_if(false, true, true); + LL| | + LL| | assign_nested_func_call(true, false, true); + LL| | assign_nested_func_call(false, false, true); + LL| | assign_nested_func_call(false, true, true); + LL| | + LL| | func_call_nested_if(true, false, true); + LL| | func_call_nested_if(false, false, true); + LL| | func_call_nested_if(false, true, true); + LL| | + LL| | func_call_with_unary_not(true, false); + LL| | func_call_with_unary_not(false, true); + LL| |} + diff --git a/tests/coverage/mcdc/nested_in_boolean_exprs.rs b/tests/coverage/mcdc/nested_in_boolean_exprs.rs new file mode 100644 index 0000000000000..050b45e692dd1 --- /dev/null +++ b/tests/coverage/mcdc/nested_in_boolean_exprs.rs @@ -0,0 +1,49 @@ +#![feature(coverage_attribute)] +//@ edition: 2021 +//@ min-llvm-version: 19 +//@ compile-flags: -Zcoverage-options=mcdc +//@ llvm-cov-flags: --show-branches=count --show-mcdc + +use core::hint::black_box; + +fn assign_nested_if(a: bool, b: bool, c: bool) { + let x = a || if b && c { false } else { true }; + black_box(x); +} + +fn foo(a: bool) -> bool { + black_box(a) +} + +fn assign_nested_func_call(a: bool, b: bool, c: bool) { + let x = a || foo(b && c); + black_box(x); +} + +fn func_call_nested_if(a: bool, b: bool, c: bool) { + let x = foo(a || if b && c { false } else { true }); + black_box(x); +} + +fn func_call_with_unary_not(a: bool, b: bool) { + let x = a || foo(!b); + black_box(x); +} + +#[coverage(off)] +fn main() { + assign_nested_if(true, false, true); + assign_nested_if(false, false, true); + assign_nested_if(false, true, true); + + assign_nested_func_call(true, false, true); + assign_nested_func_call(false, false, true); + assign_nested_func_call(false, true, true); + + func_call_nested_if(true, false, true); + func_call_nested_if(false, false, true); + func_call_nested_if(false, true, true); + + func_call_with_unary_not(true, false); + func_call_with_unary_not(false, true); +} From 3531e9099543abe8371cd2c237f5fbab7508f3cd Mon Sep 17 00:00:00 2001 From: zhuyunxing Date: Tue, 9 Jul 2024 13:57:57 +0800 Subject: [PATCH 3/5] coverage. Add discard end blocks for decisions to reset condbitmap without updating global bitmap --- compiler/rustc_middle/src/mir/coverage.rs | 12 ++++++-- compiler/rustc_middle/src/mir/pretty.rs | 12 ++++++-- .../src/builder/coverageinfo/mcdc.rs | 4 +-- .../src/coverage/mappings.rs | 28 ++++++++++++------- .../rustc_mir_transform/src/coverage/mod.rs | 11 +++++++- 5 files changed, 50 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_middle/src/mir/coverage.rs b/compiler/rustc_middle/src/mir/coverage.rs index 1411892212af1..aa91de7f53155 100644 --- a/compiler/rustc_middle/src/mir/coverage.rs +++ b/compiler/rustc_middle/src/mir/coverage.rs @@ -307,13 +307,21 @@ pub struct DecisionInfo { #[derive(TyEncodable, TyDecodable, Hash, HashStable)] pub struct MCDCDecisionSpan { pub span: Span, - pub end_markers: Vec, + // Blocks where update test vectors of the decision. + pub update_end_markers: Vec, + // Block where discard written condition bitmap of the decision. + pub discard_end_markers: Vec, pub decision_depth: u16, } impl MCDCDecisionSpan { pub fn new(span: Span) -> Self { - Self { span, end_markers: Vec::new(), decision_depth: 0 } + Self { + span, + update_end_markers: Vec::new(), + discard_end_markers: Vec::new(), + decision_depth: 0, + } } } diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index fcfaed8180fbf..6a7b24a205134 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -584,12 +584,20 @@ fn write_coverage_info_hi( did_print = true; } - for (coverage::MCDCDecisionSpan { span, end_markers, decision_depth }, conditions) in mcdc_spans + for ( + coverage::MCDCDecisionSpan { + span, + update_end_markers: end_markers, + discard_end_markers: discard_markers, + decision_depth, + }, + conditions, + ) in mcdc_spans { let num_conditions = conditions.len(); writeln!( w, - "{INDENT}coverage mcdc decision {{ num_conditions: {num_conditions:?}, end: {end_markers:?}, depth: {decision_depth:?} }} => {span:?}" + "{INDENT}coverage mcdc decision {{ num_conditions: {num_conditions:?}, end: {end_markers:?}, discard_markers: {discard_markers:?} depth: {decision_depth:?} }} => {span:?}" )?; for coverage::MCDCBranchSpan { span, condition_info, true_markers, false_markers } in conditions diff --git a/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs b/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs index 46896fa566aeb..7926fe5989dee 100644 --- a/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs +++ b/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs @@ -148,10 +148,10 @@ impl BooleanDecisionCtx { false_next_id: None, }); if condition_info.true_next_id.is_none() { - self.decision_info.end_markers.push(true_marker); + self.decision_info.update_end_markers.push(true_marker); } if condition_info.false_next_id.is_none() { - self.decision_info.end_markers.push(false_marker); + self.decision_info.update_end_markers.push(false_marker); } self.conditions.push(MCDCBranchSpan { diff --git a/compiler/rustc_mir_transform/src/coverage/mappings.rs b/compiler/rustc_mir_transform/src/coverage/mappings.rs index 5b457708d6dec..f9850812d067e 100644 --- a/compiler/rustc_mir_transform/src/coverage/mappings.rs +++ b/compiler/rustc_mir_transform/src/coverage/mappings.rs @@ -1,6 +1,4 @@ -use std::collections::BTreeSet; - -use rustc_data_structures::fx::FxIndexMap; +use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::graph::DirectedGraph; use rustc_index::IndexVec; use rustc_index::bit_set::DenseBitSet; @@ -51,7 +49,8 @@ pub(super) struct MCDCBranch { #[derive(Debug)] pub(super) struct MCDCDecision { pub(super) span: Span, - pub(super) end_bcbs: BTreeSet, + pub(super) update_end_bcbs: FxIndexSet, + pub(super) discard_end_bcbs: FxIndexSet, pub(super) bitmap_idx: usize, pub(super) num_test_vectors: usize, pub(super) decision_depth: u16, @@ -306,11 +305,16 @@ pub(super) fn extract_mcdc_mappings( } let decision_span = unexpand_into_body_span(decision.span, body_span)?; - let end_bcbs = decision - .end_markers + let update_end_bcbs = decision + .update_end_markers + .iter() + .filter_map(|&marker| bcb_from_marker(marker)) + .collect(); + let discard_end_bcbs = decision + .discard_end_markers .iter() - .map(|&marker| bcb_from_marker(marker)) - .collect::>()?; + .filter_map(|&marker| bcb_from_marker(marker)) + .collect(); let mut branch_mappings: Vec<_> = branches.into_iter().filter_map(extract_condition_mapping).collect(); @@ -342,7 +346,8 @@ pub(super) fn extract_mcdc_mappings( Some(( MCDCDecision { span, - end_bcbs, + update_end_bcbs, + discard_end_bcbs, bitmap_idx, num_test_vectors, decision_depth: decision.decision_depth, @@ -400,7 +405,10 @@ fn calc_test_vectors_index(conditions: &mut Vec) -> usize { } } } - assert!(next_conditions.is_empty(), "the decision tree has untouched nodes"); + assert!( + next_conditions.is_empty(), + "the decision tree has untouched nodes, next_conditions: {next_conditions:?}" + ); let mut cur_idx = 0; // LLVM hopes the end nodes are sorted in descending order by `num_paths` so that it can // optimize bitmap size for decisions in tree form such as `a && b && c && d && ...`. diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 023a711d403af..03df8c8d8de26 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -277,7 +277,7 @@ fn inject_mcdc_statements<'tcx>( ) { for (decision, conditions) in &extracted_mappings.mcdc_mappings { // Inject test vector update first because `inject_statement` always insert new statement at head. - for &end in &decision.end_bcbs { + for &end in &decision.update_end_bcbs { let end_bb = graph[end].leader_bb(); inject_statement( mir_body, @@ -289,6 +289,15 @@ fn inject_mcdc_statements<'tcx>( ); } + for &discard in &decision.discard_end_bcbs { + let discard_bb = graph[discard].leader_bb(); + inject_statement( + mir_body, + CoverageKind::CondBitmapReset { decision_depth: decision.decision_depth }, + discard_bb, + ); + } + for mappings::MCDCBranch { span: _, true_bcbs, From dfaf4a040851fff05dca1307af5a73a9ab509868 Mon Sep 17 00:00:00 2001 From: zhuyunxing Date: Tue, 9 Jul 2024 14:08:06 +0800 Subject: [PATCH 4/5] coverage. Trace Candidate and MatchPair with coverage id --- compiler/rustc_middle/src/mir/coverage.rs | 84 +++++++++++++++++++ .../src/builder/matches/match_pair.rs | 1 + .../src/builder/matches/mod.rs | 33 ++++++++ 3 files changed, 118 insertions(+) diff --git a/compiler/rustc_middle/src/mir/coverage.rs b/compiler/rustc_middle/src/mir/coverage.rs index aa91de7f53155..93654f394f41e 100644 --- a/compiler/rustc_middle/src/mir/coverage.rs +++ b/compiler/rustc_middle/src/mir/coverage.rs @@ -362,3 +362,87 @@ impl CoverageIdsInfo { } } } +/// Identify subcandidates in a candidate. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SubcandidateId(usize); + +impl SubcandidateId { + pub const ROOT: SubcandidateId = SubcandidateId(0); + pub fn is_root(&self) -> bool { + *self == Self::ROOT + } + + pub fn next_subcandidate_id(&self) -> Self { + Self(self.0 + 1) + } +} + +/// Identify MatchPair in a candidate. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MatchPairId(usize); + +impl MatchPairId { + pub const INVALID: MatchPairId = MatchPairId(0); + pub const START: MatchPairId = MatchPairId(1); + pub fn next_match_pair_id(&self) -> Self { + Self(self.0 + 1) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct CandidateCovId { + pub decision_id: DecisionId, + pub subcandidate_id: SubcandidateId, +} + +impl Default for CandidateCovId { + fn default() -> Self { + Self { decision_id: DecisionId::MAX, subcandidate_id: SubcandidateId(usize::MAX) } + } +} + +impl CandidateCovId { + pub fn is_valid(&self) -> bool { + *self != Self::default() + } + + pub fn new_match_info( + &mut self, + match_id: MatchPairId, + span: Span, + fully_matched: bool, + ) -> MatchCoverageInfo { + let key = MatchKey { + decision_id: self.decision_id, + match_id, + subcandidate_id: self.subcandidate_id, + }; + MatchCoverageInfo { key, span, fully_matched } + } +} + +/// Key for matched patterns. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct MatchKey { + pub decision_id: DecisionId, + pub match_id: MatchPairId, + pub subcandidate_id: SubcandidateId, +} + +impl Default for MatchKey { + fn default() -> Self { + Self { + decision_id: DecisionId::MAX, + match_id: MatchPairId(0), + subcandidate_id: SubcandidateId(0), + } + } +} + +/// Information about matched patterns. +#[derive(Clone, Copy, Debug)] +pub struct MatchCoverageInfo { + pub key: MatchKey, + pub span: Span, + pub fully_matched: bool, +} diff --git a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs index bca57817d6617..573a2a78a2d66 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -263,6 +263,7 @@ impl<'tcx> MatchPairTree<'tcx> { subpairs, pattern_ty: pattern.ty, pattern_span: pattern.span, + coverage_id: Default::default(), } } } diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index feab47785346f..863b1633c12d2 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -1094,6 +1094,10 @@ struct Candidate<'tcx> { /// The earliest block that has only candidates >= this one as descendents. Used for false /// edges, see the doc for [`Builder::match_expr`]. false_edge_start_block: Option, + + #[allow(unused)] + /// The id to identify the candidate in coverage instrument. + coverage_id: coverage::CandidateCovId, } impl<'tcx> Candidate<'tcx> { @@ -1122,6 +1126,7 @@ impl<'tcx> Candidate<'tcx> { otherwise_block: None, pre_binding_block: None, false_edge_start_block: None, + coverage_id: coverage::CandidateCovId::default(), } } @@ -1152,6 +1157,31 @@ impl<'tcx> Candidate<'tcx> { |_| {}, ); } + + #[allow(unused)] + pub(crate) fn set_coverage_id(&mut self, coverage_id: coverage::CandidateCovId) { + self.coverage_id = coverage_id; + // Assign id for match pairs only if this candidate is the root. + if !coverage_id.subcandidate_id.is_root() { + return; + }; + let mut latest_match_id = coverage::MatchPairId::START; + let mut next_match_id = || { + let id = latest_match_id; + latest_match_id = id.next_match_pair_id(); + id + }; + let mut match_pairs = self.match_pairs.iter_mut().collect::>(); + while let Some(match_pair) = match_pairs.pop() { + match_pair.coverage_id = next_match_id(); + match_pairs.extend(match_pair.subpairs.iter_mut()); + if let TestCase::Or { ref mut pats } = match_pair.test_case { + match_pairs.extend( + pats.iter_mut().map(|flat_pat| flat_pat.match_pairs.iter_mut()).flatten(), + ); + } + } + } } /// A depth-first traversal of the `Candidate` and all of its recursive @@ -1276,6 +1306,9 @@ pub(crate) struct MatchPairTree<'tcx> { pattern_ty: Ty<'tcx>, /// Span field of the pattern this node was created from. pattern_span: Span, + + /// Key to identify the match pair in coverage. + coverage_id: coverage::MatchPairId, } /// See [`Test`] for more. From 0421337d480fc8642116698b53e619ada5b92e26 Mon Sep 17 00:00:00 2001 From: zhuyunxing Date: Thu, 29 Aug 2024 16:28:05 +0800 Subject: [PATCH 5/5] coverage. Instrument mcdc for pattern matching --- compiler/rustc_middle/src/mir/coverage.rs | 8 +- .../src/builder/coverageinfo/mcdc.rs | 32 +- .../src/builder/coverageinfo/mcdc/matching.rs | 875 ++++++++++++++++++ .../src/builder/matches/mod.rs | 54 +- .../src/builder/matches/test.rs | 37 +- .../rustc_mir_transform/src/coverage/mod.rs | 7 +- tests/coverage/mcdc/if_let.cov-map | 318 +++++++ tests/coverage/mcdc/if_let.coverage | 272 ++++++ tests/coverage/mcdc/if_let.rs | 83 ++ tests/coverage/mcdc/match_misc.cov-map | 499 ++++++++++ tests/coverage/mcdc/match_misc.coverage | 554 +++++++++++ tests/coverage/mcdc/match_misc.rs | 190 ++++ tests/coverage/mcdc/match_pattern.cov-map | 402 ++++++++ tests/coverage/mcdc/match_pattern.coverage | 392 ++++++++ tests/coverage/mcdc/match_pattern.rs | 100 ++ 15 files changed, 3801 insertions(+), 22 deletions(-) create mode 100644 compiler/rustc_mir_build/src/builder/coverageinfo/mcdc/matching.rs create mode 100644 tests/coverage/mcdc/if_let.cov-map create mode 100644 tests/coverage/mcdc/if_let.coverage create mode 100644 tests/coverage/mcdc/if_let.rs create mode 100644 tests/coverage/mcdc/match_misc.cov-map create mode 100644 tests/coverage/mcdc/match_misc.coverage create mode 100644 tests/coverage/mcdc/match_misc.rs create mode 100644 tests/coverage/mcdc/match_pattern.cov-map create mode 100644 tests/coverage/mcdc/match_pattern.coverage create mode 100644 tests/coverage/mcdc/match_pattern.rs diff --git a/compiler/rustc_middle/src/mir/coverage.rs b/compiler/rustc_middle/src/mir/coverage.rs index 93654f394f41e..44e4422f13fd8 100644 --- a/compiler/rustc_middle/src/mir/coverage.rs +++ b/compiler/rustc_middle/src/mir/coverage.rs @@ -276,7 +276,7 @@ pub struct BranchSpan { pub false_marker: BlockMarkerId, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(TyEncodable, TyDecodable, Hash, HashStable)] pub struct ConditionInfo { pub condition_id: ConditionId, @@ -402,8 +402,10 @@ impl Default for CandidateCovId { } impl CandidateCovId { - pub fn is_valid(&self) -> bool { - *self != Self::default() + /// Return `true` is this `CandidateCovId` is assigned properly through coverage mechanism + /// and can be mapped as a decision. + pub fn is_valid(self) -> bool { + self != Self::default() } pub fn new_match_info( diff --git a/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs b/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs index 7926fe5989dee..498f08372a7fa 100644 --- a/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs +++ b/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc.rs @@ -1,7 +1,9 @@ +mod matching; use std::cell::Cell; use std::collections::VecDeque; use std::rc::Rc; +use matching::{LateMatchingState, MatchingDecisionCtx}; use rustc_data_structures::fx::FxIndexMap; use rustc_middle::bug; use rustc_middle::mir::BasicBlock; @@ -174,14 +176,17 @@ impl BooleanDecisionCtx { #[derive(Debug)] enum DecisionCtx { Boolean(BooleanDecisionCtx), - #[allow(unused)] - Matching, + Matching(MatchingDecisionCtx), } impl DecisionCtx { fn new_boolean(id: DecisionId) -> Self { Self::Boolean(BooleanDecisionCtx::new(id)) } + + fn new_matching(info: &[(Span, DecisionId)]) -> Self { + Self::Matching(MatchingDecisionCtx::new(info)) + } } pub(crate) struct MCDCStateGuard { @@ -270,6 +275,7 @@ pub(crate) struct MCDCInfoBuilder { normal_branch_spans: Vec, mcdc_targets: FxIndexMap, state_stack: Vec, + late_matching_state: LateMatchingState, decision_id_gen: DecisionIdGen, } @@ -279,6 +285,7 @@ impl MCDCInfoBuilder { normal_branch_spans: vec![], mcdc_targets: FxIndexMap::default(), state_stack: vec![], + late_matching_state: Default::default(), decision_id_gen: DecisionIdGen::default(), } } @@ -293,6 +300,11 @@ impl MCDCInfoBuilder { &mut self.state_stack[current_idx] } + fn current_processing_ctx_mut(&mut self) -> Option<&mut DecisionCtx> { + self.ensure_active_state(); + self.state_stack.last_mut().and_then(|state| state.current_ctx.as_mut()) + } + fn ensure_active_state(&mut self) { let mut active_state_idx = None; // Down to the first non-stashed state or non-empty state, which can be ensured to be @@ -353,7 +365,8 @@ impl MCDCInfoBuilder { let num_conditions = info.conditions.len(); match num_conditions { 0 => { - unreachable!("Decision with no condition is not expected"); + // Irrefutable patterns caused by empty types can lead to here. + false } // Ignore decisions with only one condition given that mcdc for them is completely equivalent to branch coverage. 2..=MAX_CONDITIONS_IN_DECISION => { @@ -445,17 +458,26 @@ impl MCDCInfoBuilder { let (id, decision, conditions) = ctx.into_done(); let info = MCDCTargetInfo { decision, conditions, nested_decisions_id }; - let entry_decision_id = self.append_mcdc_info(tcx, id, info).then_some(id); - self.on_ctx_finished(tcx, entry_decision_id); + if self.late_matching_state.is_guard_decision(id) { + self.late_matching_state.add_guard_decision(id, info); + } else { + let entry_id = self.append_mcdc_info(tcx, id, info).then_some(id); + self.on_ctx_finished(tcx, entry_id) + } } pub(crate) fn into_done( self, ) -> (Vec, Vec<(MCDCDecisionSpan, Vec)>) { + assert!( + !self.has_processing_decision() && self.late_matching_state.is_empty(), + "has unfinished decisions" + ); let MCDCInfoBuilder { normal_branch_spans, mcdc_targets, state_stack: _, + late_matching_state: _, decision_id_gen: _, } = self; diff --git a/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc/matching.rs b/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc/matching.rs new file mode 100644 index 0000000000000..95a660fd6f268 --- /dev/null +++ b/compiler/rustc_mir_build/src/builder/coverageinfo/mcdc/matching.rs @@ -0,0 +1,875 @@ +use std::collections::VecDeque; + +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; +use rustc_middle::bug; +use rustc_middle::mir::coverage::{ + BlockMarkerId, CandidateCovId, ConditionId, ConditionInfo, DecisionId, MCDCBranchSpan, + MCDCDecisionSpan, MatchCoverageInfo, MatchKey, MatchPairId, SubcandidateId, +}; +use rustc_middle::mir::{BasicBlock, SourceInfo, TerminatorKind}; +use rustc_middle::ty::TyCtxt; +use rustc_span::Span; + +use crate::builder::coverageinfo::mcdc::{DecisionCtx, MCDCInfoBuilder, MCDCTargetInfo}; +use crate::builder::matches::Candidate; +use crate::builder::{Builder, CFG}; + +/// Represent a matched target. This target might contain several +/// patterns from different candidates. +/// +/// We represents the process of matching as a graph. +/// +/// For instance, pattern decision `(A, B, C | D(E), F | G)` generates graph like: +/// * +/// | +/// A +/// | +/// B +/// ∨------∧------∨ +/// C D +/// | | +/// | E +/// ∧------∨------∧ +/// * +/// ∨------∧------∨ +/// F G +/// `*` represents a virtual node. Matching graph have following properties: +/// * Every subcandidate is represented as a branch in the graph. +/// * Start with a virtual node, because we always start from the root subcandidate. (Remember that the root subcandidate might have no pattern) +/// * If two or more branches are merged, they are merged into a virtual node. +/// +/// With the help of matching graph we can construct decision tree of the pattern: +/// * The true next of a node is its leftmost successor. +/// * The false next of a node is its sibling node at right sharing same predecssor. +/// * If (true/false) next of a node is a virtual node, the actual next is the (true/false) next of the virtual node. +/// +/// Specific to this graph, +/// * `A` and `B` are located in root subcandidate. +/// * `C` is located in subcandidate 1. +/// * `D`, `E` are located in subcandidate 2. +/// * `F` is located in subcandidate 3. +/// * `G` is located in subcandidate 4. +/// +/// Thus the true next of `A` is `B`, the false next is none (this pattern is not matched if failed to match `A`). +/// The true next of `B` is `C`, the false next is none. +/// The true next of `C` is `F` (the true next of a virtual node), the false next is `D`. +/// The true next of `D` is `E`, the false next is none. +/// The true next of `E` is `F` (through a virtual node), the false next is none. +/// The true next of `F` is none (end matching here), the false next is `G`. +/// The true and false next of `G` both are none. +#[derive(Debug, Default)] +struct MatchNode { + matched_keys: Vec, + // Index in matching graph of the next tested node if this node is evaluated to true. + true_next: Option, + // Index in matching graph of the next tested node if this node is evaluated to false. + false_next: Option, + // A match node is virtual if it is not related to any patterns. + is_virtual: bool, +} + +impl MatchNode { + fn real_node(matched_keys: Vec) -> Self { + Self { matched_keys, true_next: None, false_next: None, is_virtual: false } + } + + fn virtual_node(subcandidate_id: SubcandidateId) -> Self { + let key = MatchKey { + decision_id: DecisionId::ZERO, + subcandidate_id, + match_id: MatchPairId::INVALID, + }; + Self { matched_keys: vec![key], true_next: None, false_next: None, is_virtual: true } + } + + fn node_id(&self) -> MatchPairId { + self.matched_keys.first().expect("MatchNode must have at least one key").match_id + } +} + +// Information about patterns generating conditions. +#[derive(Debug)] +struct MatchPairInfo { + span: Span, + fully_matched_block_pairs: Vec<(BasicBlock, BasicBlock)>, +} + +// Context to process a pattern decision (without guard). +#[derive(Debug)] +struct CandidateCtx { + span: Span, + latest_subcandidate_id: SubcandidateId, + parent_subcandidate_map: FxHashMap, + last_child_node_map: FxHashMap, + match_pairs_info: FxHashMap, + matching_graph: Vec, +} + +impl CandidateCtx { + fn new(span: Span) -> Self { + Self { + span, + latest_subcandidate_id: SubcandidateId::ROOT, + parent_subcandidate_map: FxHashMap::default(), + last_child_node_map: FxHashMap::default(), + match_pairs_info: FxHashMap::default(), + matching_graph: vec![MatchNode::virtual_node(SubcandidateId::ROOT)], + } + } + fn next_subcandidate_in(&mut self, parent_subcandidate_id: SubcandidateId) -> SubcandidateId { + let id = self.latest_subcandidate_id.next_subcandidate_id(); + self.latest_subcandidate_id = id; + self.parent_subcandidate_map.insert(id, parent_subcandidate_id); + id + } + + fn on_visiting_patterns(&mut self, matched_info: Vec) { + let keys = matched_info + .into_iter() + .filter_map(|info| info.fully_matched.then_some(info.key)) + .collect::>(); + + if keys.is_empty() { + return; + } + + let parent_subcandidate_id = + self.parent_subcandidate_map.get(&keys[0].subcandidate_id).copied(); + assert!( + keys.iter().skip(1).all(|key| self + .parent_subcandidate_map + .get(&key.subcandidate_id) + .copied() + == parent_subcandidate_id), + "Patterns simultaneously matched should have same parent subcandidate" + ); + let current_node = MatchNode::real_node(keys); + let is_predecessor_of_current = |node: &MatchNode| { + if current_node.matched_keys.iter().all(|this_matched| { + node.matched_keys + .iter() + .any(|prev| prev.subcandidate_id == this_matched.subcandidate_id) + }) { + return true; + } + parent_subcandidate_id.is_some_and(|parent_subcandidate| { + node.matched_keys.iter().any(|prev| prev.subcandidate_id == parent_subcandidate) + }) + }; + if let Some(predecessor_idx) = self + .matching_graph + .iter() + .rev() + .position(is_predecessor_of_current) + .map(|rev_pos| self.matching_graph.len() - (1 + rev_pos)) + { + let current_idx = self.matching_graph.len(); + if self.matching_graph[predecessor_idx].true_next.is_none() { + self.matching_graph[predecessor_idx].true_next = Some(current_idx); + } + if let Some(elder_sibling_idx) = + self.last_child_node_map.insert(predecessor_idx, current_idx) + { + self.matching_graph[elder_sibling_idx].false_next = Some(current_idx); + } + } + self.matching_graph.push(current_node); + } + + fn on_matching_patterns( + &mut self, + test_block: BasicBlock, + matched_block: BasicBlock, + matched_infos: Vec, + ) { + for matched in matched_infos { + let info = self.match_pairs_info.entry(matched.key.match_id).or_insert_with(|| { + MatchPairInfo { span: matched.span, fully_matched_block_pairs: vec![] } + }); + if matched.fully_matched { + info.fully_matched_block_pairs.push((test_block, matched_block)); + } + } + } + + fn on_merging_subcandidates( + &mut self, + next_subcandidate_id: SubcandidateId, + merging_subcandidate_ids: impl Iterator, + ) { + let merged_node = MatchNode::virtual_node(next_subcandidate_id); + let current_idx = self.matching_graph.len(); + let mut merging_subcandidates = merging_subcandidate_ids.collect::>(); + 'r: for prev_node in self.matching_graph.iter_mut().rev() { + for key in &prev_node.matched_keys { + if merging_subcandidates.remove(&key.subcandidate_id) { + assert!( + prev_node.true_next.is_none(), + "merged node must be the tail of its branch" + ); + prev_node.true_next = Some(current_idx); + if merging_subcandidates.is_empty() { + break 'r; + } + } + } + } + self.matching_graph.push(merged_node); + } + + fn generate_condition_info(&self) -> FxIndexMap { + let mut condition_infos_map = FxIndexMap::::default(); + let mut condition_counter = 0; + let mut new_condition_info = || { + let condition_id = ConditionId::from_usize(condition_counter); + condition_counter += 1; + ConditionInfo { condition_id, true_next_id: None, false_next_id: None } + }; + let find_next_node = |next_idx: Option, branch: bool| -> Option<&MatchNode> { + let mut next_node = &self.matching_graph[next_idx?]; + while next_node.is_virtual { + let next_idx = if branch { next_node.true_next } else { next_node.false_next }; + next_node = &self.matching_graph[next_idx?]; + } + Some(next_node) + }; + + for node in &self.matching_graph { + if node.is_virtual { + continue; + } + condition_infos_map.entry(node.node_id()).or_insert_with(&mut new_condition_info); + + let [true_next_id, false_next_id] = [(true, node.true_next), (false, node.false_next)] + .map(|(branch, next_idx)| { + find_next_node(next_idx, branch).map(|next_node| { + condition_infos_map + .entry(next_node.node_id()) + .or_insert_with(&mut new_condition_info) + .condition_id + }) + }); + + let condition_info = + condition_infos_map.get_mut(&node.node_id()).expect("ensured to be inserted above"); + + assert!( + condition_info.true_next_id.is_none() + || condition_info.true_next_id == true_next_id, + "only has one true next" + ); + assert!( + condition_info.false_next_id.is_none() + || condition_info.false_next_id == false_next_id, + "only has one false next" + ); + + condition_info.true_next_id = true_next_id; + condition_info.false_next_id = false_next_id; + } + condition_infos_map + } + + fn into_matching_decision( + self, + mut into_branch_blocks: impl FnMut( + Span, + &[(BasicBlock, BasicBlock)], + &FxIndexSet, + ) -> (Vec, Vec), + ) -> MCDCTargetInfo { + // Note. Conditions tested in same blocks are from different subcandidates. + // Since one condition might be the `false next` of another, it can not update + // condbitmap in another's matched block as if it is evaluated to `false` (though we know it is in fact), + // otherwise we may not update test vector index right. `matched_blocks_in_decision` here is used to record + // such blocks. + // In future we could differentiate blocks updating condbitmap from blocks increment counts to get + // better results. + let mut matched_blocks_in_decision = FxIndexSet::default(); + let conditions: Vec<_> = self + .generate_condition_info() + .into_iter() + .map(|(match_id, condition_info)| { + let &MatchPairInfo { span, ref fully_matched_block_pairs } = + self.match_pairs_info.get(&match_id).expect("all match pairs are recorded"); + + matched_blocks_in_decision + .extend(fully_matched_block_pairs.iter().map(|pair| pair.1)); + let (true_markers, false_markers) = into_branch_blocks( + span, + fully_matched_block_pairs, + &matched_blocks_in_decision, + ); + + MCDCBranchSpan { span, condition_info, true_markers, false_markers } + }) + .collect(); + let decision = MCDCDecisionSpan::new(self.span); + MCDCTargetInfo { decision, conditions, nested_decisions_id: vec![] } + } +} + +#[derive(Debug)] +pub(super) struct MatchingDecisionCtx { + candidates: FxIndexMap, + test_blocks: FxIndexMap>, +} + +impl MatchingDecisionCtx { + pub(super) fn new(candidates: &[(Span, DecisionId)]) -> Self { + Self { + candidates: candidates + .into_iter() + .map(|&(span, id)| (id, CandidateCtx::new(span))) + .collect(), + test_blocks: FxIndexMap::default(), + } + } + + fn visit_conditions(&mut self, patterns: &Vec) { + for (decision_id, infos) in group_match_info_by_decision(patterns) { + let candidate_ctx = self.candidates.get_mut(&decision_id).expect("unknown candidate"); + candidate_ctx.on_visiting_patterns(infos); + } + } + + fn match_conditions( + &mut self, + cfg: &mut CFG<'_>, + test_block: BasicBlock, + target_patterns: impl Iterator)>, + ) { + let mut otherwise_blocks = FxIndexSet::default(); + let mut matched_blocks = Vec::with_capacity(target_patterns.size_hint().0); + for (block, patterns) in target_patterns { + if patterns.is_empty() { + otherwise_blocks.insert(block); + continue; + } + for (decision_id, infos) in group_match_info_by_decision(&patterns) { + let candidate_ctx = + self.candidates.get_mut(&decision_id).expect("unknown candidate"); + candidate_ctx.on_matching_patterns(test_block, block, infos); + } + matched_blocks.push(block); + } + let fail_block = + find_fail_block(cfg, test_block, matched_blocks.iter().copied(), otherwise_blocks); + self.test_blocks + .insert(test_block, matched_blocks.into_iter().chain(fail_block.into_iter()).collect()); + } + + fn finish_matching_tree( + self, + mut inject_block_marker: impl FnMut(Span, BasicBlock) -> BlockMarkerId, + ) -> FxIndexMap { + let mut block_markers_map = FxHashMap::::default(); + + let mut into_branch_blocks = + |span: Span, + test_match_pairs: &[(BasicBlock, BasicBlock)], + excluded_unmatched_blocks: &FxIndexSet| { + let mut true_markers = Vec::with_capacity(test_match_pairs.len()); + let mut false_markers = Vec::with_capacity(test_match_pairs.len()); + let mut into_marker = |block: BasicBlock| { + *block_markers_map + .entry(block) + .or_insert_with(|| inject_block_marker(span, block)) + }; + for &(test_block, matched_block) in test_match_pairs { + true_markers.push(into_marker(matched_block)); + false_markers.extend( + self.test_blocks + .get(&test_block) + .expect("all test blocks must be recorded") + .into_iter() + .copied() + .filter_map(|block| { + (block != matched_block + && !excluded_unmatched_blocks.contains(&block)) + .then(|| into_marker(block)) + }), + ); + } + (true_markers, false_markers) + }; + self.candidates + .into_iter() + .map(|(decision_id, candidate_ctx)| { + (decision_id, candidate_ctx.into_matching_decision(&mut into_branch_blocks)) + }) + .collect() + } +} + +fn group_match_info_by_decision( + matched_infos: &[MatchCoverageInfo], +) -> impl IntoIterator)> { + let mut keys_by_decision = FxIndexMap::>::default(); + for pattern_info in matched_infos { + keys_by_decision + .entry(pattern_info.key.decision_id) + .or_default() + .push(pattern_info.clone()); + } + keys_by_decision +} + +/// Upon testing there probably is a block to go if all patterns failed to match. +/// We should increment false count and update condbitmap index of all tested conditions in this block. +/// Considering the `otherwise_block` might be reused by several tests, we inject a `fail_block` +/// here to do such stuff. +fn find_fail_block( + cfg: &mut CFG<'_>, + test_block: BasicBlock, + matched_blocks: impl Iterator, + otherwise_blocks: FxIndexSet, +) -> Vec { + let matched_blocks = FxIndexSet::from_iter(matched_blocks); + let mut prev_fail_blocks = vec![]; + let mut blocks = VecDeque::from([test_block]); + // Some tests might contain multiple sub tests. For example, to test range pattern `0..5`, first + // test if the value >= 0, then test if the value < 5. So `matched_block` might not be a successor of + // the `test_block` and there are two blocks which both are predecessors of `fail_block`. + while let Some(block) = blocks.pop_front() { + for successor in cfg.block_data(block).terminator().successors() { + if matched_blocks.contains(&successor) { + continue; + } else if otherwise_blocks.contains(&successor) { + prev_fail_blocks.push(block); + } else { + blocks.push_back(successor); + } + } + } + otherwise_blocks + .into_iter() + .map(|otherwise_block| { + let fail_block = cfg.start_new_block(); + cfg.terminate( + fail_block, + cfg.block_data(test_block).terminator().source_info, + TerminatorKind::Goto { target: otherwise_block }, + ); + + for &prev_block in &prev_fail_blocks { + let otherwise_ref = cfg + .block_data_mut(prev_block) + .terminator_mut() + .successors_mut() + .find(|block| **block == otherwise_block) + .expect("otherwise_block is ensured to be one of successors above"); + *otherwise_ref = fail_block; + } + fail_block + }) + .collect() +} + +/// Context handling matching decisions with if guards. +/// After lowering matching tree, rustc build mir for if guards and code in arms candidate by candidate. +/// In case decisions in arm blocks are taken as nested decisions, the unfinished candidates are moved into +/// this context to wait for their guards. +#[derive(Debug)] +struct LateMatchingCtx { + candidates: FxIndexMap, + finished_arms_count: usize, + otherwise_block: Option, + nested_decisions_in_guards: Vec, +} + +impl LateMatchingCtx { + fn finish_arm( + &mut self, + decision_id: DecisionId, + mut inject_block_marker: impl FnMut(Span) -> BlockMarkerId, + guard_info: Option, + ) { + let Some(MCDCTargetInfo { decision, conditions, .. }) = + self.candidates.get_mut(&decision_id) + else { + return; + }; + + let arm_block = inject_block_marker(decision.span); + decision.update_end_markers.push(arm_block); + if let Some(mut guard) = guard_info { + decision.span = decision.span.to(guard.decision.span); + let rebase_condition_id = + |id: ConditionId| ConditionId::from_usize(id.as_usize() + conditions.len()); + for branch in &mut guard.conditions { + let ConditionInfo { condition_id, true_next_id, false_next_id } = + &mut branch.condition_info; + *condition_id = rebase_condition_id(*condition_id); + *true_next_id = true_next_id.map(rebase_condition_id); + *false_next_id = false_next_id.map(rebase_condition_id); + } + let guard_entry_id = rebase_condition_id(ConditionId::START); + conditions + .iter_mut() + .filter(|branch| branch.condition_info.true_next_id.is_none()) + .for_each(|branch| branch.condition_info.true_next_id = Some(guard_entry_id)); + conditions.extend(guard.conditions); + self.nested_decisions_in_guards.extend(guard.nested_decisions_id); + } + self.finished_arms_count += 1; + } + + fn all_arms_finished(&self) -> bool { + self.finished_arms_count == self.candidates.len() + } + + fn into_done(mut self) -> (FxIndexMap, Vec) { + let Some(all_unmatched_block) = self.otherwise_block.or_else(|| { + self.candidates.pop().map(|(_, target_info)| target_info.decision.update_end_markers[0]) + }) else { + return (Default::default(), vec![]); + }; + // Update test vector bits of a candidate in arm blocks of it and all candidates below it. + let mut unmatched_blocks: Vec<_> = self + .candidates + .values() + .skip(1) + .map(|target| target.decision.update_end_markers[0]) + .chain(std::iter::once(all_unmatched_block)) + .rev() + .collect(); + // Discard condition bitmap of a candidate in arm blocks of all candidates above it. + // This is to avoid weird result if multiple candidates all match the value. + let mut discard_blocks = vec![]; + for target in self.candidates.values_mut() { + target.decision.update_end_markers.extend(unmatched_blocks.clone()); + target.decision.discard_end_markers.extend(discard_blocks.clone()); + unmatched_blocks.pop(); + discard_blocks.push(target.decision.update_end_markers[0]); + } + (self.candidates, self.nested_decisions_in_guards) + } +} + +#[derive(Debug, Default)] +pub(super) struct LateMatchingState { + matching_ctx: Vec, + // Map decision id to guard decision id. + matching_guard_map: FxIndexMap, + guard_decisions_info: FxIndexMap>, +} + +impl LateMatchingState { + pub(super) fn is_empty(&self) -> bool { + self.matching_ctx.is_empty() + && self.guard_decisions_info.is_empty() + && self.matching_guard_map.is_empty() + } + + fn push_ctx(&mut self, ctx: LateMatchingCtx) { + self.matching_ctx.push(ctx); + } + + fn check_decision_exist(&self, decision_id: DecisionId) -> bool { + let Some(ctx) = self.matching_ctx.last() else { return false }; + let [min, max] = [ctx.candidates.first(), ctx.candidates.last()] + .map(|opt| *opt.expect("ctx must have candidates").0); + min <= decision_id && decision_id <= max + } + + fn declare_guard_for( + &mut self, + decision: DecisionId, + new_guard_id: impl FnOnce() -> DecisionId, + ) -> Option { + if !self.check_decision_exist(decision) { + return None; + } + + let guard_id = self.matching_guard_map.entry(decision).or_insert_with(|| { + let guard_id = new_guard_id(); + self.guard_decisions_info.insert(guard_id, None); + guard_id + }); + + Some(*guard_id) + } + + pub(super) fn is_guard_decision(&self, boolean_decision_id: DecisionId) -> bool { + self.guard_decisions_info.contains_key(&boolean_decision_id) + } + + pub(super) fn add_guard_decision( + &mut self, + boolean_decision_id: DecisionId, + info: MCDCTargetInfo, + ) { + if let Some(Some(guard)) = self.guard_decisions_info.get_mut(&boolean_decision_id) { + assert_eq!( + guard.decision.span, info.decision.span, + "Guard for sub branches must have the same span" + ); + guard.decision.update_end_markers.extend(info.decision.update_end_markers); + guard.decision.discard_end_markers.extend(info.decision.discard_end_markers); + for (this, other) in guard.conditions.iter_mut().zip(info.conditions.into_iter()) { + assert_eq!( + this.condition_info, other.condition_info, + "Guard for sub branches must have decision tree" + ); + assert_eq!( + this.span, other.span, + "Conditions of guard for sub branches must have the same span" + ); + this.true_markers.extend(other.true_markers); + this.false_markers.extend(other.false_markers); + } + } else { + self.guard_decisions_info.insert(boolean_decision_id, Some(info)); + } + } + + fn finish_arm( + &mut self, + id: DecisionId, + inject_block_marker: impl FnMut(Span) -> BlockMarkerId, + ) -> Option { + let ctx = self.matching_ctx.last_mut()?; + let guard = self + .matching_guard_map + .swap_remove(&id) + .and_then(|guard_id| self.guard_decisions_info.swap_remove(&guard_id)) + .flatten(); + ctx.finish_arm(id, inject_block_marker, guard); + if ctx.all_arms_finished() { self.matching_ctx.pop() } else { None } + } +} + +impl MCDCInfoBuilder { + fn create_pattern_decision(&mut self, spans: impl Iterator) -> Vec { + self.ensure_active_state(); + let state = self.state_stack.last_mut().expect("ensured just now"); + let decision_info: Vec<_> = + spans.map(|span| (span, self.decision_id_gen.next_decision_id())).collect(); + assert!(state.is_empty(), "active state for new pattern decision must be empty"); + state.current_ctx = Some(DecisionCtx::new_matching(&decision_info)); + decision_info.into_iter().map(|(_, decision_id)| decision_id).collect() + } + + fn finish_matching_decision_tree( + &mut self, + otherwise_block: Option, + mut inject_block_marker: impl FnMut(Span, BasicBlock) -> BlockMarkerId, + ) { + let state = self.current_state_mut(); + + let Some((DecisionCtx::Matching(ctx), nested_decisions_id)) = state.take_ctx() else { + bug!("no processing pattern decision") + }; + assert!( + nested_decisions_id.is_empty(), + "no other decisions can be nested in matching tree" + ); + let otherwise_block = + otherwise_block.map(|block| inject_block_marker(Span::default(), block)); + let candidates = ctx.finish_matching_tree(inject_block_marker); + let late_ctx = LateMatchingCtx { + candidates, + finished_arms_count: 0, + otherwise_block, + nested_decisions_in_guards: nested_decisions_id, + }; + + self.late_matching_state.push_ctx(late_ctx); + } + + fn prepare_matching_guard(&mut self, decision_id: DecisionId) { + assert!( + self.current_state_mut().current_ctx.is_none(), + "When visit matching guard there should be no processing decisions" + ); + if let Some(guard_id) = self + .late_matching_state + .declare_guard_for(decision_id, || self.decision_id_gen.next_decision_id()) + { + self.current_state_mut().current_ctx = Some(DecisionCtx::new_boolean(guard_id)); + } + } + + fn visit_evaluated_matching_candidate( + &mut self, + tcx: TyCtxt<'_>, + decision_id: DecisionId, + inject_block_marker: impl FnMut(Span) -> BlockMarkerId, + ) { + let Some(ctx) = self.late_matching_state.finish_arm(decision_id, inject_block_marker) + else { + return; + }; + + let (targets, mut nested_decisions_id) = ctx.into_done(); + let mut entry_id = None; + for (id, mut info) in targets.into_iter().rev() { + info.nested_decisions_id = nested_decisions_id.clone(); + if self.append_mcdc_info(tcx, id, info) { + nested_decisions_id = vec![id]; + entry_id = Some(id); + } + } + self.on_ctx_finished(tcx, entry_id); + } +} + +impl Builder<'_, '_> { + /// Prepare for matching decisions if mcdc is enabled. The returned decision ids should be used to generate [`CandidateCovId`] for candidates. + /// Do nothing if mcdc is not enabled or the candidates is not proper for mcdc. + pub(crate) fn mcdc_create_matching_decisions( + &mut self, + candidates: &mut [&mut Candidate<'_>], + refutable: bool, + ) { + let can_mapped_decisions = refutable || candidates.len() > 1; + if can_mapped_decisions + && let Some(coverage_info) = self.coverage_info.as_mut() + && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut() + { + let ids = mcdc_info + .create_pattern_decision(candidates.iter().map(|candidate| candidate.span())); + assert_eq!(ids.len(), candidates.len(), "every candidate must get a decision id"); + candidates.iter_mut().zip(ids.into_iter()).for_each(|(candidate, decision_id)| { + candidate.set_coverage_id(CandidateCovId { + decision_id, + subcandidate_id: SubcandidateId::ROOT, + }) + }); + } + } + + /// Create and assign [`CandidateCovId`] for subcandidates if mcdc is enabled. + /// Do nothing if mcdc is not enabled or the candidate is ignored. + pub(crate) fn mcdc_create_subcandidates( + &mut self, + candidate_id: CandidateCovId, + subcandidates: &mut [Candidate<'_>], + ) { + if candidate_id.is_valid() + && let Some(coverage_info) = self.coverage_info.as_mut() + && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut() + && let Some(DecisionCtx::Matching(ctx)) = mcdc_info.current_processing_ctx_mut() + && let Some(candidate_ctx) = ctx.candidates.get_mut(&candidate_id.decision_id) + { + subcandidates.iter_mut().for_each(|subcandidate| { + let id = CandidateCovId { + decision_id: candidate_id.decision_id, + subcandidate_id: candidate_ctx + .next_subcandidate_in(candidate_id.subcandidate_id), + }; + subcandidate.set_coverage_id(id); + }); + } + } + + /// Notify the mcdc builder that some candidates are merged. This can happen on or patterns without bindings. + /// Do nothing if mcdc is not enabled or the candidates is not proper for mcdc. + pub(crate) fn mcdc_merge_subcandidates( + &mut self, + candidate_id: CandidateCovId, + subcandidates: impl Iterator, + ) { + if candidate_id.is_valid() + && let Some(coverage_info) = self.coverage_info.as_mut() + && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut() + && let Some(DecisionCtx::Matching(ctx)) = mcdc_info.current_processing_ctx_mut() + && let Some(candidate_ctx) = ctx.candidates.get_mut(&candidate_id.decision_id) + { + candidate_ctx.on_merging_subcandidates(candidate_id.subcandidate_id, subcandidates); + } + } + + /// Notify the mcdc builder some patterns are preparing for test. This function must be called in same order as match targets are determined. + /// Do nothing if mcdc is not enabled. + pub(crate) fn mcdc_visit_pattern_conditions<'a>( + &mut self, + patterns: impl Iterator>, + ) { + if let Some(coverage_info) = self.coverage_info.as_mut() + && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut() + && let Some(DecisionCtx::Matching(ctx)) = mcdc_info.current_processing_ctx_mut() + { + patterns.for_each(|infos| ctx.visit_conditions(infos)); + } + } + + /// Inform the mcdc builder where the patterns are tested and the blocks to go if the patterns are matched. + /// Before this call `test_block` must be injected terminator. + /// If mcdc is not enabled do nothing. + pub(crate) fn mcdc_match_pattern_conditions( + &mut self, + test_block: BasicBlock, + target_patterns: impl Iterator)>, + ) { + if let Some(coverage_info) = self.coverage_info.as_mut() + && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut() + && let Some(DecisionCtx::Matching(ctx)) = mcdc_info.current_processing_ctx_mut() + { + ctx.match_conditions(&mut self.cfg, test_block, target_patterns); + } + } + + /// Notify the mcdc builder matching tree is finished lowering. + /// This function should be called before diving into arms and guards. + /// The `otherwise_block` should be provided if and only if the candidates are from refutable statements (`if let` or `let else`). + /// Do nothing if mcdc is not enabled. + pub(crate) fn mcdc_finish_matching_tree( + &mut self, + mut candidates: impl Iterator, + otherwise_block: Option, + ) { + let has_decision = candidates.any(CandidateCovId::is_valid); + if has_decision + && let Some(coverage_info) = self.coverage_info.as_mut() + && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut() + { + let inject_block_marker = |span: Span, block: BasicBlock| { + coverage_info.markers.inject_block_marker( + &mut self.cfg, + SourceInfo { span, scope: self.source_scope }, + block, + ) + }; + mcdc_info.finish_matching_decision_tree(otherwise_block, inject_block_marker); + } + } + + /// Notify the mcdc builder a guard is to be lowered. + /// Do nothing if mcdc is not enabled or the candidates is not proper for mcdc. + pub(crate) fn mcdc_visit_matching_guard(&mut self, coverage_id: CandidateCovId) { + if coverage_id.is_valid() + && let Some(coverage_info) = self.coverage_info.as_mut() + && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut() + { + mcdc_info.prepare_matching_guard(coverage_id.decision_id); + } + } + + /// Notify the mcdc builder a candidate has been totally processed. + /// Do nothing if mcdc is not enabled or the candidates is not proper for mcdc. + pub(crate) fn mcdc_visit_matching_decision_end( + &mut self, + coverage_id: CandidateCovId, + arm_block: BasicBlock, + ) { + if coverage_id.is_valid() + && let Some(coverage_info) = self.coverage_info.as_mut() + && let Some(mcdc_info) = coverage_info.mcdc_info.as_mut() + { + let inject_block_marker = |span: Span| { + coverage_info.markers.inject_block_marker( + &mut self.cfg, + SourceInfo { span, scope: self.source_scope }, + arm_block, + ) + }; + mcdc_info.visit_evaluated_matching_candidate( + self.tcx, + coverage_id.decision_id, + inject_block_marker, + ); + } + } +} diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 863b1633c12d2..2711321446b7f 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -456,6 +456,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { opt_scrutinee_place, ); + let coverage_id = branch.coverage_id; + let arm_block = this.bind_pattern( outer_source_info, branch, @@ -465,6 +467,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { EmitStorageLive::Yes, ); + this.mcdc_visit_matching_decision_end(coverage_id, arm_block); + this.fixed_temps_scope = old_dedup_scope; if let Some(source_scope) = scope { @@ -1022,7 +1026,7 @@ impl<'tcx> FlatPat<'tcx> { /// of candidates, where each "leaf" candidate represents one of the ways for /// the arm pattern to successfully match. #[derive(Debug)] -struct Candidate<'tcx> { +pub(crate) struct Candidate<'tcx> { /// For the candidate to match, all of these must be satisfied... /// /// --- @@ -1095,8 +1099,7 @@ struct Candidate<'tcx> { /// edges, see the doc for [`Builder::match_expr`]. false_edge_start_block: Option, - #[allow(unused)] - /// The id to identify the candidate in coverage instrument. + /// Identify the candidate in coverage instrument. coverage_id: coverage::CandidateCovId, } @@ -1158,7 +1161,10 @@ impl<'tcx> Candidate<'tcx> { ); } - #[allow(unused)] + pub(crate) fn span(&self) -> Span { + self.extra_data.span + } + pub(crate) fn set_coverage_id(&mut self, coverage_id: coverage::CandidateCovId) { self.coverage_id = coverage_id; // Assign id for match pairs only if this candidate is the root. @@ -1413,12 +1419,15 @@ struct MatchTreeSubBranch<'tcx> { ascriptions: Vec>, /// Whether the sub-branch corresponds to a never pattern. is_never: bool, + /// The coverage id of this sub-branch. + coverage_id: coverage::CandidateCovId, } /// A branch in the output of match lowering. #[derive(Debug)] struct MatchTreeBranch<'tcx> { sub_branches: Vec>, + coverage_id: coverage::CandidateCovId, } /// The result of generating MIR for a pattern-matching expression. Each input branch/arm/pattern @@ -1468,6 +1477,7 @@ impl<'tcx> MatchTreeSubBranch<'tcx> { .chain(candidate.extra_data.ascriptions) .collect(), is_never: candidate.extra_data.is_never, + coverage_id: candidate.coverage_id, } } } @@ -1475,6 +1485,7 @@ impl<'tcx> MatchTreeSubBranch<'tcx> { impl<'tcx> MatchTreeBranch<'tcx> { fn from_candidate(candidate: Candidate<'tcx>) -> Self { let mut sub_branches = Vec::new(); + let coverage_id = candidate.coverage_id; traverse_candidate( candidate, &mut Vec::new(), @@ -1489,7 +1500,7 @@ impl<'tcx> MatchTreeBranch<'tcx> { parent_data.pop(); }, ); - MatchTreeBranch { sub_branches } + MatchTreeBranch { sub_branches, coverage_id } } } @@ -1540,8 +1551,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // If none of the arms match, we branch to `otherwise_block`. When lowering a `match` // expression, exhaustiveness checking ensures that this block is unreachable. let mut candidate_refs = candidates.iter_mut().collect::>(); + self.mcdc_create_matching_decisions(&mut candidate_refs, refutable); let otherwise_block = self.match_candidates(match_start_span, scrutinee_span, block, &mut candidate_refs); + self.mcdc_finish_matching_tree( + candidate_refs.iter().map(|c| c.coverage_id), + refutable.then_some(otherwise_block), + ); // Set up false edges so that the borrow-checker cannot make use of the specific CFG we // generated. We falsely branch from each candidate to the one below it to make it as if we @@ -1900,6 +1916,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { .map(|flat_pat| Candidate::from_flat_pat(flat_pat, candidate.has_guard)) .collect(); candidate.subcandidates[0].false_edge_start_block = candidate.false_edge_start_block; + self.mcdc_create_subcandidates(candidate.coverage_id, &mut candidate.subcandidates); } /// Try to merge all of the subcandidates of the given candidate into one. This avoids @@ -1974,6 +1991,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { return; } + self.mcdc_merge_subcandidates( + candidate.coverage_id, + candidate.subcandidates.iter().map(|cand| cand.coverage_id.subcandidate_id), + ); + let mut last_otherwise = None; let shared_pre_binding_block = self.cfg.start_new_block(); // This candidate is about to become a leaf, so unset `or_span`. @@ -2166,10 +2188,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ) -> ( &'b mut [&'c mut Candidate<'tcx>], FxIndexMap, Vec<&'b mut Candidate<'tcx>>>, + Option, Vec>>, ) { // For each of the possible outcomes, collect vector of candidates that apply if the test // has that particular outcome. let mut target_candidates: FxIndexMap<_, Vec<&mut Candidate<'_>>> = Default::default(); + let mut mcdc_match_records = (self.tcx.sess.instrument_coverage_mcdc() + && candidates.first().is_some_and(|candidate| candidate.coverage_id.is_valid())) + .then(FxIndexMap::default); let total_candidate_count = candidates.len(); @@ -2177,13 +2203,16 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // point we may encounter a candidate where the test is not relevant; at that point, we stop // sorting. while let Some(candidate) = candidates.first_mut() { - let Some(branch) = + let Some((branch, match_cov_info)) = self.sort_candidate(match_place, test, candidate, &target_candidates) else { break; }; let (candidate, rest) = candidates.split_first_mut().unwrap(); target_candidates.entry(branch).or_insert_with(Vec::new).push(candidate); + if let Some(records) = mcdc_match_records.as_mut() { + records.entry(branch).or_insert_with(Vec::new).push(match_cov_info); + } candidates = rest; } @@ -2195,7 +2224,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { debug!("tested_candidates: {}", total_candidate_count - candidates.len()); debug!("untested_candidates: {}", candidates.len()); - (candidates, target_candidates) + (candidates, target_candidates, mcdc_match_records) } /// This is the most subtle part of the match lowering algorithm. At this point, there are @@ -2309,9 +2338,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // For each of the N possible test outcomes, build the vector of candidates that applies if // the test has that particular outcome. This also mutates the candidates to remove match // pairs that are fully satisfied by the relevant outcome. - let (remaining_candidates, target_candidates) = + let (remaining_candidates, target_candidates, mcdc_match_records) = self.sort_candidates(match_place, &test, candidates); + if let Some(match_records) = mcdc_match_records.as_ref() { + self.mcdc_visit_pattern_conditions(match_records.values()); + } + // The block that we should branch to if none of the `target_candidates` match. let remainder_start = self.cfg.start_new_block(); @@ -2342,6 +2375,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { match_place, &test, target_blocks, + mcdc_match_records, ); remainder_start.and(remaining_candidates) @@ -2402,6 +2436,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } + let coverage_id = branch.coverage_id; let success = self.bind_pattern( self.source_info(pat.span), branch, @@ -2411,6 +2446,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { emit_storage_live, ); + self.mcdc_visit_matching_decision_end(coverage_id, success); + // If branch coverage is enabled, record this branch. self.visit_coverage_conditional_let(pat, success, built_tree.otherwise_block); @@ -2484,6 +2521,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let (post_guard_block, otherwise_post_guard_block) = self.in_if_then_scope(match_scope, guard_span, |this| { guard_span = this.thir[guard].span; + this.mcdc_visit_matching_guard(sub_branch.coverage_id); this.then_else_break( block, guard, diff --git a/compiler/rustc_mir_build/src/builder/matches/test.rs b/compiler/rustc_mir_build/src/builder/matches/test.rs index 834f8be0d007a..be954587bf9ae 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -76,11 +76,22 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { place: Place<'tcx>, test: &Test<'tcx>, target_blocks: FxIndexMap, BasicBlock>, + mut target_coverage_info: Option< + FxIndexMap, Vec>, + >, ) { let place_ty = place.ty(&self.local_decls, self.tcx); debug!(?place, ?place_ty); let target_block = |branch| target_blocks.get(&branch).copied().unwrap_or(otherwise_block); - + let mut target_coverage_blocks = + FxIndexMap::>::default(); + let mut mcdc_add_matched_block = |branch: &TestBranch<'tcx>, success_block| { + let Some(coverage_info) = target_coverage_info.as_mut() else { + return; + }; + target_coverage_blocks + .insert(success_block, coverage_info.swap_remove(branch).unwrap_or_default()); + }; let source_info = self.source_info(test.span); match test.kind { TestKind::Switch { adt_def } => { @@ -88,6 +99,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let switch_targets = SwitchTargets::new( adt_def.discriminants(self.tcx).filter_map(|(idx, discr)| { if let Some(&block) = target_blocks.get(&TestBranch::Variant(idx)) { + mcdc_add_matched_block(&TestBranch::Variant(idx), block); Some((discr.val, block)) } else { None @@ -120,6 +132,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let switch_targets = SwitchTargets::new( target_blocks.iter().filter_map(|(&branch, &block)| { if let TestBranch::Constant(_, bits) = branch { + mcdc_add_matched_block(&branch, block); Some((bits, block)) } else { None @@ -137,6 +150,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { TestKind::If => { let success_block = target_block(TestBranch::Success); let fail_block = target_block(TestBranch::Failure); + mcdc_add_matched_block(&TestBranch::Success, success_block); let terminator = TerminatorKind::if_(Operand::Copy(place), success_block, fail_block); self.cfg.terminate(block, self.source_info(match_start_span), terminator); @@ -152,6 +166,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let mut place = place; let mut block = block; + mcdc_add_matched_block(&TestBranch::Success, success_block); match ty.kind() { ty::Adt(def, _) if tcx.is_lang_item(def.did(), LangItem::String) => { if !tcx.features().string_deref_patterns() { @@ -214,6 +229,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { TestKind::Range(ref range) => { let success = target_block(TestBranch::Success); let fail = target_block(TestBranch::Failure); + mcdc_add_matched_block(&TestBranch::Success, success); // Test `val` by computing `lo <= val && val <= hi`, using primitive comparisons. let val = Operand::Copy(place); @@ -260,6 +276,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let success_block = target_block(TestBranch::Success); let fail_block = target_block(TestBranch::Failure); + mcdc_add_matched_block(&TestBranch::Success, success_block); // result = actual == expected OR result = actual < expected // branch based on result self.compare( @@ -293,6 +310,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.terminate(block, source_info, TerminatorKind::Unreachable); } } + if let Some(mut coverage_info) = target_coverage_info { + // This argument should have been `fail_block` from each `TestKind` arm, but they all are the same now. + target_coverage_blocks.insert( + target_block(TestBranch::Failure), + coverage_info.swap_remove(&TestBranch::Failure).unwrap_or_default(), + ); + self.mcdc_match_pattern_conditions(block, target_coverage_blocks.into_iter()); + } } /// Perform `let temp = ::deref(&place)`. @@ -524,7 +549,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { test: &Test<'tcx>, candidate: &mut Candidate<'tcx>, sorted_candidates: &FxIndexMap, Vec<&mut Candidate<'tcx>>>, - ) -> Option> { + ) -> Option<(TestBranch<'tcx>, coverage::MatchCoverageInfo)> { // Find the match_pair for this place (if any). At present, // afaik, there can be at most one. (In the future, if we // adopted a more general `@` operator, there might be more @@ -749,6 +774,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } }; + let match_info = candidate.coverage_id.new_match_info( + match_pair.coverage_id, + match_pair.pattern_span, + fully_matched, + ); + if fully_matched { // Replace the match pair by its sub-pairs. let match_pair = candidate.match_pairs.remove(match_pair_index); @@ -757,7 +788,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { candidate.match_pairs.sort_by_key(|pair| matches!(pair.test_case, TestCase::Or { .. })); } - ret + ret.map(|branch| (branch, match_info)) } } diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 03df8c8d8de26..fd76d54ed266c 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -165,9 +165,10 @@ fn create_mappings( )); let mut term_for_sum_of_bcbs = |bcbs| { - coverage_counters - .term_for_sum_of_bcbs(bcbs) - .expect("all BCBs with spans were given counters") + // Some patterns may have folded conditions. E.g The first `true` in the second part of `(false, false) | (true, true)` is + // always matched if tested (If the first value of the tuple was `false`, this `true` is never tested). + // Such condition has no counter for one branch hence we treat it as folded. + coverage_counters.term_for_sum_of_bcbs(bcbs).unwrap_or(mir::coverage::CovTerm::Zero) }; // MCDC branch mappings are appended with their decisions in case decisions were ignored. diff --git a/tests/coverage/mcdc/if_let.cov-map b/tests/coverage/mcdc/if_let.cov-map new file mode 100644 index 0000000000000..c9ad46aa8bbf4 --- /dev/null +++ b/tests/coverage/mcdc/if_let.cov-map @@ -0,0 +1,318 @@ +Function name: if_let::joint_or_patterns +Raw bytes (373): 0x[01, 01, 44, 01, 4b, 4f, 0d, 05, 09, 4f, 0d, 05, 09, 01, 1b, 05, 2d, 2d, 05, 01, 27, 47, 2d, 05, 0d, 01, 33, 4b, 31, 4f, 0d, 05, 09, 31, 09, 01, 47, 05, 0d, 4f, 0d, 05, 09, 01, 87, 02, 8b, 02, 1d, 8f, 02, 19, 11, 15, 8b, 02, 1d, 8f, 02, 19, 11, 15, 01, 73, 11, 21, 21, 11, 01, ef, 01, f3, 01, 21, 11, 19, 01, d3, 01, d7, 01, 29, db, 01, 21, f3, 01, 1d, 11, 19, 21, bf, 01, 15, 25, ce, 01, ba, 01, 01, d3, 01, d7, 01, 29, db, 01, 21, f3, 01, 1d, 11, 19, 21, bf, 01, 15, 25, c7, 01, 15, cb, 01, 25, 29, 1d, 01, d3, 01, d7, 01, 29, db, 01, 21, f3, 01, 1d, 11, 19, 29, 25, 1d, 15, 01, ef, 01, f3, 01, 21, 11, 19, 01, 87, 02, 8b, 02, 1d, 8f, 02, 19, 11, 15, 8b, 02, 1d, 8f, 02, 19, 11, 15, 1a, 01, 26, 01, 00, 27, 28, 08, 05, 01, 0c, 00, 40, 20, 02, 4b, 00, 0c, 00, 40, 30, 16, 1f, 01, 02, 03, 00, 0d, 00, 1c, 30, 22, 0d, 02, 04, 00, 00, 14, 00, 1b, 30, 2d, 05, 03, 04, 00, 00, 1f, 00, 28, 30, 2e, 3f, 04, 00, 05, 00, 2a, 00, 33, 30, 31, 09, 05, 00, 00, 00, 36, 00, 3f, 42, 00, 43, 00, 46, 02, 00, 47, 02, 06, 4b, 02, 05, 00, 06, 28, 10, 05, 03, 0c, 00, 40, 20, f6, 01, 87, 02, 00, 0c, 00, 40, 30, 6e, 77, 01, 02, 03, 00, 0d, 00, 1c, 30, ea, 01, 19, 02, 04, 00, 00, 14, 00, 1b, ce, 01, 00, 19, 00, 1a, 30, 21, 11, 03, 04, 00, 00, 1f, 00, 28, ba, 01, 00, 26, 00, 27, 30, a3, 01, c3, 01, 04, 00, 05, 00, 2a, 00, 33, ce, 01, 00, 31, 00, 32, 30, e3, 01, e7, 01, 05, 00, 00, 00, 36, 00, 3f, 29, 00, 3d, 00, 3e, ea, 01, 00, 43, 00, 46, f6, 01, 00, 47, 02, 06, 87, 02, 02, 05, 00, 06, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 68 +- expression 0 operands: lhs = Counter(0), rhs = Expression(18, Add) +- expression 1 operands: lhs = Expression(19, Add), rhs = Counter(3) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +- expression 3 operands: lhs = Expression(19, Add), rhs = Counter(3) +- expression 4 operands: lhs = Counter(1), rhs = Counter(2) +- expression 5 operands: lhs = Counter(0), rhs = Expression(6, Add) +- expression 6 operands: lhs = Counter(1), rhs = Counter(11) +- expression 7 operands: lhs = Counter(11), rhs = Counter(1) +- expression 8 operands: lhs = Counter(0), rhs = Expression(9, Add) +- expression 9 operands: lhs = Expression(17, Add), rhs = Counter(11) +- expression 10 operands: lhs = Counter(1), rhs = Counter(3) +- expression 11 operands: lhs = Counter(0), rhs = Expression(12, Add) +- expression 12 operands: lhs = Expression(18, Add), rhs = Counter(12) +- expression 13 operands: lhs = Expression(19, Add), rhs = Counter(3) +- expression 14 operands: lhs = Counter(1), rhs = Counter(2) +- expression 15 operands: lhs = Counter(12), rhs = Counter(2) +- expression 16 operands: lhs = Counter(0), rhs = Expression(17, Add) +- expression 17 operands: lhs = Counter(1), rhs = Counter(3) +- expression 18 operands: lhs = Expression(19, Add), rhs = Counter(3) +- expression 19 operands: lhs = Counter(1), rhs = Counter(2) +- expression 20 operands: lhs = Counter(0), rhs = Expression(65, Add) +- expression 21 operands: lhs = Expression(66, Add), rhs = Counter(7) +- expression 22 operands: lhs = Expression(67, Add), rhs = Counter(6) +- expression 23 operands: lhs = Counter(4), rhs = Counter(5) +- expression 24 operands: lhs = Expression(66, Add), rhs = Counter(7) +- expression 25 operands: lhs = Expression(67, Add), rhs = Counter(6) +- expression 26 operands: lhs = Counter(4), rhs = Counter(5) +- expression 27 operands: lhs = Counter(0), rhs = Expression(28, Add) +- expression 28 operands: lhs = Counter(4), rhs = Counter(8) +- expression 29 operands: lhs = Counter(8), rhs = Counter(4) +- expression 30 operands: lhs = Counter(0), rhs = Expression(59, Add) +- expression 31 operands: lhs = Expression(60, Add), rhs = Counter(8) +- expression 32 operands: lhs = Counter(4), rhs = Counter(6) +- expression 33 operands: lhs = Counter(0), rhs = Expression(52, Add) +- expression 34 operands: lhs = Expression(53, Add), rhs = Counter(10) +- expression 35 operands: lhs = Expression(54, Add), rhs = Counter(8) +- expression 36 operands: lhs = Expression(60, Add), rhs = Counter(7) +- expression 37 operands: lhs = Counter(4), rhs = Counter(6) +- expression 38 operands: lhs = Counter(8), rhs = Expression(47, Add) +- expression 39 operands: lhs = Counter(5), rhs = Counter(9) +- expression 40 operands: lhs = Expression(51, Sub), rhs = Expression(46, Sub) +- expression 41 operands: lhs = Counter(0), rhs = Expression(52, Add) +- expression 42 operands: lhs = Expression(53, Add), rhs = Counter(10) +- expression 43 operands: lhs = Expression(54, Add), rhs = Counter(8) +- expression 44 operands: lhs = Expression(60, Add), rhs = Counter(7) +- expression 45 operands: lhs = Counter(4), rhs = Counter(6) +- expression 46 operands: lhs = Counter(8), rhs = Expression(47, Add) +- expression 47 operands: lhs = Counter(5), rhs = Counter(9) +- expression 48 operands: lhs = Expression(49, Add), rhs = Counter(5) +- expression 49 operands: lhs = Expression(50, Add), rhs = Counter(9) +- expression 50 operands: lhs = Counter(10), rhs = Counter(7) +- expression 51 operands: lhs = Counter(0), rhs = Expression(52, Add) +- expression 52 operands: lhs = Expression(53, Add), rhs = Counter(10) +- expression 53 operands: lhs = Expression(54, Add), rhs = Counter(8) +- expression 54 operands: lhs = Expression(60, Add), rhs = Counter(7) +- expression 55 operands: lhs = Counter(4), rhs = Counter(6) +- expression 56 operands: lhs = Counter(10), rhs = Counter(9) +- expression 57 operands: lhs = Counter(7), rhs = Counter(5) +- expression 58 operands: lhs = Counter(0), rhs = Expression(59, Add) +- expression 59 operands: lhs = Expression(60, Add), rhs = Counter(8) +- expression 60 operands: lhs = Counter(4), rhs = Counter(6) +- expression 61 operands: lhs = Counter(0), rhs = Expression(65, Add) +- expression 62 operands: lhs = Expression(66, Add), rhs = Counter(7) +- expression 63 operands: lhs = Expression(67, Add), rhs = Counter(6) +- expression 64 operands: lhs = Counter(4), rhs = Counter(5) +- expression 65 operands: lhs = Expression(66, Add), rhs = Counter(7) +- expression 66 operands: lhs = Expression(67, Add), rhs = Counter(6) +- expression 67 operands: lhs = Counter(4), rhs = Counter(5) +Number of file 0 mappings: 26 +- Code(Counter(0)) at (prev + 38, 1) to (start + 0, 39) +- MCDCDecision { bitmap_idx: 8, conditions_num: 5 } at (prev + 1, 12) to (start + 0, 64) +- Branch { true: Expression(0, Sub), false: Expression(18, Add) } at (prev + 0, 12) to (start + 0, 64) + true = (c0 - ((c1 + c2) + c3)) + false = ((c1 + c2) + c3) +- MCDCBranch { true: Expression(5, Sub), false: Expression(7, Add), condition_id: 1, true_next_id: 2, false_next_id: 3 } at (prev + 0, 13) to (start + 0, 28) + true = (c0 - (c1 + c11)) + false = (c11 + c1) +- MCDCBranch { true: Expression(8, Sub), false: Counter(3), condition_id: 2, true_next_id: 4, false_next_id: 0 } at (prev + 0, 20) to (start + 0, 27) + true = (c0 - ((c1 + c3) + c11)) + false = c3 +- MCDCBranch { true: Counter(11), false: Counter(1), condition_id: 3, true_next_id: 4, false_next_id: 0 } at (prev + 0, 31) to (start + 0, 40) + true = c11 + false = c1 +- MCDCBranch { true: Expression(11, Sub), false: Expression(15, Add), condition_id: 4, true_next_id: 0, false_next_id: 5 } at (prev + 0, 42) to (start + 0, 51) + true = (c0 - (((c1 + c2) + c3) + c12)) + false = (c12 + c2) +- MCDCBranch { true: Counter(12), false: Counter(2), condition_id: 5, true_next_id: 0, false_next_id: 0 } at (prev + 0, 54) to (start + 0, 63) + true = c12 + false = c2 +- Code(Expression(16, Sub)) at (prev + 0, 67) to (start + 0, 70) + = (c0 - (c1 + c3)) +- Code(Expression(0, Sub)) at (prev + 0, 71) to (start + 2, 6) + = (c0 - ((c1 + c2) + c3)) +- Code(Expression(18, Add)) at (prev + 2, 5) to (start + 0, 6) + = ((c1 + c2) + c3) +- MCDCDecision { bitmap_idx: 16, conditions_num: 5 } at (prev + 3, 12) to (start + 0, 64) +- Branch { true: Expression(61, Sub), false: Expression(65, Add) } at (prev + 0, 12) to (start + 0, 64) + true = (c0 - (((c4 + c5) + c6) + c7)) + false = (((c4 + c5) + c6) + c7) +- MCDCBranch { true: Expression(27, Sub), false: Expression(29, Add), condition_id: 1, true_next_id: 2, false_next_id: 3 } at (prev + 0, 13) to (start + 0, 28) + true = (c0 - (c4 + c8)) + false = (c8 + c4) +- MCDCBranch { true: Expression(58, Sub), false: Counter(6), condition_id: 2, true_next_id: 4, false_next_id: 0 } at (prev + 0, 20) to (start + 0, 27) + true = (c0 - ((c4 + c6) + c8)) + false = c6 +- Code(Expression(51, Sub)) at (prev + 0, 25) to (start + 0, 26) + = (c0 - ((((c4 + c6) + c7) + c8) + c10)) +- MCDCBranch { true: Counter(8), false: Counter(4), condition_id: 3, true_next_id: 4, false_next_id: 0 } at (prev + 0, 31) to (start + 0, 40) + true = c8 + false = c4 +- Code(Expression(46, Sub)) at (prev + 0, 38) to (start + 0, 39) + = (c8 - (c5 + c9)) +- MCDCBranch { true: Expression(40, Add), false: Expression(48, Add), condition_id: 4, true_next_id: 0, false_next_id: 5 } at (prev + 0, 42) to (start + 0, 51) + true = ((c0 - ((((c4 + c6) + c7) + c8) + c10)) + (c8 - (c5 + c9))) + false = (((c10 + c7) + c9) + c5) +- Code(Expression(51, Sub)) at (prev + 0, 49) to (start + 0, 50) + = (c0 - ((((c4 + c6) + c7) + c8) + c10)) +- MCDCBranch { true: Expression(56, Add), false: Expression(57, Add), condition_id: 5, true_next_id: 0, false_next_id: 0 } at (prev + 0, 54) to (start + 0, 63) + true = (c10 + c9) + false = (c7 + c5) +- Code(Counter(10)) at (prev + 0, 61) to (start + 0, 62) +- Code(Expression(58, Sub)) at (prev + 0, 67) to (start + 0, 70) + = (c0 - ((c4 + c6) + c8)) +- Code(Expression(61, Sub)) at (prev + 0, 71) to (start + 2, 6) + = (c0 - (((c4 + c5) + c6) + c7)) +- Code(Expression(65, Add)) at (prev + 2, 5) to (start + 0, 6) + = (((c4 + c5) + c6) + c7) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c12 + +Function name: if_let::joint_pattern_with_or +Raw bytes (121): 0x[01, 01, 13, 01, 47, 4b, 0d, 05, 09, 4b, 0d, 05, 09, 01, 3f, 4b, 11, 05, 09, 11, 09, 01, 2b, 47, 11, 4b, 0d, 05, 09, 01, 05, 01, 3f, 4b, 11, 05, 09, 4b, 0d, 05, 09, 0b, 01, 20, 01, 00, 2b, 28, 05, 04, 01, 0c, 00, 34, 20, 02, 47, 00, 0c, 00, 34, 30, 3a, 23, 02, 03, 04, 00, 0d, 00, 1c, 30, 26, 0d, 03, 00, 00, 00, 14, 00, 1b, 30, 11, 09, 04, 00, 00, 00, 1f, 00, 28, 30, 36, 05, 01, 02, 00, 00, 2a, 00, 33, 3a, 00, 37, 00, 3a, 02, 00, 3b, 02, 06, 47, 02, 05, 00, 06, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 19 +- expression 0 operands: lhs = Counter(0), rhs = Expression(17, Add) +- expression 1 operands: lhs = Expression(18, Add), rhs = Counter(3) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +- expression 3 operands: lhs = Expression(18, Add), rhs = Counter(3) +- expression 4 operands: lhs = Counter(1), rhs = Counter(2) +- expression 5 operands: lhs = Counter(0), rhs = Expression(15, Add) +- expression 6 operands: lhs = Expression(18, Add), rhs = Counter(4) +- expression 7 operands: lhs = Counter(1), rhs = Counter(2) +- expression 8 operands: lhs = Counter(4), rhs = Counter(2) +- expression 9 operands: lhs = Counter(0), rhs = Expression(10, Add) +- expression 10 operands: lhs = Expression(17, Add), rhs = Counter(4) +- expression 11 operands: lhs = Expression(18, Add), rhs = Counter(3) +- expression 12 operands: lhs = Counter(1), rhs = Counter(2) +- expression 13 operands: lhs = Counter(0), rhs = Counter(1) +- expression 14 operands: lhs = Counter(0), rhs = Expression(15, Add) +- expression 15 operands: lhs = Expression(18, Add), rhs = Counter(4) +- expression 16 operands: lhs = Counter(1), rhs = Counter(2) +- expression 17 operands: lhs = Expression(18, Add), rhs = Counter(3) +- expression 18 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 11 +- Code(Counter(0)) at (prev + 32, 1) to (start + 0, 43) +- MCDCDecision { bitmap_idx: 5, conditions_num: 4 } at (prev + 1, 12) to (start + 0, 52) +- Branch { true: Expression(0, Sub), false: Expression(17, Add) } at (prev + 0, 12) to (start + 0, 52) + true = (c0 - ((c1 + c2) + c3)) + false = ((c1 + c2) + c3) +- MCDCBranch { true: Expression(14, Sub), false: Expression(8, Add), condition_id: 2, true_next_id: 3, false_next_id: 4 } at (prev + 0, 13) to (start + 0, 28) + true = (c0 - ((c1 + c2) + c4)) + false = (c4 + c2) +- MCDCBranch { true: Expression(9, Sub), false: Counter(3), condition_id: 3, true_next_id: 0, false_next_id: 0 } at (prev + 0, 20) to (start + 0, 27) + true = (c0 - (((c1 + c2) + c3) + c4)) + false = c3 +- MCDCBranch { true: Counter(4), false: Counter(2), condition_id: 4, true_next_id: 0, false_next_id: 0 } at (prev + 0, 31) to (start + 0, 40) + true = c4 + false = c2 +- MCDCBranch { true: Expression(13, Sub), false: Counter(1), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 42) to (start + 0, 51) + true = (c0 - c1) + false = c1 +- Code(Expression(14, Sub)) at (prev + 0, 55) to (start + 0, 58) + = (c0 - ((c1 + c2) + c4)) +- Code(Expression(0, Sub)) at (prev + 0, 59) to (start + 2, 6) + = (c0 - ((c1 + c2) + c3)) +- Code(Expression(17, Add)) at (prev + 2, 5) to (start + 0, 6) + = ((c1 + c2) + c3) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c4 + +Function name: if_let::let_else +Raw bytes (75): 0x[01, 01, 06, 01, 17, 05, 09, 05, 09, 01, 05, 01, 05, 05, 09, 09, 01, 31, 01, 00, 19, 28, 03, 02, 01, 09, 00, 18, 20, 02, 17, 00, 09, 00, 18, 30, 12, 05, 01, 02, 00, 00, 09, 00, 18, 30, 02, 09, 02, 00, 00, 00, 10, 00, 17, 12, 00, 1b, 00, 20, 17, 00, 28, 00, 2e, 02, 01, 05, 00, 13, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 6 +- expression 0 operands: lhs = Counter(0), rhs = Expression(5, Add) +- expression 1 operands: lhs = Counter(1), rhs = Counter(2) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +- expression 3 operands: lhs = Counter(0), rhs = Counter(1) +- expression 4 operands: lhs = Counter(0), rhs = Counter(1) +- expression 5 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 9 +- Code(Counter(0)) at (prev + 49, 1) to (start + 0, 25) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 1, 9) to (start + 0, 24) +- Branch { true: Expression(0, Sub), false: Expression(5, Add) } at (prev + 0, 9) to (start + 0, 24) + true = (c0 - (c1 + c2)) + false = (c1 + c2) +- MCDCBranch { true: Expression(4, Sub), false: Counter(1), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 9) to (start + 0, 24) + true = (c0 - c1) + false = c1 +- MCDCBranch { true: Expression(0, Sub), false: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 16) to (start + 0, 23) + true = (c0 - (c1 + c2)) + false = c2 +- Code(Expression(4, Sub)) at (prev + 0, 27) to (start + 0, 32) + = (c0 - c1) +- Code(Expression(5, Add)) at (prev + 0, 40) to (start + 0, 46) + = (c1 + c2) +- Code(Expression(0, Sub)) at (prev + 1, 5) to (start + 0, 19) + = (c0 - (c1 + c2)) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c2 + +Function name: if_let::simple_joint_pattern +Raw bytes (97): 0x[01, 01, 0c, 01, 2b, 2f, 0d, 05, 09, 2f, 0d, 05, 09, 01, 05, 01, 2f, 05, 09, 01, 2f, 05, 09, 2f, 0d, 05, 09, 0a, 01, 1a, 01, 00, 2a, 28, 04, 03, 01, 0c, 00, 28, 20, 02, 2b, 00, 0c, 00, 28, 30, 16, 05, 01, 02, 00, 00, 0d, 00, 1c, 30, 02, 0d, 03, 00, 00, 00, 14, 00, 1b, 30, 22, 09, 02, 03, 00, 00, 1e, 00, 27, 22, 00, 2b, 00, 2e, 02, 00, 2f, 02, 06, 2b, 02, 05, 00, 06, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 12 +- expression 0 operands: lhs = Counter(0), rhs = Expression(10, Add) +- expression 1 operands: lhs = Expression(11, Add), rhs = Counter(3) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +- expression 3 operands: lhs = Expression(11, Add), rhs = Counter(3) +- expression 4 operands: lhs = Counter(1), rhs = Counter(2) +- expression 5 operands: lhs = Counter(0), rhs = Counter(1) +- expression 6 operands: lhs = Counter(0), rhs = Expression(11, Add) +- expression 7 operands: lhs = Counter(1), rhs = Counter(2) +- expression 8 operands: lhs = Counter(0), rhs = Expression(11, Add) +- expression 9 operands: lhs = Counter(1), rhs = Counter(2) +- expression 10 operands: lhs = Expression(11, Add), rhs = Counter(3) +- expression 11 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 10 +- Code(Counter(0)) at (prev + 26, 1) to (start + 0, 42) +- MCDCDecision { bitmap_idx: 4, conditions_num: 3 } at (prev + 1, 12) to (start + 0, 40) +- Branch { true: Expression(0, Sub), false: Expression(10, Add) } at (prev + 0, 12) to (start + 0, 40) + true = (c0 - ((c1 + c2) + c3)) + false = ((c1 + c2) + c3) +- MCDCBranch { true: Expression(5, Sub), false: Counter(1), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 13) to (start + 0, 28) + true = (c0 - c1) + false = c1 +- MCDCBranch { true: Expression(0, Sub), false: Counter(3), condition_id: 3, true_next_id: 0, false_next_id: 0 } at (prev + 0, 20) to (start + 0, 27) + true = (c0 - ((c1 + c2) + c3)) + false = c3 +- MCDCBranch { true: Expression(8, Sub), false: Counter(2), condition_id: 2, true_next_id: 3, false_next_id: 0 } at (prev + 0, 30) to (start + 0, 39) + true = (c0 - (c1 + c2)) + false = c2 +- Code(Expression(8, Sub)) at (prev + 0, 43) to (start + 0, 46) + = (c0 - (c1 + c2)) +- Code(Expression(0, Sub)) at (prev + 0, 47) to (start + 2, 6) + = (c0 - ((c1 + c2) + c3)) +- Code(Expression(10, Add)) at (prev + 2, 5) to (start + 0, 6) + = ((c1 + c2) + c3) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c3 + +Function name: if_let::simple_or_pattern +Raw bytes (66): 0x[01, 01, 04, 01, 05, 01, 0b, 05, 09, 09, 05, 08, 01, 14, 01, 01, 27, 28, 03, 02, 01, 0c, 00, 21, 20, 02, 05, 00, 0c, 00, 21, 30, 06, 0f, 01, 00, 02, 00, 0c, 00, 15, 30, 09, 05, 02, 00, 00, 00, 18, 00, 21, 02, 00, 28, 02, 06, 05, 02, 05, 00, 06, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 4 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Expression(2, Add) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +- expression 3 operands: lhs = Counter(2), rhs = Counter(1) +Number of file 0 mappings: 8 +- Code(Counter(0)) at (prev + 20, 1) to (start + 1, 39) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 1, 12) to (start + 0, 33) +- Branch { true: Expression(0, Sub), false: Counter(1) } at (prev + 0, 12) to (start + 0, 33) + true = (c0 - c1) + false = c1 +- MCDCBranch { true: Expression(1, Sub), false: Expression(3, Add), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 12) to (start + 0, 21) + true = (c0 - (c1 + c2)) + false = (c2 + c1) +- MCDCBranch { true: Counter(2), false: Counter(1), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 24) to (start + 0, 33) + true = c2 + false = c1 +- Code(Expression(0, Sub)) at (prev + 0, 40) to (start + 2, 6) + = (c0 - c1) +- Code(Counter(1)) at (prev + 2, 5) to (start + 0, 6) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c2 + +Function name: if_let::single_nested_pattern +Raw bytes (75): 0x[01, 01, 06, 01, 17, 05, 09, 05, 09, 01, 05, 01, 05, 05, 09, 09, 01, 0e, 01, 00, 24, 28, 03, 02, 01, 0c, 00, 1b, 20, 02, 17, 00, 0c, 00, 1b, 30, 12, 05, 01, 02, 00, 00, 0c, 00, 1b, 30, 02, 09, 02, 00, 00, 00, 13, 00, 1a, 12, 00, 1e, 00, 21, 02, 00, 22, 02, 06, 17, 02, 05, 00, 06, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 6 +- expression 0 operands: lhs = Counter(0), rhs = Expression(5, Add) +- expression 1 operands: lhs = Counter(1), rhs = Counter(2) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +- expression 3 operands: lhs = Counter(0), rhs = Counter(1) +- expression 4 operands: lhs = Counter(0), rhs = Counter(1) +- expression 5 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 9 +- Code(Counter(0)) at (prev + 14, 1) to (start + 0, 36) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 1, 12) to (start + 0, 27) +- Branch { true: Expression(0, Sub), false: Expression(5, Add) } at (prev + 0, 12) to (start + 0, 27) + true = (c0 - (c1 + c2)) + false = (c1 + c2) +- MCDCBranch { true: Expression(4, Sub), false: Counter(1), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 12) to (start + 0, 27) + true = (c0 - c1) + false = c1 +- MCDCBranch { true: Expression(0, Sub), false: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 19) to (start + 0, 26) + true = (c0 - (c1 + c2)) + false = c2 +- Code(Expression(4, Sub)) at (prev + 0, 30) to (start + 0, 33) + = (c0 - c1) +- Code(Expression(0, Sub)) at (prev + 0, 34) to (start + 2, 6) + = (c0 - (c1 + c2)) +- Code(Expression(5, Add)) at (prev + 2, 5) to (start + 0, 6) + = (c1 + c2) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c2 + diff --git a/tests/coverage/mcdc/if_let.coverage b/tests/coverage/mcdc/if_let.coverage new file mode 100644 index 0000000000000..3fd32f204e4d3 --- /dev/null +++ b/tests/coverage/mcdc/if_let.coverage @@ -0,0 +1,272 @@ + LL| |#![feature(coverage_attribute)] + LL| |//@ edition: 2021 + LL| |//@ min-llvm-version: 19 + LL| |//@ compile-flags: -Zcoverage-options=mcdc + LL| |//@ llvm-cov-flags: --show-branches=count --show-mcdc + LL| | + LL| |#[derive(Clone, Copy)] + LL| |enum Pat { + LL| | A(Option), + LL| | B(i32), + LL| | C(i32), + LL| |} + LL| | + LL| 2|fn single_nested_pattern(pat: Pat) { + LL| 1| if let Pat::A(Some(_)) = pat { + ------------------ + | Branch (LL:12): [True: 1, False: 1] + | Branch (LL:12): [True: 1, False: 1] + | Branch (LL:19): [True: 1, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:12) to (LL:27) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:12) + | Condition C2 --> (LL:19) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, T = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| say("matched"); + LL| 1| } + LL| 2|} + LL| | + LL| 2|fn simple_or_pattern(pat: Pat) { + LL| 2| if let Pat::B(_) | Pat::C(_) = pat { + ------------------ + | Branch (LL:12): [True: 1, False: 1] + | Branch (LL:12): [True: 0, False: 2] + | Branch (LL:24): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:12) to (LL:33) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:12) + | Condition C2 --> (LL:24) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, F = F } + | 2 { F, T = T } + | + | C1-Pair: not covered + | C2-Pair: covered: (1,2) + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| say("matched"); + LL| 1| } + LL| 2|} + LL| | + LL| 3|fn simple_joint_pattern(pat: (Pat, Pat)) { + LL| 1| if let (Pat::A(Some(_)), Pat::B(_)) = pat { + ------------------ + | Branch (LL:12): [True: 1, False: 2] + | Branch (LL:13): [True: 2, False: 1] + | Branch (LL:20): [True: 1, False: 0] + | Branch (LL:30): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:12) to (LL:40) + | + | Number of Conditions: 3 + | Condition C1 --> (LL:13) + | Condition C2 --> (LL:20) + | Condition C3 --> (LL:30) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3 Result + | 1 { F, -, - = F } + | 2 { T, -, F = F } + | 3 { T, T, T = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: not covered + | C3-Pair: covered: (2,3) + | MC/DC Coverage for Decision: 66.67% + | + ------------------ + LL| 1| say("matched"); + LL| 2| } + LL| 3|} + LL| | + LL| 4|fn joint_pattern_with_or(pat: (Pat, Pat)) { + LL| 2| if let (Pat::A(Some(_)) | Pat::C(_), Pat::B(_)) = pat { + ^1 + ------------------ + | Branch (LL:12): [True: 2, False: 2] + | Branch (LL:13): [True: 1, False: 2] + | Branch (LL:20): [True: 1, False: 0] + | Branch (LL:31): [True: 1, False: 1] + | Branch (LL:42): [True: 3, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:12) to (LL:52) + | + | Number of Conditions: 4 + | Condition C1 --> (LL:42) + | Condition C2 --> (LL:13) + | Condition C3 --> (LL:20) + | Condition C4 --> (LL:31) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3, C4 Result + | 1 { F, -, -, - = F } + | 2 { T, F, -, F = F } + | 3 { T, F, -, T = T } + | 4 { T, T, T, - = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: covered: (2,4) + | C3-Pair: not covered + | C4-Pair: covered: (2,3) + | MC/DC Coverage for Decision: 75.00% + | + ------------------ + LL| 2| say("matched"); + LL| 2| } + LL| 4|} + LL| | + LL| 4|fn joint_or_patterns(pat: (Pat, Pat)) { + LL| 2| if let (Pat::A(Some(_)) | Pat::C(_), Pat::B(_) | Pat::C(_)) = pat { + ------------------ + | Branch (LL:12): [True: 2, False: 2] + | Branch (LL:13): [True: 1, False: 3] + | Branch (LL:20): [True: 1, False: 0] + | Branch (LL:31): [True: 1, False: 2] + | Branch (LL:42): [True: 2, False: 0] + | Branch (LL:54): [True: 0, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:12) to (LL:64) + | + | Number of Conditions: 5 + | Condition C1 --> (LL:13) + | Condition C2 --> (LL:20) + | Condition C3 --> (LL:31) + | Condition C4 --> (LL:42) + | Condition C5 --> (LL:54) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3, C4, C5 Result + | 1 { F, -, F, -, - = F } + | 2 { F, -, T, T, - = T } + | 3 { T, T, -, T, - = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: not covered + | C3-Pair: covered: (1,2) + | C4-Pair: not covered + | C5-Pair: not covered + | MC/DC Coverage for Decision: 40.00% + | + ------------------ + LL| 2| say("matched"); + LL| 2| } + LL| | + LL| | // Try to use the matched value + LL| 2| if let (Pat::A(Some(a)) | Pat::C(a), Pat::B(b) | Pat::C(b)) = pat { + ^1 ^1 ^1 ^0 ^1 + ------------------ + | Branch (LL:12): [True: 2, False: 2] + | Branch (LL:13): [True: 1, False: 3] + | Branch (LL:20): [True: 1, False: 0] + | Branch (LL:31): [True: 1, False: 2] + | Branch (LL:42): [True: 2, False: 0] + | Branch (LL:54): [True: 0, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:12) to (LL:64) + | + | Number of Conditions: 5 + | Condition C1 --> (LL:13) + | Condition C2 --> (LL:20) + | Condition C3 --> (LL:31) + | Condition C4 --> (LL:42) + | Condition C5 --> (LL:54) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3, C4, C5 Result + | 1 { F, -, F, -, - = F } + | 2 { F, -, T, T, - = T } + | 3 { T, T, -, T, - = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: not covered + | C3-Pair: covered: (1,2) + | C4-Pair: not covered + | C5-Pair: not covered + | MC/DC Coverage for Decision: 40.00% + | + ------------------ + LL| 2| say(&format!("matched {a} and {b}")); + LL| 2| } + LL| 4|} + LL| | + LL| 2|fn let_else(value: Pat) { + LL| 1| let Pat::A(Some(_)) = value else { return }; + ------------------ + | Branch (LL:9): [True: 1, False: 1] + | Branch (LL:9): [True: 1, False: 1] + | Branch (LL:16): [True: 1, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:24) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:9) + | Condition C2 --> (LL:16) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, T = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| say("matched"); + LL| 2|} + LL| | + LL| |#[coverage(off)] + LL| |fn main() { + LL| | single_nested_pattern(Pat::A(Some(5))); + LL| | single_nested_pattern(Pat::B(5)); + LL| | + LL| | simple_or_pattern(Pat::A(None)); + LL| | simple_or_pattern(Pat::C(3)); + LL| | + LL| | simple_joint_pattern((Pat::A(Some(1)), Pat::B(2))); + LL| | simple_joint_pattern((Pat::A(Some(1)), Pat::C(2))); + LL| | simple_joint_pattern((Pat::B(1), Pat::B(2))); + LL| | + LL| | joint_pattern_with_or((Pat::A(Some(1)), Pat::B(2))); + LL| | joint_pattern_with_or((Pat::B(1), Pat::C(2))); + LL| | joint_pattern_with_or((Pat::B(1), Pat::B(2))); + LL| | joint_pattern_with_or((Pat::C(1), Pat::B(2))); + LL| | + LL| | joint_or_patterns((Pat::A(Some(1)), Pat::B(2))); + LL| | joint_or_patterns((Pat::B(1), Pat::C(2))); + LL| | joint_or_patterns((Pat::B(1), Pat::B(2))); + LL| | joint_or_patterns((Pat::C(1), Pat::B(2))); + LL| | + LL| | let_else(Pat::A(Some(5))); + LL| | let_else(Pat::B(3)); + LL| |} + LL| | + LL| |#[coverage(off)] + LL| |fn say(message: &str) { + LL| | core::hint::black_box(message); + LL| |} + diff --git a/tests/coverage/mcdc/if_let.rs b/tests/coverage/mcdc/if_let.rs new file mode 100644 index 0000000000000..629f922d88edd --- /dev/null +++ b/tests/coverage/mcdc/if_let.rs @@ -0,0 +1,83 @@ +#![feature(coverage_attribute)] +//@ edition: 2021 +//@ min-llvm-version: 19 +//@ compile-flags: -Zcoverage-options=mcdc +//@ llvm-cov-flags: --show-branches=count --show-mcdc + +#[derive(Clone, Copy)] +enum Pat { + A(Option), + B(i32), + C(i32), +} + +fn single_nested_pattern(pat: Pat) { + if let Pat::A(Some(_)) = pat { + say("matched"); + } +} + +fn simple_or_pattern(pat: Pat) { + if let Pat::B(_) | Pat::C(_) = pat { + say("matched"); + } +} + +fn simple_joint_pattern(pat: (Pat, Pat)) { + if let (Pat::A(Some(_)), Pat::B(_)) = pat { + say("matched"); + } +} + +fn joint_pattern_with_or(pat: (Pat, Pat)) { + if let (Pat::A(Some(_)) | Pat::C(_), Pat::B(_)) = pat { + say("matched"); + } +} + +fn joint_or_patterns(pat: (Pat, Pat)) { + if let (Pat::A(Some(_)) | Pat::C(_), Pat::B(_) | Pat::C(_)) = pat { + say("matched"); + } + + // Try to use the matched value + if let (Pat::A(Some(a)) | Pat::C(a), Pat::B(b) | Pat::C(b)) = pat { + say(&format!("matched {a} and {b}")); + } +} + +fn let_else(value: Pat) { + let Pat::A(Some(_)) = value else { return }; + say("matched"); +} + +#[coverage(off)] +fn main() { + single_nested_pattern(Pat::A(Some(5))); + single_nested_pattern(Pat::B(5)); + + simple_or_pattern(Pat::A(None)); + simple_or_pattern(Pat::C(3)); + + simple_joint_pattern((Pat::A(Some(1)), Pat::B(2))); + simple_joint_pattern((Pat::A(Some(1)), Pat::C(2))); + simple_joint_pattern((Pat::B(1), Pat::B(2))); + + joint_pattern_with_or((Pat::A(Some(1)), Pat::B(2))); + joint_pattern_with_or((Pat::B(1), Pat::C(2))); + joint_pattern_with_or((Pat::B(1), Pat::B(2))); + joint_pattern_with_or((Pat::C(1), Pat::B(2))); + + joint_or_patterns((Pat::A(Some(1)), Pat::B(2))); + joint_or_patterns((Pat::B(1), Pat::C(2))); + joint_or_patterns((Pat::B(1), Pat::B(2))); + joint_or_patterns((Pat::C(1), Pat::B(2))); + + let_else(Pat::A(Some(5))); + let_else(Pat::B(3)); +} + +#[coverage(off)] +fn say(message: &str) { + core::hint::black_box(message); +} diff --git a/tests/coverage/mcdc/match_misc.cov-map b/tests/coverage/mcdc/match_misc.cov-map new file mode 100644 index 0000000000000..f3c40c15c5040 --- /dev/null +++ b/tests/coverage/mcdc/match_misc.cov-map @@ -0,0 +1,499 @@ +Function name: match_misc::empty_matching_decision +Raw bytes (129): 0x[01, 01, 08, 01, 05, 05, 09, 01, 09, 01, 13, 09, 0d, 0d, 11, 01, 1f, 09, 11, 11, 01, 22, 01, 01, 0e, 09, 02, 09, 00, 0a, 28, 06, 02, 00, 09, 00, 1d, 01, 00, 0e, 00, 13, 30, 05, 02, 01, 02, 00, 00, 0e, 00, 13, 05, 00, 17, 00, 1d, 30, 09, 06, 02, 00, 00, 00, 17, 00, 1d, 09, 00, 21, 00, 32, 11, 01, 09, 00, 0a, 28, 03, 02, 00, 09, 00, 1c, 0a, 00, 0e, 00, 13, 30, 0d, 0e, 01, 02, 00, 00, 0e, 00, 13, 0d, 00, 17, 00, 1c, 30, 11, 16, 02, 00, 00, 00, 17, 00, 1c, 11, 00, 20, 00, 30, 1a, 01, 0e, 00, 1a, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 8 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(1), rhs = Counter(2) +- expression 2 operands: lhs = Counter(0), rhs = Counter(2) +- expression 3 operands: lhs = Counter(0), rhs = Expression(4, Add) +- expression 4 operands: lhs = Counter(2), rhs = Counter(3) +- expression 5 operands: lhs = Counter(3), rhs = Counter(4) +- expression 6 operands: lhs = Counter(0), rhs = Expression(7, Add) +- expression 7 operands: lhs = Counter(2), rhs = Counter(4) +Number of file 0 mappings: 17 +- Code(Counter(0)) at (prev + 34, 1) to (start + 1, 14) +- Code(Counter(2)) at (prev + 2, 9) to (start + 0, 10) +- MCDCDecision { bitmap_idx: 6, conditions_num: 2 } at (prev + 0, 9) to (start + 0, 29) +- Code(Counter(0)) at (prev + 0, 14) to (start + 0, 19) +- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 14) to (start + 0, 19) + true = c1 + false = (c0 - c1) +- Code(Counter(1)) at (prev + 0, 23) to (start + 0, 29) +- MCDCBranch { true: Counter(2), false: Expression(1, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 23) to (start + 0, 29) + true = c2 + false = (c1 - c2) +- Code(Counter(2)) at (prev + 0, 33) to (start + 0, 50) +- Code(Counter(4)) at (prev + 1, 9) to (start + 0, 10) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 0, 9) to (start + 0, 28) +- Code(Expression(2, Sub)) at (prev + 0, 14) to (start + 0, 19) + = (c0 - c2) +- MCDCBranch { true: Counter(3), false: Expression(3, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 14) to (start + 0, 19) + true = c3 + false = (c0 - (c2 + c3)) +- Code(Counter(3)) at (prev + 0, 23) to (start + 0, 28) +- MCDCBranch { true: Counter(4), false: Expression(5, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 23) to (start + 0, 28) + true = c4 + false = (c3 - c4) +- Code(Counter(4)) at (prev + 0, 32) to (start + 0, 48) +- Code(Expression(6, Sub)) at (prev + 1, 14) to (start + 0, 26) + = (c0 - (c2 + c4)) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c4 + +Function name: match_misc::empty_subcandidate +Raw bytes (87): 0x[01, 01, 0d, 01, 33, 00, 09, 09, 00, 01, 13, 2f, 11, 33, 0d, 00, 09, 11, 0d, 01, 2f, 33, 0d, 00, 09, 33, 0d, 00, 09, 08, 01, 78, 01, 01, 0e, 28, 04, 03, 02, 09, 00, 1e, 30, 02, 0b, 01, 02, 00, 00, 09, 00, 12, 30, 0e, 1f, 02, 00, 03, 00, 10, 00, 11, 30, 11, 0d, 03, 00, 00, 00, 1c, 00, 1d, 22, 00, 22, 00, 2e, 2f, 04, 26, 00, 32, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 13 +- expression 0 operands: lhs = Counter(0), rhs = Expression(12, Add) +- expression 1 operands: lhs = Zero, rhs = Counter(2) +- expression 2 operands: lhs = Counter(2), rhs = Zero +- expression 3 operands: lhs = Counter(0), rhs = Expression(4, Add) +- expression 4 operands: lhs = Expression(11, Add), rhs = Counter(4) +- expression 5 operands: lhs = Expression(12, Add), rhs = Counter(3) +- expression 6 operands: lhs = Zero, rhs = Counter(2) +- expression 7 operands: lhs = Counter(4), rhs = Counter(3) +- expression 8 operands: lhs = Counter(0), rhs = Expression(11, Add) +- expression 9 operands: lhs = Expression(12, Add), rhs = Counter(3) +- expression 10 operands: lhs = Zero, rhs = Counter(2) +- expression 11 operands: lhs = Expression(12, Add), rhs = Counter(3) +- expression 12 operands: lhs = Zero, rhs = Counter(2) +Number of file 0 mappings: 8 +- Code(Counter(0)) at (prev + 120, 1) to (start + 1, 14) +- MCDCDecision { bitmap_idx: 4, conditions_num: 3 } at (prev + 2, 9) to (start + 0, 30) +- MCDCBranch { true: Expression(0, Sub), false: Expression(2, Add), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 9) to (start + 0, 18) + true = (c0 - (Zero + c2)) + false = (c2 + Zero) +- MCDCBranch { true: Expression(3, Sub), false: Expression(7, Add), condition_id: 2, true_next_id: 0, false_next_id: 3 } at (prev + 0, 16) to (start + 0, 17) + true = (c0 - (((Zero + c2) + c3) + c4)) + false = (c4 + c3) +- MCDCBranch { true: Counter(4), false: Counter(3), condition_id: 3, true_next_id: 0, false_next_id: 0 } at (prev + 0, 28) to (start + 0, 29) + true = c4 + false = c3 +- Code(Expression(8, Sub)) at (prev + 0, 34) to (start + 0, 46) + = (c0 - ((Zero + c2) + c3)) +- Code(Expression(11, Add)) at (prev + 4, 38) to (start + 0, 50) + = ((Zero + c2) + c3) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c4 + +Function name: match_misc::implicit_folded_condition +Raw bytes (90): 0x[01, 01, 09, 01, 05, 01, 23, 05, 0d, 05, 09, 17, 23, 01, 09, 05, 0d, 23, 09, 05, 0d, 09, 01, 82, 01, 01, 01, 0e, 28, 05, 04, 03, 09, 00, 26, 30, 02, 05, 01, 02, 03, 00, 0a, 00, 0f, 30, 06, 0d, 02, 00, 00, 00, 11, 00, 16, 30, 05, 00, 03, 04, 00, 00, 1b, 00, 1f, 30, 09, 0e, 04, 00, 00, 00, 21, 00, 25, 12, 00, 2a, 00, 35, 1e, 01, 0e, 00, 1d, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 9 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Expression(8, Add) +- expression 2 operands: lhs = Counter(1), rhs = Counter(3) +- expression 3 operands: lhs = Counter(1), rhs = Counter(2) +- expression 4 operands: lhs = Expression(5, Add), rhs = Expression(8, Add) +- expression 5 operands: lhs = Counter(0), rhs = Counter(2) +- expression 6 operands: lhs = Counter(1), rhs = Counter(3) +- expression 7 operands: lhs = Expression(8, Add), rhs = Counter(2) +- expression 8 operands: lhs = Counter(1), rhs = Counter(3) +Number of file 0 mappings: 9 +- Code(Counter(0)) at (prev + 130, 1) to (start + 1, 14) +- MCDCDecision { bitmap_idx: 5, conditions_num: 4 } at (prev + 3, 9) to (start + 0, 38) +- MCDCBranch { true: Expression(0, Sub), false: Counter(1), condition_id: 1, true_next_id: 2, false_next_id: 3 } at (prev + 0, 10) to (start + 0, 15) + true = (c0 - c1) + false = c1 +- MCDCBranch { true: Expression(1, Sub), false: Counter(3), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 17) to (start + 0, 22) + true = (c0 - (c1 + c3)) + false = c3 +- MCDCBranch { true: Counter(1), false: Zero, condition_id: 3, true_next_id: 4, false_next_id: 0 } at (prev + 0, 27) to (start + 0, 31) + true = c1 + false = Zero +- MCDCBranch { true: Counter(2), false: Expression(3, Sub), condition_id: 4, true_next_id: 0, false_next_id: 0 } at (prev + 0, 33) to (start + 0, 37) + true = c2 + false = (c1 - c2) +- Code(Expression(4, Sub)) at (prev + 0, 42) to (start + 0, 53) + = ((c0 + c2) - (c1 + c3)) +- Code(Expression(7, Sub)) at (prev + 1, 14) to (start + 0, 29) + = ((c1 + c3) - c2) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c3 + +Function name: match_misc::loop_over_iterator +Raw bytes (31): 0x[01, 01, 01, 05, 01, 05, 01, 09, 01, 00, 19, 02, 01, 09, 00, 0c, 05, 00, 10, 00, 19, 02, 00, 1a, 02, 06, 01, 03, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 1 +- expression 0 operands: lhs = Counter(1), rhs = Counter(0) +Number of file 0 mappings: 5 +- Code(Counter(0)) at (prev + 9, 1) to (start + 0, 25) +- Code(Expression(0, Sub)) at (prev + 1, 9) to (start + 0, 12) + = (c1 - c0) +- Code(Counter(1)) at (prev + 0, 16) to (start + 0, 25) +- Code(Expression(0, Sub)) at (prev + 0, 26) to (start + 2, 6) + = (c1 - c0) +- Code(Counter(0)) at (prev + 3, 1) to (start + 0, 2) +Highest counter ID seen: c1 + +Function name: match_misc::match_failure_test_kind +Raw bytes (109): 0x[01, 01, 0d, 01, 05, 01, 2b, 33, 0d, 05, 00, 0d, 00, 01, 2b, 33, 0d, 05, 00, 26, 00, 01, 2b, 33, 0d, 05, 00, 05, 00, 0b, 01, 51, 01, 01, 14, 28, 06, 02, 02, 09, 00, 16, 30, 02, 05, 01, 02, 00, 00, 0a, 00, 0f, 30, 26, 13, 02, 00, 00, 00, 11, 00, 15, 26, 00, 1a, 00, 25, 28, 03, 02, 01, 09, 00, 19, 30, 02, 05, 01, 02, 00, 00, 0a, 00, 0f, 30, 0d, 23, 02, 00, 00, 00, 11, 00, 18, 0d, 00, 1d, 00, 28, 33, 01, 0e, 00, 1a, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 13 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Expression(10, Add) +- expression 2 operands: lhs = Expression(12, Add), rhs = Counter(3) +- expression 3 operands: lhs = Counter(1), rhs = Zero +- expression 4 operands: lhs = Counter(3), rhs = Zero +- expression 5 operands: lhs = Counter(0), rhs = Expression(10, Add) +- expression 6 operands: lhs = Expression(12, Add), rhs = Counter(3) +- expression 7 operands: lhs = Counter(1), rhs = Zero +- expression 8 operands: lhs = Expression(9, Sub), rhs = Zero +- expression 9 operands: lhs = Counter(0), rhs = Expression(10, Add) +- expression 10 operands: lhs = Expression(12, Add), rhs = Counter(3) +- expression 11 operands: lhs = Counter(1), rhs = Zero +- expression 12 operands: lhs = Counter(1), rhs = Zero +Number of file 0 mappings: 11 +- Code(Counter(0)) at (prev + 81, 1) to (start + 1, 20) +- MCDCDecision { bitmap_idx: 6, conditions_num: 2 } at (prev + 2, 9) to (start + 0, 22) +- MCDCBranch { true: Expression(0, Sub), false: Counter(1), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 10) to (start + 0, 15) + true = (c0 - c1) + false = c1 +- MCDCBranch { true: Expression(9, Sub), false: Expression(4, Add), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 17) to (start + 0, 21) + true = (c0 - ((c1 + Zero) + c3)) + false = (c3 + Zero) +- Code(Expression(9, Sub)) at (prev + 0, 26) to (start + 0, 37) + = (c0 - ((c1 + Zero) + c3)) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 1, 9) to (start + 0, 25) +- MCDCBranch { true: Expression(0, Sub), false: Counter(1), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 10) to (start + 0, 15) + true = (c0 - c1) + false = c1 +- MCDCBranch { true: Counter(3), false: Expression(8, Add), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 17) to (start + 0, 24) + true = c3 + false = ((c0 - ((c1 + Zero) + c3)) + Zero) +- Code(Counter(3)) at (prev + 0, 29) to (start + 0, 40) +- Code(Expression(12, Add)) at (prev + 1, 14) to (start + 0, 26) + = (c1 + Zero) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c3 + +Function name: match_misc::match_with_macros +Raw bytes (24): 0x[01, 01, 00, 04, 01, 11, 01, 00, 20, 05, 0d, 05, 00, 18, 01, 00, 19, 00, 1c, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 17, 1) to (start + 0, 32) +- Code(Counter(1)) at (prev + 13, 5) to (start + 0, 24) +- Code(Counter(0)) at (prev + 0, 25) to (start + 0, 28) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c1 + +Function name: match_misc::multi_matched_candidates +Raw bytes (141): 0x[01, 01, 0e, 01, 07, 2f, 11, 05, 09, 01, 05, 01, 05, 01, 2f, 05, 09, 01, 05, 01, 27, 2b, 11, 2f, 0d, 05, 09, 01, 37, 09, 0d, 10, 01, 70, 01, 00, 2f, 02, 01, 0b, 00, 0e, 28, 07, 02, 01, 09, 00, 1c, 30, 1e, 05, 01, 02, 00, 00, 09, 00, 12, 09, 00, 10, 00, 11, 1e, 00, 16, 00, 17, 30, 09, 16, 02, 00, 00, 00, 16, 00, 1c, 09, 00, 1b, 00, 2c, 28, 04, 03, 01, 09, 00, 1b, 30, 1e, 05, 01, 02, 00, 00, 09, 00, 12, 30, 02, 11, 02, 03, 00, 00, 10, 00, 11, 02, 00, 16, 00, 17, 30, 0d, 22, 03, 00, 00, 00, 16, 00, 1b, 0d, 00, 1a, 00, 2c, 32, 01, 0e, 00, 1a, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 14 +- expression 0 operands: lhs = Counter(0), rhs = Expression(1, Add) +- expression 1 operands: lhs = Expression(11, Add), rhs = Counter(4) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +- expression 3 operands: lhs = Counter(0), rhs = Counter(1) +- expression 4 operands: lhs = Counter(0), rhs = Counter(1) +- expression 5 operands: lhs = Counter(0), rhs = Expression(11, Add) +- expression 6 operands: lhs = Counter(1), rhs = Counter(2) +- expression 7 operands: lhs = Counter(0), rhs = Counter(1) +- expression 8 operands: lhs = Counter(0), rhs = Expression(9, Add) +- expression 9 operands: lhs = Expression(10, Add), rhs = Counter(4) +- expression 10 operands: lhs = Expression(11, Add), rhs = Counter(3) +- expression 11 operands: lhs = Counter(1), rhs = Counter(2) +- expression 12 operands: lhs = Counter(0), rhs = Expression(13, Add) +- expression 13 operands: lhs = Counter(2), rhs = Counter(3) +Number of file 0 mappings: 16 +- Code(Counter(0)) at (prev + 112, 1) to (start + 0, 47) +- Code(Expression(0, Sub)) at (prev + 1, 11) to (start + 0, 14) + = (c0 - ((c1 + c2) + c4)) +- MCDCDecision { bitmap_idx: 7, conditions_num: 2 } at (prev + 1, 9) to (start + 0, 28) +- MCDCBranch { true: Expression(7, Sub), false: Counter(1), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 9) to (start + 0, 18) + true = (c0 - c1) + false = c1 +- Code(Counter(2)) at (prev + 0, 16) to (start + 0, 17) +- Code(Expression(7, Sub)) at (prev + 0, 22) to (start + 0, 23) + = (c0 - c1) +- MCDCBranch { true: Counter(2), false: Expression(5, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 22) to (start + 0, 28) + true = c2 + false = (c0 - (c1 + c2)) +- Code(Counter(2)) at (prev + 0, 27) to (start + 0, 44) +- MCDCDecision { bitmap_idx: 4, conditions_num: 3 } at (prev + 1, 9) to (start + 0, 27) +- MCDCBranch { true: Expression(7, Sub), false: Counter(1), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 9) to (start + 0, 18) + true = (c0 - c1) + false = c1 +- MCDCBranch { true: Expression(0, Sub), false: Counter(4), condition_id: 2, true_next_id: 3, false_next_id: 0 } at (prev + 0, 16) to (start + 0, 17) + true = (c0 - ((c1 + c2) + c4)) + false = c4 +- Code(Expression(0, Sub)) at (prev + 0, 22) to (start + 0, 23) + = (c0 - ((c1 + c2) + c4)) +- MCDCBranch { true: Counter(3), false: Expression(8, Sub), condition_id: 3, true_next_id: 0, false_next_id: 0 } at (prev + 0, 22) to (start + 0, 27) + true = c3 + false = (c0 - (((c1 + c2) + c3) + c4)) +- Code(Counter(3)) at (prev + 0, 26) to (start + 0, 44) +- Code(Expression(12, Sub)) at (prev + 1, 14) to (start + 0, 26) + = (c0 - (c2 + c3)) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c4 + +Function name: match_misc::nested_matching +Raw bytes (83): 0x[01, 01, 05, 01, 05, 05, 0d, 05, 09, 05, 09, 01, 0d, 0b, 01, 66, 01, 01, 09, 28, 03, 02, 01, 08, 03, 06, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 30, 0d, 06, 02, 00, 00, 00, 0d, 03, 06, 05, 00, 13, 00, 16, 20, 0e, 09, 01, 09, 00, 12, 0e, 00, 10, 00, 1c, 09, 01, 0e, 00, 13, 0d, 01, 07, 02, 06, 12, 02, 05, 00, 06, 01, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 5 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(1), rhs = Counter(3) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +- expression 3 operands: lhs = Counter(1), rhs = Counter(2) +- expression 4 operands: lhs = Counter(0), rhs = Counter(3) +Number of file 0 mappings: 11 +- Code(Counter(0)) at (prev + 102, 1) to (start + 1, 9) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 1, 8) to (start + 3, 6) +- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 8) to (start + 0, 9) + true = c1 + false = (c0 - c1) +- MCDCBranch { true: Counter(3), false: Expression(1, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 13) to (start + 3, 6) + true = c3 + false = (c1 - c3) +- Code(Counter(1)) at (prev + 0, 19) to (start + 0, 22) +- Branch { true: Expression(3, Sub), false: Counter(2) } at (prev + 1, 9) to (start + 0, 18) + true = (c1 - c2) + false = c2 +- Code(Expression(3, Sub)) at (prev + 0, 16) to (start + 0, 28) + = (c1 - c2) +- Code(Counter(2)) at (prev + 1, 14) to (start + 0, 19) +- Code(Counter(3)) at (prev + 1, 7) to (start + 2, 6) +- Code(Expression(4, Sub)) at (prev + 2, 5) to (start + 0, 6) + = (c0 - c3) +- Code(Counter(0)) at (prev + 1, 1) to (start + 0, 2) +Highest counter ID seen: c3 + +Function name: match_misc::overlapping_decisions +Raw bytes (154): 0x[01, 01, 17, 01, 57, 00, 09, 02, 00, 1e, 00, 09, 47, 00, 11, 02, 00, 09, 47, 00, 11, 11, 00, 4a, 00, 01, 4f, 53, 19, 57, 00, 00, 09, 43, 47, 09, 19, 00, 11, 01, 4f, 53, 19, 57, 00, 00, 09, 01, 00, 0e, 01, 34, 01, 00, 3b, 02, 01, 0b, 00, 0e, 28, 08, 02, 01, 09, 00, 1b, 30, 09, 1b, 01, 02, 00, 00, 0a, 00, 11, 30, 11, 0f, 02, 00, 00, 00, 13, 00, 1a, 11, 00, 1f, 00, 2f, 28, 05, 04, 01, 09, 00, 2a, 30, 09, 1b, 01, 02, 03, 00, 0a, 00, 11, 30, 1e, 27, 02, 00, 00, 00, 13, 00, 17, 30, 02, 00, 03, 04, 00, 00, 1c, 00, 20, 30, 19, 2b, 04, 00, 00, 00, 22, 00, 29, 3e, 00, 2e, 00, 4a, 4a, 01, 19, 00, 24, 5a, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 23 +- expression 0 operands: lhs = Counter(0), rhs = Expression(21, Add) +- expression 1 operands: lhs = Zero, rhs = Counter(2) +- expression 2 operands: lhs = Expression(0, Sub), rhs = Zero +- expression 3 operands: lhs = Expression(7, Sub), rhs = Zero +- expression 4 operands: lhs = Counter(2), rhs = Expression(17, Add) +- expression 5 operands: lhs = Zero, rhs = Counter(4) +- expression 6 operands: lhs = Expression(0, Sub), rhs = Zero +- expression 7 operands: lhs = Counter(2), rhs = Expression(17, Add) +- expression 8 operands: lhs = Zero, rhs = Counter(4) +- expression 9 operands: lhs = Counter(4), rhs = Zero +- expression 10 operands: lhs = Expression(18, Sub), rhs = Zero +- expression 11 operands: lhs = Counter(0), rhs = Expression(19, Add) +- expression 12 operands: lhs = Expression(20, Add), rhs = Counter(6) +- expression 13 operands: lhs = Expression(21, Add), rhs = Zero +- expression 14 operands: lhs = Zero, rhs = Counter(2) +- expression 15 operands: lhs = Expression(16, Add), rhs = Expression(17, Add) +- expression 16 operands: lhs = Counter(2), rhs = Counter(6) +- expression 17 operands: lhs = Zero, rhs = Counter(4) +- expression 18 operands: lhs = Counter(0), rhs = Expression(19, Add) +- expression 19 operands: lhs = Expression(20, Add), rhs = Counter(6) +- expression 20 operands: lhs = Expression(21, Add), rhs = Zero +- expression 21 operands: lhs = Zero, rhs = Counter(2) +- expression 22 operands: lhs = Counter(0), rhs = Zero +Number of file 0 mappings: 14 +- Code(Counter(0)) at (prev + 52, 1) to (start + 0, 59) +- Code(Expression(0, Sub)) at (prev + 1, 11) to (start + 0, 14) + = (c0 - (Zero + c2)) +- MCDCDecision { bitmap_idx: 8, conditions_num: 2 } at (prev + 1, 9) to (start + 0, 27) +- MCDCBranch { true: Counter(2), false: Expression(6, Add), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 10) to (start + 0, 17) + true = c2 + false = ((c0 - (Zero + c2)) + Zero) +- MCDCBranch { true: Counter(4), false: Expression(3, Add), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 19) to (start + 0, 26) + true = c4 + false = ((c2 - (Zero + c4)) + Zero) +- Code(Counter(4)) at (prev + 0, 31) to (start + 0, 47) +- MCDCDecision { bitmap_idx: 5, conditions_num: 4 } at (prev + 1, 9) to (start + 0, 42) +- MCDCBranch { true: Counter(2), false: Expression(6, Add), condition_id: 1, true_next_id: 2, false_next_id: 3 } at (prev + 0, 10) to (start + 0, 17) + true = c2 + false = ((c0 - (Zero + c2)) + Zero) +- MCDCBranch { true: Expression(7, Sub), false: Expression(9, Add), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 19) to (start + 0, 23) + true = (c2 - (Zero + c4)) + false = (c4 + Zero) +- MCDCBranch { true: Expression(0, Sub), false: Zero, condition_id: 3, true_next_id: 4, false_next_id: 0 } at (prev + 0, 28) to (start + 0, 32) + true = (c0 - (Zero + c2)) + false = Zero +- MCDCBranch { true: Counter(6), false: Expression(10, Add), condition_id: 4, true_next_id: 0, false_next_id: 0 } at (prev + 0, 34) to (start + 0, 41) + true = c6 + false = ((c0 - (((Zero + c2) + Zero) + c6)) + Zero) +- Code(Expression(15, Sub)) at (prev + 0, 46) to (start + 0, 74) + = ((c2 + c6) - (Zero + c4)) +- Code(Expression(18, Sub)) at (prev + 1, 25) to (start + 0, 36) + = (c0 - (((Zero + c2) + Zero) + c6)) +- Code(Expression(22, Sub)) at (prev + 2, 1) to (start + 0, 2) + = (c0 - Zero) +Highest counter ID seen: c6 + +Function name: match_misc::partial_matched_decision +Raw bytes (182): 0x[01, 01, 1f, 01, 07, 37, 1d, 3b, 19, 3f, 15, 05, 11, 1b, 05, 1f, 15, 23, 19, 1d, 11, 2b, 05, 2f, 15, 11, 19, 01, 37, 3b, 19, 3f, 15, 05, 11, 05, 0d, 4b, 05, 4f, 15, 73, 19, 02, 1d, 0d, 11, 5f, 05, 6f, 15, 73, 11, 02, 1d, 6f, 05, 73, 11, 02, 1d, 15, 19, 05, 0d, 10, 01, 3c, 01, 03, 0e, 28, 09, 02, 04, 09, 00, 14, 30, 02, 17, 01, 00, 02, 00, 09, 00, 0d, 30, 1d, 27, 02, 00, 00, 00, 10, 00, 14, 32, 00, 18, 00, 29, 09, 01, 09, 00, 14, 28, 06, 02, 00, 09, 00, 1b, 30, 0d, 7a, 02, 00, 00, 00, 09, 00, 14, 30, 11, 47, 01, 00, 02, 00, 17, 00, 1b, 57, 00, 1f, 00, 31, 28, 03, 02, 01, 09, 00, 14, 30, 19, 5b, 01, 00, 02, 00, 09, 00, 0d, 30, 15, 6b, 02, 00, 00, 00, 10, 00, 14, 77, 00, 18, 00, 25, 7a, 01, 0e, 00, 10, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 31 +- expression 0 operands: lhs = Counter(0), rhs = Expression(1, Add) +- expression 1 operands: lhs = Expression(13, Add), rhs = Counter(7) +- expression 2 operands: lhs = Expression(14, Add), rhs = Counter(6) +- expression 3 operands: lhs = Expression(15, Add), rhs = Counter(5) +- expression 4 operands: lhs = Counter(1), rhs = Counter(4) +- expression 5 operands: lhs = Expression(6, Add), rhs = Counter(1) +- expression 6 operands: lhs = Expression(7, Add), rhs = Counter(5) +- expression 7 operands: lhs = Expression(8, Add), rhs = Counter(6) +- expression 8 operands: lhs = Counter(7), rhs = Counter(4) +- expression 9 operands: lhs = Expression(10, Add), rhs = Counter(1) +- expression 10 operands: lhs = Expression(11, Add), rhs = Counter(5) +- expression 11 operands: lhs = Counter(4), rhs = Counter(6) +- expression 12 operands: lhs = Counter(0), rhs = Expression(13, Add) +- expression 13 operands: lhs = Expression(14, Add), rhs = Counter(6) +- expression 14 operands: lhs = Expression(15, Add), rhs = Counter(5) +- expression 15 operands: lhs = Counter(1), rhs = Counter(4) +- expression 16 operands: lhs = Counter(1), rhs = Counter(3) +- expression 17 operands: lhs = Expression(18, Add), rhs = Counter(1) +- expression 18 operands: lhs = Expression(19, Add), rhs = Counter(5) +- expression 19 operands: lhs = Expression(28, Add), rhs = Counter(6) +- expression 20 operands: lhs = Expression(0, Sub), rhs = Counter(7) +- expression 21 operands: lhs = Counter(3), rhs = Counter(4) +- expression 22 operands: lhs = Expression(23, Add), rhs = Counter(1) +- expression 23 operands: lhs = Expression(27, Add), rhs = Counter(5) +- expression 24 operands: lhs = Expression(28, Add), rhs = Counter(4) +- expression 25 operands: lhs = Expression(0, Sub), rhs = Counter(7) +- expression 26 operands: lhs = Expression(27, Add), rhs = Counter(1) +- expression 27 operands: lhs = Expression(28, Add), rhs = Counter(4) +- expression 28 operands: lhs = Expression(0, Sub), rhs = Counter(7) +- expression 29 operands: lhs = Counter(5), rhs = Counter(6) +- expression 30 operands: lhs = Counter(1), rhs = Counter(3) +Number of file 0 mappings: 16 +- Code(Counter(0)) at (prev + 60, 1) to (start + 3, 14) +- MCDCDecision { bitmap_idx: 9, conditions_num: 2 } at (prev + 4, 9) to (start + 0, 20) +- MCDCBranch { true: Expression(0, Sub), false: Expression(5, Add), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 9) to (start + 0, 13) + true = (c0 - ((((c1 + c4) + c5) + c6) + c7)) + false = ((((c7 + c4) + c6) + c5) + c1) +- MCDCBranch { true: Counter(7), false: Expression(9, Add), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 16) to (start + 0, 20) + true = c7 + false = (((c4 + c6) + c5) + c1) +- Code(Expression(12, Sub)) at (prev + 0, 24) to (start + 0, 41) + = (c0 - (((c1 + c4) + c5) + c6)) +- Code(Counter(2)) at (prev + 1, 9) to (start + 0, 20) +- MCDCDecision { bitmap_idx: 6, conditions_num: 2 } at (prev + 0, 9) to (start + 0, 27) +- MCDCBranch { true: Counter(3), false: Expression(30, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 9) to (start + 0, 20) + true = c3 + false = (c1 - c3) +- MCDCBranch { true: Counter(4), false: Expression(17, Add), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 23) to (start + 0, 27) + true = c4 + false = (((((c0 - ((((c1 + c4) + c5) + c6) + c7)) + c7) + c6) + c5) + c1) +- Code(Expression(21, Add)) at (prev + 0, 31) to (start + 0, 49) + = (c3 + c4) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 1, 9) to (start + 0, 20) +- MCDCBranch { true: Counter(6), false: Expression(22, Add), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 9) to (start + 0, 13) + true = c6 + false = (((((c0 - ((((c1 + c4) + c5) + c6) + c7)) + c7) + c4) + c5) + c1) +- MCDCBranch { true: Counter(5), false: Expression(26, Add), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 16) to (start + 0, 20) + true = c5 + false = ((((c0 - ((((c1 + c4) + c5) + c6) + c7)) + c7) + c4) + c1) +- Code(Expression(29, Add)) at (prev + 0, 24) to (start + 0, 37) + = (c5 + c6) +- Code(Expression(30, Sub)) at (prev + 1, 14) to (start + 0, 16) + = (c1 - c3) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c7 + +Function name: match_misc::partial_matched_with_several_blocks +Raw bytes (83): 0x[01, 01, 09, 01, 09, 01, 23, 09, 11, 01, 1f, 23, 15, 09, 11, 01, 1f, 23, 15, 09, 11, 0b, 01, 48, 01, 01, 0e, 05, 02, 09, 00, 14, 20, 09, 02, 00, 09, 00, 14, 09, 00, 18, 00, 22, 0d, 01, 09, 00, 14, 20, 11, 06, 00, 09, 00, 14, 11, 00, 18, 00, 28, 20, 1a, 15, 01, 09, 00, 0d, 1a, 00, 11, 00, 22, 15, 01, 0e, 00, 1a, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 9 +- expression 0 operands: lhs = Counter(0), rhs = Counter(2) +- expression 1 operands: lhs = Counter(0), rhs = Expression(8, Add) +- expression 2 operands: lhs = Counter(2), rhs = Counter(4) +- expression 3 operands: lhs = Counter(0), rhs = Expression(7, Add) +- expression 4 operands: lhs = Expression(8, Add), rhs = Counter(5) +- expression 5 operands: lhs = Counter(2), rhs = Counter(4) +- expression 6 operands: lhs = Counter(0), rhs = Expression(7, Add) +- expression 7 operands: lhs = Expression(8, Add), rhs = Counter(5) +- expression 8 operands: lhs = Counter(2), rhs = Counter(4) +Number of file 0 mappings: 11 +- Code(Counter(0)) at (prev + 72, 1) to (start + 1, 14) +- Code(Counter(1)) at (prev + 2, 9) to (start + 0, 20) +- Branch { true: Counter(2), false: Expression(0, Sub) } at (prev + 0, 9) to (start + 0, 20) + true = c2 + false = (c0 - c2) +- Code(Counter(2)) at (prev + 0, 24) to (start + 0, 34) +- Code(Counter(3)) at (prev + 1, 9) to (start + 0, 20) +- Branch { true: Counter(4), false: Expression(1, Sub) } at (prev + 0, 9) to (start + 0, 20) + true = c4 + false = (c0 - (c2 + c4)) +- Code(Counter(4)) at (prev + 0, 24) to (start + 0, 40) +- Branch { true: Expression(6, Sub), false: Counter(5) } at (prev + 1, 9) to (start + 0, 13) + true = (c0 - ((c2 + c4) + c5)) + false = c5 +- Code(Expression(6, Sub)) at (prev + 0, 17) to (start + 0, 34) + = (c0 - ((c2 + c4) + c5)) +- Code(Counter(5)) at (prev + 1, 14) to (start + 0, 26) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c5 + +Function name: match_misc::skipped_matching_decision +Raw bytes (63): 0x[01, 01, 05, 01, 05, 01, 13, 05, 09, 01, 13, 05, 09, 09, 01, 2b, 01, 01, 0e, 05, 02, 09, 00, 0a, 01, 00, 0e, 00, 0f, 20, 05, 02, 00, 0e, 00, 14, 05, 00, 13, 00, 2b, 20, 0e, 09, 01, 09, 00, 0b, 0e, 00, 0f, 00, 18, 09, 01, 0e, 00, 1a, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 5 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Expression(4, Add) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +- expression 3 operands: lhs = Counter(0), rhs = Expression(4, Add) +- expression 4 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 9 +- Code(Counter(0)) at (prev + 43, 1) to (start + 1, 14) +- Code(Counter(1)) at (prev + 2, 9) to (start + 0, 10) +- Code(Counter(0)) at (prev + 0, 14) to (start + 0, 15) +- Branch { true: Counter(1), false: Expression(0, Sub) } at (prev + 0, 14) to (start + 0, 20) + true = c1 + false = (c0 - c1) +- Code(Counter(1)) at (prev + 0, 19) to (start + 0, 43) +- Branch { true: Expression(3, Sub), false: Counter(2) } at (prev + 1, 9) to (start + 0, 11) + true = (c0 - (c1 + c2)) + false = c2 +- Code(Expression(3, Sub)) at (prev + 0, 15) to (start + 0, 24) + = (c0 - (c1 + c2)) +- Code(Counter(2)) at (prev + 1, 14) to (start + 0, 26) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c2 + +Function name: match_misc::uncoverable_condition +Raw bytes (84): 0x[01, 01, 09, 00, 09, 01, 0b, 00, 0d, 0d, 00, 01, 23, 03, 0d, 01, 23, 03, 0d, 03, 0d, 0a, 01, 5f, 01, 00, 2b, 03, 01, 0b, 00, 0e, 28, 03, 02, 01, 09, 00, 1f, 30, 06, 0f, 01, 02, 00, 00, 0a, 00, 13, 1a, 00, 11, 00, 3c, 30, 1a, 09, 02, 00, 00, 00, 15, 00, 1e, 0d, 01, 11, 00, 12, 03, 00, 25, 00, 26, 23, 00, 2c, 00, 3f, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 9 +- expression 0 operands: lhs = Zero, rhs = Counter(2) +- expression 1 operands: lhs = Counter(0), rhs = Expression(2, Add) +- expression 2 operands: lhs = Zero, rhs = Counter(3) +- expression 3 operands: lhs = Counter(3), rhs = Zero +- expression 4 operands: lhs = Counter(0), rhs = Expression(8, Add) +- expression 5 operands: lhs = Expression(0, Add), rhs = Counter(3) +- expression 6 operands: lhs = Counter(0), rhs = Expression(8, Add) +- expression 7 operands: lhs = Expression(0, Add), rhs = Counter(3) +- expression 8 operands: lhs = Expression(0, Add), rhs = Counter(3) +Number of file 0 mappings: 10 +- Code(Counter(0)) at (prev + 95, 1) to (start + 0, 43) +- Code(Expression(0, Add)) at (prev + 1, 11) to (start + 0, 14) + = (Zero + c2) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 1, 9) to (start + 0, 31) +- MCDCBranch { true: Expression(1, Sub), false: Expression(3, Add), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 10) to (start + 0, 19) + true = (c0 - (Zero + c3)) + false = (c3 + Zero) +- Code(Expression(6, Sub)) at (prev + 0, 17) to (start + 0, 60) + = (c0 - ((Zero + c2) + c3)) +- MCDCBranch { true: Expression(6, Sub), false: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 30) + true = (c0 - ((Zero + c2) + c3)) + false = c2 +- Code(Counter(3)) at (prev + 1, 17) to (start + 0, 18) +- Code(Expression(0, Add)) at (prev + 0, 37) to (start + 0, 38) + = (Zero + c2) +- Code(Expression(8, Add)) at (prev + 0, 44) to (start + 0, 63) + = ((Zero + c2) + c3) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c3 + diff --git a/tests/coverage/mcdc/match_misc.coverage b/tests/coverage/mcdc/match_misc.coverage new file mode 100644 index 0000000000000..ce5e0d55c4cf3 --- /dev/null +++ b/tests/coverage/mcdc/match_misc.coverage @@ -0,0 +1,554 @@ + LL| |#![feature(coverage_attribute)] + LL| |//@ edition: 2021 + LL| |//@ min-llvm-version: 19 + LL| |//@ compile-flags: -Zcoverage-options=mcdc + LL| |//@ llvm-cov-flags: --show-branches=count --show-mcdc + LL| | + LL| |// Loop over iterator contains pattern matching implicitly, + LL| |// do not generate mappings for it. + LL| 1|fn loop_over_iterator() { + LL| 4| for val in [1, 2, 3] { + ^3 + LL| 3| say(&val.to_string()); + LL| 3| } + LL| 1|} + LL| | + LL| |// Macro makes all conditions share same span. + LL| |// But now we don't generate mappings for it + LL| 3|fn match_with_macros(val: i32) { + LL| | macro_rules! variant_identifier { + LL| | ( + LL| | $val:expr, ($($index:expr),*) + LL| | )=> { + LL| | match $val { + LL| | $( + LL| | $index => say(&format!("{}",$index)), + LL| | )* + LL| | _ => say("not matched"), + LL| | } + LL| | } + LL| |} + LL| 3| variant_identifier!(val, (0, 1, 2)); + ^1 + LL| 3|} + LL| | + LL| |// No match pairs when lowering matching tree. + LL| 2|fn empty_matching_decision(val: i32) { + LL| 2| match val { + LL| 2| x if x > 8 && x < 10 => say("in (8, 10)"), + ^0 ^1 ^0 + ------------------ + | Branch (LL:14): [True: 1, False: 1] + | Branch (LL:23): [True: 0, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:29) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:14) + | Condition C2 --> (LL:23) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, F = F } + | + | C1-Pair: not covered + | C2-Pair: not covered + | MC/DC Coverage for Decision: 0.00% + | + ------------------ + LL| 2| x if x > 4 && x < 7 => say("in (4, 7)"), + ^1 ^1 + ------------------ + | Branch (LL:14): [True: 2, False: 0] + | Branch (LL:23): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:28) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:14) + | Condition C2 --> (LL:23) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { T, F = F } + | 2 { T, T = T } + | + | C1-Pair: not covered + | C2-Pair: covered: (1,2) + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| _ => say("other"), + LL| | } + LL| 2|} + LL| | + LL| |// Matching decision skips the first candidate + LL| 2|fn skipped_matching_decision(val: i32) { + LL| 2| match val { + LL| 2| x if x >= 0 => say("non-negative"), + ^0 ^0 + ------------------ + | Branch (LL:14): [True: 0, False: 2] + ------------------ + LL| 1| -1 => say("-1"), + ------------------ + | Branch (LL:9): [True: 1, False: 1] + ------------------ + LL| 1| _ => say("other"), + LL| | } + LL| 2|} + LL| | + LL| |// The first two candidates share same condition. + LL| 3|fn overlapping_decisions(val: (Option, Option)) { + LL| 1| match val { + LL| 1| (Some(_), Some(_)) => say("both some"), + ------------------ + | Branch (LL:10): [True: 2, False: 1] + | Branch (LL:19): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:27) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:19) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, F = F } + | 3 { T, T = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: covered: (2,3) + | MC/DC Coverage for Decision: 100.00% + | + ------------------ + LL| 1| (Some(_), None) | (None, Some(_)) => say("one and only one some"), + ------------------ + | Branch (LL:10): [True: 2, False: 1] + | Branch (LL:19): [True: 1, False: 1] + | Branch (LL:28): [True: 1, False: 0] + | Branch (LL:34): [True: 0, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:42) + | + | Number of Conditions: 4 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:19) + | Condition C3 --> (LL:28) + | Condition C4 --> (LL:34) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3, C4 Result + | 1 { F, -, T, F = F } + | 2 { T, T, -, - = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | C3-Pair: not covered + | C4-Pair: not covered + | MC/DC Coverage for Decision: 25.00% + | + ------------------ + LL| 1| (None, None) => say("none"), + LL| | } + LL| 3|} + LL| | + LL| 4|fn partial_matched_decision(val: u8) { + LL| 4| // `b'-'` is the second test while `b'0'..=b'9'` is the last, though they + LL| 4| // are in same candidate. + LL| 4| match val { + LL| 2| b'"' | b'r' => say("quote or r"), + ------------------ + | Branch (LL:9): [True: 1, False: 3] + | Branch (LL:16): [True: 1, False: 2] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:20) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:9) + | Condition C2 --> (LL:16) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, F = F } + | 2 { F, T = T } + | 3 { T, - = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: covered: (1,2) + | MC/DC Coverage for Decision: 100.00% + | + ------------------ + LL| 2| b'0'..=b'9' | b'-' => say("number or -"), + ^1 + ------------------ + | Branch (LL:9): [True: 1, False: 0] + | Branch (LL:23): [True: 1, False: 3] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:27) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:23) + | Condition C2 --> (LL:9) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, T = T } + | 2 { T, - = T } + | + | C1-Pair: not covered + | C2-Pair: not covered + | MC/DC Coverage for Decision: 0.00% + | + ------------------ + LL| 0| b't' | b'f' => say("t or f"), + ------------------ + | Branch (LL:9): [True: 0, False: 4] + | Branch (LL:16): [True: 0, False: 4] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:20) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:9) + | Condition C2 --> (LL:16) + | + | Executed MC/DC Test Vectors: + | + | None. + | + | C1-Pair: not covered + | C2-Pair: not covered + | MC/DC Coverage for Decision: 0.00% + | + ------------------ + LL| 0| _ => {} + LL| | } + LL| 4|} + LL| | + LL| |// Patterns are tested with several basic blocks. + LL| 3|fn partial_matched_with_several_blocks(val: u8) { + LL| 3| match val { + LL| 1| b'a'..=b'f' => say("hex"), + ------------------ + | Branch (LL:9): [True: 1, False: 2] + ------------------ + LL| 2| b'A'..=b'F' => say("hex upper"), + ^1 + ------------------ + | Branch (LL:9): [True: 1, False: 1] + ------------------ + LL| 1| b'_' => say("underscore"), + ------------------ + | Branch (LL:9): [True: 1, False: 0] + ------------------ + LL| 0| _ => say("break"), + LL| | } + LL| 3|} + LL| | + LL| 2|fn match_failure_test_kind(val: bool, opt: Option) { + LL| 2| match (val, opt) { + LL| 1| (false, None) => say("none"), + ------------------ + | Branch (LL:10): [True: 2, False: 0] + | Branch (LL:17): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:22) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:17) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { T, F = F } + | 2 { T, T = T } + | + | C1-Pair: not covered + | C2-Pair: covered: (1,2) + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| (false, Some(_)) => say("some"), + ------------------ + | Branch (LL:10): [True: 2, False: 0] + | Branch (LL:17): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:25) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:17) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { T, T = T } + | + | C1-Pair: not covered + | C2-Pair: not covered + | MC/DC Coverage for Decision: 0.00% + | + ------------------ + LL| 0| _ => say("other"), + LL| | } + LL| 2|} + LL| | + LL| |enum Pat { + LL| | A(i32), + LL| | B(i32), + LL| |} + LL| | + LL| |// The last arm is shown like a condition but it never fails if tested. + LL| 2|fn uncoverable_condition(val: (Pat, Pat)) { + LL| 0| match val { + LL| 1| (Pat::A(a), Pat::A(b)) => say(&(a + b).to_string()), + ------------------ + | Branch (LL:10): [True: 1, False: 1] + | Branch (LL:21): [True: 1, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:31) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:21) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, T = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| (Pat::B(a), _) | (_, Pat::B(a)) => say(&a.to_string()), + ^0 + LL| | } + LL| 2|} + LL| | + LL| 3|fn nested_matching(a: bool, val: Pat) { + LL| 3| if a && match val { + ^2 + ------------------ + | Branch (LL:8): [True: 2, False: 1] + | Branch (LL:13): [True: 1, False: 1] + ------------------ + LL| 2| Pat::A(x) => x == 2, + ------------------ + | Branch (LL:9): [True: 2, False: 0] + ------------------ + LL| 0| _ => false, + LL| 1| } { + ------------------ + |---> MC/DC Decision Region (LL:8) to (LL:6) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:8) + | Condition C2 --> (LL:13) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, F = F } + | 3 { T, T = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: covered: (2,3) + | MC/DC Coverage for Decision: 100.00% + | + ------------------ + LL| 1| say("yes"); + LL| 2| } + LL| 3|} + LL| | + LL| |// It's possible to match two arms once. + LL| 2|fn multi_matched_candidates(val: Pat, a: i32) { + LL| 1| match val { + LL| 2| Pat::A(f) if f == a => say("first"), + ^1 ^1 + ------------------ + | Branch (LL:9): [True: 2, False: 0] + | Branch (LL:22): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:28) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:9) + | Condition C2 --> (LL:22) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { T, F = F } + | 2 { T, T = T } + | + | C1-Pair: not covered + | C2-Pair: covered: (1,2) + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| Pat::A(1) if a > 0 => say("second"), + ------------------ + | Branch (LL:9): [True: 2, False: 0] + | Branch (LL:16): [True: 1, False: 0] + | Branch (LL:22): [True: 1, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:27) + | + | Number of Conditions: 3 + | Condition C1 --> (LL:9) + | Condition C2 --> (LL:16) + | Condition C3 --> (LL:22) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3 Result + | 1 { T, T, T = T } + | + | C1-Pair: not covered + | C2-Pair: not covered + | C3-Pair: not covered + | MC/DC Coverage for Decision: 0.00% + | + ------------------ + LL| 0| _ => say("other"), + LL| | } + LL| 2|} + LL| | + LL| 2|fn empty_subcandidate(val: Pat) { + LL| 2| match val { + LL| 1| Pat::A(1) | Pat::A(2) => say("first"), + ------------------ + | Branch (LL:9): [True: 1, False: 1] + | Branch (LL:16): [True: 1, False: 0] + | Branch (LL:28): [True: 0, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:30) + | + | Number of Conditions: 3 + | Condition C1 --> (LL:9) + | Condition C2 --> (LL:16) + | Condition C3 --> (LL:28) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3 Result + | 1 { F, -, - = F } + | 2 { T, T, - = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | C3-Pair: not covered + | MC/DC Coverage for Decision: 33.33% + | + ------------------ + LL| | // The first two condition in this pattern is redundant indeed. + LL| | // But this piece of code is legitimate and it could cause a subcandidate + LL| | // with no match pair. + LL| 1| Pat::A(_) | Pat::B(_) | _ => say("other"), + LL| | } + LL| 2|} + LL| | + LL| 4|fn implicit_folded_condition(val: (bool, bool)) { + LL| 4| match val { + LL| | // The first `true` is always matched if tested. + LL| 2| (false, false) | (true, true) => say("same"), + ------------------ + | Branch (LL:10): [True: 2, False: 2] + | Branch (LL:17): [True: 1, False: 1] + | Branch (LL:27): [True: 2, False: 0] + | Branch (LL:33): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:38) + | + | Number of Conditions: 4 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:17) + | Condition C3 --> (LL:27) + | Condition C4 --> (LL:33) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3, C4 Result + | 1 { F, -, T, F = F } + | 2 { T, F, -, - = F } + | 3 { F, -, T, T = T } + | 4 { T, T, -, - = T } + | + | C1-Pair: covered: (2,3) + | C2-Pair: covered: (2,4) + | C3-Pair: not covered + | C4-Pair: covered: (1,3) + | MC/DC Coverage for Decision: 75.00% + | + ------------------ + LL| 2| _ => say("not same"), + LL| | } + LL| 4|} + LL| | + LL| |#[coverage(off)] + LL| |fn main() { + LL| | loop_over_iterator(); + LL| | + LL| | match_with_macros(0); + LL| | match_with_macros(2); + LL| | match_with_macros(5); + LL| | + LL| | empty_matching_decision(12); + LL| | empty_matching_decision(5); + LL| | + LL| | skipped_matching_decision(-1); + LL| | skipped_matching_decision(-5); + LL| | + LL| | overlapping_decisions((Some(1), Some(2))); + LL| | overlapping_decisions((Some(1), None)); + LL| | overlapping_decisions((None, None)); + LL| | + LL| | partial_matched_decision(b'"'); + LL| | partial_matched_decision(b'r'); + LL| | partial_matched_decision(b'7'); + LL| | partial_matched_decision(b'-'); + LL| | + LL| | partial_matched_with_several_blocks(b'd'); + LL| | partial_matched_with_several_blocks(b'D'); + LL| | partial_matched_with_several_blocks(b'_'); + LL| | + LL| | match_failure_test_kind(false, None); + LL| | match_failure_test_kind(false, Some(1)); + LL| | + LL| | uncoverable_condition((Pat::A(1), Pat::A(2))); + LL| | uncoverable_condition((Pat::B(1), Pat::B(2))); + LL| | + LL| | nested_matching(true, Pat::A(1)); + LL| | nested_matching(true, Pat::A(2)); + LL| | nested_matching(false, Pat::A(2)); + LL| | + LL| | multi_matched_candidates(Pat::A(1), 1); + LL| | multi_matched_candidates(Pat::A(1), 8); + LL| | + LL| | empty_subcandidate(Pat::A(1)); + LL| | empty_subcandidate(Pat::B(1)); + LL| | + LL| | implicit_folded_condition((false, false)); + LL| | implicit_folded_condition((false, true)); + LL| | implicit_folded_condition((true, false)); + LL| | implicit_folded_condition((true, true)); + LL| |} + LL| | + LL| |#[coverage(off)] + LL| |fn say(message: &str) { + LL| | core::hint::black_box(message); + LL| |} + diff --git a/tests/coverage/mcdc/match_misc.rs b/tests/coverage/mcdc/match_misc.rs new file mode 100644 index 0000000000000..6e622e3c1f650 --- /dev/null +++ b/tests/coverage/mcdc/match_misc.rs @@ -0,0 +1,190 @@ +#![feature(coverage_attribute)] +//@ edition: 2021 +//@ min-llvm-version: 19 +//@ compile-flags: -Zcoverage-options=mcdc +//@ llvm-cov-flags: --show-branches=count --show-mcdc + +// Loop over iterator contains pattern matching implicitly, +// do not generate mappings for it. +fn loop_over_iterator() { + for val in [1, 2, 3] { + say(&val.to_string()); + } +} + +// Macro makes all conditions share same span. +// But now we don't generate mappings for it +fn match_with_macros(val: i32) { + macro_rules! variant_identifier { + ( + $val:expr, ($($index:expr),*) + )=> { + match $val { + $( + $index => say(&format!("{}",$index)), + )* + _ => say("not matched"), + } + } +} + variant_identifier!(val, (0, 1, 2)); +} + +// No match pairs when lowering matching tree. +fn empty_matching_decision(val: i32) { + match val { + x if x > 8 && x < 10 => say("in (8, 10)"), + x if x > 4 && x < 7 => say("in (4, 7)"), + _ => say("other"), + } +} + +// Matching decision skips the first candidate +fn skipped_matching_decision(val: i32) { + match val { + x if x >= 0 => say("non-negative"), + -1 => say("-1"), + _ => say("other"), + } +} + +// The first two candidates share same condition. +fn overlapping_decisions(val: (Option, Option)) { + match val { + (Some(_), Some(_)) => say("both some"), + (Some(_), None) | (None, Some(_)) => say("one and only one some"), + (None, None) => say("none"), + } +} + +fn partial_matched_decision(val: u8) { + // `b'-'` is the second test while `b'0'..=b'9'` is the last, though they + // are in same candidate. + match val { + b'"' | b'r' => say("quote or r"), + b'0'..=b'9' | b'-' => say("number or -"), + b't' | b'f' => say("t or f"), + _ => {} + } +} + +// Patterns are tested with several basic blocks. +fn partial_matched_with_several_blocks(val: u8) { + match val { + b'a'..=b'f' => say("hex"), + b'A'..=b'F' => say("hex upper"), + b'_' => say("underscore"), + _ => say("break"), + } +} + +fn match_failure_test_kind(val: bool, opt: Option) { + match (val, opt) { + (false, None) => say("none"), + (false, Some(_)) => say("some"), + _ => say("other"), + } +} + +enum Pat { + A(i32), + B(i32), +} + +// The last arm is shown like a condition but it never fails if tested. +fn uncoverable_condition(val: (Pat, Pat)) { + match val { + (Pat::A(a), Pat::A(b)) => say(&(a + b).to_string()), + (Pat::B(a), _) | (_, Pat::B(a)) => say(&a.to_string()), + } +} + +fn nested_matching(a: bool, val: Pat) { + if a && match val { + Pat::A(x) => x == 2, + _ => false, + } { + say("yes"); + } +} + +// It's possible to match two arms once. +fn multi_matched_candidates(val: Pat, a: i32) { + match val { + Pat::A(f) if f == a => say("first"), + Pat::A(1) if a > 0 => say("second"), + _ => say("other"), + } +} + +fn empty_subcandidate(val: Pat) { + match val { + Pat::A(1) | Pat::A(2) => say("first"), + // The first two condition in this pattern is redundant indeed. + // But this piece of code is legitimate and it could cause a subcandidate + // with no match pair. + Pat::A(_) | Pat::B(_) | _ => say("other"), + } +} + +fn implicit_folded_condition(val: (bool, bool)) { + match val { + // The first `true` is always matched if tested. + (false, false) | (true, true) => say("same"), + _ => say("not same"), + } +} + +#[coverage(off)] +fn main() { + loop_over_iterator(); + + match_with_macros(0); + match_with_macros(2); + match_with_macros(5); + + empty_matching_decision(12); + empty_matching_decision(5); + + skipped_matching_decision(-1); + skipped_matching_decision(-5); + + overlapping_decisions((Some(1), Some(2))); + overlapping_decisions((Some(1), None)); + overlapping_decisions((None, None)); + + partial_matched_decision(b'"'); + partial_matched_decision(b'r'); + partial_matched_decision(b'7'); + partial_matched_decision(b'-'); + + partial_matched_with_several_blocks(b'd'); + partial_matched_with_several_blocks(b'D'); + partial_matched_with_several_blocks(b'_'); + + match_failure_test_kind(false, None); + match_failure_test_kind(false, Some(1)); + + uncoverable_condition((Pat::A(1), Pat::A(2))); + uncoverable_condition((Pat::B(1), Pat::B(2))); + + nested_matching(true, Pat::A(1)); + nested_matching(true, Pat::A(2)); + nested_matching(false, Pat::A(2)); + + multi_matched_candidates(Pat::A(1), 1); + multi_matched_candidates(Pat::A(1), 8); + + empty_subcandidate(Pat::A(1)); + empty_subcandidate(Pat::B(1)); + + implicit_folded_condition((false, false)); + implicit_folded_condition((false, true)); + implicit_folded_condition((true, false)); + implicit_folded_condition((true, true)); +} + +#[coverage(off)] +fn say(message: &str) { + core::hint::black_box(message); +} diff --git a/tests/coverage/mcdc/match_pattern.cov-map b/tests/coverage/mcdc/match_pattern.cov-map new file mode 100644 index 0000000000000..ed10e28a71665 --- /dev/null +++ b/tests/coverage/mcdc/match_pattern.cov-map @@ -0,0 +1,402 @@ +Function name: match_pattern::joint_or_patterns +Raw bytes (454): 0x[01, 01, 50, 5b, 09, 5f, 39, 05, 35, 01, 13, 05, 3d, 3d, 05, 01, 1f, 23, 3d, 05, 39, 01, 2b, 5b, 41, 5f, 39, 05, 35, 41, 35, 01, 5b, 5f, 39, 05, 35, 5b, 67, 5f, 39, 05, 35, 09, 0d, 5b, 67, 5f, 39, 05, 35, 09, 0d, 09, 0d, af, 02, 15, b3, 02, 25, b7, 02, 21, 11, 1d, 01, 7f, 11, 29, 29, 11, 01, 8b, 01, eb, 01, 29, 11, 21, 01, df, 01, e3, 01, 31, e7, 01, 29, eb, 01, 25, 11, 21, 29, cb, 01, 1d, 2d, da, 01, c6, 01, 01, df, 01, e3, 01, 31, e7, 01, 29, eb, 01, 25, 11, 21, 29, cb, 01, 1d, 2d, d3, 01, 1d, d7, 01, 2d, 31, 25, 01, df, 01, e3, 01, 31, e7, 01, 29, eb, 01, 25, 11, 21, 31, 2d, 25, 1d, 01, af, 02, b3, 02, 25, b7, 02, 21, 11, 1d, af, 02, 15, b3, 02, 25, b7, 02, 21, 11, 1d, af, 02, bf, 02, b3, 02, 25, b7, 02, 21, 11, 1d, 15, 19, af, 02, bf, 02, b3, 02, 25, b7, 02, 21, 11, 1d, 15, 19, 15, 19, 20, 01, 2e, 01, 00, 27, 02, 01, 0b, 00, 0e, 28, 0b, 05, 01, 09, 00, 3d, 30, 0e, 17, 01, 02, 03, 00, 0a, 00, 19, 30, 1a, 39, 02, 04, 00, 00, 11, 00, 18, 30, 3d, 05, 03, 04, 00, 00, 1c, 00, 25, 30, 26, 37, 04, 00, 05, 00, 27, 00, 30, 30, 41, 35, 05, 00, 00, 00, 33, 00, 3c, 3a, 00, 41, 00, 63, 28, 03, 02, 01, 09, 00, 1f, 30, 02, 09, 01, 02, 00, 00, 0a, 00, 13, 30, 56, 0d, 02, 00, 00, 00, 15, 00, 1e, 56, 00, 23, 00, 39, 67, 01, 0e, 00, 23, 86, 02, 04, 0b, 00, 0e, 28, 16, 05, 01, 09, 00, 3d, 30, 7a, 83, 01, 01, 02, 03, 00, 0a, 00, 19, 30, 86, 01, 21, 02, 04, 00, 00, 11, 00, 18, da, 01, 00, 16, 00, 17, 30, 29, 11, 03, 04, 00, 00, 1c, 00, 25, c6, 01, 00, 23, 00, 24, 30, af, 01, cf, 01, 04, 00, 05, 00, 27, 00, 30, da, 01, 00, 2e, 00, 2f, 30, ef, 01, f3, 01, 05, 00, 00, 00, 33, 00, 3c, 31, 00, 3a, 00, 3b, f6, 01, 01, 0d, 00, 45, 28, 0e, 02, 02, 09, 00, 1f, 30, 86, 02, 15, 01, 02, 00, 00, 0a, 00, 13, 30, aa, 02, 19, 02, 00, 00, 00, 15, 00, 1e, aa, 02, 00, 23, 00, 39, bf, 02, 01, 0e, 00, 23, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 80 +- expression 0 operands: lhs = Expression(22, Add), rhs = Counter(2) +- expression 1 operands: lhs = Expression(23, Add), rhs = Counter(14) +- expression 2 operands: lhs = Counter(1), rhs = Counter(13) +- expression 3 operands: lhs = Counter(0), rhs = Expression(4, Add) +- expression 4 operands: lhs = Counter(1), rhs = Counter(15) +- expression 5 operands: lhs = Counter(15), rhs = Counter(1) +- expression 6 operands: lhs = Counter(0), rhs = Expression(7, Add) +- expression 7 operands: lhs = Expression(8, Add), rhs = Counter(15) +- expression 8 operands: lhs = Counter(1), rhs = Counter(14) +- expression 9 operands: lhs = Counter(0), rhs = Expression(10, Add) +- expression 10 operands: lhs = Expression(22, Add), rhs = Counter(16) +- expression 11 operands: lhs = Expression(23, Add), rhs = Counter(14) +- expression 12 operands: lhs = Counter(1), rhs = Counter(13) +- expression 13 operands: lhs = Counter(16), rhs = Counter(13) +- expression 14 operands: lhs = Counter(0), rhs = Expression(22, Add) +- expression 15 operands: lhs = Expression(23, Add), rhs = Counter(14) +- expression 16 operands: lhs = Counter(1), rhs = Counter(13) +- expression 17 operands: lhs = Expression(22, Add), rhs = Expression(25, Add) +- expression 18 operands: lhs = Expression(23, Add), rhs = Counter(14) +- expression 19 operands: lhs = Counter(1), rhs = Counter(13) +- expression 20 operands: lhs = Counter(2), rhs = Counter(3) +- expression 21 operands: lhs = Expression(22, Add), rhs = Expression(25, Add) +- expression 22 operands: lhs = Expression(23, Add), rhs = Counter(14) +- expression 23 operands: lhs = Counter(1), rhs = Counter(13) +- expression 24 operands: lhs = Counter(2), rhs = Counter(3) +- expression 25 operands: lhs = Counter(2), rhs = Counter(3) +- expression 26 operands: lhs = Expression(75, Add), rhs = Counter(5) +- expression 27 operands: lhs = Expression(76, Add), rhs = Counter(9) +- expression 28 operands: lhs = Expression(77, Add), rhs = Counter(8) +- expression 29 operands: lhs = Counter(4), rhs = Counter(7) +- expression 30 operands: lhs = Counter(0), rhs = Expression(31, Add) +- expression 31 operands: lhs = Counter(4), rhs = Counter(10) +- expression 32 operands: lhs = Counter(10), rhs = Counter(4) +- expression 33 operands: lhs = Counter(0), rhs = Expression(34, Add) +- expression 34 operands: lhs = Expression(58, Add), rhs = Counter(10) +- expression 35 operands: lhs = Counter(4), rhs = Counter(8) +- expression 36 operands: lhs = Counter(0), rhs = Expression(55, Add) +- expression 37 operands: lhs = Expression(56, Add), rhs = Counter(12) +- expression 38 operands: lhs = Expression(57, Add), rhs = Counter(10) +- expression 39 operands: lhs = Expression(58, Add), rhs = Counter(9) +- expression 40 operands: lhs = Counter(4), rhs = Counter(8) +- expression 41 operands: lhs = Counter(10), rhs = Expression(50, Add) +- expression 42 operands: lhs = Counter(7), rhs = Counter(11) +- expression 43 operands: lhs = Expression(54, Sub), rhs = Expression(49, Sub) +- expression 44 operands: lhs = Counter(0), rhs = Expression(55, Add) +- expression 45 operands: lhs = Expression(56, Add), rhs = Counter(12) +- expression 46 operands: lhs = Expression(57, Add), rhs = Counter(10) +- expression 47 operands: lhs = Expression(58, Add), rhs = Counter(9) +- expression 48 operands: lhs = Counter(4), rhs = Counter(8) +- expression 49 operands: lhs = Counter(10), rhs = Expression(50, Add) +- expression 50 operands: lhs = Counter(7), rhs = Counter(11) +- expression 51 operands: lhs = Expression(52, Add), rhs = Counter(7) +- expression 52 operands: lhs = Expression(53, Add), rhs = Counter(11) +- expression 53 operands: lhs = Counter(12), rhs = Counter(9) +- expression 54 operands: lhs = Counter(0), rhs = Expression(55, Add) +- expression 55 operands: lhs = Expression(56, Add), rhs = Counter(12) +- expression 56 operands: lhs = Expression(57, Add), rhs = Counter(10) +- expression 57 operands: lhs = Expression(58, Add), rhs = Counter(9) +- expression 58 operands: lhs = Counter(4), rhs = Counter(8) +- expression 59 operands: lhs = Counter(12), rhs = Counter(11) +- expression 60 operands: lhs = Counter(9), rhs = Counter(7) +- expression 61 operands: lhs = Counter(0), rhs = Expression(75, Add) +- expression 62 operands: lhs = Expression(76, Add), rhs = Counter(9) +- expression 63 operands: lhs = Expression(77, Add), rhs = Counter(8) +- expression 64 operands: lhs = Counter(4), rhs = Counter(7) +- expression 65 operands: lhs = Expression(75, Add), rhs = Counter(5) +- expression 66 operands: lhs = Expression(76, Add), rhs = Counter(9) +- expression 67 operands: lhs = Expression(77, Add), rhs = Counter(8) +- expression 68 operands: lhs = Counter(4), rhs = Counter(7) +- expression 69 operands: lhs = Expression(75, Add), rhs = Expression(79, Add) +- expression 70 operands: lhs = Expression(76, Add), rhs = Counter(9) +- expression 71 operands: lhs = Expression(77, Add), rhs = Counter(8) +- expression 72 operands: lhs = Counter(4), rhs = Counter(7) +- expression 73 operands: lhs = Counter(5), rhs = Counter(6) +- expression 74 operands: lhs = Expression(75, Add), rhs = Expression(79, Add) +- expression 75 operands: lhs = Expression(76, Add), rhs = Counter(9) +- expression 76 operands: lhs = Expression(77, Add), rhs = Counter(8) +- expression 77 operands: lhs = Counter(4), rhs = Counter(7) +- expression 78 operands: lhs = Counter(5), rhs = Counter(6) +- expression 79 operands: lhs = Counter(5), rhs = Counter(6) +Number of file 0 mappings: 32 +- Code(Counter(0)) at (prev + 46, 1) to (start + 0, 39) +- Code(Expression(0, Sub)) at (prev + 1, 11) to (start + 0, 14) + = (((c1 + c13) + c14) - c2) +- MCDCDecision { bitmap_idx: 11, conditions_num: 5 } at (prev + 1, 9) to (start + 0, 61) +- MCDCBranch { true: Expression(3, Sub), false: Expression(5, Add), condition_id: 1, true_next_id: 2, false_next_id: 3 } at (prev + 0, 10) to (start + 0, 25) + true = (c0 - (c1 + c15)) + false = (c15 + c1) +- MCDCBranch { true: Expression(6, Sub), false: Counter(14), condition_id: 2, true_next_id: 4, false_next_id: 0 } at (prev + 0, 17) to (start + 0, 24) + true = (c0 - ((c1 + c14) + c15)) + false = c14 +- MCDCBranch { true: Counter(15), false: Counter(1), condition_id: 3, true_next_id: 4, false_next_id: 0 } at (prev + 0, 28) to (start + 0, 37) + true = c15 + false = c1 +- MCDCBranch { true: Expression(9, Sub), false: Expression(13, Add), condition_id: 4, true_next_id: 0, false_next_id: 5 } at (prev + 0, 39) to (start + 0, 48) + true = (c0 - (((c1 + c13) + c14) + c16)) + false = (c16 + c13) +- MCDCBranch { true: Counter(16), false: Counter(13), condition_id: 5, true_next_id: 0, false_next_id: 0 } at (prev + 0, 51) to (start + 0, 60) + true = c16 + false = c13 +- Code(Expression(14, Sub)) at (prev + 0, 65) to (start + 0, 99) + = (c0 - ((c1 + c13) + c14)) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 1, 9) to (start + 0, 31) +- MCDCBranch { true: Expression(0, Sub), false: Counter(2), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 10) to (start + 0, 19) + true = (((c1 + c13) + c14) - c2) + false = c2 +- MCDCBranch { true: Expression(21, Sub), false: Counter(3), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 30) + true = (((c1 + c13) + c14) - (c2 + c3)) + false = c3 +- Code(Expression(21, Sub)) at (prev + 0, 35) to (start + 0, 57) + = (((c1 + c13) + c14) - (c2 + c3)) +- Code(Expression(25, Add)) at (prev + 1, 14) to (start + 0, 35) + = (c2 + c3) +- Code(Expression(65, Sub)) at (prev + 4, 11) to (start + 0, 14) + = ((((c4 + c7) + c8) + c9) - c5) +- MCDCDecision { bitmap_idx: 22, conditions_num: 5 } at (prev + 1, 9) to (start + 0, 61) +- MCDCBranch { true: Expression(30, Sub), false: Expression(32, Add), condition_id: 1, true_next_id: 2, false_next_id: 3 } at (prev + 0, 10) to (start + 0, 25) + true = (c0 - (c4 + c10)) + false = (c10 + c4) +- MCDCBranch { true: Expression(33, Sub), false: Counter(8), condition_id: 2, true_next_id: 4, false_next_id: 0 } at (prev + 0, 17) to (start + 0, 24) + true = (c0 - ((c4 + c8) + c10)) + false = c8 +- Code(Expression(54, Sub)) at (prev + 0, 22) to (start + 0, 23) + = (c0 - ((((c4 + c8) + c9) + c10) + c12)) +- MCDCBranch { true: Counter(10), false: Counter(4), condition_id: 3, true_next_id: 4, false_next_id: 0 } at (prev + 0, 28) to (start + 0, 37) + true = c10 + false = c4 +- Code(Expression(49, Sub)) at (prev + 0, 35) to (start + 0, 36) + = (c10 - (c7 + c11)) +- MCDCBranch { true: Expression(43, Add), false: Expression(51, Add), condition_id: 4, true_next_id: 0, false_next_id: 5 } at (prev + 0, 39) to (start + 0, 48) + true = ((c0 - ((((c4 + c8) + c9) + c10) + c12)) + (c10 - (c7 + c11))) + false = (((c12 + c9) + c11) + c7) +- Code(Expression(54, Sub)) at (prev + 0, 46) to (start + 0, 47) + = (c0 - ((((c4 + c8) + c9) + c10) + c12)) +- MCDCBranch { true: Expression(59, Add), false: Expression(60, Add), condition_id: 5, true_next_id: 0, false_next_id: 0 } at (prev + 0, 51) to (start + 0, 60) + true = (c12 + c11) + false = (c9 + c7) +- Code(Counter(12)) at (prev + 0, 58) to (start + 0, 59) +- Code(Expression(61, Sub)) at (prev + 1, 13) to (start + 0, 69) + = (c0 - (((c4 + c7) + c8) + c9)) +- MCDCDecision { bitmap_idx: 14, conditions_num: 2 } at (prev + 2, 9) to (start + 0, 31) +- MCDCBranch { true: Expression(65, Sub), false: Counter(5), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 10) to (start + 0, 19) + true = ((((c4 + c7) + c8) + c9) - c5) + false = c5 +- MCDCBranch { true: Expression(74, Sub), false: Counter(6), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 30) + true = ((((c4 + c7) + c8) + c9) - (c5 + c6)) + false = c6 +- Code(Expression(74, Sub)) at (prev + 0, 35) to (start + 0, 57) + = ((((c4 + c7) + c8) + c9) - (c5 + c6)) +- Code(Expression(79, Add)) at (prev + 1, 14) to (start + 0, 35) + = (c5 + c6) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c16 + +Function name: match_pattern::joint_pattern_with_or +Raw bytes (158): 0x[01, 01, 19, 01, 07, 0b, 19, 43, 15, 05, 0d, 19, 0d, 01, 1b, 3b, 19, 3f, 15, 43, 11, 05, 0d, 01, 53, 05, 15, 15, 05, 01, 3b, 3f, 15, 43, 11, 05, 0d, 15, 09, 4e, 05, 01, 53, 05, 15, 15, 09, 5f, 11, 63, 0d, 05, 09, 0e, 01, 26, 01, 00, 2b, 02, 01, 0b, 00, 0e, 28, 08, 04, 01, 09, 00, 31, 30, 02, 13, 02, 03, 04, 00, 0a, 00, 19, 30, 16, 11, 03, 00, 00, 00, 11, 00, 18, 30, 19, 0d, 04, 00, 00, 00, 1c, 00, 25, 30, 4e, 33, 01, 02, 00, 00, 27, 00, 30, 36, 00, 35, 00, 53, 28, 03, 02, 01, 09, 00, 1f, 30, 56, 09, 02, 00, 00, 00, 0a, 00, 13, 30, 15, 4b, 01, 02, 00, 00, 15, 00, 1e, 56, 00, 23, 00, 39, 5b, 01, 0e, 00, 23, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 25 +- expression 0 operands: lhs = Counter(0), rhs = Expression(1, Add) +- expression 1 operands: lhs = Expression(2, Add), rhs = Counter(6) +- expression 2 operands: lhs = Expression(16, Add), rhs = Counter(5) +- expression 3 operands: lhs = Counter(1), rhs = Counter(3) +- expression 4 operands: lhs = Counter(6), rhs = Counter(3) +- expression 5 operands: lhs = Counter(0), rhs = Expression(6, Add) +- expression 6 operands: lhs = Expression(14, Add), rhs = Counter(6) +- expression 7 operands: lhs = Expression(15, Add), rhs = Counter(5) +- expression 8 operands: lhs = Expression(16, Add), rhs = Counter(4) +- expression 9 operands: lhs = Counter(1), rhs = Counter(3) +- expression 10 operands: lhs = Counter(0), rhs = Expression(20, Add) +- expression 11 operands: lhs = Counter(1), rhs = Counter(5) +- expression 12 operands: lhs = Counter(5), rhs = Counter(1) +- expression 13 operands: lhs = Counter(0), rhs = Expression(14, Add) +- expression 14 operands: lhs = Expression(15, Add), rhs = Counter(5) +- expression 15 operands: lhs = Expression(16, Add), rhs = Counter(4) +- expression 16 operands: lhs = Counter(1), rhs = Counter(3) +- expression 17 operands: lhs = Counter(5), rhs = Counter(2) +- expression 18 operands: lhs = Expression(19, Sub), rhs = Counter(1) +- expression 19 operands: lhs = Counter(0), rhs = Expression(20, Add) +- expression 20 operands: lhs = Counter(1), rhs = Counter(5) +- expression 21 operands: lhs = Counter(5), rhs = Counter(2) +- expression 22 operands: lhs = Expression(23, Add), rhs = Counter(4) +- expression 23 operands: lhs = Expression(24, Add), rhs = Counter(3) +- expression 24 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 14 +- Code(Counter(0)) at (prev + 38, 1) to (start + 0, 43) +- Code(Expression(0, Sub)) at (prev + 1, 11) to (start + 0, 14) + = (c0 - (((c1 + c3) + c5) + c6)) +- MCDCDecision { bitmap_idx: 8, conditions_num: 4 } at (prev + 1, 9) to (start + 0, 49) +- MCDCBranch { true: Expression(0, Sub), false: Expression(4, Add), condition_id: 2, true_next_id: 3, false_next_id: 4 } at (prev + 0, 10) to (start + 0, 25) + true = (c0 - (((c1 + c3) + c5) + c6)) + false = (c6 + c3) +- MCDCBranch { true: Expression(5, Sub), false: Counter(4), condition_id: 3, true_next_id: 0, false_next_id: 0 } at (prev + 0, 17) to (start + 0, 24) + true = (c0 - ((((c1 + c3) + c4) + c5) + c6)) + false = c4 +- MCDCBranch { true: Counter(6), false: Counter(3), condition_id: 4, true_next_id: 0, false_next_id: 0 } at (prev + 0, 28) to (start + 0, 37) + true = c6 + false = c3 +- MCDCBranch { true: Expression(19, Sub), false: Expression(12, Add), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 39) to (start + 0, 48) + true = (c0 - (c1 + c5)) + false = (c5 + c1) +- Code(Expression(13, Sub)) at (prev + 0, 53) to (start + 0, 83) + = (c0 - (((c1 + c3) + c4) + c5)) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 1, 9) to (start + 0, 31) +- MCDCBranch { true: Expression(21, Sub), false: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 10) to (start + 0, 19) + true = (c5 - c2) + false = c2 +- MCDCBranch { true: Counter(5), false: Expression(18, Add), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 30) + true = c5 + false = ((c0 - (c1 + c5)) + c1) +- Code(Expression(21, Sub)) at (prev + 0, 35) to (start + 0, 57) + = (c5 - c2) +- Code(Expression(22, Add)) at (prev + 1, 14) to (start + 0, 35) + = (((c1 + c2) + c3) + c4) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c6 + +Function name: match_pattern::partial_matched +Raw bytes (93): 0x[01, 01, 0a, 01, 05, 01, 27, 05, 09, 05, 09, 01, 23, 27, 0d, 05, 09, 01, 23, 27, 0d, 05, 09, 0b, 01, 3f, 01, 01, 0e, 28, 03, 02, 02, 09, 00, 23, 30, 05, 02, 01, 00, 02, 00, 09, 00, 13, 05, 00, 0a, 00, 12, 30, 09, 06, 02, 00, 00, 00, 16, 00, 23, 09, 00, 1a, 00, 22, 27, 00, 27, 00, 4f, 20, 0d, 1e, 01, 09, 00, 1d, 0d, 00, 10, 00, 49, 1e, 01, 0e, 00, 23, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 10 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Expression(9, Add) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +- expression 3 operands: lhs = Counter(1), rhs = Counter(2) +- expression 4 operands: lhs = Counter(0), rhs = Expression(8, Add) +- expression 5 operands: lhs = Expression(9, Add), rhs = Counter(3) +- expression 6 operands: lhs = Counter(1), rhs = Counter(2) +- expression 7 operands: lhs = Counter(0), rhs = Expression(8, Add) +- expression 8 operands: lhs = Expression(9, Add), rhs = Counter(3) +- expression 9 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 11 +- Code(Counter(0)) at (prev + 63, 1) to (start + 1, 14) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 2, 9) to (start + 0, 35) +- MCDCBranch { true: Counter(1), false: Expression(0, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 9) to (start + 0, 19) + true = c1 + false = (c0 - c1) +- Code(Counter(1)) at (prev + 0, 10) to (start + 0, 18) +- MCDCBranch { true: Counter(2), false: Expression(1, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 22) to (start + 0, 35) + true = c2 + false = (c0 - (c1 + c2)) +- Code(Counter(2)) at (prev + 0, 26) to (start + 0, 34) +- Code(Expression(9, Add)) at (prev + 0, 39) to (start + 0, 79) + = (c1 + c2) +- Branch { true: Counter(3), false: Expression(7, Sub) } at (prev + 1, 9) to (start + 0, 29) + true = c3 + false = (c0 - ((c1 + c2) + c3)) +- Code(Counter(3)) at (prev + 0, 16) to (start + 0, 73) +- Code(Expression(7, Sub)) at (prev + 1, 14) to (start + 0, 35) + = (c0 - ((c1 + c2) + c3)) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c3 + +Function name: match_pattern::simple_joint_pattern +Raw bytes (142): 0x[01, 01, 16, 01, 07, 37, 15, 05, 0d, 01, 43, 05, 15, 15, 05, 01, 2f, 33, 15, 37, 11, 05, 0d, 01, 2f, 33, 15, 37, 11, 05, 0d, 3e, 05, 01, 43, 05, 15, 15, 09, 15, 09, 53, 11, 57, 0d, 05, 09, 0d, 01, 1e, 01, 00, 2a, 02, 01, 0b, 00, 0e, 28, 07, 03, 01, 09, 00, 25, 30, 3e, 17, 01, 02, 00, 00, 0a, 00, 19, 30, 2a, 11, 03, 00, 00, 00, 11, 00, 18, 30, 02, 0d, 02, 03, 00, 00, 1b, 00, 24, 2a, 00, 29, 00, 43, 28, 03, 02, 01, 09, 00, 1f, 30, 15, 3b, 01, 02, 00, 00, 0a, 00, 13, 30, 4a, 09, 02, 00, 00, 00, 15, 00, 1e, 4a, 00, 23, 00, 39, 4f, 01, 0e, 00, 23, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 22 +- expression 0 operands: lhs = Counter(0), rhs = Expression(1, Add) +- expression 1 operands: lhs = Expression(13, Add), rhs = Counter(5) +- expression 2 operands: lhs = Counter(1), rhs = Counter(3) +- expression 3 operands: lhs = Counter(0), rhs = Expression(16, Add) +- expression 4 operands: lhs = Counter(1), rhs = Counter(5) +- expression 5 operands: lhs = Counter(5), rhs = Counter(1) +- expression 6 operands: lhs = Counter(0), rhs = Expression(11, Add) +- expression 7 operands: lhs = Expression(12, Add), rhs = Counter(5) +- expression 8 operands: lhs = Expression(13, Add), rhs = Counter(4) +- expression 9 operands: lhs = Counter(1), rhs = Counter(3) +- expression 10 operands: lhs = Counter(0), rhs = Expression(11, Add) +- expression 11 operands: lhs = Expression(12, Add), rhs = Counter(5) +- expression 12 operands: lhs = Expression(13, Add), rhs = Counter(4) +- expression 13 operands: lhs = Counter(1), rhs = Counter(3) +- expression 14 operands: lhs = Expression(15, Sub), rhs = Counter(1) +- expression 15 operands: lhs = Counter(0), rhs = Expression(16, Add) +- expression 16 operands: lhs = Counter(1), rhs = Counter(5) +- expression 17 operands: lhs = Counter(5), rhs = Counter(2) +- expression 18 operands: lhs = Counter(5), rhs = Counter(2) +- expression 19 operands: lhs = Expression(20, Add), rhs = Counter(4) +- expression 20 operands: lhs = Expression(21, Add), rhs = Counter(3) +- expression 21 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 13 +- Code(Counter(0)) at (prev + 30, 1) to (start + 0, 42) +- Code(Expression(0, Sub)) at (prev + 1, 11) to (start + 0, 14) + = (c0 - ((c1 + c3) + c5)) +- MCDCDecision { bitmap_idx: 7, conditions_num: 3 } at (prev + 1, 9) to (start + 0, 37) +- MCDCBranch { true: Expression(15, Sub), false: Expression(5, Add), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 10) to (start + 0, 25) + true = (c0 - (c1 + c5)) + false = (c5 + c1) +- MCDCBranch { true: Expression(10, Sub), false: Counter(4), condition_id: 3, true_next_id: 0, false_next_id: 0 } at (prev + 0, 17) to (start + 0, 24) + true = (c0 - (((c1 + c3) + c4) + c5)) + false = c4 +- MCDCBranch { true: Expression(0, Sub), false: Counter(3), condition_id: 2, true_next_id: 3, false_next_id: 0 } at (prev + 0, 27) to (start + 0, 36) + true = (c0 - ((c1 + c3) + c5)) + false = c3 +- Code(Expression(10, Sub)) at (prev + 0, 41) to (start + 0, 67) + = (c0 - (((c1 + c3) + c4) + c5)) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 1, 9) to (start + 0, 31) +- MCDCBranch { true: Counter(5), false: Expression(14, Add), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 10) to (start + 0, 19) + true = c5 + false = ((c0 - (c1 + c5)) + c1) +- MCDCBranch { true: Expression(18, Sub), false: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 30) + true = (c5 - c2) + false = c2 +- Code(Expression(18, Sub)) at (prev + 0, 35) to (start + 0, 57) + = (c5 - c2) +- Code(Expression(19, Add)) at (prev + 1, 14) to (start + 0, 35) + = (((c1 + c2) + c3) + c4) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c5 + +Function name: match_pattern::simple_or_pattern +Raw bytes (59): 0x[01, 01, 04, 01, 07, 05, 09, 09, 05, 01, 05, 07, 01, 17, 01, 01, 0e, 28, 03, 02, 02, 09, 00, 1e, 30, 02, 0b, 01, 00, 02, 00, 09, 00, 12, 30, 09, 05, 02, 00, 00, 00, 15, 00, 1e, 0e, 00, 22, 00, 37, 05, 01, 0e, 00, 1e, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 4 +- expression 0 operands: lhs = Counter(0), rhs = Expression(1, Add) +- expression 1 operands: lhs = Counter(1), rhs = Counter(2) +- expression 2 operands: lhs = Counter(2), rhs = Counter(1) +- expression 3 operands: lhs = Counter(0), rhs = Counter(1) +Number of file 0 mappings: 7 +- Code(Counter(0)) at (prev + 23, 1) to (start + 1, 14) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 2, 9) to (start + 0, 30) +- MCDCBranch { true: Expression(0, Sub), false: Expression(2, Add), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 9) to (start + 0, 18) + true = (c0 - (c1 + c2)) + false = (c2 + c1) +- MCDCBranch { true: Counter(2), false: Counter(1), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 30) + true = c2 + false = c1 +- Code(Expression(3, Sub)) at (prev + 0, 34) to (start + 0, 55) + = (c0 - c1) +- Code(Counter(1)) at (prev + 1, 14) to (start + 0, 30) +- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2) +Highest counter ID seen: c2 + +Function name: match_pattern::single_nested_pattern +Raw bytes (154): 0x[01, 01, 1b, 01, 07, 5f, 11, 00, 09, 33, 00, 11, 09, 4e, 00, 01, 53, 57, 15, 5b, 11, 5f, 00, 00, 09, 33, 00, 11, 09, 01, 53, 57, 15, 5b, 11, 5f, 00, 00, 09, 15, 00, 01, 53, 57, 15, 5b, 11, 5f, 00, 00, 09, 67, 00, 02, 09, 01, 00, 0e, 01, 0e, 01, 00, 24, 02, 01, 0b, 00, 0e, 28, 06, 02, 01, 09, 00, 18, 30, 02, 2f, 01, 02, 00, 00, 09, 00, 18, 30, 15, 17, 02, 00, 00, 00, 10, 00, 17, 15, 00, 1c, 00, 32, 28, 03, 02, 01, 09, 00, 15, 30, 02, 2f, 01, 02, 00, 00, 09, 00, 15, 30, 4e, 4b, 02, 00, 00, 00, 10, 00, 14, 4e, 00, 19, 00, 2f, 20, 11, 63, 01, 09, 00, 12, 11, 00, 16, 00, 26, 09, 01, 16, 00, 26, 6a, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 27 +- expression 0 operands: lhs = Counter(0), rhs = Expression(1, Add) +- expression 1 operands: lhs = Expression(23, Add), rhs = Counter(4) +- expression 2 operands: lhs = Zero, rhs = Counter(2) +- expression 3 operands: lhs = Expression(12, Add), rhs = Zero +- expression 4 operands: lhs = Counter(4), rhs = Counter(2) +- expression 5 operands: lhs = Expression(19, Sub), rhs = Zero +- expression 6 operands: lhs = Counter(0), rhs = Expression(20, Add) +- expression 7 operands: lhs = Expression(21, Add), rhs = Counter(5) +- expression 8 operands: lhs = Expression(22, Add), rhs = Counter(4) +- expression 9 operands: lhs = Expression(23, Add), rhs = Zero +- expression 10 operands: lhs = Zero, rhs = Counter(2) +- expression 11 operands: lhs = Expression(12, Add), rhs = Zero +- expression 12 operands: lhs = Counter(4), rhs = Counter(2) +- expression 13 operands: lhs = Counter(0), rhs = Expression(20, Add) +- expression 14 operands: lhs = Expression(21, Add), rhs = Counter(5) +- expression 15 operands: lhs = Expression(22, Add), rhs = Counter(4) +- expression 16 operands: lhs = Expression(23, Add), rhs = Zero +- expression 17 operands: lhs = Zero, rhs = Counter(2) +- expression 18 operands: lhs = Counter(5), rhs = Zero +- expression 19 operands: lhs = Counter(0), rhs = Expression(20, Add) +- expression 20 operands: lhs = Expression(21, Add), rhs = Counter(5) +- expression 21 operands: lhs = Expression(22, Add), rhs = Counter(4) +- expression 22 operands: lhs = Expression(23, Add), rhs = Zero +- expression 23 operands: lhs = Zero, rhs = Counter(2) +- expression 24 operands: lhs = Expression(25, Add), rhs = Zero +- expression 25 operands: lhs = Expression(0, Sub), rhs = Counter(2) +- expression 26 operands: lhs = Counter(0), rhs = Zero +Number of file 0 mappings: 14 +- Code(Counter(0)) at (prev + 14, 1) to (start + 0, 36) +- Code(Expression(0, Sub)) at (prev + 1, 11) to (start + 0, 14) + = (c0 - ((Zero + c2) + c4)) +- MCDCDecision { bitmap_idx: 6, conditions_num: 2 } at (prev + 1, 9) to (start + 0, 24) +- MCDCBranch { true: Expression(0, Sub), false: Expression(11, Add), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 9) to (start + 0, 24) + true = (c0 - ((Zero + c2) + c4)) + false = ((c4 + c2) + Zero) +- MCDCBranch { true: Counter(5), false: Expression(5, Add), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 16) to (start + 0, 23) + true = c5 + false = ((c0 - ((((Zero + c2) + Zero) + c4) + c5)) + Zero) +- Code(Counter(5)) at (prev + 0, 28) to (start + 0, 50) +- MCDCDecision { bitmap_idx: 3, conditions_num: 2 } at (prev + 1, 9) to (start + 0, 21) +- MCDCBranch { true: Expression(0, Sub), false: Expression(11, Add), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 9) to (start + 0, 21) + true = (c0 - ((Zero + c2) + c4)) + false = ((c4 + c2) + Zero) +- MCDCBranch { true: Expression(19, Sub), false: Expression(18, Add), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 16) to (start + 0, 20) + true = (c0 - ((((Zero + c2) + Zero) + c4) + c5)) + false = (c5 + Zero) +- Code(Expression(19, Sub)) at (prev + 0, 25) to (start + 0, 47) + = (c0 - ((((Zero + c2) + Zero) + c4) + c5)) +- Branch { true: Counter(4), false: Expression(24, Add) } at (prev + 1, 9) to (start + 0, 18) + true = c4 + false = (((c0 - ((Zero + c2) + c4)) + c2) + Zero) +- Code(Counter(4)) at (prev + 0, 22) to (start + 0, 38) +- Code(Counter(2)) at (prev + 1, 22) to (start + 0, 38) +- Code(Expression(26, Sub)) at (prev + 2, 1) to (start + 0, 2) + = (c0 - Zero) +Highest counter ID seen: c5 + diff --git a/tests/coverage/mcdc/match_pattern.coverage b/tests/coverage/mcdc/match_pattern.coverage new file mode 100644 index 0000000000000..8eab2e953fdda --- /dev/null +++ b/tests/coverage/mcdc/match_pattern.coverage @@ -0,0 +1,392 @@ + LL| |#![feature(coverage_attribute)] + LL| |//@ edition: 2021 + LL| |//@ min-llvm-version: 19 + LL| |//@ compile-flags: -Zcoverage-options=mcdc + LL| |//@ llvm-cov-flags: --show-branches=count --show-mcdc + LL| | + LL| |#[derive(Clone, Copy)] + LL| |enum Pat { + LL| | A(Option), + LL| | B(i32), + LL| | C(i32), + LL| |} + LL| | + LL| 2|fn single_nested_pattern(pat: Pat) { + LL| 1| match pat { + LL| 1| Pat::A(Some(_)) => say("matched A::Some"), + ------------------ + | Branch (LL:9): [True: 1, False: 1] + | Branch (LL:16): [True: 1, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:24) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:9) + | Condition C2 --> (LL:16) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, T = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 0| Pat::A(None) => say("matched A::None"), + ------------------ + | Branch (LL:9): [True: 1, False: 1] + | Branch (LL:16): [True: 0, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:21) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:9) + | Condition C2 --> (LL:16) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | + | C1-Pair: not covered + | C2-Pair: not covered + | MC/DC Coverage for Decision: 0.00% + | + ------------------ + LL| 1| Pat::B(_) => say("matched B"), + ------------------ + | Branch (LL:9): [True: 1, False: 1] + ------------------ + LL| 0| Pat::C(_) => say("matched C"), + LL| | } + LL| 2|} + LL| | + LL| 2|fn simple_or_pattern(pat: Pat) { + LL| 2| match pat { + LL| 1| Pat::B(_) | Pat::C(_) => say("matched B or C"), + ------------------ + | Branch (LL:9): [True: 0, False: 2] + | Branch (LL:21): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:30) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:9) + | Condition C2 --> (LL:21) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, F = F } + | 2 { F, T = T } + | + | C1-Pair: not covered + | C2-Pair: covered: (1,2) + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| _ => say("matched A"), + LL| | } + LL| 2|} + LL| | + LL| 3|fn simple_joint_pattern(pat: (Pat, Pat)) { + LL| 1| match pat { + LL| 1| (Pat::A(Some(_)), Pat::B(_)) => say("matched A::Some + B"), + ------------------ + | Branch (LL:10): [True: 2, False: 1] + | Branch (LL:17): [True: 1, False: 0] + | Branch (LL:27): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:37) + | + | Number of Conditions: 3 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:17) + | Condition C3 --> (LL:27) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3 Result + | 1 { F, -, - = F } + | 2 { T, -, F = F } + | 3 { T, T, T = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: not covered + | C3-Pair: covered: (2,3) + | MC/DC Coverage for Decision: 66.67% + | + ------------------ + LL| 0| (Pat::B(_), Pat::C(_)) => say("matched B and C"), + ------------------ + | Branch (LL:10): [True: 1, False: 2] + | Branch (LL:21): [True: 0, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:31) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:21) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, F = F } + | + | C1-Pair: not covered + | C2-Pair: not covered + | MC/DC Coverage for Decision: 0.00% + | + ------------------ + LL| 2| _ => say("matched others"), + LL| | } + LL| 3|} + LL| | + LL| 4|fn joint_pattern_with_or(pat: (Pat, Pat)) { + LL| 1| match pat { + LL| 2| (Pat::A(Some(_)) | Pat::C(_), Pat::B(_)) => say("matched A::Some | C + B"), + ------------------ + | Branch (LL:10): [True: 1, False: 2] + | Branch (LL:17): [True: 1, False: 0] + | Branch (LL:28): [True: 1, False: 1] + | Branch (LL:39): [True: 3, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:49) + | + | Number of Conditions: 4 + | Condition C1 --> (LL:39) + | Condition C2 --> (LL:10) + | Condition C3 --> (LL:17) + | Condition C4 --> (LL:28) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3, C4 Result + | 1 { F, -, -, - = F } + | 2 { T, F, -, F = F } + | 3 { T, F, -, T = T } + | 4 { T, T, T, - = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: covered: (2,4) + | C3-Pair: not covered + | C4-Pair: covered: (2,3) + | MC/DC Coverage for Decision: 75.00% + | + ------------------ + LL| 1| (Pat::B(_), Pat::C(_)) => say("matched B and C"), + ------------------ + | Branch (LL:10): [True: 1, False: 0] + | Branch (LL:21): [True: 1, False: 3] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:31) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:21) + | Condition C2 --> (LL:10) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, - = F } + | 2 { T, T = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| _ => say("matched others"), + LL| | } + LL| 4|} + LL| | + LL| 4|fn joint_or_patterns(pat: (Pat, Pat)) { + LL| 2| match pat { + LL| 2| (Pat::A(Some(_)) | Pat::C(_), Pat::B(_) | Pat::C(_)) => say("matched A::Some | C + B | C"), + ------------------ + | Branch (LL:10): [True: 1, False: 3] + | Branch (LL:17): [True: 1, False: 0] + | Branch (LL:28): [True: 1, False: 2] + | Branch (LL:39): [True: 2, False: 0] + | Branch (LL:51): [True: 0, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:61) + | + | Number of Conditions: 5 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:17) + | Condition C3 --> (LL:28) + | Condition C4 --> (LL:39) + | Condition C5 --> (LL:51) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3, C4, C5 Result + | 1 { F, -, F, -, - = F } + | 2 { F, -, T, T, - = T } + | 3 { T, T, -, T, - = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: not covered + | C3-Pair: covered: (1,2) + | C4-Pair: not covered + | C5-Pair: not covered + | MC/DC Coverage for Decision: 40.00% + | + ------------------ + LL| 1| (Pat::B(_), Pat::C(_)) => say("matched B and C"), + ------------------ + | Branch (LL:10): [True: 2, False: 0] + | Branch (LL:21): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:31) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:21) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { T, F = F } + | 2 { T, T = T } + | + | C1-Pair: not covered + | C2-Pair: covered: (1,2) + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| _ => say("matched others"), + LL| | } + LL| | + LL| | // Try to use the matched value + LL| 2| match pat { + LL| 1| (Pat::A(Some(a)) | Pat::C(a), Pat::B(b) | Pat::C(b)) => { + ^0 + ------------------ + | Branch (LL:10): [True: 1, False: 3] + | Branch (LL:17): [True: 1, False: 0] + | Branch (LL:28): [True: 1, False: 2] + | Branch (LL:39): [True: 2, False: 0] + | Branch (LL:51): [True: 0, False: 0] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:61) + | + | Number of Conditions: 5 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:17) + | Condition C3 --> (LL:28) + | Condition C4 --> (LL:39) + | Condition C5 --> (LL:51) + | + | Executed MC/DC Test Vectors: + | + | C1, C2, C3, C4, C5 Result + | 1 { F, -, F, -, - = F } + | 2 { F, -, T, T, - = T } + | 3 { T, T, -, T, - = T } + | + | C1-Pair: covered: (1,3) + | C2-Pair: not covered + | C3-Pair: covered: (1,2) + | C4-Pair: not covered + | C5-Pair: not covered + | MC/DC Coverage for Decision: 40.00% + | + ------------------ + LL| 2| say(&format!("matched A::Some | C ({a}) + B | C ({b})")) + LL| | } + LL| 1| (Pat::B(_), Pat::C(_)) => say("matched B and C"), + ------------------ + | Branch (LL:10): [True: 2, False: 0] + | Branch (LL:21): [True: 1, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:31) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:10) + | Condition C2 --> (LL:21) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { T, F = F } + | 2 { T, T = T } + | + | C1-Pair: not covered + | C2-Pair: covered: (1,2) + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| _ => say("matched others"), + LL| | } + LL| 4|} + LL| | + LL| 2|fn partial_matched(arr: &[i32]) { + LL| 2| match arr { + LL| 1| [selected] | [_, selected] => say(&format!("match arm 1: {selected}")), + ^0 + ------------------ + | Branch (LL:9): [True: 1, False: 1] + | Branch (LL:22): [True: 0, False: 1] + ------------------ + |---> MC/DC Decision Region (LL:9) to (LL:35) + | + | Number of Conditions: 2 + | Condition C1 --> (LL:9) + | Condition C2 --> (LL:22) + | + | Executed MC/DC Test Vectors: + | + | C1, C2 Result + | 1 { F, F = F } + | 2 { T, - = T } + | + | C1-Pair: covered: (1,2) + | C2-Pair: not covered + | MC/DC Coverage for Decision: 50.00% + | + ------------------ + LL| 1| [_, _, selected, ..] => say(&format!("match arm 2: {selected}")), + ------------------ + | Branch (LL:9): [True: 1, False: 0] + ------------------ + LL| 0| _ => say("matched others"), + LL| | } + LL| 2|} + LL| | + LL| |#[coverage(off)] + LL| |fn main() { + LL| | single_nested_pattern(Pat::A(Some(5))); + LL| | single_nested_pattern(Pat::B(5)); + LL| | + LL| | simple_or_pattern(Pat::A(None)); + LL| | simple_or_pattern(Pat::C(3)); + LL| | + LL| | simple_joint_pattern((Pat::A(Some(1)), Pat::B(2))); + LL| | simple_joint_pattern((Pat::A(Some(1)), Pat::C(2))); + LL| | simple_joint_pattern((Pat::B(1), Pat::B(2))); + LL| | + LL| | joint_pattern_with_or((Pat::A(Some(1)), Pat::B(2))); + LL| | joint_pattern_with_or((Pat::B(1), Pat::C(2))); + LL| | joint_pattern_with_or((Pat::B(1), Pat::B(2))); + LL| | joint_pattern_with_or((Pat::C(1), Pat::B(2))); + LL| | + LL| | joint_or_patterns((Pat::A(Some(1)), Pat::B(2))); + LL| | joint_or_patterns((Pat::B(1), Pat::C(2))); + LL| | joint_or_patterns((Pat::B(1), Pat::B(2))); + LL| | joint_or_patterns((Pat::C(1), Pat::B(2))); + LL| | + LL| | partial_matched(&[1]); + LL| | partial_matched(&[1, 2, 3]); + LL| |} + LL| | + LL| |#[coverage(off)] + LL| |fn say(message: &str) { + LL| | core::hint::black_box(message); + LL| |} + diff --git a/tests/coverage/mcdc/match_pattern.rs b/tests/coverage/mcdc/match_pattern.rs new file mode 100644 index 0000000000000..30fd4335a63cf --- /dev/null +++ b/tests/coverage/mcdc/match_pattern.rs @@ -0,0 +1,100 @@ +#![feature(coverage_attribute)] +//@ edition: 2021 +//@ min-llvm-version: 19 +//@ compile-flags: -Zcoverage-options=mcdc +//@ llvm-cov-flags: --show-branches=count --show-mcdc + +#[derive(Clone, Copy)] +enum Pat { + A(Option), + B(i32), + C(i32), +} + +fn single_nested_pattern(pat: Pat) { + match pat { + Pat::A(Some(_)) => say("matched A::Some"), + Pat::A(None) => say("matched A::None"), + Pat::B(_) => say("matched B"), + Pat::C(_) => say("matched C"), + } +} + +fn simple_or_pattern(pat: Pat) { + match pat { + Pat::B(_) | Pat::C(_) => say("matched B or C"), + _ => say("matched A"), + } +} + +fn simple_joint_pattern(pat: (Pat, Pat)) { + match pat { + (Pat::A(Some(_)), Pat::B(_)) => say("matched A::Some + B"), + (Pat::B(_), Pat::C(_)) => say("matched B and C"), + _ => say("matched others"), + } +} + +fn joint_pattern_with_or(pat: (Pat, Pat)) { + match pat { + (Pat::A(Some(_)) | Pat::C(_), Pat::B(_)) => say("matched A::Some | C + B"), + (Pat::B(_), Pat::C(_)) => say("matched B and C"), + _ => say("matched others"), + } +} + +fn joint_or_patterns(pat: (Pat, Pat)) { + match pat { + (Pat::A(Some(_)) | Pat::C(_), Pat::B(_) | Pat::C(_)) => say("matched A::Some | C + B | C"), + (Pat::B(_), Pat::C(_)) => say("matched B and C"), + _ => say("matched others"), + } + + // Try to use the matched value + match pat { + (Pat::A(Some(a)) | Pat::C(a), Pat::B(b) | Pat::C(b)) => { + say(&format!("matched A::Some | C ({a}) + B | C ({b})")) + } + (Pat::B(_), Pat::C(_)) => say("matched B and C"), + _ => say("matched others"), + } +} + +fn partial_matched(arr: &[i32]) { + match arr { + [selected] | [_, selected] => say(&format!("match arm 1: {selected}")), + [_, _, selected, ..] => say(&format!("match arm 2: {selected}")), + _ => say("matched others"), + } +} + +#[coverage(off)] +fn main() { + single_nested_pattern(Pat::A(Some(5))); + single_nested_pattern(Pat::B(5)); + + simple_or_pattern(Pat::A(None)); + simple_or_pattern(Pat::C(3)); + + simple_joint_pattern((Pat::A(Some(1)), Pat::B(2))); + simple_joint_pattern((Pat::A(Some(1)), Pat::C(2))); + simple_joint_pattern((Pat::B(1), Pat::B(2))); + + joint_pattern_with_or((Pat::A(Some(1)), Pat::B(2))); + joint_pattern_with_or((Pat::B(1), Pat::C(2))); + joint_pattern_with_or((Pat::B(1), Pat::B(2))); + joint_pattern_with_or((Pat::C(1), Pat::B(2))); + + joint_or_patterns((Pat::A(Some(1)), Pat::B(2))); + joint_or_patterns((Pat::B(1), Pat::C(2))); + joint_or_patterns((Pat::B(1), Pat::B(2))); + joint_or_patterns((Pat::C(1), Pat::B(2))); + + partial_matched(&[1]); + partial_matched(&[1, 2, 3]); +} + +#[coverage(off)] +fn say(message: &str) { + core::hint::black_box(message); +}