Skip to content

Commit 8d98f23

Browse files
committed
coverage: Enlarge empty spans during MIR instrumentation, not codegen
This allows us to assume that coverage spans will only be discarded during codegen in very unusual situations.
1 parent da1e442 commit 8d98f23

File tree

3 files changed

+49
-38
lines changed

3 files changed

+49
-38
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/spans.rs

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ impl Coords {
3939
/// or other expansions), and if it does happen then skipping a span or function is
4040
/// better than an ICE or `llvm-cov` failure that the user might have no way to avoid.
4141
pub(crate) fn make_coords(source_map: &SourceMap, file: &SourceFile, span: Span) -> Option<Coords> {
42-
let span = ensure_non_empty_span(source_map, span)?;
42+
if span.is_empty() {
43+
debug_assert!(false, "can't make coords from empty span: {span:?}");
44+
return None;
45+
}
4346

4447
let lo = span.lo();
4548
let hi = span.hi();
@@ -70,29 +73,6 @@ pub(crate) fn make_coords(source_map: &SourceMap, file: &SourceFile, span: Span)
7073
})
7174
}
7275

73-
fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> {
74-
if !span.is_empty() {
75-
return Some(span);
76-
}
77-
78-
// The span is empty, so try to enlarge it to cover an adjacent '{' or '}'.
79-
source_map
80-
.span_to_source(span, |src, start, end| try {
81-
// Adjusting span endpoints by `BytePos(1)` is normally a bug,
82-
// but in this case we have specifically checked that the character
83-
// we're skipping over is one of two specific ASCII characters, so
84-
// adjusting by exactly 1 byte is correct.
85-
if src.as_bytes().get(end).copied() == Some(b'{') {
86-
Some(span.with_hi(span.hi() + BytePos(1)))
87-
} else if start > 0 && src.as_bytes()[start - 1] == b'}' {
88-
Some(span.with_lo(span.lo() - BytePos(1)))
89-
} else {
90-
None
91-
}
92-
})
93-
.ok()?
94-
}
95-
9676
/// If `llvm-cov` sees a source region that is improperly ordered (end < start),
9777
/// it will immediately exit with a fatal error. To prevent that from happening,
9878
/// discard regions that are improperly ordered, or might be interpreted in a

compiler/rustc_mir_transform/src/coverage/spans.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use rustc_data_structures::fx::FxHashSet;
22
use rustc_middle::mir;
33
use rustc_middle::ty::TyCtxt;
4-
use rustc_span::{DesugaringKind, ExpnKind, MacroKind, Span};
4+
use rustc_span::source_map::SourceMap;
5+
use rustc_span::{BytePos, DesugaringKind, ExpnKind, MacroKind, Span};
56
use tracing::instrument;
67

78
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
@@ -83,8 +84,18 @@ pub(super) fn extract_refined_covspans<'tcx>(
8384
// Discard any span that overlaps with a hole.
8485
discard_spans_overlapping_holes(&mut covspans, &holes);
8586

86-
// Perform more refinement steps after holes have been dealt with.
87+
// Discard spans that overlap in unwanted ways.
8788
let mut covspans = remove_unwanted_overlapping_spans(covspans);
89+
90+
// For all empty spans, either enlarge them to be non-empty, or discard them.
91+
let source_map = tcx.sess.source_map();
92+
covspans.retain_mut(|covspan| {
93+
let Some(span) = ensure_non_empty_span(source_map, covspan.span) else { return false };
94+
covspan.span = span;
95+
true
96+
});
97+
98+
// Merge covspans that can be merged.
8899
covspans.dedup_by(|b, a| a.merge_if_eligible(b));
89100

90101
code_mappings.extend(covspans.into_iter().map(|Covspan { span, bcb }| {
@@ -230,3 +241,26 @@ fn compare_spans(a: Span, b: Span) -> std::cmp::Ordering {
230241
// - Both have the same start and span A extends further right
231242
.then_with(|| Ord::cmp(&a.hi(), &b.hi()).reverse())
232243
}
244+
245+
fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> {
246+
if !span.is_empty() {
247+
return Some(span);
248+
}
249+
250+
// The span is empty, so try to enlarge it to cover an adjacent '{' or '}'.
251+
source_map
252+
.span_to_source(span, |src, start, end| try {
253+
// Adjusting span endpoints by `BytePos(1)` is normally a bug,
254+
// but in this case we have specifically checked that the character
255+
// we're skipping over is one of two specific ASCII characters, so
256+
// adjusting by exactly 1 byte is correct.
257+
if src.as_bytes().get(end).copied() == Some(b'{') {
258+
Some(span.with_hi(span.hi() + BytePos(1)))
259+
} else if start > 0 && src.as_bytes()[start - 1] == b'}' {
260+
Some(span.with_lo(span.lo() - BytePos(1)))
261+
} else {
262+
None
263+
}
264+
})
265+
.ok()?
266+
}

tests/coverage/async_closure.cov-map

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,32 +37,29 @@ Number of file 0 mappings: 8
3737
Highest counter ID seen: c0
3838

3939
Function name: async_closure::main::{closure#0}
40-
Raw bytes (14): 0x[01, 01, 00, 02, 01, 0b, 22, 00, 23, 01, 00, 23, 00, 24]
40+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0b, 22, 00, 24]
4141
Number of files: 1
4242
- file 0 => $DIR/async_closure.rs
4343
Number of expressions: 0
44-
Number of file 0 mappings: 2
45-
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 35)
46-
- Code(Counter(0)) at (prev + 0, 35) to (start + 0, 36)
44+
Number of file 0 mappings: 1
45+
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 36)
4746
Highest counter ID seen: c0
4847

4948
Function name: async_closure::main::{closure#0}
50-
Raw bytes (14): 0x[01, 01, 00, 02, 01, 0b, 22, 00, 23, 01, 00, 23, 00, 24]
49+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0b, 22, 00, 24]
5150
Number of files: 1
5251
- file 0 => $DIR/async_closure.rs
5352
Number of expressions: 0
54-
Number of file 0 mappings: 2
55-
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 35)
56-
- Code(Counter(0)) at (prev + 0, 35) to (start + 0, 36)
53+
Number of file 0 mappings: 1
54+
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 36)
5755
Highest counter ID seen: c0
5856

5957
Function name: async_closure::main::{closure#0}::{closure#0}::<i16>
60-
Raw bytes (14): 0x[01, 01, 00, 02, 01, 0b, 22, 00, 23, 01, 00, 23, 00, 24]
58+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0b, 22, 00, 24]
6159
Number of files: 1
6260
- file 0 => $DIR/async_closure.rs
6361
Number of expressions: 0
64-
Number of file 0 mappings: 2
65-
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 35)
66-
- Code(Counter(0)) at (prev + 0, 35) to (start + 0, 36)
62+
Number of file 0 mappings: 1
63+
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 36)
6764
Highest counter ID seen: c0
6865

0 commit comments

Comments
 (0)