rustc_mir_transform/coverage/spans/from_mir.rs
1use std::iter;
2
3use rustc_middle::bug;
4use rustc_middle::mir::coverage::CoverageKind;
5use rustc_middle::mir::{
6 self, FakeReadCause, Statement, StatementKind, Terminator, TerminatorKind,
7};
8use rustc_span::Span;
9
10use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
11
12#[derive(Debug)]
13pub(crate) struct RawSpanFromMir {
14 /// A span that has been extracted from a MIR statement/terminator, but
15 /// hasn't been "unexpanded", so it might not lie within the function body
16 /// span and might be part of an expansion with a different context.
17 pub(crate) raw_span: Span,
18 pub(crate) bcb: BasicCoverageBlock,
19}
20
21/// Generates an initial set of coverage spans from the statements and
22/// terminators in the function's MIR body, each associated with its
23/// corresponding node in the coverage graph.
24///
25/// This is necessarily an inexact process, because MIR isn't designed to
26/// capture source spans at the level of detail we would want for coverage,
27/// but it's good enough to be better than nothing.
28pub(crate) fn extract_raw_spans_from_mir<'tcx>(
29 mir_body: &mir::Body<'tcx>,
30 graph: &CoverageGraph,
31) -> Vec<RawSpanFromMir> {
32 let mut raw_spans = vec![];
33
34 // We only care about blocks that are part of the coverage graph.
35 for (bcb, bcb_data) in graph.iter_enumerated() {
36 let make_raw_span = |raw_span: Span| RawSpanFromMir { raw_span, bcb };
37
38 // A coverage graph node can consist of multiple basic blocks.
39 for &bb in &bcb_data.basic_blocks {
40 let bb_data = &mir_body[bb];
41
42 let statements = bb_data.statements.iter();
43 raw_spans.extend(statements.filter_map(filtered_statement_span).map(make_raw_span));
44
45 // There's only one terminator, but wrap it in an iterator to
46 // mirror the handling of statements.
47 let terminator = iter::once(bb_data.terminator());
48 raw_spans.extend(terminator.filter_map(filtered_terminator_span).map(make_raw_span));
49 }
50 }
51
52 raw_spans
53}
54
55/// If the MIR `Statement` has a span contributive to computing coverage spans,
56/// return it; otherwise return `None`.
57fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span> {
58 match statement.kind {
59 // These statements have spans that are often outside the scope of the executed source code
60 // for their parent `BasicBlock`.
61 StatementKind::StorageLive(_)
62 | StatementKind::StorageDead(_)
63 | StatementKind::ConstEvalCounter
64 | StatementKind::BackwardIncompatibleDropHint { .. }
65 | StatementKind::Nop => None,
66
67 // FIXME(#78546): MIR InstrumentCoverage - Can the source_info.span for `FakeRead`
68 // statements be more consistent?
69 //
70 // FakeReadCause::ForGuardBinding, in this example:
71 // match somenum {
72 // x if x < 1 => { ... }
73 // }...
74 // The BasicBlock within the match arm code included one of these statements, but the span
75 // for it covered the `1` in this source. The actual statements have nothing to do with that
76 // source span:
77 // FakeRead(ForGuardBinding, _4);
78 // where `_4` is:
79 // _4 = &_1; (at the span for the first `x`)
80 // and `_1` is the `Place` for `somenum`.
81 //
82 // If and when the Issue is resolved, remove this special case match pattern:
83 StatementKind::FakeRead(box (FakeReadCause::ForGuardBinding, _)) => None,
84
85 // Retain spans from most other statements.
86 StatementKind::FakeRead(_)
87 | StatementKind::Intrinsic(..)
88 | StatementKind::Coverage(
89 // The purpose of `SpanMarker` is to be matched and accepted here.
90 CoverageKind::SpanMarker,
91 )
92 | StatementKind::Assign(_)
93 | StatementKind::SetDiscriminant { .. }
94 | StatementKind::Deinit(..)
95 | StatementKind::Retag(_, _)
96 | StatementKind::PlaceMention(..)
97 | StatementKind::AscribeUserType(_, _) => Some(statement.source_info.span),
98
99 // Block markers are used for branch coverage, so ignore them here.
100 StatementKind::Coverage(CoverageKind::BlockMarker { .. }) => None,
101
102 // These coverage statements should not exist prior to coverage instrumentation.
103 StatementKind::Coverage(CoverageKind::VirtualCounter { .. }) => bug!(
104 "Unexpected coverage statement found during coverage instrumentation: {statement:?}"
105 ),
106 }
107}
108
109/// If the MIR `Terminator` has a span contributive to computing coverage spans,
110/// return it; otherwise return `None`.
111fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option<Span> {
112 match terminator.kind {
113 // These terminators have spans that don't positively contribute to computing a reasonable
114 // span of actually executed source code. (For example, SwitchInt terminators extracted from
115 // an `if condition { block }` has a span that includes the executed block, if true,
116 // but for coverage, the code region executed, up to *and* through the SwitchInt,
117 // actually stops before the if's block.)
118 TerminatorKind::Unreachable
119 | TerminatorKind::Assert { .. }
120 | TerminatorKind::Drop { .. }
121 | TerminatorKind::SwitchInt { .. }
122 | TerminatorKind::FalseEdge { .. }
123 | TerminatorKind::Goto { .. } => None,
124
125 // Call `func` operand can have a more specific span when part of a chain of calls
126 TerminatorKind::Call { ref func, .. } | TerminatorKind::TailCall { ref func, .. } => {
127 let mut span = terminator.source_info.span;
128 if let mir::Operand::Constant(constant) = func
129 && span.contains(constant.span)
130 {
131 span = constant.span;
132 }
133 Some(span)
134 }
135
136 // Retain spans from all other terminators
137 TerminatorKind::UnwindResume
138 | TerminatorKind::UnwindTerminate(_)
139 | TerminatorKind::Return
140 | TerminatorKind::Yield { .. }
141 | TerminatorKind::CoroutineDrop
142 | TerminatorKind::FalseUnwind { .. }
143 | TerminatorKind::InlineAsm { .. } => Some(terminator.source_info.span),
144 }
145}
146
147#[derive(Debug)]
148pub(crate) struct Hole {
149 pub(crate) span: Span,
150}
151
152impl Hole {
153 pub(crate) fn merge_if_overlapping_or_adjacent(&mut self, other: &mut Self) -> bool {
154 if !self.span.overlaps_or_adjacent(other.span) {
155 return false;
156 }
157
158 self.span = self.span.to(other.span);
159 true
160 }
161}