rustc_mir_transform/coverage/
spans.rs1use rustc_middle::mir::coverage::{Mapping, MappingKind, START_BCB};
2use rustc_middle::ty::TyCtxt;
3use rustc_span::source_map::SourceMap;
4use rustc_span::{BytePos, DesugaringKind, ExpnId, ExpnKind, MacroKind, Span};
5use tracing::instrument;
6
7use crate::coverage::expansion::{ExpnTree, SpanWithBcb};
8use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
9use crate::coverage::hir_info::ExtractedHirInfo;
10
11pub(super) fn extract_refined_covspans<'tcx>(
12 tcx: TyCtxt<'tcx>,
13 hir_info: &ExtractedHirInfo,
14 graph: &CoverageGraph,
15 expn_tree: &ExpnTree,
16 mappings: &mut Vec<Mapping>,
17) {
18 if hir_info.is_async_fn {
19 if let Some(span) = hir_info.fn_sig_span {
24 mappings.push(Mapping { span, kind: MappingKind::Code { bcb: START_BCB } })
25 }
26 return;
27 }
28
29 let Some(node) = expn_tree.get(hir_info.body_span.ctxt().outer_expn()) else { return };
32
33 let mut covspans = vec![];
34
35 for &SpanWithBcb { span, bcb } in &node.spans {
36 covspans.push(Covspan { span, bcb });
37 }
38
39 for &child_expn_id in &node.child_expn_ids {
42 if let Some(covspan) = single_covspan_for_child_expn(tcx, graph, &expn_tree, child_expn_id)
43 {
44 covspans.push(covspan);
45 }
46 }
47
48 if let Some(body_span) = node.body_span {
49 covspans.retain(|covspan: &Covspan| {
50 let covspan_span = covspan.span;
51 if !body_span.contains(covspan_span) || body_span.source_equal(covspan_span) {
55 return false;
56 }
57
58 if !body_span.eq_ctxt(covspan_span) {
61 debug_assert!(
62 false,
63 "span context mismatch: body_span={body_span:?}, covspan.span={covspan_span:?}"
64 );
65 return false;
66 }
67
68 true
69 });
70 }
71
72 if covspans.is_empty() {
74 return;
75 }
76
77 if let Some(span) = node.fn_sig_span.or_else(|| try { node.body_span?.shrink_to_lo() }) {
82 covspans.push(Covspan { span, bcb: START_BCB });
83 }
84
85 let compare_covspans = |a: &Covspan, b: &Covspan| {
86 compare_spans(a.span, b.span)
87 .then_with(|| graph.cmp_in_dominator_order(a.bcb, b.bcb).reverse())
89 };
90 covspans.sort_by(compare_covspans);
91
92 covspans.dedup_by(|b, a| a.span.source_equal(b.span));
97
98 let mut holes = node.hole_spans.iter().copied().map(|span| Hole { span }).collect::<Vec<_>>();
100
101 holes.sort_by(|a, b| compare_spans(a.span, b.span));
102 holes.dedup_by(|b, a| a.merge_if_overlapping_or_adjacent(b));
103
104 discard_spans_overlapping_holes(&mut covspans, &holes);
106
107 let mut covspans = remove_unwanted_overlapping_spans(covspans);
109
110 let source_map = tcx.sess.source_map();
112 covspans.retain_mut(|covspan| {
113 let Some(span) = ensure_non_empty_span(source_map, covspan.span) else { return false };
114 covspan.span = span;
115 true
116 });
117
118 covspans.dedup_by(|b, a| a.merge_if_eligible(b));
120
121 mappings.extend(covspans.into_iter().map(|Covspan { span, bcb }| {
122 Mapping { span, kind: MappingKind::Code { bcb } }
124 }));
125}
126
127fn single_covspan_for_child_expn(
129 tcx: TyCtxt<'_>,
130 graph: &CoverageGraph,
131 expn_tree: &ExpnTree,
132 expn_id: ExpnId,
133) -> Option<Covspan> {
134 let node = expn_tree.get(expn_id)?;
135
136 let bcbs =
137 expn_tree.iter_node_and_descendants(expn_id).flat_map(|n| n.spans.iter().map(|s| s.bcb));
138
139 let bcb = match node.expn_kind {
140 ExpnKind::Macro(MacroKind::Bang, _) | ExpnKind::Desugaring(DesugaringKind::Await) => {
143 bcbs.min_by(|&a, &b| graph.cmp_in_dominator_order(a, b))?
144 }
145 _ => bcbs.max_by(|&a, &b| graph.cmp_in_dominator_order(a, b))?,
148 };
149
150 let mut span = node.call_site?;
153 if matches!(node.expn_kind, ExpnKind::Macro(MacroKind::Bang, _)) {
154 span = tcx.sess.source_map().span_through_char(span, '!');
155 }
156
157 Some(Covspan { span, bcb })
158}
159
160fn discard_spans_overlapping_holes(covspans: &mut Vec<Covspan>, holes: &[Hole]) {
165 debug_assert!(covspans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
166 debug_assert!(holes.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
167 debug_assert!(holes.array_windows().all(|[a, b]| !a.span.overlaps_or_adjacent(b.span)));
168
169 let mut curr_hole = 0usize;
170 let mut overlaps_hole = |covspan: &Covspan| -> bool {
171 while let Some(hole) = holes.get(curr_hole) {
172 if hole.span.hi() <= covspan.span.lo() {
175 curr_hole += 1;
176 continue;
177 }
178
179 return hole.span.overlaps(covspan.span);
180 }
181
182 false
184 };
185
186 covspans.retain(|covspan| !overlaps_hole(covspan));
187}
188
189#[instrument(level = "debug")]
192fn remove_unwanted_overlapping_spans(sorted_spans: Vec<Covspan>) -> Vec<Covspan> {
193 debug_assert!(sorted_spans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
194
195 let mut pending = vec![];
198 let mut refined = vec![];
199
200 for curr in sorted_spans {
201 pending.retain(|prev: &Covspan| {
202 if prev.span.hi() <= curr.span.lo() {
203 refined.push(prev.clone());
206 false
207 } else {
208 prev.bcb == curr.bcb
212 }
213 });
214 pending.push(curr);
215 }
216
217 refined.extend(pending);
219 refined
220}
221
222#[derive(Clone, Debug)]
223struct Covspan {
224 span: Span,
225 bcb: BasicCoverageBlock,
226}
227
228impl Covspan {
229 fn merge_if_eligible(&mut self, other: &Self) -> bool {
235 let eligible_for_merge =
236 |a: &Self, b: &Self| (a.bcb == b.bcb) && a.span.overlaps_or_adjacent(b.span);
237
238 if eligible_for_merge(self, other) {
239 self.span = self.span.to(other.span);
240 true
241 } else {
242 false
243 }
244 }
245}
246
247fn compare_spans(a: Span, b: Span) -> std::cmp::Ordering {
249 Ord::cmp(&a.lo(), &b.lo())
251 .then_with(|| Ord::cmp(&a.hi(), &b.hi()).reverse())
257}
258
259fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> {
260 if !span.is_empty() {
261 return Some(span);
262 }
263
264 source_map
266 .span_to_source(span, |src, start, end| try {
267 if src.as_bytes().get(end).copied() == Some(b'{') {
272 Some(span.with_hi(span.hi() + BytePos(1)))
273 } else if start > 0 && src.as_bytes()[start - 1] == b'}' {
274 Some(span.with_lo(span.lo() - BytePos(1)))
275 } else {
276 None
277 }
278 })
279 .ok()?
280}
281
282#[derive(Debug)]
283struct Hole {
284 span: Span,
285}
286
287impl Hole {
288 fn merge_if_overlapping_or_adjacent(&mut self, other: &mut Self) -> bool {
289 if !self.span.overlaps_or_adjacent(other.span) {
290 return false;
291 }
292
293 self.span = self.span.to(other.span);
294 true
295 }
296}