blob: dfeaa90dc2e22fbb1e981144a68c680cf33f0a2d [file] [log] [blame]
use std::iter;
use rustc_middle::bug;
use rustc_middle::mir::coverage::CoverageKind;
use rustc_middle::mir::{
self, FakeReadCause, Statement, StatementKind, Terminator, TerminatorKind,
};
use rustc_span::Span;
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
#[derive(Debug)]
pub(crate) struct RawSpanFromMir {
/// A span that has been extracted from a MIR statement/terminator, but
/// hasn't been "unexpanded", so it might not lie within the function body
/// span and might be part of an expansion with a different context.
pub(crate) raw_span: Span,
pub(crate) bcb: BasicCoverageBlock,
}
/// Generates an initial set of coverage spans from the statements and
/// terminators in the function's MIR body, each associated with its
/// corresponding node in the coverage graph.
///
/// This is necessarily an inexact process, because MIR isn't designed to
/// capture source spans at the level of detail we would want for coverage,
/// but it's good enough to be better than nothing.
pub(crate) fn extract_raw_spans_from_mir<'tcx>(
mir_body: &mir::Body<'tcx>,
graph: &CoverageGraph,
) -> Vec<RawSpanFromMir> {
let mut raw_spans = vec![];
// We only care about blocks that are part of the coverage graph.
for (bcb, bcb_data) in graph.iter_enumerated() {
let make_raw_span = |raw_span: Span| RawSpanFromMir { raw_span, bcb };
// A coverage graph node can consist of multiple basic blocks.
for &bb in &bcb_data.basic_blocks {
let bb_data = &mir_body[bb];
let statements = bb_data.statements.iter();
raw_spans.extend(statements.filter_map(filtered_statement_span).map(make_raw_span));
// There's only one terminator, but wrap it in an iterator to
// mirror the handling of statements.
let terminator = iter::once(bb_data.terminator());
raw_spans.extend(terminator.filter_map(filtered_terminator_span).map(make_raw_span));
}
}
raw_spans
}
/// If the MIR `Statement` has a span contributive to computing coverage spans,
/// return it; otherwise return `None`.
fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span> {
match statement.kind {
// These statements have spans that are often outside the scope of the executed source code
// for their parent `BasicBlock`.
StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::ConstEvalCounter
| StatementKind::BackwardIncompatibleDropHint { .. }
| StatementKind::Nop => None,
// FIXME(#78546): MIR InstrumentCoverage - Can the source_info.span for `FakeRead`
// statements be more consistent?
//
// FakeReadCause::ForGuardBinding, in this example:
// match somenum {
// x if x < 1 => { ... }
// }...
// The BasicBlock within the match arm code included one of these statements, but the span
// for it covered the `1` in this source. The actual statements have nothing to do with that
// source span:
// FakeRead(ForGuardBinding, _4);
// where `_4` is:
// _4 = &_1; (at the span for the first `x`)
// and `_1` is the `Place` for `somenum`.
//
// If and when the Issue is resolved, remove this special case match pattern:
StatementKind::FakeRead(box (FakeReadCause::ForGuardBinding, _)) => None,
// Retain spans from most other statements.
StatementKind::FakeRead(_)
| StatementKind::Intrinsic(..)
| StatementKind::Coverage(
// The purpose of `SpanMarker` is to be matched and accepted here.
CoverageKind::SpanMarker,
)
| StatementKind::Assign(_)
| StatementKind::SetDiscriminant { .. }
| StatementKind::Deinit(..)
| StatementKind::Retag(_, _)
| StatementKind::PlaceMention(..)
| StatementKind::AscribeUserType(_, _) => Some(statement.source_info.span),
// Block markers are used for branch coverage, so ignore them here.
StatementKind::Coverage(CoverageKind::BlockMarker { .. }) => None,
// These coverage statements should not exist prior to coverage instrumentation.
StatementKind::Coverage(CoverageKind::VirtualCounter { .. }) => bug!(
"Unexpected coverage statement found during coverage instrumentation: {statement:?}"
),
}
}
/// If the MIR `Terminator` has a span contributive to computing coverage spans,
/// return it; otherwise return `None`.
fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option<Span> {
match terminator.kind {
// These terminators have spans that don't positively contribute to computing a reasonable
// span of actually executed source code. (For example, SwitchInt terminators extracted from
// an `if condition { block }` has a span that includes the executed block, if true,
// but for coverage, the code region executed, up to *and* through the SwitchInt,
// actually stops before the if's block.)
TerminatorKind::Unreachable
| TerminatorKind::Assert { .. }
| TerminatorKind::Drop { .. }
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::Goto { .. } => None,
// Call `func` operand can have a more specific span when part of a chain of calls
TerminatorKind::Call { ref func, .. } | TerminatorKind::TailCall { ref func, .. } => {
let mut span = terminator.source_info.span;
if let mir::Operand::Constant(constant) = func
&& span.contains(constant.span)
{
span = constant.span;
}
Some(span)
}
// Retain spans from all other terminators
TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return
| TerminatorKind::Yield { .. }
| TerminatorKind::CoroutineDrop
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::InlineAsm { .. } => Some(terminator.source_info.span),
}
}
#[derive(Debug)]
pub(crate) struct Hole {
pub(crate) span: Span,
}
impl Hole {
pub(crate) fn merge_if_overlapping_or_adjacent(&mut self, other: &mut Self) -> bool {
if !self.span.overlaps_or_adjacent(other.span) {
return false;
}
self.span = self.span.to(other.span);
true
}
}