Rollup merge of #124217 - Zalathar:pre-branch, r=oli-obk

coverage: Prepare for improved branch coverage

When trying to rebase my new branch coverage work (including #124154) on top of the introduction of MC/DC coverage (#123409), I found it a lot harder than anticipated. With the benefit of hindsight, the branch coverage code and MC/DC code have become more interdependent than I'm happy with.

This PR therefore disentangles them a bit, so that it will be easier for both areas of code to evolve independently without interference.

---

This PR also includes a few extra branch coverage tests that I had sitting around from my current branch coverage work. They mostly just demonstrate that certain language constructs listed in #124118 currently don't have branch coverage support.

``@rustbot`` label +A-code-coverage
This commit is contained in:
Guillaume Gomez 2024-04-22 20:25:59 +02:00 committed by GitHub
commit 17c28799f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 993 additions and 94 deletions

View file

@ -314,7 +314,9 @@ fn default() -> Self {
#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub struct MCDCBranchSpan {
pub span: Span,
pub condition_info: ConditionInfo,
/// If `None`, this actually represents a normal branch span inserted for
/// code that was too complex for MC/DC.
pub condition_info: Option<ConditionInfo>,
pub true_marker: BlockMarkerId,
pub false_marker: BlockMarkerId,
}

View file

@ -491,7 +491,7 @@ fn write_coverage_branch_info(
writeln!(
w,
"{INDENT}coverage mcdc branch {{ condition_id: {:?}, true: {true_marker:?}, false: {false_marker:?} }} => {span:?}",
condition_info.condition_id
condition_info.map(|info| info.condition_id)
)?;
}

View file

@ -7,13 +7,13 @@
BlockMarkerId, BranchSpan, ConditionId, ConditionInfo, CoverageKind, MCDCBranchSpan,
MCDCDecisionSpan,
};
use rustc_middle::mir::{self, BasicBlock, UnOp};
use rustc_middle::mir::{self, BasicBlock, SourceInfo, UnOp};
use rustc_middle::thir::{ExprId, ExprKind, LogicalOp, Thir};
use rustc_middle::ty::TyCtxt;
use rustc_span::def_id::LocalDefId;
use rustc_span::Span;
use crate::build::Builder;
use crate::build::{Builder, CFG};
use crate::errors::MCDCExceedsConditionNumLimit;
pub(crate) struct BranchInfoBuilder {
@ -22,6 +22,7 @@ pub(crate) struct BranchInfoBuilder {
num_block_markers: usize,
branch_spans: Vec<BranchSpan>,
mcdc_branch_spans: Vec<MCDCBranchSpan>,
mcdc_decision_spans: Vec<MCDCDecisionSpan>,
mcdc_state: Option<MCDCState>,
@ -95,13 +96,7 @@ fn visit_with_not_info(&mut self, thir: &Thir<'_>, expr_id: ExprId, not_info: No
}
}
fn record_conditions_operation(&mut self, logical_op: LogicalOp, span: Span) {
if let Some(mcdc_state) = self.mcdc_state.as_mut() {
mcdc_state.record_conditions(logical_op, span);
}
}
fn fetch_condition_info(
fn fetch_mcdc_condition_info(
&mut self,
tcx: TyCtxt<'_>,
true_marker: BlockMarkerId,
@ -121,14 +116,9 @@ fn fetch_condition_info(
_ => {
// Do not generate mcdc mappings and statements for decisions with too many conditions.
let rebase_idx = self.mcdc_branch_spans.len() - decision.conditions_num + 1;
let to_normal_branches = self.mcdc_branch_spans.split_off(rebase_idx);
self.branch_spans.extend(to_normal_branches.into_iter().map(
|MCDCBranchSpan { span, true_marker, false_marker, .. }| BranchSpan {
span,
true_marker,
false_marker,
},
));
for branch in &mut self.mcdc_branch_spans[rebase_idx..] {
branch.condition_info = None;
}
// ConditionInfo of this branch shall also be reset.
condition_info = None;
@ -144,12 +134,42 @@ fn fetch_condition_info(
condition_info
}
fn add_two_way_branch<'tcx>(
&mut self,
cfg: &mut CFG<'tcx>,
source_info: SourceInfo,
true_block: BasicBlock,
false_block: BasicBlock,
) {
let true_marker = self.inject_block_marker(cfg, source_info, true_block);
let false_marker = self.inject_block_marker(cfg, source_info, false_block);
self.branch_spans.push(BranchSpan { span: source_info.span, true_marker, false_marker });
}
fn next_block_marker_id(&mut self) -> BlockMarkerId {
let id = BlockMarkerId::from_usize(self.num_block_markers);
self.num_block_markers += 1;
id
}
fn inject_block_marker(
&mut self,
cfg: &mut CFG<'_>,
source_info: SourceInfo,
block: BasicBlock,
) -> BlockMarkerId {
let id = self.next_block_marker_id();
let marker_statement = mir::Statement {
source_info,
kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
};
cfg.push(block, marker_statement);
id
}
pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
let Self {
nots: _,
@ -157,7 +177,7 @@ pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> {
branch_spans,
mcdc_branch_spans,
mcdc_decision_spans,
..
mcdc_state: _,
} = self;
if num_block_markers == 0 {
@ -325,7 +345,7 @@ pub(crate) fn visit_coverage_branch_condition(
mut else_block: BasicBlock,
) {
// Bail out if branch coverage is not enabled for this function.
let Some(branch_info) = self.coverage_branch_info.as_ref() else { return };
let Some(branch_info) = self.coverage_branch_info.as_mut() else { return };
// If this condition expression is nested within one or more `!` expressions,
// replace it with the enclosing `!` collected by `visit_unary_not`.
@ -335,47 +355,34 @@ pub(crate) fn visit_coverage_branch_condition(
std::mem::swap(&mut then_block, &mut else_block);
}
}
let source_info = self.source_info(self.thir[expr_id].span);
// Now that we have `source_info`, we can upgrade to a &mut reference.
let branch_info = self.coverage_branch_info.as_mut().expect("upgrading & to &mut");
let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope };
let mut inject_branch_marker = |block: BasicBlock| {
let id = branch_info.next_block_marker_id();
let marker_statement = mir::Statement {
source_info,
kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }),
};
self.cfg.push(block, marker_statement);
id
};
let true_marker = inject_branch_marker(then_block);
let false_marker = inject_branch_marker(else_block);
if let Some(condition_info) =
branch_info.fetch_condition_info(self.tcx, true_marker, false_marker)
{
// Separate path for handling branches when MC/DC is enabled.
if branch_info.mcdc_state.is_some() {
let mut inject_block_marker =
|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);
branch_info.mcdc_branch_spans.push(MCDCBranchSpan {
span: source_info.span,
condition_info,
true_marker,
false_marker,
});
} else {
branch_info.branch_spans.push(BranchSpan {
span: source_info.span,
true_marker,
false_marker,
});
return;
}
branch_info.add_two_way_branch(&mut self.cfg, source_info, then_block, else_block);
}
pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) {
if let Some(branch_info) = self.coverage_branch_info.as_mut() {
branch_info.record_conditions_operation(logical_op, span);
if let Some(branch_info) = self.coverage_branch_info.as_mut()
&& let Some(mcdc_state) = branch_info.mcdc_state.as_mut()
{
mcdc_state.record_conditions(logical_op, span);
}
}
}

View file

@ -9,7 +9,7 @@
use self::counters::{CounterIncrementSite, CoverageCounters};
use self::graph::{BasicCoverageBlock, CoverageGraph};
use self::spans::{BcbMapping, BcbMappingKind, CoverageSpans};
use self::spans::{BcbBranchPair, BcbMapping, BcbMappingKind, CoverageSpans};
use crate::MirPass;
@ -141,21 +141,25 @@ fn create_mappings<'tcx>(
let mut mappings = Vec::new();
mappings.extend(coverage_spans.all_bcb_mappings().filter_map(
mappings.extend(coverage_spans.mappings.iter().filter_map(
|BcbMapping { kind: bcb_mapping_kind, span }| {
let kind = match *bcb_mapping_kind {
BcbMappingKind::Code(bcb) => MappingKind::Code(term_for_bcb(bcb)),
BcbMappingKind::Branch { true_bcb, false_bcb } => MappingKind::Branch {
true_term: term_for_bcb(true_bcb),
false_term: term_for_bcb(false_bcb),
},
BcbMappingKind::MCDCBranch { true_bcb, false_bcb, condition_info } => {
MappingKind::MCDCBranch {
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),
mcdc_params: condition_info,
}
}
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),
mcdc_params,
},
BcbMappingKind::MCDCDecision { bitmap_idx, conditions_num, .. } => {
MappingKind::MCDCDecision(DecisionInfo { bitmap_idx, conditions_num })
}
@ -165,6 +169,16 @@ fn create_mappings<'tcx>(
},
));
mappings.extend(coverage_spans.branch_pairs.iter().filter_map(
|&BcbBranchPair { span, true_bcb, false_bcb }| {
let true_term = term_for_bcb(true_bcb);
let false_term = term_for_bcb(false_bcb);
let kind = MappingKind::Branch { true_term, false_term };
let code_region = make_code_region(source_map, file_name, span, body_span)?;
Some(Mapping { kind, code_region })
},
));
mappings
}
@ -233,7 +247,7 @@ 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
coverage_spans.all_bcb_mappings().filter_map(|mapping| match &mapping.kind {
coverage_spans.mappings.iter().filter_map(|mapping| match &mapping.kind {
BcbMappingKind::MCDCDecision { end_bcbs, bitmap_idx, .. } => {
Some((end_bcbs, *bitmap_idx))
}
@ -247,9 +261,9 @@ fn inject_mcdc_statements<'tcx>(
}
for (true_bcb, false_bcb, condition_id) in
coverage_spans.all_bcb_mappings().filter_map(|mapping| match mapping.kind {
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))
Some((true_bcb, false_bcb, condition_info?.condition_id))
}
_ => None,
})

View file

@ -15,13 +15,17 @@
pub(super) enum BcbMappingKind {
/// Associates an ordinary executable code span with its corresponding BCB.
Code(BasicCoverageBlock),
/// Associates a branch span with BCBs for its true and false arms.
Branch { true_bcb: BasicCoverageBlock, false_bcb: BasicCoverageBlock },
// Ordinary branch mappings are stored separately, so they don't have a
// variant in this enum.
//
/// Associates a mcdc branch span with condition info besides fields for normal branch.
MCDCBranch {
true_bcb: BasicCoverageBlock,
false_bcb: BasicCoverageBlock,
condition_info: ConditionInfo,
/// If `None`, this actually represents a normal branch mapping inserted
/// for code that was too complex for MC/DC.
condition_info: Option<ConditionInfo>,
},
/// Associates a mcdc decision with its join BCB.
MCDCDecision { end_bcbs: BTreeSet<BasicCoverageBlock>, bitmap_idx: u32, conditions_num: u16 },
@ -33,9 +37,20 @@ pub(super) struct BcbMapping {
pub(super) span: Span,
}
/// This is separate from [`BcbMappingKind`] to help prepare for larger changes
/// that will be needed for improved branch coverage in the future.
/// (See <https://github.com/rust-lang/rust/pull/124217>.)
#[derive(Debug)]
pub(super) struct BcbBranchPair {
pub(super) span: Span,
pub(super) true_bcb: BasicCoverageBlock,
pub(super) false_bcb: BasicCoverageBlock,
}
pub(super) struct CoverageSpans {
bcb_has_mappings: BitSet<BasicCoverageBlock>,
mappings: Vec<BcbMapping>,
pub(super) mappings: Vec<BcbMapping>,
pub(super) branch_pairs: Vec<BcbBranchPair>,
test_vector_bitmap_bytes: u32,
}
@ -44,10 +59,6 @@ pub(super) fn bcb_has_coverage_spans(&self, bcb: BasicCoverageBlock) -> bool {
self.bcb_has_mappings.contains(bcb)
}
pub(super) fn all_bcb_mappings(&self) -> impl Iterator<Item = &BcbMapping> {
self.mappings.iter()
}
pub(super) fn test_vector_bitmap_bytes(&self) -> u32 {
self.test_vector_bitmap_bytes
}
@ -63,6 +74,7 @@ pub(super) fn generate_coverage_spans(
basic_coverage_blocks: &CoverageGraph,
) -> Option<CoverageSpans> {
let mut mappings = vec![];
let mut branch_pairs = vec![];
if hir_info.is_async_fn {
// An async function desugars into a function that returns a future,
@ -84,14 +96,20 @@ pub(super) fn generate_coverage_spans(
BcbMapping { kind: BcbMappingKind::Code(bcb), span }
}));
mappings.extend(from_mir::extract_branch_mappings(
branch_pairs.extend(from_mir::extract_branch_pairs(
mir_body,
hir_info,
basic_coverage_blocks,
));
mappings.extend(from_mir::extract_mcdc_mappings(
mir_body,
hir_info.body_span,
basic_coverage_blocks,
));
}
if mappings.is_empty() {
if mappings.is_empty() && branch_pairs.is_empty() {
return None;
}
@ -104,8 +122,7 @@ pub(super) fn generate_coverage_spans(
for BcbMapping { kind, span: _ } in &mappings {
match *kind {
BcbMappingKind::Code(bcb) => insert(bcb),
BcbMappingKind::Branch { true_bcb, false_bcb }
| BcbMappingKind::MCDCBranch { true_bcb, false_bcb, .. } => {
BcbMappingKind::MCDCBranch { true_bcb, false_bcb, .. } => {
insert(true_bcb);
insert(false_bcb);
}
@ -118,8 +135,12 @@ pub(super) fn generate_coverage_spans(
}
}
}
for &BcbBranchPair { true_bcb, false_bcb, .. } in &branch_pairs {
insert(true_bcb);
insert(false_bcb);
}
Some(CoverageSpans { bcb_has_mappings, mappings, test_vector_bitmap_bytes })
Some(CoverageSpans { bcb_has_mappings, mappings, branch_pairs, test_vector_bitmap_bytes })
}
#[derive(Debug)]

View file

@ -13,7 +13,7 @@
use crate::coverage::graph::{
BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph, START_BCB,
};
use crate::coverage::spans::{BcbMapping, BcbMappingKind};
use crate::coverage::spans::{BcbBranchPair, BcbMapping, BcbMappingKind};
use crate::coverage::ExtractedHirInfo;
/// Traverses the MIR body to produce an initial collection of coverage-relevant
@ -366,15 +366,10 @@ fn new(
}
}
pub(super) fn extract_branch_mappings(
fn resolve_block_markers(
branch_info: &mir::coverage::BranchInfo,
mir_body: &mir::Body<'_>,
body_span: Span,
basic_coverage_blocks: &CoverageGraph,
) -> Vec<BcbMapping> {
let Some(branch_info) = mir_body.coverage_branch_info.as_deref() else {
return vec![];
};
) -> IndexVec<BlockMarkerId, Option<BasicBlock>> {
let mut block_markers = IndexVec::<BlockMarkerId, Option<BasicBlock>>::from_elem_n(
None,
branch_info.num_block_markers,
@ -389,6 +384,58 @@ pub(super) fn extract_branch_mappings(
}
}
block_markers
}
// FIXME: There is currently a lot of redundancy between
// `extract_branch_pairs` and `extract_mcdc_mappings`. This is needed so
// that they can each be modified without interfering with the other, but in
// the long term we should try to bring them together again when branch coverage
// and MC/DC coverage support are more mature.
pub(super) fn extract_branch_pairs(
mir_body: &mir::Body<'_>,
hir_info: &ExtractedHirInfo,
basic_coverage_blocks: &CoverageGraph,
) -> Vec<BcbBranchPair> {
let Some(branch_info) = mir_body.coverage_branch_info.as_deref() else { return vec![] };
let block_markers = resolve_block_markers(branch_info, mir_body);
branch_info
.branch_spans
.iter()
.filter_map(|&BranchSpan { span: raw_span, true_marker, false_marker }| {
// 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_with_visible_macro(raw_span, hir_info.body_span)?;
let bcb_from_marker =
|marker: BlockMarkerId| basic_coverage_blocks.bcb_from_bb(block_markers[marker]?);
let true_bcb = bcb_from_marker(true_marker)?;
let false_bcb = bcb_from_marker(false_marker)?;
Some(BcbBranchPair { span, true_bcb, false_bcb })
})
.collect::<Vec<_>>()
}
pub(super) fn extract_mcdc_mappings(
mir_body: &mir::Body<'_>,
body_span: Span,
basic_coverage_blocks: &CoverageGraph,
) -> Vec<BcbMapping> {
let Some(branch_info) = mir_body.coverage_branch_info.as_deref() else {
return vec![];
};
let block_markers = resolve_block_markers(branch_info, mir_body);
let bcb_from_marker =
|marker: BlockMarkerId| basic_coverage_blocks.bcb_from_bb(block_markers[marker]?);
@ -406,12 +453,6 @@ pub(super) fn extract_branch_mappings(
Some((span, true_bcb, false_bcb))
};
let branch_filter_map = |&BranchSpan { span: raw_span, true_marker, false_marker }| {
check_branch_bcb(raw_span, true_marker, false_marker).map(|(span, true_bcb, false_bcb)| {
BcbMapping { kind: BcbMappingKind::Branch { true_bcb, false_bcb }, span }
})
};
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(
@ -446,10 +487,7 @@ pub(super) fn extract_branch_mappings(
})
};
branch_info
.branch_spans
.iter()
.filter_map(branch_filter_map)
std::iter::empty()
.chain(branch_info.mcdc_branch_spans.iter().filter_map(mcdc_branch_filter_map))
.chain(branch_info.mcdc_decision_spans.iter().filter_map(decision_filter_map))
.collect::<Vec<_>>()

View file

@ -0,0 +1,41 @@
Function name: if_let::if_let
Raw bytes (38): 0x[01, 01, 02, 05, 09, 09, 02, 06, 01, 0c, 01, 01, 10, 02, 03, 11, 00, 12, 05, 00, 16, 00, 1b, 02, 00, 1c, 02, 06, 09, 02, 0c, 02, 06, 07, 03, 05, 01, 02]
Number of files: 1
- file 0 => global file 1
Number of expressions: 2
- expression 0 operands: lhs = Counter(1), rhs = Counter(2)
- expression 1 operands: lhs = Counter(2), rhs = Expression(0, Sub)
Number of file 0 mappings: 6
- Code(Counter(0)) at (prev + 12, 1) to (start + 1, 16)
- Code(Expression(0, Sub)) at (prev + 3, 17) to (start + 0, 18)
= (c1 - c2)
- Code(Counter(1)) at (prev + 0, 22) to (start + 0, 27)
- Code(Expression(0, Sub)) at (prev + 0, 28) to (start + 2, 6)
= (c1 - c2)
- Code(Counter(2)) at (prev + 2, 12) to (start + 2, 6)
- Code(Expression(1, Add)) at (prev + 3, 5) to (start + 1, 2)
= (c2 + (c1 - c2))
Function name: if_let::if_let_chain
Raw bytes (52): 0x[01, 01, 04, 01, 05, 05, 09, 0f, 0d, 05, 09, 08, 01, 17, 01, 00, 33, 02, 01, 11, 00, 12, 01, 00, 16, 00, 17, 0d, 01, 15, 00, 16, 02, 00, 1a, 00, 1b, 0d, 01, 05, 03, 06, 0f, 03, 0c, 02, 06, 0b, 03, 05, 01, 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(1), rhs = Counter(2)
- expression 2 operands: lhs = Expression(3, Add), rhs = Counter(3)
- expression 3 operands: lhs = Counter(1), rhs = Counter(2)
Number of file 0 mappings: 8
- Code(Counter(0)) at (prev + 23, 1) to (start + 0, 51)
- Code(Expression(0, Sub)) at (prev + 1, 17) to (start + 0, 18)
= (c0 - c1)
- Code(Counter(0)) at (prev + 0, 22) to (start + 0, 23)
- Code(Counter(3)) at (prev + 1, 21) to (start + 0, 22)
- Code(Expression(0, Sub)) at (prev + 0, 26) to (start + 0, 27)
= (c0 - c1)
- Code(Counter(3)) at (prev + 1, 5) to (start + 3, 6)
- Code(Expression(3, Add)) at (prev + 3, 12) to (start + 2, 6)
= (c1 + c2)
- Code(Expression(2, Add)) at (prev + 3, 5) to (start + 1, 2)
= ((c1 + c2) + c3)

View file

@ -0,0 +1,62 @@
LL| |#![feature(coverage_attribute, let_chains)]
LL| |//@ edition: 2021
LL| |//@ compile-flags: -Zcoverage-options=branch
LL| |//@ llvm-cov-flags: --show-branches=count
LL| |
LL| |macro_rules! no_merge {
LL| | () => {
LL| | for _ in 0..1 {}
LL| | };
LL| |}
LL| |
LL| 3|fn if_let(input: Option<&str>) {
LL| 3| no_merge!();
LL| |
LL| 3| if let Some(x) = input {
^2
LL| 2| say(x);
LL| 2| } else {
LL| 1| say("none");
LL| 1| }
LL| 3| say("done");
LL| 3|}
LL| |
LL| 15|fn if_let_chain(a: Option<&str>, b: Option<&str>) {
LL| 15| if let Some(x) = a
^12
LL| 12| && let Some(y) = b
^8
LL| 8| {
LL| 8| say(x);
LL| 8| say(y);
LL| 8| } else {
LL| 7| say("not both");
LL| 7| }
LL| 15| say("done");
LL| 15|}
LL| |
LL| |#[coverage(off)]
LL| |fn say(message: &str) {
LL| | core::hint::black_box(message);
LL| |}
LL| |
LL| |#[coverage(off)]
LL| |fn main() {
LL| | if_let(Some("x"));
LL| | if_let(Some("x"));
LL| | if_let(None);
LL| |
LL| | for _ in 0..8 {
LL| | if_let_chain(Some("a"), Some("b"));
LL| | }
LL| | for _ in 0..4 {
LL| | if_let_chain(Some("a"), None);
LL| | }
LL| | for _ in 0..2 {
LL| | if_let_chain(None, Some("b"));
LL| | }
LL| | if_let_chain(None, None);
LL| |}
LL| |
LL| |// FIXME(#124118) Actually instrument if-let and let-chains for branch coverage.

View file

@ -0,0 +1,58 @@
#![feature(coverage_attribute, let_chains)]
//@ edition: 2021
//@ compile-flags: -Zcoverage-options=branch
//@ llvm-cov-flags: --show-branches=count
macro_rules! no_merge {
() => {
for _ in 0..1 {}
};
}
fn if_let(input: Option<&str>) {
no_merge!();
if let Some(x) = input {
say(x);
} else {
say("none");
}
say("done");
}
fn if_let_chain(a: Option<&str>, b: Option<&str>) {
if let Some(x) = a
&& let Some(y) = b
{
say(x);
say(y);
} else {
say("not both");
}
say("done");
}
#[coverage(off)]
fn say(message: &str) {
core::hint::black_box(message);
}
#[coverage(off)]
fn main() {
if_let(Some("x"));
if_let(Some("x"));
if_let(None);
for _ in 0..8 {
if_let_chain(Some("a"), Some("b"));
}
for _ in 0..4 {
if_let_chain(Some("a"), None);
}
for _ in 0..2 {
if_let_chain(None, Some("b"));
}
if_let_chain(None, None);
}
// FIXME(#124118) Actually instrument if-let and let-chains for branch coverage.

View file

@ -0,0 +1,18 @@
Function name: let_else::let_else
Raw bytes (38): 0x[01, 01, 02, 05, 09, 09, 02, 06, 01, 0c, 01, 01, 10, 02, 03, 0e, 00, 0f, 05, 00, 13, 00, 18, 09, 01, 09, 01, 0f, 02, 04, 05, 00, 0b, 07, 01, 01, 00, 02]
Number of files: 1
- file 0 => global file 1
Number of expressions: 2
- expression 0 operands: lhs = Counter(1), rhs = Counter(2)
- expression 1 operands: lhs = Counter(2), rhs = Expression(0, Sub)
Number of file 0 mappings: 6
- Code(Counter(0)) at (prev + 12, 1) to (start + 1, 16)
- Code(Expression(0, Sub)) at (prev + 3, 14) to (start + 0, 15)
= (c1 - c2)
- Code(Counter(1)) at (prev + 0, 19) to (start + 0, 24)
- Code(Counter(2)) at (prev + 1, 9) to (start + 1, 15)
- Code(Expression(0, Sub)) at (prev + 4, 5) to (start + 0, 11)
= (c1 - c2)
- Code(Expression(1, Add)) at (prev + 1, 1) to (start + 0, 2)
= (c2 + (c1 - c2))

View file

@ -0,0 +1,37 @@
LL| |#![feature(coverage_attribute)]
LL| |//@ edition: 2021
LL| |//@ compile-flags: -Zcoverage-options=branch
LL| |//@ llvm-cov-flags: --show-branches=count
LL| |
LL| |macro_rules! no_merge {
LL| | () => {
LL| | for _ in 0..1 {}
LL| | };
LL| |}
LL| |
LL| 3|fn let_else(value: Option<&str>) {
LL| 3| no_merge!();
LL| |
LL| 3| let Some(x) = value else {
^2
LL| 1| say("none");
LL| 1| return;
LL| | };
LL| |
LL| 2| say(x);
LL| 3|}
LL| |
LL| |#[coverage(off)]
LL| |fn say(message: &str) {
LL| | core::hint::black_box(message);
LL| |}
LL| |
LL| |#[coverage(off)]
LL| |fn main() {
LL| | let_else(Some("x"));
LL| | let_else(Some("x"));
LL| | let_else(None);
LL| |}
LL| |
LL| |// FIXME(#124118) Actually instrument let-else for branch coverage.

View file

@ -0,0 +1,35 @@
#![feature(coverage_attribute)]
//@ edition: 2021
//@ compile-flags: -Zcoverage-options=branch
//@ llvm-cov-flags: --show-branches=count
macro_rules! no_merge {
() => {
for _ in 0..1 {}
};
}
fn let_else(value: Option<&str>) {
no_merge!();
let Some(x) = value else {
say("none");
return;
};
say(x);
}
#[coverage(off)]
fn say(message: &str) {
core::hint::black_box(message);
}
#[coverage(off)]
fn main() {
let_else(Some("x"));
let_else(Some("x"));
let_else(None);
}
// FIXME(#124118) Actually instrument let-else for branch coverage.

View file

@ -0,0 +1,92 @@
Function name: match_arms::guards
Raw bytes (88): 0x[01, 01, 08, 07, 15, 0b, 11, 0f, 0d, 00, 09, 17, 25, 1b, 21, 1f, 1d, 03, 19, 0c, 01, 30, 01, 01, 10, 29, 03, 0b, 00, 10, 19, 01, 11, 00, 29, 20, 19, 09, 00, 17, 00, 1b, 1d, 01, 11, 00, 29, 20, 1d, 0d, 00, 17, 00, 1b, 21, 01, 11, 00, 29, 20, 21, 11, 00, 17, 00, 1b, 25, 01, 11, 00, 29, 20, 25, 15, 00, 17, 00, 1b, 03, 01, 0e, 00, 18, 13, 03, 05, 01, 02]
Number of files: 1
- file 0 => global file 1
Number of expressions: 8
- expression 0 operands: lhs = Expression(1, Add), rhs = Counter(5)
- expression 1 operands: lhs = Expression(2, Add), rhs = Counter(4)
- expression 2 operands: lhs = Expression(3, Add), rhs = Counter(3)
- expression 3 operands: lhs = Zero, rhs = Counter(2)
- expression 4 operands: lhs = Expression(5, Add), rhs = Counter(9)
- expression 5 operands: lhs = Expression(6, Add), rhs = Counter(8)
- expression 6 operands: lhs = Expression(7, Add), rhs = Counter(7)
- expression 7 operands: lhs = Expression(0, Add), rhs = Counter(6)
Number of file 0 mappings: 12
- Code(Counter(0)) at (prev + 48, 1) to (start + 1, 16)
- Code(Counter(10)) at (prev + 3, 11) to (start + 0, 16)
- Code(Counter(6)) at (prev + 1, 17) to (start + 0, 41)
- Branch { true: Counter(6), false: Counter(2) } at (prev + 0, 23) to (start + 0, 27)
true = c6
false = c2
- Code(Counter(7)) at (prev + 1, 17) to (start + 0, 41)
- Branch { true: Counter(7), false: Counter(3) } at (prev + 0, 23) to (start + 0, 27)
true = c7
false = c3
- Code(Counter(8)) at (prev + 1, 17) to (start + 0, 41)
- Branch { true: Counter(8), false: Counter(4) } at (prev + 0, 23) to (start + 0, 27)
true = c8
false = c4
- Code(Counter(9)) at (prev + 1, 17) to (start + 0, 41)
- Branch { true: Counter(9), false: Counter(5) } at (prev + 0, 23) to (start + 0, 27)
true = c9
false = c5
- Code(Expression(0, Add)) at (prev + 1, 14) to (start + 0, 24)
= ((((Zero + c2) + c3) + c4) + c5)
- Code(Expression(4, Add)) at (prev + 3, 5) to (start + 1, 2)
= ((((((((Zero + c2) + c3) + c4) + c5) + c6) + c7) + c8) + c9)
Function name: match_arms::match_arms
Raw bytes (51): 0x[01, 01, 06, 05, 07, 0b, 11, 09, 0d, 13, 02, 17, 09, 11, 0d, 07, 01, 18, 01, 01, 10, 05, 03, 0b, 00, 10, 11, 01, 11, 00, 21, 0d, 01, 11, 00, 21, 09, 01, 11, 00, 21, 02, 01, 11, 00, 21, 0f, 03, 05, 01, 02]
Number of files: 1
- file 0 => global file 1
Number of expressions: 6
- expression 0 operands: lhs = Counter(1), rhs = Expression(1, Add)
- expression 1 operands: lhs = Expression(2, Add), rhs = Counter(4)
- expression 2 operands: lhs = Counter(2), rhs = Counter(3)
- expression 3 operands: lhs = Expression(4, Add), rhs = Expression(0, Sub)
- expression 4 operands: lhs = Expression(5, Add), rhs = Counter(2)
- expression 5 operands: lhs = Counter(4), rhs = Counter(3)
Number of file 0 mappings: 7
- Code(Counter(0)) at (prev + 24, 1) to (start + 1, 16)
- Code(Counter(1)) at (prev + 3, 11) to (start + 0, 16)
- Code(Counter(4)) at (prev + 1, 17) to (start + 0, 33)
- Code(Counter(3)) at (prev + 1, 17) to (start + 0, 33)
- Code(Counter(2)) at (prev + 1, 17) to (start + 0, 33)
- Code(Expression(0, Sub)) at (prev + 1, 17) to (start + 0, 33)
= (c1 - ((c2 + c3) + c4))
- Code(Expression(3, Add)) at (prev + 3, 5) to (start + 1, 2)
= (((c4 + c3) + c2) + (c1 - ((c2 + c3) + c4)))
Function name: match_arms::or_patterns
Raw bytes (75): 0x[01, 01, 0d, 11, 0d, 05, 2f, 33, 11, 09, 0d, 09, 2a, 05, 2f, 33, 11, 09, 0d, 03, 27, 09, 2a, 05, 2f, 33, 11, 09, 0d, 09, 01, 25, 01, 01, 10, 05, 03, 0b, 00, 10, 11, 01, 11, 00, 12, 0d, 00, 1e, 00, 1f, 03, 00, 24, 00, 2e, 09, 01, 11, 00, 12, 2a, 00, 1e, 00, 1f, 27, 00, 24, 00, 2e, 23, 03, 05, 01, 02]
Number of files: 1
- file 0 => global file 1
Number of expressions: 13
- expression 0 operands: lhs = Counter(4), rhs = Counter(3)
- expression 1 operands: lhs = Counter(1), rhs = Expression(11, Add)
- expression 2 operands: lhs = Expression(12, Add), rhs = Counter(4)
- expression 3 operands: lhs = Counter(2), rhs = Counter(3)
- expression 4 operands: lhs = Counter(2), rhs = Expression(10, Sub)
- expression 5 operands: lhs = Counter(1), rhs = Expression(11, Add)
- expression 6 operands: lhs = Expression(12, Add), rhs = Counter(4)
- expression 7 operands: lhs = Counter(2), rhs = Counter(3)
- expression 8 operands: lhs = Expression(0, Add), rhs = Expression(9, Add)
- expression 9 operands: lhs = Counter(2), rhs = Expression(10, Sub)
- expression 10 operands: lhs = Counter(1), rhs = Expression(11, Add)
- expression 11 operands: lhs = Expression(12, Add), rhs = Counter(4)
- expression 12 operands: lhs = Counter(2), rhs = Counter(3)
Number of file 0 mappings: 9
- Code(Counter(0)) at (prev + 37, 1) to (start + 1, 16)
- Code(Counter(1)) at (prev + 3, 11) to (start + 0, 16)
- Code(Counter(4)) at (prev + 1, 17) to (start + 0, 18)
- Code(Counter(3)) at (prev + 0, 30) to (start + 0, 31)
- Code(Expression(0, Add)) at (prev + 0, 36) to (start + 0, 46)
= (c4 + c3)
- Code(Counter(2)) at (prev + 1, 17) to (start + 0, 18)
- Code(Expression(10, Sub)) at (prev + 0, 30) to (start + 0, 31)
= (c1 - ((c2 + c3) + c4))
- Code(Expression(9, Add)) at (prev + 0, 36) to (start + 0, 46)
= (c2 + (c1 - ((c2 + c3) + c4)))
- Code(Expression(8, Add)) at (prev + 3, 5) to (start + 1, 2)
= ((c4 + c3) + (c2 + (c1 - ((c2 + c3) + c4))))

View file

@ -0,0 +1,105 @@
LL| |#![feature(coverage_attribute)]
LL| |//@ edition: 2021
LL| |//@ compile-flags: -Zcoverage-options=branch
LL| |//@ llvm-cov-flags: --show-branches=count
LL| |
LL| |// Tests for branch coverage of various kinds of match arms.
LL| |
LL| |// Helper macro to prevent start-of-function spans from being merged into
LL| |// spans on the lines we care about.
LL| |macro_rules! no_merge {
LL| | () => {
LL| | for _ in 0..1 {}
LL| | };
LL| |}
LL| |
LL| |#[derive(Clone, Copy, Debug)]
LL| |enum Enum {
LL| | A(u32),
LL| | B(u32),
LL| | C(u32),
LL| | D(u32),
LL| |}
LL| |
LL| 15|fn match_arms(value: Enum) {
LL| 15| no_merge!();
LL| |
LL| 15| match value {
LL| 8| Enum::D(d) => consume(d),
LL| 4| Enum::C(c) => consume(c),
LL| 2| Enum::B(b) => consume(b),
LL| 1| Enum::A(a) => consume(a),
LL| | }
LL| |
LL| 15| consume(0);
LL| 15|}
LL| |
LL| 15|fn or_patterns(value: Enum) {
LL| 15| no_merge!();
LL| |
LL| 15| match value {
LL| 12| Enum::D(x) | Enum::C(x) => consume(x),
^8 ^4
LL| 3| Enum::B(y) | Enum::A(y) => consume(y),
^2 ^1
LL| | }
LL| |
LL| 15| consume(0);
LL| 15|}
LL| |
LL| 45|fn guards(value: Enum, cond: bool) {
LL| 45| no_merge!();
LL| |
LL| 3| match value {
LL| 8| Enum::D(d) if cond => consume(d),
------------------
| Branch (LL:23): [True: 8, False: 16]
------------------
LL| 4| Enum::C(c) if cond => consume(c),
------------------
| Branch (LL:23): [True: 4, False: 8]
------------------
LL| 2| Enum::B(b) if cond => consume(b),
------------------
| Branch (LL:23): [True: 2, False: 4]
------------------
LL| 1| Enum::A(a) if cond => consume(a),
------------------
| Branch (LL:23): [True: 1, False: 2]
------------------
LL| 30| _ => consume(0),
LL| | }
LL| |
LL| 45| consume(0);
LL| 45|}
LL| |
LL| |#[coverage(off)]
LL| |fn consume<T>(x: T) {
LL| | core::hint::black_box(x);
LL| |}
LL| |
LL| |#[coverage(off)]
LL| |fn main() {
LL| | #[coverage(off)]
LL| | fn call_everything(e: Enum) {
LL| | match_arms(e);
LL| | or_patterns(e);
LL| | for cond in [false, false, true] {
LL| | guards(e, cond);
LL| | }
LL| | }
LL| |
LL| | call_everything(Enum::A(0));
LL| | for b in 0..2 {
LL| | call_everything(Enum::B(b));
LL| | }
LL| | for c in 0..4 {
LL| | call_everything(Enum::C(c));
LL| | }
LL| | for d in 0..8 {
LL| | call_everything(Enum::D(d));
LL| | }
LL| |}
LL| |
LL| |// FIXME(#124118) Actually instrument match arms for branch coverage.

View file

@ -0,0 +1,90 @@
#![feature(coverage_attribute)]
//@ edition: 2021
//@ compile-flags: -Zcoverage-options=branch
//@ llvm-cov-flags: --show-branches=count
// Tests for branch coverage of various kinds of match arms.
// Helper macro to prevent start-of-function spans from being merged into
// spans on the lines we care about.
macro_rules! no_merge {
() => {
for _ in 0..1 {}
};
}
#[derive(Clone, Copy, Debug)]
enum Enum {
A(u32),
B(u32),
C(u32),
D(u32),
}
fn match_arms(value: Enum) {
no_merge!();
match value {
Enum::D(d) => consume(d),
Enum::C(c) => consume(c),
Enum::B(b) => consume(b),
Enum::A(a) => consume(a),
}
consume(0);
}
fn or_patterns(value: Enum) {
no_merge!();
match value {
Enum::D(x) | Enum::C(x) => consume(x),
Enum::B(y) | Enum::A(y) => consume(y),
}
consume(0);
}
fn guards(value: Enum, cond: bool) {
no_merge!();
match value {
Enum::D(d) if cond => consume(d),
Enum::C(c) if cond => consume(c),
Enum::B(b) if cond => consume(b),
Enum::A(a) if cond => consume(a),
_ => consume(0),
}
consume(0);
}
#[coverage(off)]
fn consume<T>(x: T) {
core::hint::black_box(x);
}
#[coverage(off)]
fn main() {
#[coverage(off)]
fn call_everything(e: Enum) {
match_arms(e);
or_patterns(e);
for cond in [false, false, true] {
guards(e, cond);
}
}
call_everything(Enum::A(0));
for b in 0..2 {
call_everything(Enum::B(b));
}
for c in 0..4 {
call_everything(Enum::C(c));
}
for d in 0..8 {
call_everything(Enum::D(d));
}
}
// FIXME(#124118) Actually instrument match arms for branch coverage.

View file

@ -0,0 +1,17 @@
Function name: match_trivial::_uninhabited (unused)
Raw bytes (9): 0x[01, 01, 00, 01, 00, 16, 01, 01, 10]
Number of files: 1
- file 0 => global file 1
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Zero) at (prev + 22, 1) to (start + 1, 16)
Function name: match_trivial::trivial
Raw bytes (14): 0x[01, 01, 00, 02, 01, 1e, 01, 01, 10, 05, 03, 0b, 05, 02]
Number of files: 1
- file 0 => global file 1
Number of expressions: 0
Number of file 0 mappings: 2
- Code(Counter(0)) at (prev + 30, 1) to (start + 1, 16)
- Code(Counter(1)) at (prev + 3, 11) to (start + 5, 2)

View file

@ -0,0 +1,49 @@
LL| |#![feature(coverage_attribute)]
LL| |//@ edition: 2021
LL| |//@ compile-flags: -Zcoverage-options=branch
LL| |//@ llvm-cov-flags: --show-branches=count
LL| |
LL| |// When instrumenting match expressions for branch coverage, make sure we don't
LL| |// cause an ICE or produce weird coverage output for matches with <2 arms.
LL| |
LL| |// Helper macro to prevent start-of-function spans from being merged into
LL| |// spans on the lines we care about.
LL| |macro_rules! no_merge {
LL| | () => {
LL| | for _ in 0..1 {}
LL| | };
LL| |}
LL| |
LL| |enum Uninhabited {}
LL| |enum Trivial {
LL| | Value,
LL| |}
LL| |
LL| 0|fn _uninhabited(x: Uninhabited) {
LL| 0| no_merge!();
LL| |
LL| | match x {}
LL| |
LL| | consume("done");
LL| |}
LL| |
LL| 1|fn trivial(x: Trivial) {
LL| 1| no_merge!();
LL| |
LL| 1| match x {
LL| 1| Trivial::Value => consume("trivial"),
LL| 1| }
LL| 1|
LL| 1| consume("done");
LL| 1|}
LL| |
LL| |#[coverage(off)]
LL| |fn consume<T>(x: T) {
LL| | core::hint::black_box(x);
LL| |}
LL| |
LL| |#[coverage(off)]
LL| |fn main() {
LL| | trivial(Trivial::Value);
LL| |}

View file

@ -0,0 +1,48 @@
#![feature(coverage_attribute)]
//@ edition: 2021
//@ compile-flags: -Zcoverage-options=branch
//@ llvm-cov-flags: --show-branches=count
// When instrumenting match expressions for branch coverage, make sure we don't
// cause an ICE or produce weird coverage output for matches with <2 arms.
// Helper macro to prevent start-of-function spans from being merged into
// spans on the lines we care about.
macro_rules! no_merge {
() => {
for _ in 0..1 {}
};
}
enum Uninhabited {}
enum Trivial {
Value,
}
fn _uninhabited(x: Uninhabited) {
no_merge!();
match x {}
consume("done");
}
fn trivial(x: Trivial) {
no_merge!();
match x {
Trivial::Value => consume("trivial"),
}
consume("done");
}
#[coverage(off)]
fn consume<T>(x: T) {
core::hint::black_box(x);
}
#[coverage(off)]
fn main() {
trivial(Trivial::Value);
}

View file

@ -0,0 +1,138 @@
- // MIR for `main` before InstrumentCoverage
+ // MIR for `main` after InstrumentCoverage
fn main() -> () {
let mut _0: ();
let mut _1: Enum;
let mut _2: isize;
let _3: u32;
let mut _4: u32;
let _5: u32;
let mut _6: u32;
let _7: u32;
let mut _8: u32;
let _9: u32;
let mut _10: u32;
scope 1 {
debug d => _3;
}
scope 2 {
debug c => _5;
}
scope 3 {
debug b => _7;
}
scope 4 {
debug a => _9;
}
+ coverage ExpressionId(0) => Expression { lhs: Counter(1), op: Add, rhs: Counter(2) };
+ coverage ExpressionId(1) => Expression { lhs: Expression(0), op: Add, rhs: Counter(3) };
+ coverage ExpressionId(2) => Expression { lhs: Counter(0), op: Subtract, rhs: Expression(1) };
+ coverage ExpressionId(3) => Expression { lhs: Counter(3), op: Add, rhs: Counter(2) };
+ coverage ExpressionId(4) => Expression { lhs: Expression(3), op: Add, rhs: Counter(1) };
+ coverage ExpressionId(5) => Expression { lhs: Expression(4), op: Add, rhs: Expression(2) };
+ coverage Code(Counter(0)) => $DIR/branch_match_arms.rs:14:1 - 15:21;
+ coverage Code(Counter(3)) => $DIR/branch_match_arms.rs:16:17 - 16:33;
+ coverage Code(Counter(2)) => $DIR/branch_match_arms.rs:17:17 - 17:33;
+ coverage Code(Counter(1)) => $DIR/branch_match_arms.rs:18:17 - 18:33;
+ coverage Code(Expression(2)) => $DIR/branch_match_arms.rs:19:17 - 19:33;
+ coverage Code(Expression(5)) => $DIR/branch_match_arms.rs:21:1 - 21:2;
+
bb0: {
+ Coverage::CounterIncrement(0);
StorageLive(_1);
_1 = Enum::A(const 0_u32);
PlaceMention(_1);
_2 = discriminant(_1);
switchInt(move _2) -> [0: bb5, 1: bb4, 2: bb3, 3: bb2, otherwise: bb1];
}
bb1: {
FakeRead(ForMatchedPlace(None), _1);
unreachable;
}
bb2: {
+ Coverage::CounterIncrement(3);
falseEdge -> [real: bb6, imaginary: bb3];
}
bb3: {
+ Coverage::CounterIncrement(2);
falseEdge -> [real: bb8, imaginary: bb4];
}
bb4: {
+ Coverage::CounterIncrement(1);
falseEdge -> [real: bb10, imaginary: bb5];
}
bb5: {
+ Coverage::ExpressionUsed(2);
StorageLive(_9);
_9 = ((_1 as A).0: u32);
StorageLive(_10);
_10 = _9;
_0 = consume(move _10) -> [return: bb12, unwind: bb14];
}
bb6: {
StorageLive(_3);
_3 = ((_1 as D).0: u32);
StorageLive(_4);
_4 = _3;
_0 = consume(move _4) -> [return: bb7, unwind: bb14];
}
bb7: {
StorageDead(_4);
StorageDead(_3);
goto -> bb13;
}
bb8: {
StorageLive(_5);
_5 = ((_1 as C).0: u32);
StorageLive(_6);
_6 = _5;
_0 = consume(move _6) -> [return: bb9, unwind: bb14];
}
bb9: {
StorageDead(_6);
StorageDead(_5);
goto -> bb13;
}
bb10: {
StorageLive(_7);
_7 = ((_1 as B).0: u32);
StorageLive(_8);
_8 = _7;
_0 = consume(move _8) -> [return: bb11, unwind: bb14];
}
bb11: {
StorageDead(_8);
StorageDead(_7);
goto -> bb13;
}
bb12: {
StorageDead(_10);
StorageDead(_9);
goto -> bb13;
}
bb13: {
+ Coverage::ExpressionUsed(5);
StorageDead(_1);
return;
}
bb14 (cleanup): {
resume;
}
}

View file

@ -0,0 +1,27 @@
#![feature(coverage_attribute)]
//@ test-mir-pass: InstrumentCoverage
//@ compile-flags: -Cinstrument-coverage -Zno-profiler-runtime -Zcoverage-options=branch
// skip-filecheck
enum Enum {
A(u32),
B(u32),
C(u32),
D(u32),
}
// EMIT_MIR branch_match_arms.main.InstrumentCoverage.diff
fn main() {
match Enum::A(0) {
Enum::D(d) => consume(d),
Enum::C(c) => consume(c),
Enum::B(b) => consume(b),
Enum::A(a) => consume(a),
}
}
#[inline(never)]
#[coverage(off)]
fn consume(x: u32) {
core::hint::black_box(x);
}