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, ExpnKind, MacroKind, Span, SyntaxContext};
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()) 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_context in &node.child_contexts {
42 if let Some(covspan) = single_covspan_for_child_context(tcx, &expn_tree, child_context) {
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;
64 }
65
66 true
67 });
68 }
69
70 if covspans.is_empty() {
72 return;
73 }
74
75 if let Some(span) = node.fn_sig_span.or_else(|| try { node.body_span?.shrink_to_lo() }) {
80 covspans.push(Covspan { span, bcb: START_BCB });
81 }
82
83 let compare_covspans = |a: &Covspan, b: &Covspan| {
84 compare_spans(a.span, b.span)
85 .then_with(|| graph.cmp_in_dominator_order(a.bcb, b.bcb).reverse())
87 };
88 covspans.sort_by(compare_covspans);
89
90 covspans.dedup_by(|b, a| a.span.source_equal(b.span));
95
96 let mut holes = node.hole_spans.iter().copied().map(|span| Hole { span }).collect::<Vec<_>>();
98
99 holes.sort_by(|a, b| compare_spans(a.span, b.span));
100 holes.dedup_by(|b, a| a.merge_if_overlapping_or_adjacent(b));
101
102 discard_spans_overlapping_holes(&mut covspans, &holes);
104
105 let mut covspans = remove_unwanted_overlapping_spans(covspans);
107
108 let source_map = tcx.sess.source_map();
110 covspans.retain_mut(|covspan| {
111 let Some(span) = ensure_non_empty_span(source_map, covspan.span) else { return false };
112 covspan.span = span;
113 true
114 });
115
116 covspans.dedup_by(|b, a| a.merge_if_eligible(b));
118
119 mappings.extend(covspans.into_iter().map(|Covspan { span, bcb }| {
120 Mapping { span, kind: MappingKind::Code { bcb } }
122 }));
123}
124
125fn single_covspan_for_child_context(
127 tcx: TyCtxt<'_>,
128 expn_tree: &ExpnTree,
129 child_context: SyntaxContext,
130) -> Option<Covspan> {
131 let node = expn_tree.get(child_context)?;
132 let minmax_bcbs = node.minmax_bcbs?;
133
134 let bcb = match node.expn_kind {
135 ExpnKind::Macro(MacroKind::Bang, _) | ExpnKind::Desugaring(DesugaringKind::Await) => {
138 minmax_bcbs.min
139 }
140 _ => minmax_bcbs.max,
143 };
144
145 let mut span = node.call_site?;
148 if matches!(node.expn_kind, ExpnKind::Macro(MacroKind::Bang, _)) {
149 span = tcx.sess.source_map().span_through_char(span, '!');
150 }
151
152 Some(Covspan { span, bcb })
153}
154
155fn discard_spans_overlapping_holes(covspans: &mut Vec<Covspan>, holes: &[Hole]) {
160 debug_assert!(covspans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
161 debug_assert!(holes.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
162 debug_assert!(holes.array_windows().all(|[a, b]| !a.span.overlaps_or_adjacent(b.span)));
163
164 let mut curr_hole = 0usize;
165 let mut overlaps_hole = |covspan: &Covspan| -> bool {
166 while let Some(hole) = holes.get(curr_hole) {
167 if hole.span.hi() <= covspan.span.lo() {
170 curr_hole += 1;
171 continue;
172 }
173
174 return hole.span.overlaps(covspan.span);
175 }
176
177 false
179 };
180
181 covspans.retain(|covspan| !overlaps_hole(covspan));
182}
183
184#[instrument(level = "debug")]
187fn remove_unwanted_overlapping_spans(sorted_spans: Vec<Covspan>) -> Vec<Covspan> {
188 debug_assert!(sorted_spans.is_sorted_by(|a, b| compare_spans(a.span, b.span).is_le()));
189
190 let mut pending = vec![];
193 let mut refined = vec![];
194
195 for curr in sorted_spans {
196 pending.retain(|prev: &Covspan| {
197 if prev.span.hi() <= curr.span.lo() {
198 refined.push(prev.clone());
201 false
202 } else {
203 prev.bcb == curr.bcb
207 }
208 });
209 pending.push(curr);
210 }
211
212 refined.extend(pending);
214 refined
215}
216
217#[derive(Clone, Debug)]
218struct Covspan {
219 span: Span,
220 bcb: BasicCoverageBlock,
221}
222
223impl Covspan {
224 fn merge_if_eligible(&mut self, other: &Self) -> bool {
230 let eligible_for_merge =
231 |a: &Self, b: &Self| (a.bcb == b.bcb) && a.span.overlaps_or_adjacent(b.span);
232
233 if eligible_for_merge(self, other) {
234 self.span = self.span.to(other.span);
235 true
236 } else {
237 false
238 }
239 }
240}
241
242fn compare_spans(a: Span, b: Span) -> std::cmp::Ordering {
244 Ord::cmp(&a.lo(), &b.lo())
246 .then_with(|| Ord::cmp(&a.hi(), &b.hi()).reverse())
252}
253
254fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> {
255 if !span.is_empty() {
256 return Some(span);
257 }
258
259 source_map
261 .span_to_source(span, |src, start, end| try {
262 if src.as_bytes().get(end).copied() == Some(b'{') {
267 Some(span.with_hi(span.hi() + BytePos(1)))
268 } else if start > 0 && src.as_bytes()[start - 1] == b'}' {
269 Some(span.with_lo(span.lo() - BytePos(1)))
270 } else {
271 None
272 }
273 })
274 .ok()?
275}
276
277#[derive(Debug)]
278struct Hole {
279 span: Span,
280}
281
282impl Hole {
283 fn merge_if_overlapping_or_adjacent(&mut self, other: &mut Self) -> bool {
284 if !self.span.overlaps_or_adjacent(other.span) {
285 return false;
286 }
287
288 self.span = self.span.to(other.span);
289 true
290 }
291}