Auto merge of #124255 - RenjiSann:renji/mcdc-nested-expressions, r=Zalathar
Some checks failed
CI / Calculate job matrix (push) Failing after 9s
CI / master (push) Has been skipped
CI / bors build finished (push) Has been skipped
CI / ${{ matrix.name }} (push) Has been skipped

MCDC coverage: support nested decision coverage

#123409 provided the initial MCDC coverage implementation.

As referenced in #124144, it does not currently support "nested" decisions, like the following example :

```rust
fn nested_if_in_condition(a: bool, b: bool, c: bool) {
    if a && if b || c { true } else { false } {
        say("yes");
    } else {
        say("no");
    }
}
```

Note that there is an if-expression (`if b || c ...`) embedded inside a boolean expression in the decision of an outer if-expression.

This PR proposes a workaround for this cases, by introducing a Decision context stack, and by handing several `temporary condition bitmaps` instead of just one.
When instrumenting boolean expressions, if the current node is a leaf condition (i.e. not a `||`/`&&` logical operator nor a `!` not operator), we insert a new decision context, such that if there are more boolean expressions inside the condition, they are handled as separate expressions.

On the codegen LLVM side, we allocate as many `temp_cond_bitmap`s as necessary to handle the maximum encountered decision depth.
This commit is contained in:
bors 2024-04-29 11:54:49 +00:00
commit 7a58674259
12 changed files with 699 additions and 70 deletions

View File

@ -27,6 +27,7 @@
use rustc_target::spec::{HasTargetSpec, SanitizerSet, Target};
use smallvec::SmallVec;
use std::borrow::Cow;
use std::ffi::CString;
use std::iter;
use std::ops::Deref;
use std::ptr;
@ -1709,7 +1710,8 @@ pub(crate) fn mcdc_parameters(
fn_name: &'ll Value,
hash: &'ll Value,
bitmap_bytes: &'ll Value,
) -> &'ll Value {
max_decision_depth: u32,
) -> Vec<&'ll Value> {
debug!("mcdc_parameters() with args ({:?}, {:?}, {:?})", fn_name, hash, bitmap_bytes);
assert!(llvm_util::get_version() >= (18, 0, 0), "MCDC intrinsics require LLVM 18 or later");
@ -1722,6 +1724,8 @@ pub(crate) fn mcdc_parameters(
let args = &[fn_name, hash, bitmap_bytes];
let args = self.check_call("call", llty, llfn, args);
let mut cond_bitmaps = vec![];
unsafe {
let _ = llvm::LLVMRustBuildCall(
self.llbuilder,
@ -1733,17 +1737,22 @@ pub(crate) fn mcdc_parameters(
0 as c_uint,
);
// Create condition bitmap named `mcdc.addr`.
let mut bx = Builder::with_cx(self.cx);
bx.position_at_start(llvm::LLVMGetFirstBasicBlock(self.llfn()));
let cond_bitmap = {
let alloca =
llvm::LLVMBuildAlloca(bx.llbuilder, bx.cx.type_i32(), c"mcdc.addr".as_ptr());
llvm::LLVMSetAlignment(alloca, 4);
alloca
};
bx.store(self.const_i32(0), cond_bitmap, self.tcx().data_layout.i32_align.abi);
cond_bitmap
for i in 0..=max_decision_depth {
let mut bx = Builder::with_cx(self.cx);
bx.position_at_start(llvm::LLVMGetFirstBasicBlock(self.llfn()));
let name = CString::new(format!("mcdc.addr.{i}")).unwrap();
let cond_bitmap = {
let alloca =
llvm::LLVMBuildAlloca(bx.llbuilder, bx.cx.type_i32(), name.as_ptr());
llvm::LLVMSetAlignment(alloca, 4);
alloca
};
bx.store(self.const_i32(0), cond_bitmap, self.tcx().data_layout.i32_align.abi);
cond_bitmaps.push(cond_bitmap);
}
}
cond_bitmaps
}
pub(crate) fn mcdc_tvbitmap_update(

View File

@ -30,7 +30,7 @@ pub struct CrateCoverageContext<'ll, 'tcx> {
pub(crate) function_coverage_map:
RefCell<FxIndexMap<Instance<'tcx>, FunctionCoverageCollector<'tcx>>>,
pub(crate) pgo_func_name_var_map: RefCell<FxHashMap<Instance<'tcx>, &'ll llvm::Value>>,
pub(crate) mcdc_condition_bitmap_map: RefCell<FxHashMap<Instance<'tcx>, &'ll llvm::Value>>,
pub(crate) mcdc_condition_bitmap_map: RefCell<FxHashMap<Instance<'tcx>, Vec<&'ll llvm::Value>>>,
}
impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> {
@ -49,9 +49,20 @@ pub fn take_function_coverage_map(
}
/// LLVM use a temp value to record evaluated mcdc test vector of each decision, which is called condition bitmap.
/// This value is named `mcdc.addr` (same as clang) and is a 32-bit integer.
fn try_get_mcdc_condition_bitmap(&self, instance: &Instance<'tcx>) -> Option<&'ll llvm::Value> {
self.mcdc_condition_bitmap_map.borrow().get(instance).copied()
/// In order to handle nested decisions, several condition bitmaps can be
/// allocated for a function body.
/// These values are named `mcdc.addr.{i}` and are a 32-bit integers.
/// They respectively hold the condition bitmaps for decisions with a depth of `i`.
fn try_get_mcdc_condition_bitmap(
&self,
instance: &Instance<'tcx>,
decision_depth: u16,
) -> Option<&'ll llvm::Value> {
self.mcdc_condition_bitmap_map
.borrow()
.get(instance)
.and_then(|bitmap_map| bitmap_map.get(decision_depth as usize))
.copied() // Dereference Option<&&Value> to Option<&Value>
}
}
@ -143,7 +154,7 @@ fn add_coverage(&mut self, instance: Instance<'tcx>, kind: &CoverageKind) {
CoverageKind::ExpressionUsed { id } => {
func_coverage.mark_expression_id_seen(id);
}
CoverageKind::CondBitmapUpdate { id, value, .. } => {
CoverageKind::CondBitmapUpdate { id, value, decision_depth } => {
drop(coverage_map);
assert_ne!(
id.as_u32(),
@ -151,7 +162,7 @@ fn add_coverage(&mut self, instance: Instance<'tcx>, kind: &CoverageKind) {
"ConditionId of evaluated conditions should never be zero"
);
let cond_bitmap = coverage_context
.try_get_mcdc_condition_bitmap(&instance)
.try_get_mcdc_condition_bitmap(&instance, decision_depth)
.expect("mcdc cond bitmap should have been allocated for updating");
let cond_loc = bx.const_i32(id.as_u32() as i32 - 1);
let bool_value = bx.const_bool(value);
@ -159,10 +170,10 @@ fn add_coverage(&mut self, instance: Instance<'tcx>, kind: &CoverageKind) {
let hash = bx.const_u64(function_coverage_info.function_source_hash);
bx.mcdc_condbitmap_update(fn_name, hash, cond_loc, cond_bitmap, bool_value);
}
CoverageKind::TestVectorBitmapUpdate { bitmap_idx } => {
CoverageKind::TestVectorBitmapUpdate { bitmap_idx, decision_depth } => {
drop(coverage_map);
let cond_bitmap = coverage_context
.try_get_mcdc_condition_bitmap(&instance)
.try_get_mcdc_condition_bitmap(&instance, decision_depth)
.expect("mcdc cond bitmap should have been allocated for merging into the global bitmap");
let bitmap_bytes = bx.tcx().coverage_ids_info(instance.def).mcdc_bitmap_bytes;
assert!(bitmap_idx < bitmap_bytes, "bitmap index of the decision out of range");
@ -195,7 +206,8 @@ fn ensure_mcdc_parameters<'ll, 'tcx>(
let fn_name = bx.get_pgo_func_name_var(instance);
let hash = bx.const_u64(function_coverage_info.function_source_hash);
let bitmap_bytes = bx.const_u32(function_coverage_info.mcdc_bitmap_bytes);
let cond_bitmap = bx.mcdc_parameters(fn_name, hash, bitmap_bytes);
let max_decision_depth = function_coverage_info.mcdc_max_decision_depth;
let cond_bitmap = bx.mcdc_parameters(fn_name, hash, bitmap_bytes, max_decision_depth as u32);
bx.coverage_context()
.expect("already checked above")
.mcdc_condition_bitmap_map

View File

@ -132,7 +132,7 @@ pub enum CoverageKind {
///
/// If this statement does not survive MIR optimizations, the condition would never be
/// taken as evaluated.
CondBitmapUpdate { id: ConditionId, value: bool },
CondBitmapUpdate { id: ConditionId, value: bool, decision_depth: u16 },
/// Marks the point in MIR control flow represented by a evaluated decision.
///
@ -140,7 +140,7 @@ pub enum CoverageKind {
///
/// If this statement does not survive MIR optimizations, the decision would never be
/// taken as evaluated.
TestVectorBitmapUpdate { bitmap_idx: u32 },
TestVectorBitmapUpdate { bitmap_idx: u32, decision_depth: u16 },
}
impl Debug for CoverageKind {
@ -151,11 +151,17 @@ fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
BlockMarker { id } => write!(fmt, "BlockMarker({:?})", id.index()),
CounterIncrement { id } => write!(fmt, "CounterIncrement({:?})", id.index()),
ExpressionUsed { id } => write!(fmt, "ExpressionUsed({:?})", id.index()),
CondBitmapUpdate { id, value } => {
write!(fmt, "CondBitmapUpdate({:?}, {:?})", id.index(), value)
CondBitmapUpdate { id, value, decision_depth } => {
write!(
fmt,
"CondBitmapUpdate({:?}, {:?}, depth={:?})",
id.index(),
value,
decision_depth
)
}
TestVectorBitmapUpdate { bitmap_idx } => {
write!(fmt, "TestVectorUpdate({:?})", bitmap_idx)
TestVectorBitmapUpdate { bitmap_idx, decision_depth } => {
write!(fmt, "TestVectorUpdate({:?}, depth={:?})", bitmap_idx, decision_depth)
}
}
}
@ -269,6 +275,9 @@ pub struct FunctionCoverageInfo {
pub mcdc_bitmap_bytes: u32,
pub expressions: IndexVec<ExpressionId, Expression>,
pub mappings: Vec<Mapping>,
/// The depth of the deepest decision is used to know how many
/// temp condbitmaps should be allocated for the function.
pub mcdc_max_decision_depth: u16,
}
/// Branch information recorded during THIR-to-MIR lowering, and stored in MIR.
@ -319,6 +328,7 @@ pub struct MCDCBranchSpan {
pub condition_info: Option<ConditionInfo>,
pub true_marker: BlockMarkerId,
pub false_marker: BlockMarkerId,
pub decision_depth: u16,
}
#[derive(Copy, Clone, Debug)]
@ -334,4 +344,5 @@ pub struct MCDCDecisionSpan {
pub span: Span,
pub conditions_num: usize,
pub end_markers: Vec<BlockMarkerId>,
pub decision_depth: u16,
}

View File

@ -496,20 +496,27 @@ fn write_coverage_branch_info(
)?;
}
for coverage::MCDCBranchSpan { span, condition_info, true_marker, false_marker } in
mcdc_branch_spans
for coverage::MCDCBranchSpan {
span,
condition_info,
true_marker,
false_marker,
decision_depth,
} in mcdc_branch_spans
{
writeln!(
w,
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?}, depth: {decision_depth:?} }} => {span:?}",
condition_info.map(|info| info.condition_id)
)?;
}
for coverage::MCDCDecisionSpan { span, conditions_num, end_markers } in mcdc_decision_spans {
for coverage::MCDCDecisionSpan { span, conditions_num, end_markers, decision_depth } in
mcdc_decision_spans
{
writeln!(
w,
"{INDENT}coverage mcdc decision {{ conditions_num: {conditions_num:?}, end: {end_markers:?} }} => {span:?}"
"{INDENT}coverage mcdc decision {{ conditions_num: {conditions_num:?}, end: {end_markers:?}, depth: {decision_depth:?} }} => {span:?}"
)?;
}

View File

@ -101,10 +101,14 @@ fn fetch_mcdc_condition_info(
tcx: TyCtxt<'_>,
true_marker: BlockMarkerId,
false_marker: BlockMarkerId,
) -> Option<ConditionInfo> {
) -> Option<(u16, ConditionInfo)> {
let mcdc_state = self.mcdc_state.as_mut()?;
let decision_depth = mcdc_state.decision_depth();
let (mut condition_info, decision_result) =
mcdc_state.take_condition(true_marker, false_marker);
// 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) = decision_result {
match decision.conditions_num {
0 => {
@ -131,7 +135,7 @@ fn fetch_mcdc_condition_info(
}
}
}
condition_info
condition_info.map(|cond_info| (decision_depth, cond_info))
}
fn add_two_way_branch<'tcx>(
@ -199,17 +203,32 @@ pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged.
const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6;
struct MCDCState {
#[derive(Default)]
struct MCDCDecisionCtx {
/// To construct condition evaluation tree.
decision_stack: VecDeque<ConditionInfo>,
processing_decision: Option<MCDCDecisionSpan>,
}
struct MCDCState {
decision_ctx_stack: Vec<MCDCDecisionCtx>,
}
impl MCDCState {
fn new_if_enabled(tcx: TyCtxt<'_>) -> Option<Self> {
tcx.sess
.instrument_coverage_mcdc()
.then(|| Self { decision_stack: VecDeque::new(), processing_decision: None })
.then(|| Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] })
}
/// 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 {
u16::try_from(
self.decision_ctx_stack.len().checked_sub(1).expect("Unexpected empty decision stack"),
)
.expect("decision depth did not fit in u16, this is likely to be an instrumentation error")
}
// At first we assign ConditionIds for each sub expression.
@ -253,19 +272,23 @@ fn new_if_enabled(tcx: TyCtxt<'_>) -> Option<Self> {
// - 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 = match self.processing_decision.as_mut() {
let decision_depth = self.decision_depth();
let decision_ctx =
self.decision_ctx_stack.last_mut().expect("Unexpected empty decision_ctx_stack");
let decision = match decision_ctx.processing_decision.as_mut() {
Some(decision) => {
decision.span = decision.span.to(span);
decision
}
None => self.processing_decision.insert(MCDCDecisionSpan {
None => decision_ctx.processing_decision.insert(MCDCDecisionSpan {
span,
conditions_num: 0,
end_markers: vec![],
decision_depth,
}),
};
let parent_condition = self.decision_stack.pop_back().unwrap_or_default();
let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_default();
let lhs_id = if parent_condition.condition_id == ConditionId::NONE {
decision.conditions_num += 1;
ConditionId::from(decision.conditions_num)
@ -305,8 +328,8 @@ fn record_conditions(&mut self, op: LogicalOp, span: Span) {
}
};
// We visit expressions tree in pre-order, so place the left-hand side on the top.
self.decision_stack.push_back(rhs);
self.decision_stack.push_back(lhs);
decision_ctx.decision_stack.push_back(rhs);
decision_ctx.decision_stack.push_back(lhs);
}
fn take_condition(
@ -314,10 +337,12 @@ fn take_condition(
true_marker: BlockMarkerId,
false_marker: BlockMarkerId,
) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) {
let Some(condition_info) = self.decision_stack.pop_back() else {
let decision_ctx =
self.decision_ctx_stack.last_mut().expect("Unexpected empty decision_ctx_stack");
let Some(condition_info) = decision_ctx.decision_stack.pop_back() else {
return (None, None);
};
let Some(decision) = self.processing_decision.as_mut() else {
let Some(decision) = decision_ctx.processing_decision.as_mut() else {
bug!("Processing decision should have been created before any conditions are taken");
};
if condition_info.true_next_id == ConditionId::NONE {
@ -327,8 +352,8 @@ fn take_condition(
decision.end_markers.push(false_marker);
}
if self.decision_stack.is_empty() {
(Some(condition_info), self.processing_decision.take())
if decision_ctx.decision_stack.is_empty() {
(Some(condition_info), decision_ctx.processing_decision.take())
} else {
(Some(condition_info), None)
}
@ -364,13 +389,17 @@ pub(crate) fn visit_coverage_branch_condition(
|block| branch_info.inject_block_marker(&mut self.cfg, source_info, block);
let true_marker = inject_block_marker(then_block);
let false_marker = inject_block_marker(else_block);
let condition_info =
branch_info.fetch_mcdc_condition_info(self.tcx, true_marker, false_marker);
let (decision_depth, condition_info) = branch_info
.fetch_mcdc_condition_info(self.tcx, true_marker, false_marker)
.map_or((0, None), |(decision_depth, condition_info)| {
(decision_depth, Some(condition_info))
});
branch_info.mcdc_branch_spans.push(MCDCBranchSpan {
span: source_info.span,
condition_info,
true_marker,
false_marker,
decision_depth,
});
return;
}
@ -385,4 +414,20 @@ pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp,
mcdc_state.record_conditions(logical_op, span);
}
}
pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) {
if let Some(branch_info) = self.coverage_branch_info.as_mut()
&& let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
{
mcdc_state.decision_ctx_stack.push(MCDCDecisionCtx::default());
};
}
pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) {
if let Some(branch_info) = self.coverage_branch_info.as_mut()
&& let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
{
mcdc_state.decision_ctx_stack.pop().expect("Unexpected empty decision stack");
};
}
}

View File

@ -148,8 +148,14 @@ fn then_else_break_inner(
let mut block = block;
let temp_scope = args.temp_scope_override.unwrap_or_else(|| this.local_scope());
let mutability = Mutability::Mut;
// 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, Some(temp_scope), expr_id, mutability));
this.mcdc_decrement_depth_if_enabled();
let operand = Operand::Move(Place::from(place));
let then_block = this.cfg.start_new_block();

View File

@ -102,12 +102,23 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir:
inject_mcdc_statements(mir_body, &basic_coverage_blocks, &coverage_spans);
let mcdc_max_decision_depth = coverage_spans
.mappings
.iter()
.filter_map(|bcb_mapping| match bcb_mapping.kind {
BcbMappingKind::MCDCDecision { decision_depth, .. } => Some(decision_depth),
_ => None,
})
.max()
.unwrap_or(0);
mir_body.function_coverage_info = Some(Box::new(FunctionCoverageInfo {
function_source_hash: hir_info.function_source_hash,
num_counters: coverage_counters.num_counters(),
mcdc_bitmap_bytes: coverage_spans.test_vector_bitmap_bytes(),
expressions: coverage_counters.into_expressions(),
mappings,
mcdc_max_decision_depth,
}));
}
@ -145,16 +156,17 @@ fn create_mappings<'tcx>(
|BcbMapping { kind: bcb_mapping_kind, span }| {
let kind = match *bcb_mapping_kind {
BcbMappingKind::Code(bcb) => MappingKind::Code(term_for_bcb(bcb)),
BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info: None } => {
MappingKind::Branch {
true_term: term_for_bcb(true_bcb),
false_term: term_for_bcb(false_bcb),
}
}
BcbMappingKind::MCDCBranch {
true_bcb, false_bcb, condition_info: None, ..
} => MappingKind::Branch {
true_term: term_for_bcb(true_bcb),
false_term: term_for_bcb(false_bcb),
},
BcbMappingKind::MCDCBranch {
true_bcb,
false_bcb,
condition_info: Some(mcdc_params),
..
} => MappingKind::MCDCBranch {
true_term: term_for_bcb(true_bcb),
false_term: term_for_bcb(false_bcb),
@ -246,24 +258,28 @@ fn inject_mcdc_statements<'tcx>(
}
// Inject test vector update first because `inject_statement` always insert new statement at head.
for (end_bcbs, bitmap_idx) in
for (end_bcbs, bitmap_idx, decision_depth) in
coverage_spans.mappings.iter().filter_map(|mapping| match &mapping.kind {
BcbMappingKind::MCDCDecision { end_bcbs, bitmap_idx, .. } => {
Some((end_bcbs, *bitmap_idx))
BcbMappingKind::MCDCDecision { end_bcbs, bitmap_idx, decision_depth, .. } => {
Some((end_bcbs, *bitmap_idx, *decision_depth))
}
_ => None,
})
{
for end in end_bcbs {
let end_bb = basic_coverage_blocks[*end].leader_bb();
inject_statement(mir_body, CoverageKind::TestVectorBitmapUpdate { bitmap_idx }, end_bb);
inject_statement(
mir_body,
CoverageKind::TestVectorBitmapUpdate { bitmap_idx, decision_depth },
end_bb,
);
}
}
for (true_bcb, false_bcb, condition_id) in
for (true_bcb, false_bcb, condition_id, decision_depth) in
coverage_spans.mappings.iter().filter_map(|mapping| match mapping.kind {
BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info } => {
Some((true_bcb, false_bcb, condition_info?.condition_id))
BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info, decision_depth } => {
Some((true_bcb, false_bcb, condition_info?.condition_id, decision_depth))
}
_ => None,
})
@ -271,13 +287,13 @@ fn inject_mcdc_statements<'tcx>(
let true_bb = basic_coverage_blocks[true_bcb].leader_bb();
inject_statement(
mir_body,
CoverageKind::CondBitmapUpdate { id: condition_id, value: true },
CoverageKind::CondBitmapUpdate { id: condition_id, value: true, decision_depth },
true_bb,
);
let false_bb = basic_coverage_blocks[false_bcb].leader_bb();
inject_statement(
mir_body,
CoverageKind::CondBitmapUpdate { id: condition_id, value: false },
CoverageKind::CondBitmapUpdate { id: condition_id, value: false, decision_depth },
false_bb,
);
}

View File

@ -26,9 +26,15 @@ pub(super) enum BcbMappingKind {
/// If `None`, this actually represents a normal branch mapping inserted
/// for code that was too complex for MC/DC.
condition_info: Option<ConditionInfo>,
decision_depth: u16,
},
/// Associates a mcdc decision with its join BCB.
MCDCDecision { end_bcbs: BTreeSet<BasicCoverageBlock>, bitmap_idx: u32, conditions_num: u16 },
MCDCDecision {
end_bcbs: BTreeSet<BasicCoverageBlock>,
bitmap_idx: u32,
conditions_num: u16,
decision_depth: u16,
},
}
#[derive(Debug)]

View File

@ -453,15 +453,25 @@ pub(super) fn extract_mcdc_mappings(
Some((span, true_bcb, false_bcb))
};
let mcdc_branch_filter_map =
|&MCDCBranchSpan { span: raw_span, true_marker, false_marker, condition_info }| {
check_branch_bcb(raw_span, true_marker, false_marker).map(
|(span, true_bcb, false_bcb)| BcbMapping {
kind: BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info },
span,
let mcdc_branch_filter_map = |&MCDCBranchSpan {
span: raw_span,
true_marker,
false_marker,
condition_info,
decision_depth,
}| {
check_branch_bcb(raw_span, true_marker, false_marker).map(|(span, true_bcb, false_bcb)| {
BcbMapping {
kind: BcbMappingKind::MCDCBranch {
true_bcb,
false_bcb,
condition_info,
decision_depth,
},
)
};
span,
}
})
};
let mut next_bitmap_idx = 0;
@ -482,6 +492,7 @@ pub(super) fn extract_mcdc_mappings(
end_bcbs,
bitmap_idx,
conditions_num: decision.conditions_num as u16,
decision_depth: decision.decision_depth,
},
span,
})

View File

@ -0,0 +1,201 @@
Function name: mcdc_nested_if::doubly_nested_if_in_condition
Raw bytes (168): 0x[01, 01, 0e, 01, 05, 05, 11, 05, 11, 26, 19, 05, 11, 19, 1d, 19, 1d, 1d, 22, 26, 19, 05, 11, 11, 15, 09, 02, 0d, 37, 09, 02, 14, 01, 0f, 01, 01, 09, 28, 02, 02, 01, 08, 00, 4e, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 4e, 05, 00, 10, 00, 11, 28, 01, 02, 00, 10, 00, 36, 30, 11, 26, 01, 00, 02, 00, 10, 00, 11, 30, 15, 21, 02, 00, 00, 00, 15, 00, 36, 26, 00, 18, 00, 19, 28, 00, 02, 00, 18, 00, 1e, 30, 19, 22, 01, 02, 00, 00, 18, 00, 19, 19, 00, 1d, 00, 1e, 30, 1a, 1d, 02, 00, 00, 00, 1d, 00, 1e, 1a, 00, 21, 00, 25, 1f, 00, 2f, 00, 34, 2b, 00, 39, 00, 3e, 21, 00, 48, 00, 4c, 0d, 00, 4f, 02, 06, 37, 02, 0c, 02, 06, 33, 03, 01, 00, 02]
Number of files: 1
- file 0 => global file 1
Number of expressions: 14
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
- expression 1 operands: lhs = Counter(1), rhs = Counter(4)
- expression 2 operands: lhs = Counter(1), rhs = Counter(4)
- expression 3 operands: lhs = Expression(9, Sub), rhs = Counter(6)
- expression 4 operands: lhs = Counter(1), rhs = Counter(4)
- expression 5 operands: lhs = Counter(6), rhs = Counter(7)
- expression 6 operands: lhs = Counter(6), rhs = Counter(7)
- expression 7 operands: lhs = Counter(7), rhs = Expression(8, Sub)
- expression 8 operands: lhs = Expression(9, Sub), rhs = Counter(6)
- expression 9 operands: lhs = Counter(1), rhs = Counter(4)
- expression 10 operands: lhs = Counter(4), rhs = Counter(5)
- expression 11 operands: lhs = Counter(2), rhs = Expression(0, Sub)
- expression 12 operands: lhs = Counter(3), rhs = Expression(13, Add)
- expression 13 operands: lhs = Counter(2), rhs = Expression(0, Sub)
Number of file 0 mappings: 20
- Code(Counter(0)) at (prev + 15, 1) to (start + 1, 9)
- MCDCDecision { bitmap_idx: 2, conditions_num: 2 } at (prev + 1, 8) to (start + 0, 78)
- 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: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 13) to (start + 0, 78)
true = c3
false = c2
- Code(Counter(1)) at (prev + 0, 16) to (start + 0, 17)
- MCDCDecision { bitmap_idx: 1, conditions_num: 2 } at (prev + 0, 16) to (start + 0, 54)
- MCDCBranch { true: Counter(4), false: Expression(9, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 16) to (start + 0, 17)
true = c4
false = (c1 - c4)
- MCDCBranch { true: Counter(5), false: Counter(8), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 54)
true = c5
false = c8
- Code(Expression(9, Sub)) at (prev + 0, 24) to (start + 0, 25)
= (c1 - c4)
- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 0, 24) to (start + 0, 30)
- MCDCBranch { true: Counter(6), false: Expression(8, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 24) to (start + 0, 25)
true = c6
false = ((c1 - c4) - c6)
- Code(Counter(6)) at (prev + 0, 29) to (start + 0, 30)
- MCDCBranch { true: Expression(6, Sub), false: Counter(7), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 29) to (start + 0, 30)
true = (c6 - c7)
false = c7
- Code(Expression(6, Sub)) at (prev + 0, 33) to (start + 0, 37)
= (c6 - c7)
- Code(Expression(7, Add)) at (prev + 0, 47) to (start + 0, 52)
= (c7 + ((c1 - c4) - c6))
- Code(Expression(10, Add)) at (prev + 0, 57) to (start + 0, 62)
= (c4 + c5)
- Code(Counter(8)) at (prev + 0, 72) to (start + 0, 76)
- Code(Counter(3)) at (prev + 0, 79) to (start + 2, 6)
- Code(Expression(13, Add)) at (prev + 2, 12) to (start + 2, 6)
= (c2 + (c0 - c1))
- Code(Expression(12, Add)) at (prev + 3, 1) to (start + 0, 2)
= (c3 + (c2 + (c0 - c1)))
Function name: mcdc_nested_if::nested_if_in_condition
Raw bytes (120): 0x[01, 01, 0b, 01, 05, 05, 11, 05, 11, 1e, 15, 05, 11, 11, 15, 1e, 15, 05, 11, 09, 02, 0d, 2b, 09, 02, 0e, 01, 07, 01, 01, 09, 28, 01, 02, 01, 08, 00, 2e, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 2e, 05, 00, 10, 00, 11, 28, 00, 02, 00, 10, 00, 16, 30, 11, 1e, 01, 00, 02, 00, 10, 00, 11, 1e, 00, 15, 00, 16, 30, 15, 1a, 02, 00, 00, 00, 15, 00, 16, 17, 00, 19, 00, 1d, 1a, 00, 27, 00, 2c, 0d, 00, 2f, 02, 06, 2b, 02, 0c, 02, 06, 27, 03, 01, 00, 02]
Number of files: 1
- file 0 => global file 1
Number of expressions: 11
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
- expression 1 operands: lhs = Counter(1), rhs = Counter(4)
- expression 2 operands: lhs = Counter(1), rhs = Counter(4)
- expression 3 operands: lhs = Expression(7, Sub), rhs = Counter(5)
- expression 4 operands: lhs = Counter(1), rhs = Counter(4)
- expression 5 operands: lhs = Counter(4), rhs = Counter(5)
- expression 6 operands: lhs = Expression(7, Sub), rhs = Counter(5)
- expression 7 operands: lhs = Counter(1), rhs = Counter(4)
- expression 8 operands: lhs = Counter(2), rhs = Expression(0, Sub)
- expression 9 operands: lhs = Counter(3), rhs = Expression(10, Add)
- expression 10 operands: lhs = Counter(2), rhs = Expression(0, Sub)
Number of file 0 mappings: 14
- Code(Counter(0)) at (prev + 7, 1) to (start + 1, 9)
- MCDCDecision { bitmap_idx: 1, conditions_num: 2 } at (prev + 1, 8) to (start + 0, 46)
- 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: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 13) to (start + 0, 46)
true = c3
false = c2
- Code(Counter(1)) at (prev + 0, 16) to (start + 0, 17)
- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 0, 16) to (start + 0, 22)
- MCDCBranch { true: Counter(4), false: Expression(7, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 16) to (start + 0, 17)
true = c4
false = (c1 - c4)
- Code(Expression(7, Sub)) at (prev + 0, 21) to (start + 0, 22)
= (c1 - c4)
- MCDCBranch { true: Counter(5), false: Expression(6, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 22)
true = c5
false = ((c1 - c4) - c5)
- Code(Expression(5, Add)) at (prev + 0, 25) to (start + 0, 29)
= (c4 + c5)
- Code(Expression(6, Sub)) at (prev + 0, 39) to (start + 0, 44)
= ((c1 - c4) - c5)
- Code(Counter(3)) at (prev + 0, 47) to (start + 2, 6)
- Code(Expression(10, Add)) at (prev + 2, 12) to (start + 2, 6)
= (c2 + (c0 - c1))
- Code(Expression(9, Add)) at (prev + 3, 1) to (start + 0, 2)
= (c3 + (c2 + (c0 - c1)))
Function name: mcdc_nested_if::nested_in_then_block_in_condition
Raw bytes (176): 0x[01, 01, 12, 01, 05, 05, 11, 05, 11, 3a, 15, 05, 11, 11, 15, 33, 19, 11, 15, 19, 1d, 19, 1d, 1d, 2e, 33, 19, 11, 15, 3a, 15, 05, 11, 09, 02, 0d, 47, 09, 02, 14, 01, 22, 01, 01, 09, 28, 02, 02, 01, 08, 00, 4b, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 4b, 05, 00, 10, 00, 11, 28, 00, 02, 00, 10, 00, 16, 30, 11, 3a, 01, 00, 02, 00, 10, 00, 11, 3a, 00, 15, 00, 16, 30, 15, 36, 02, 00, 00, 00, 15, 00, 16, 33, 00, 1c, 00, 1d, 28, 01, 02, 00, 1c, 00, 22, 30, 19, 2e, 01, 02, 00, 00, 1c, 00, 1d, 19, 00, 21, 00, 22, 30, 26, 1d, 02, 00, 00, 00, 21, 00, 22, 26, 00, 25, 00, 29, 2b, 00, 33, 00, 38, 36, 00, 44, 00, 49, 0d, 00, 4c, 02, 06, 47, 02, 0c, 02, 06, 43, 03, 01, 00, 02]
Number of files: 1
- file 0 => global file 1
Number of expressions: 18
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
- expression 1 operands: lhs = Counter(1), rhs = Counter(4)
- expression 2 operands: lhs = Counter(1), rhs = Counter(4)
- expression 3 operands: lhs = Expression(14, Sub), rhs = Counter(5)
- expression 4 operands: lhs = Counter(1), rhs = Counter(4)
- expression 5 operands: lhs = Counter(4), rhs = Counter(5)
- expression 6 operands: lhs = Expression(12, Add), rhs = Counter(6)
- expression 7 operands: lhs = Counter(4), rhs = Counter(5)
- expression 8 operands: lhs = Counter(6), rhs = Counter(7)
- expression 9 operands: lhs = Counter(6), rhs = Counter(7)
- expression 10 operands: lhs = Counter(7), rhs = Expression(11, Sub)
- expression 11 operands: lhs = Expression(12, Add), rhs = Counter(6)
- expression 12 operands: lhs = Counter(4), rhs = Counter(5)
- expression 13 operands: lhs = Expression(14, Sub), rhs = Counter(5)
- expression 14 operands: lhs = Counter(1), rhs = Counter(4)
- expression 15 operands: lhs = Counter(2), rhs = Expression(0, Sub)
- expression 16 operands: lhs = Counter(3), rhs = Expression(17, Add)
- expression 17 operands: lhs = Counter(2), rhs = Expression(0, Sub)
Number of file 0 mappings: 20
- Code(Counter(0)) at (prev + 34, 1) to (start + 1, 9)
- MCDCDecision { bitmap_idx: 2, conditions_num: 2 } at (prev + 1, 8) to (start + 0, 75)
- 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: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 13) to (start + 0, 75)
true = c3
false = c2
- Code(Counter(1)) at (prev + 0, 16) to (start + 0, 17)
- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 0, 16) to (start + 0, 22)
- MCDCBranch { true: Counter(4), false: Expression(14, Sub), condition_id: 1, true_next_id: 0, false_next_id: 2 } at (prev + 0, 16) to (start + 0, 17)
true = c4
false = (c1 - c4)
- Code(Expression(14, Sub)) at (prev + 0, 21) to (start + 0, 22)
= (c1 - c4)
- MCDCBranch { true: Counter(5), false: Expression(13, Sub), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 21) to (start + 0, 22)
true = c5
false = ((c1 - c4) - c5)
- Code(Expression(12, Add)) at (prev + 0, 28) to (start + 0, 29)
= (c4 + c5)
- MCDCDecision { bitmap_idx: 1, conditions_num: 2 } at (prev + 0, 28) to (start + 0, 34)
- MCDCBranch { true: Counter(6), false: Expression(11, Sub), condition_id: 1, true_next_id: 2, false_next_id: 0 } at (prev + 0, 28) to (start + 0, 29)
true = c6
false = ((c4 + c5) - c6)
- Code(Counter(6)) at (prev + 0, 33) to (start + 0, 34)
- MCDCBranch { true: Expression(9, Sub), false: Counter(7), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 33) to (start + 0, 34)
true = (c6 - c7)
false = c7
- Code(Expression(9, Sub)) at (prev + 0, 37) to (start + 0, 41)
= (c6 - c7)
- Code(Expression(10, Add)) at (prev + 0, 51) to (start + 0, 56)
= (c7 + ((c4 + c5) - c6))
- Code(Expression(13, Sub)) at (prev + 0, 68) to (start + 0, 73)
= ((c1 - c4) - c5)
- Code(Counter(3)) at (prev + 0, 76) to (start + 2, 6)
- Code(Expression(17, Add)) at (prev + 2, 12) to (start + 2, 6)
= (c2 + (c0 - c1))
- Code(Expression(16, Add)) at (prev + 3, 1) to (start + 0, 2)
= (c3 + (c2 + (c0 - c1)))
Function name: mcdc_nested_if::nested_single_condition_decision
Raw bytes (85): 0x[01, 01, 06, 01, 05, 05, 11, 05, 11, 09, 02, 0d, 17, 09, 02, 0b, 01, 17, 01, 04, 09, 28, 00, 02, 04, 08, 00, 29, 30, 05, 02, 01, 02, 00, 00, 08, 00, 09, 30, 0d, 09, 02, 00, 00, 00, 0d, 00, 29, 05, 00, 10, 00, 11, 20, 11, 0a, 00, 10, 00, 11, 11, 00, 14, 00, 19, 0a, 00, 23, 00, 27, 0d, 00, 2a, 02, 06, 17, 02, 0c, 02, 06, 13, 03, 01, 00, 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(1), rhs = Counter(4)
- expression 2 operands: lhs = Counter(1), rhs = Counter(4)
- expression 3 operands: lhs = Counter(2), rhs = Expression(0, Sub)
- expression 4 operands: lhs = Counter(3), rhs = Expression(5, Add)
- expression 5 operands: lhs = Counter(2), rhs = Expression(0, Sub)
Number of file 0 mappings: 11
- Code(Counter(0)) at (prev + 23, 1) to (start + 4, 9)
- MCDCDecision { bitmap_idx: 0, conditions_num: 2 } at (prev + 4, 8) to (start + 0, 41)
- 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: Counter(2), condition_id: 2, true_next_id: 0, false_next_id: 0 } at (prev + 0, 13) to (start + 0, 41)
true = c3
false = c2
- Code(Counter(1)) at (prev + 0, 16) to (start + 0, 17)
- Branch { true: Counter(4), false: Expression(2, Sub) } at (prev + 0, 16) to (start + 0, 17)
true = c4
false = (c1 - c4)
- Code(Counter(4)) at (prev + 0, 20) to (start + 0, 25)
- Code(Expression(2, Sub)) at (prev + 0, 35) to (start + 0, 39)
= (c1 - c4)
- Code(Counter(3)) at (prev + 0, 42) to (start + 2, 6)
- Code(Expression(5, Add)) at (prev + 2, 12) to (start + 2, 6)
= (c2 + (c0 - c1))
- Code(Expression(4, Add)) at (prev + 3, 1) to (start + 0, 2)
= (c3 + (c2 + (c0 - c1)))

View File

@ -0,0 +1,235 @@
LL| |#![feature(coverage_attribute)]
LL| |//@ edition: 2021
LL| |//@ min-llvm-version: 18
LL| |//@ compile-flags: -Zcoverage-options=mcdc
LL| |//@ llvm-cov-flags: --show-mcdc
LL| |
LL| 4|fn nested_if_in_condition(a: bool, b: bool, c: bool) {
LL| 4| if a && if b || c { true } else { false } {
^3 ^2 ^2 ^1
------------------
|---> MC/DC Decision Region (LL:8) to (LL:46)
|
| 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%
|
|---> MC/DC Decision Region (LL:16) to (LL:22)
|
| Number of Conditions: 2
| Condition C1 --> (LL:16)
| Condition C2 --> (LL:21)
|
| Executed MC/DC Test Vectors:
|
| C1, C2 Result
| 1 { F, F = F }
| 2 { T, - = T }
| 3 { F, T = T }
|
| C1-Pair: covered: (1,2)
| C2-Pair: covered: (1,3)
| MC/DC Coverage for Decision: 100.00%
|
------------------
LL| 2| say("yes");
LL| 2| } else {
LL| 2| say("no");
LL| 2| }
LL| 4|}
LL| |
LL| 4|fn doubly_nested_if_in_condition(a: bool, b: bool, c: bool, d: bool) {
LL| 4| if a && if b || if c && d { true } else { false } { false } else { true } {
^3 ^2 ^1 ^1 ^1 ^2 ^1
------------------
|---> MC/DC Decision Region (LL:8) to (LL:78)
|
| 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%
|
|---> MC/DC Decision Region (LL:16) to (LL:54)
|
| Number of Conditions: 2
| Condition C1 --> (LL:16)
| Condition C2 --> (LL:21)
|
| Executed MC/DC Test Vectors:
|
| C1, C2 Result
| 1 { F, F = F }
| 2 { T, - = T }
| 3 { F, T = T }
|
| C1-Pair: covered: (1,2)
| C2-Pair: covered: (1,3)
| MC/DC Coverage for Decision: 100.00%
|
|---> MC/DC Decision Region (LL:24) to (LL:30)
|
| Number of Conditions: 2
| Condition C1 --> (LL:24)
| Condition C2 --> (LL:29)
|
| 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("yes");
LL| 3| } else {
LL| 3| say("no");
LL| 3| }
LL| 4|}
LL| |
LL| 3|fn nested_single_condition_decision(a: bool, b: bool) {
LL| 3| // Decision with only 1 decision should not be instrumented by MCDC because
LL| 3| // branch-coverage is equivalent to MCDC coverage in this case, and we don't
LL| 3| // want to waste bitmap space for this.
LL| 3| if a && if b { false } else { true } {
^2 ^1 ^1
------------------
|---> MC/DC Decision Region (LL:8) to (LL:41)
|
| 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| } else {
LL| 2| say("no");
LL| 2| }
LL| 3|}
LL| |
LL| 7|fn nested_in_then_block_in_condition(a: bool, b: bool, c: bool, d: bool, e: bool) {
LL| 7| if a && if b || c { if d && e { true } else { false } } else { false } {
^6 ^5 ^5 ^2 ^1 ^4 ^1
------------------
|---> MC/DC Decision Region (LL:8) to (LL:75)
|
| 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%
|
|---> MC/DC Decision Region (LL:16) to (LL:22)
|
| Number of Conditions: 2
| Condition C1 --> (LL:16)
| Condition C2 --> (LL:21)
|
| Executed MC/DC Test Vectors:
|
| C1, C2 Result
| 1 { F, F = F }
| 2 { T, - = T }
| 3 { F, T = T }
|
| C1-Pair: covered: (1,2)
| C2-Pair: covered: (1,3)
| MC/DC Coverage for Decision: 100.00%
|
|---> MC/DC Decision Region (LL:28) to (LL:34)
|
| Number of Conditions: 2
| Condition C1 --> (LL:28)
| Condition C2 --> (LL:33)
|
| 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| 6| } else {
LL| 6| say("no");
LL| 6| }
LL| 7|}
LL| |
LL| |#[coverage(off)]
LL| |fn main() {
LL| | nested_if_in_condition(true, false, false);
LL| | nested_if_in_condition(true, true, true);
LL| | nested_if_in_condition(true, false, true);
LL| | nested_if_in_condition(false, true, true);
LL| |
LL| | doubly_nested_if_in_condition(true, false, false, true);
LL| | doubly_nested_if_in_condition(true, true, true, true);
LL| | doubly_nested_if_in_condition(true, false, true, true);
LL| | doubly_nested_if_in_condition(false, true, true, true);
LL| |
LL| | nested_single_condition_decision(true, true);
LL| | nested_single_condition_decision(true, false);
LL| | nested_single_condition_decision(false, false);
LL| |
LL| | nested_in_then_block_in_condition(false, false, false, false, false);
LL| | nested_in_then_block_in_condition(true, false, false, false, false);
LL| | nested_in_then_block_in_condition(true, true, false, false, false);
LL| | nested_in_then_block_in_condition(true, false, true, false, false);
LL| | nested_in_then_block_in_condition(true, false, true, true, false);
LL| | nested_in_then_block_in_condition(true, false, true, false, true);
LL| | nested_in_then_block_in_condition(true, false, true, true, true);
LL| |}
LL| |
LL| |#[coverage(off)]
LL| |fn say(message: &str) {
LL| | core::hint::black_box(message);
LL| |}

View File

@ -0,0 +1,70 @@
#![feature(coverage_attribute)]
//@ edition: 2021
//@ min-llvm-version: 18
//@ compile-flags: -Zcoverage-options=mcdc
//@ llvm-cov-flags: --show-mcdc
fn nested_if_in_condition(a: bool, b: bool, c: bool) {
if a && if b || c { true } else { false } {
say("yes");
} else {
say("no");
}
}
fn doubly_nested_if_in_condition(a: bool, b: bool, c: bool, d: bool) {
if a && if b || if c && d { true } else { false } { false } else { true } {
say("yes");
} else {
say("no");
}
}
fn nested_single_condition_decision(a: bool, b: bool) {
// Decision with only 1 decision should not be instrumented by MCDC because
// branch-coverage is equivalent to MCDC coverage in this case, and we don't
// want to waste bitmap space for this.
if a && if b { false } else { true } {
say("yes");
} else {
say("no");
}
}
fn nested_in_then_block_in_condition(a: bool, b: bool, c: bool, d: bool, e: bool) {
if a && if b || c { if d && e { true } else { false } } else { false } {
say("yes");
} else {
say("no");
}
}
#[coverage(off)]
fn main() {
nested_if_in_condition(true, false, false);
nested_if_in_condition(true, true, true);
nested_if_in_condition(true, false, true);
nested_if_in_condition(false, true, true);
doubly_nested_if_in_condition(true, false, false, true);
doubly_nested_if_in_condition(true, true, true, true);
doubly_nested_if_in_condition(true, false, true, true);
doubly_nested_if_in_condition(false, true, true, true);
nested_single_condition_decision(true, true);
nested_single_condition_decision(true, false);
nested_single_condition_decision(false, false);
nested_in_then_block_in_condition(false, false, false, false, false);
nested_in_then_block_in_condition(true, false, false, false, false);
nested_in_then_block_in_condition(true, true, false, false, false);
nested_in_then_block_in_condition(true, false, true, false, false);
nested_in_then_block_in_condition(true, false, true, true, false);
nested_in_then_block_in_condition(true, false, true, false, true);
nested_in_then_block_in_condition(true, false, true, true, true);
}
#[coverage(off)]
fn say(message: &str) {
core::hint::black_box(message);
}