rustc_mir_transform/coverage/
spans.rs1use rustc_middle::mir;
2use rustc_middle::mir::coverage::{Mapping, MappingKind, START_BCB};
3use rustc_middle::ty::TyCtxt;
4use rustc_span::source_map::SourceMap;
5use rustc_span::{BytePos, DesugaringKind, ExpnId, ExpnKind, MacroKind, Span};
6use tracing::instrument;
7
8use crate::coverage::expansion::{self, ExpnTree, SpanWithBcb};
9use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
10use crate::coverage::hir_info::ExtractedHirInfo;
11use crate::coverage::spans::from_mir::{Hole, RawSpanFromMir};
12
13mod from_mir;
14
15pub(super) fn extract_refined_covspans<'tcx>(
16 tcx: TyCtxt<'tcx>,
17 mir_body: &mir::Body<'tcx>,
18 hir_info: &ExtractedHirInfo,
19 graph: &CoverageGraph,
20 mappings: &mut Vec<Mapping>,
21) {
22 if hir_info.is_async_fn {
23 if let Some(span) = hir_info.fn_sig_span {
28 mappings.push(Mapping { span, kind: MappingKind::Code { bcb: START_BCB } })
29 }
30 return;
31 }
32
33 let &ExtractedHirInfo { body_span, .. } = hir_info;
34
35 let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph);
36 let expn_tree = expansion::build_expn_tree(
38 raw_spans
39 .into_iter()
40 .map(|RawSpanFromMir { raw_span, bcb }| SpanWithBcb { span: raw_span, bcb }),
41 );
42
43 let mut covspans = vec![];
44 let mut push_covspan = |covspan: Covspan| {
45 let covspan_span = covspan.span;
46 if !body_span.contains(covspan_span) || body_span.source_equal(covspan_span) {
50 return;
51 }
52
53 if !body_span.eq_ctxt(covspan_span) {
56 debug_assert!(
57 false,
58 "span context mismatch: body_span={body_span:?}, covspan.span={covspan_span:?}"
59 );
60 return;
61 }
62
63 covspans.push(covspan);
64 };
65
66 if let Some(node) = expn_tree.get(body_span.ctxt().outer_expn()) {
67 for &SpanWithBcb { span, bcb } in &node.spans {
68 push_covspan(Covspan { span, bcb });
69 }
70
71 for &child_expn_id in &node.child_expn_ids {
74 if let Some(covspan) =
75 single_covspan_for_child_expn(tcx, graph, &expn_tree, child_expn_id)
76 {
77 push_covspan(covspan);
78 }
79 }
80 }
81
82 if covspans.is_empty() {
84 return;
85 }
86
87 covspans.push(Covspan {
92 span: hir_info.fn_sig_span.unwrap_or_else(|| body_span.shrink_to_lo()),
93 bcb: START_BCB,
94 });
95
96 let compare_covspans = |a: &Covspan, b: &Covspan| {
97 compare_spans(a.span, b.span)
98 .then_with(|| graph.cmp_in_dominator_order(a.bcb, b.bcb).reverse())
100 };
101 covspans.sort_by(compare_covspans);
102
103 covspans.dedup_by(|b, a| a.span.source_equal(b.span));
108
109 let mut holes = hir_info
111 .hole_spans
112 .iter()
113 .copied()
114 .filter(|&hole_span| body_span.contains(hole_span) && body_span.eq_ctxt(hole_span))
116 .map(|span| Hole { span })
117 .collect::<Vec<_>>();
118 holes.sort_by(|a, b| compare_spans(a.span, b.span));
119 holes.dedup_by(|b, a| a.merge_if_overlapping_or_adjacent(b));
120
121 discard_spans_overlapping_holes(&mut covspans, &holes);
123
124 let mut covspans = remove_unwanted_overlapping_spans(covspans);
126
127 let source_map = tcx.sess.source_map();
129 covspans.retain_mut(|covspan| {
130 let Some(span) = ensure_non_empty_span(source_map, covspan.span) else { return false };
131 covspan.span = span;
132 true
133 });
134
135 covspans.dedup_by(|b, a| a.merge_if_eligible(b));
137
138 mappings.extend(covspans.into_iter().map(|Covspan { span, bcb }| {
139 Mapping { span, kind: MappingKind::Code { bcb } }
141 }));
142}
143
144fn single_covspan_for_child_expn(
146 tcx: TyCtxt<'_>,
147 graph: &CoverageGraph,
148 expn_tree: &ExpnTree,
149 expn_id: ExpnId,
150) -> Option<Covspan> {
151 let node = expn_tree.get(expn_id)?;
152
153 let bcbs =
154 expn_tree.iter_node_and_descendants(expn_id).flat_map(|n| n.spans.iter().map(|s| s.bcb));
155
156 let bcb = match node.expn_kind {
157 ExpnKind::Macro(MacroKind::Bang, _) | ExpnKind::Desugaring(DesugaringKind::Await) => {
160 bcbs.min_by(|&a, &b| graph.cmp_in_dominator_order(a, b))?
161 }
162 _ => bcbs.max_by(|&a, &b| graph.cmp_in_dominator_order(a, b))?,
165 };
166
167 let mut span = node.call_site?;
170 if matches!(node.expn_kind, ExpnKind::Macro(MacroKind::Bang, _)) {
171 span = tcx.sess.source_map().span_through_char(span, '!');
172 }
173
174 Some(Covspan { span, bcb })
175}
176
177fn discard_spans_overlapping_holes(covspans: &mut Vec<Covspan>, holes: &[Hole]) {
182 debug_assert!(covspans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
183 debug_assert!(holes.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
184 debug_assert!(holes.array_windows().all(|[a, b]| !a.span.overlaps_or_adjacent(b.span)));
185
186 let mut curr_hole = 0usize;
187 let mut overlaps_hole = |covspan: &Covspan| -> bool {
188 while let Some(hole) = holes.get(curr_hole) {
189 if hole.span.hi() <= covspan.span.lo() {
192 curr_hole += 1;
193 continue;
194 }
195
196 return hole.span.overlaps(covspan.span);
197 }
198
199 false
201 };
202
203 covspans.retain(|covspan| !overlaps_hole(covspan));
204}
205
206#[instrument(level = "debug")]
209fn remove_unwanted_overlapping_spans(sorted_spans: Vec<Covspan>) -> Vec<Covspan> {
210 debug_assert!(sorted_spans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
211
212 let mut pending = vec![];
215 let mut refined = vec![];
216
217 for curr in sorted_spans {
218 pending.retain(|prev: &Covspan| {
219 if prev.span.hi() <= curr.span.lo() {
220 refined.push(prev.clone());
223 false
224 } else {
225 prev.bcb == curr.bcb
229 }
230 });
231 pending.push(curr);
232 }
233
234 refined.extend(pending);
236 refined
237}
238
239#[derive(Clone, Debug)]
240struct Covspan {
241 span: Span,
242 bcb: BasicCoverageBlock,
243}
244
245impl Covspan {
246 fn merge_if_eligible(&mut self, other: &Self) -> bool {
252 let eligible_for_merge =
253 |a: &Self, b: &Self| (a.bcb == b.bcb) && a.span.overlaps_or_adjacent(b.span);
254
255 if eligible_for_merge(self, other) {
256 self.span = self.span.to(other.span);
257 true
258 } else {
259 false
260 }
261 }
262}
263
264fn compare_spans(a: Span, b: Span) -> std::cmp::Ordering {
266 Ord::cmp(&a.lo(), &b.lo())
268 .then_with(|| Ord::cmp(&a.hi(), &b.hi()).reverse())
274}
275
276fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> {
277 if !span.is_empty() {
278 return Some(span);
279 }
280
281 source_map
283 .span_to_source(span, |src, start, end| try {
284 if src.as_bytes().get(end).copied() == Some(b'{') {
289 Some(span.with_hi(span.hi() + BytePos(1)))
290 } else if start > 0 && src.as_bytes()[start - 1] == b'}' {
291 Some(span.with_lo(span.lo() - BytePos(1)))
292 } else {
293 None
294 }
295 })
296 .ok()?
297}