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, &expn_tree, child_expn_id) {
43 covspans.push(covspan);
44 }
45 }
46
47 if let Some(body_span) = node.body_span {
48 covspans.retain(|covspan: &Covspan| {
49 let covspan_span = covspan.span;
50 if !body_span.contains(covspan_span) || body_span.source_equal(covspan_span) {
54 return false;
55 }
56
57 if !body_span.eq_ctxt(covspan_span) {
60 return false;
63 }
64
65 true
66 });
67 }
68
69 if covspans.is_empty() {
71 return;
72 }
73
74 if let Some(span) = node.fn_sig_span.or_else(|| try { node.body_span?.shrink_to_lo() }) {
79 covspans.push(Covspan { span, bcb: START_BCB });
80 }
81
82 let compare_covspans = |a: &Covspan, b: &Covspan| {
83 compare_spans(a.span, b.span)
84 .then_with(|| graph.cmp_in_dominator_order(a.bcb, b.bcb).reverse())
86 };
87 covspans.sort_by(compare_covspans);
88
89 covspans.dedup_by(|b, a| a.span.source_equal(b.span));
94
95 let mut holes = node.hole_spans.iter().copied().map(|span| Hole { span }).collect::<Vec<_>>();
97
98 holes.sort_by(|a, b| compare_spans(a.span, b.span));
99 holes.dedup_by(|b, a| a.merge_if_overlapping_or_adjacent(b));
100
101 discard_spans_overlapping_holes(&mut covspans, &holes);
103
104 let mut covspans = remove_unwanted_overlapping_spans(covspans);
106
107 let source_map = tcx.sess.source_map();
109 covspans.retain_mut(|covspan| {
110 let Some(span) = ensure_non_empty_span(source_map, covspan.span) else { return false };
111 covspan.span = span;
112 true
113 });
114
115 covspans.dedup_by(|b, a| a.merge_if_eligible(b));
117
118 mappings.extend(covspans.into_iter().map(|Covspan { span, bcb }| {
119 Mapping { span, kind: MappingKind::Code { bcb } }
121 }));
122}
123
124fn single_covspan_for_child_expn(
126 tcx: TyCtxt<'_>,
127 expn_tree: &ExpnTree,
128 expn_id: ExpnId,
129) -> Option<Covspan> {
130 let node = expn_tree.get(expn_id)?;
131 let minmax_bcbs = node.minmax_bcbs?;
132
133 let bcb = match node.expn_kind {
134 ExpnKind::Macro(MacroKind::Bang, _) | ExpnKind::Desugaring(DesugaringKind::Await) => {
137 minmax_bcbs.min
138 }
139 _ => minmax_bcbs.max,
142 };
143
144 let mut span = node.call_site?;
147 if matches!(node.expn_kind, ExpnKind::Macro(MacroKind::Bang, _)) {
148 span = tcx.sess.source_map().span_through_char(span, '!');
149 }
150
151 Some(Covspan { span, bcb })
152}
153
154fn discard_spans_overlapping_holes(covspans: &mut Vec<Covspan>, holes: &[Hole]) {
159 debug_assert!(covspans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
160 debug_assert!(holes.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
161 debug_assert!(holes.array_windows().all(|[a, b]| !a.span.overlaps_or_adjacent(b.span)));
162
163 let mut curr_hole = 0usize;
164 let mut overlaps_hole = |covspan: &Covspan| -> bool {
165 while let Some(hole) = holes.get(curr_hole) {
166 if hole.span.hi() <= covspan.span.lo() {
169 curr_hole += 1;
170 continue;
171 }
172
173 return hole.span.overlaps(covspan.span);
174 }
175
176 false
178 };
179
180 covspans.retain(|covspan| !overlaps_hole(covspan));
181}
182
183#[instrument(level = "debug")]
186fn remove_unwanted_overlapping_spans(sorted_spans: Vec<Covspan>) -> Vec<Covspan> {
187 debug_assert!(sorted_spans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
188
189 let mut pending = vec![];
192 let mut refined = vec![];
193
194 for curr in sorted_spans {
195 pending.retain(|prev: &Covspan| {
196 if prev.span.hi() <= curr.span.lo() {
197 refined.push(prev.clone());
200 false
201 } else {
202 prev.bcb == curr.bcb
206 }
207 });
208 pending.push(curr);
209 }
210
211 refined.extend(pending);
213 refined
214}
215
216#[derive(Clone, Debug)]
217struct Covspan {
218 span: Span,
219 bcb: BasicCoverageBlock,
220}
221
222impl Covspan {
223 fn merge_if_eligible(&mut self, other: &Self) -> bool {
229 let eligible_for_merge =
230 |a: &Self, b: &Self| (a.bcb == b.bcb) && a.span.overlaps_or_adjacent(b.span);
231
232 if eligible_for_merge(self, other) {
233 self.span = self.span.to(other.span);
234 true
235 } else {
236 false
237 }
238 }
239}
240
241fn compare_spans(a: Span, b: Span) -> std::cmp::Ordering {
243 Ord::cmp(&a.lo(), &b.lo())
245 .then_with(|| Ord::cmp(&a.hi(), &b.hi()).reverse())
251}
252
253fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> {
254 if !span.is_empty() {
255 return Some(span);
256 }
257
258 source_map
260 .span_to_source(span, |src, start, end| try {
261 if src.as_bytes().get(end).copied() == Some(b'{') {
266 Some(span.with_hi(span.hi() + BytePos(1)))
267 } else if start > 0 && src.as_bytes()[start - 1] == b'}' {
268 Some(span.with_lo(span.lo() - BytePos(1)))
269 } else {
270 None
271 }
272 })
273 .ok()?
274}
275
276#[derive(Debug)]
277struct Hole {
278 span: Span,
279}
280
281impl Hole {
282 fn merge_if_overlapping_or_adjacent(&mut self, other: &mut Self) -> bool {
283 if !self.span.overlaps_or_adjacent(other.span) {
284 return false;
285 }
286
287 self.span = self.span.to(other.span);
288 true
289 }
290}