rustc_mir_transform/coverage/spans/
from_mir.rs

1use rustc_middle::bug;
2use rustc_middle::mir::coverage::CoverageKind;
3use rustc_middle::mir::{
4    self, FakeReadCause, Statement, StatementKind, Terminator, TerminatorKind,
5};
6use rustc_span::{ExpnKind, Span};
7
8use crate::coverage::ExtractedHirInfo;
9use crate::coverage::graph::{
10    BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph, START_BCB,
11};
12use crate::coverage::spans::Covspan;
13use crate::coverage::unexpand::unexpand_into_body_span_with_expn_kind;
14
15pub(crate) struct ExtractedCovspans {
16    pub(crate) covspans: Vec<SpanFromMir>,
17}
18
19/// Traverses the MIR body to produce an initial collection of coverage-relevant
20/// spans, each associated with a node in the coverage graph (BCB) and possibly
21/// other metadata.
22pub(crate) fn extract_covspans_from_mir(
23    mir_body: &mir::Body<'_>,
24    hir_info: &ExtractedHirInfo,
25    graph: &CoverageGraph,
26) -> ExtractedCovspans {
27    let &ExtractedHirInfo { body_span, .. } = hir_info;
28
29    let mut covspans = vec![];
30
31    for (bcb, bcb_data) in graph.iter_enumerated() {
32        bcb_to_initial_coverage_spans(mir_body, body_span, bcb, bcb_data, &mut covspans);
33    }
34
35    // Only add the signature span if we found at least one span in the body.
36    if !covspans.is_empty() {
37        // If there is no usable signature span, add a fake one (before refinement)
38        // to avoid an ugly gap between the body start and the first real span.
39        // FIXME: Find a more principled way to solve this problem.
40        let fn_sig_span = hir_info.fn_sig_span_extended.unwrap_or_else(|| body_span.shrink_to_lo());
41        covspans.push(SpanFromMir::for_fn_sig(fn_sig_span));
42    }
43
44    ExtractedCovspans { covspans }
45}
46
47// Generate a set of coverage spans from the filtered set of `Statement`s and `Terminator`s of
48// the `BasicBlock`(s) in the given `BasicCoverageBlockData`. One coverage span is generated
49// for each `Statement` and `Terminator`. (Note that subsequent stages of coverage analysis will
50// merge some coverage spans, at which point a coverage span may represent multiple
51// `Statement`s and/or `Terminator`s.)
52fn bcb_to_initial_coverage_spans<'a, 'tcx>(
53    mir_body: &'a mir::Body<'tcx>,
54    body_span: Span,
55    bcb: BasicCoverageBlock,
56    bcb_data: &'a BasicCoverageBlockData,
57    initial_covspans: &mut Vec<SpanFromMir>,
58) {
59    for &bb in &bcb_data.basic_blocks {
60        let data = &mir_body[bb];
61
62        let unexpand = move |expn_span| {
63            unexpand_into_body_span_with_expn_kind(expn_span, body_span)
64                // Discard any spans that fill the entire body, because they tend
65                // to represent compiler-inserted code, e.g. implicitly returning `()`.
66                .filter(|(span, _)| !span.source_equal(body_span))
67        };
68
69        let mut extract_statement_span = |statement| {
70            let expn_span = filtered_statement_span(statement)?;
71            let (span, expn_kind) = unexpand(expn_span)?;
72
73            initial_covspans.push(SpanFromMir::new(span, expn_kind, bcb));
74            Some(())
75        };
76        for statement in data.statements.iter() {
77            extract_statement_span(statement);
78        }
79
80        let mut extract_terminator_span = |terminator| {
81            let expn_span = filtered_terminator_span(terminator)?;
82            let (span, expn_kind) = unexpand(expn_span)?;
83
84            initial_covspans.push(SpanFromMir::new(span, expn_kind, bcb));
85            Some(())
86        };
87        extract_terminator_span(data.terminator());
88    }
89}
90
91/// If the MIR `Statement` has a span contributive to computing coverage spans,
92/// return it; otherwise return `None`.
93fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span> {
94    match statement.kind {
95        // These statements have spans that are often outside the scope of the executed source code
96        // for their parent `BasicBlock`.
97        StatementKind::StorageLive(_)
98        | StatementKind::StorageDead(_)
99        | StatementKind::ConstEvalCounter
100        | StatementKind::BackwardIncompatibleDropHint { .. }
101        | StatementKind::Nop => None,
102
103        // FIXME(#78546): MIR InstrumentCoverage - Can the source_info.span for `FakeRead`
104        // statements be more consistent?
105        //
106        // FakeReadCause::ForGuardBinding, in this example:
107        //     match somenum {
108        //         x if x < 1 => { ... }
109        //     }...
110        // The BasicBlock within the match arm code included one of these statements, but the span
111        // for it covered the `1` in this source. The actual statements have nothing to do with that
112        // source span:
113        //     FakeRead(ForGuardBinding, _4);
114        // where `_4` is:
115        //     _4 = &_1; (at the span for the first `x`)
116        // and `_1` is the `Place` for `somenum`.
117        //
118        // If and when the Issue is resolved, remove this special case match pattern:
119        StatementKind::FakeRead(box (FakeReadCause::ForGuardBinding, _)) => None,
120
121        // Retain spans from most other statements.
122        StatementKind::FakeRead(_)
123        | StatementKind::Intrinsic(..)
124        | StatementKind::Coverage(
125            // The purpose of `SpanMarker` is to be matched and accepted here.
126            CoverageKind::SpanMarker,
127        )
128        | StatementKind::Assign(_)
129        | StatementKind::SetDiscriminant { .. }
130        | StatementKind::Deinit(..)
131        | StatementKind::Retag(_, _)
132        | StatementKind::PlaceMention(..)
133        | StatementKind::AscribeUserType(_, _) => Some(statement.source_info.span),
134
135        // Block markers are used for branch coverage, so ignore them here.
136        StatementKind::Coverage(CoverageKind::BlockMarker { .. }) => None,
137
138        // These coverage statements should not exist prior to coverage instrumentation.
139        StatementKind::Coverage(
140            CoverageKind::VirtualCounter { .. }
141            | CoverageKind::CondBitmapUpdate { .. }
142            | CoverageKind::TestVectorBitmapUpdate { .. },
143        ) => bug!(
144            "Unexpected coverage statement found during coverage instrumentation: {statement:?}"
145        ),
146    }
147}
148
149/// If the MIR `Terminator` has a span contributive to computing coverage spans,
150/// return it; otherwise return `None`.
151fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option<Span> {
152    match terminator.kind {
153        // These terminators have spans that don't positively contribute to computing a reasonable
154        // span of actually executed source code. (For example, SwitchInt terminators extracted from
155        // an `if condition { block }` has a span that includes the executed block, if true,
156        // but for coverage, the code region executed, up to *and* through the SwitchInt,
157        // actually stops before the if's block.)
158        TerminatorKind::Unreachable // Unreachable blocks are not connected to the MIR CFG
159        | TerminatorKind::Assert { .. }
160        | TerminatorKind::Drop { .. }
161        | TerminatorKind::SwitchInt { .. }
162        // For `FalseEdge`, only the `real` branch is taken, so it is similar to a `Goto`.
163        | TerminatorKind::FalseEdge { .. }
164        | TerminatorKind::Goto { .. } => None,
165
166        // Call `func` operand can have a more specific span when part of a chain of calls
167        TerminatorKind::Call { ref func, .. }
168        | TerminatorKind::TailCall { ref func, .. } => {
169            let mut span = terminator.source_info.span;
170            if let mir::Operand::Constant(box constant) = func {
171                if constant.span.lo() > span.lo() {
172                    span = span.with_lo(constant.span.lo());
173                }
174            }
175            Some(span)
176        }
177
178        // Retain spans from all other terminators
179        TerminatorKind::UnwindResume
180        | TerminatorKind::UnwindTerminate(_)
181        | TerminatorKind::Return
182        | TerminatorKind::Yield { .. }
183        | TerminatorKind::CoroutineDrop
184        | TerminatorKind::FalseUnwind { .. }
185        | TerminatorKind::InlineAsm { .. } => {
186            Some(terminator.source_info.span)
187        }
188    }
189}
190
191#[derive(Debug)]
192pub(crate) struct Hole {
193    pub(crate) span: Span,
194}
195
196impl Hole {
197    pub(crate) fn merge_if_overlapping_or_adjacent(&mut self, other: &mut Self) -> bool {
198        if !self.span.overlaps_or_adjacent(other.span) {
199            return false;
200        }
201
202        self.span = self.span.to(other.span);
203        true
204    }
205}
206
207#[derive(Debug)]
208pub(crate) struct SpanFromMir {
209    /// A span that has been extracted from MIR and then "un-expanded" back to
210    /// within the current function's `body_span`. After various intermediate
211    /// processing steps, this span is emitted as part of the final coverage
212    /// mappings.
213    ///
214    /// With the exception of `fn_sig_span`, this should always be contained
215    /// within `body_span`.
216    pub(crate) span: Span,
217    pub(crate) expn_kind: Option<ExpnKind>,
218    pub(crate) bcb: BasicCoverageBlock,
219}
220
221impl SpanFromMir {
222    fn for_fn_sig(fn_sig_span: Span) -> Self {
223        Self::new(fn_sig_span, None, START_BCB)
224    }
225
226    pub(crate) fn new(span: Span, expn_kind: Option<ExpnKind>, bcb: BasicCoverageBlock) -> Self {
227        Self { span, expn_kind, bcb }
228    }
229
230    pub(crate) fn into_covspan(self) -> Covspan {
231        let Self { span, expn_kind: _, bcb } = self;
232        Covspan { span, bcb }
233    }
234}