Skip to content

Commit 6b78377

Browse files
committed
Auto merge of #117123 - Zalathar:bad-counter-ids, r=petrochenkov
coverage: Consistently remove unused counter IDs from expressions/mappings If some coverage counters were removed by MIR optimizations, we need to take care not to refer to those counter IDs in coverage mappings, and instead replace them with a constant zero value. If we don't, `llvm-cov` might see a too-large counter ID and silently discard the entire function from its coverage reports. Fixes #117012.
2 parents 6a66ca2 + 230dd5b commit 6b78377

12 files changed

+474
-165
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs

+41-31
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ impl<'tcx> FunctionCoverageCollector<'tcx> {
102102
// have zero as both of their operands, and will therefore always have
103103
// a value of zero. Other expressions that refer to these as operands
104104
// can have those operands replaced with `CovTerm::Zero`.
105-
let mut zero_expressions = FxIndexSet::default();
105+
let mut zero_expressions = ZeroExpressions::default();
106106

107107
// Simplify a copy of each expression based on lower-numbered expressions,
108108
// and then update the set of always-zero expressions if necessary.
@@ -131,16 +131,16 @@ impl<'tcx> FunctionCoverageCollector<'tcx> {
131131
)
132132
};
133133

134-
// If an operand refers to an expression that is always zero, then
135-
// that operand can be replaced with `CovTerm::Zero`.
136-
let maybe_set_operand_to_zero = |operand: &mut CovTerm| match *operand {
137-
CovTerm::Expression(id) => {
134+
// If an operand refers to a counter or expression that is always
135+
// zero, then that operand can be replaced with `CovTerm::Zero`.
136+
let maybe_set_operand_to_zero = |operand: &mut CovTerm| {
137+
if let CovTerm::Expression(id) = *operand {
138138
assert_operand_expression_is_lower(id);
139-
if zero_expressions.contains(&id) {
140-
*operand = CovTerm::Zero;
141-
}
142139
}
143-
_ => (),
140+
141+
if is_zero_term(&self.counters_seen, &zero_expressions, *operand) {
142+
*operand = CovTerm::Zero;
143+
}
144144
};
145145
maybe_set_operand_to_zero(&mut lhs);
146146
maybe_set_operand_to_zero(&mut rhs);
@@ -159,7 +159,7 @@ impl<'tcx> FunctionCoverageCollector<'tcx> {
159159
}
160160
}
161161

162-
ZeroExpressions(zero_expressions)
162+
zero_expressions
163163
}
164164

165165
pub(crate) fn into_finished(self) -> FunctionCoverage<'tcx> {
@@ -205,19 +205,14 @@ impl<'tcx> FunctionCoverage<'tcx> {
205205
// thing on the Rust side unless we're confident we can do much better.
206206
// (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.)
207207

208-
let counter_from_operand = |operand: CovTerm| match operand {
209-
CovTerm::Expression(id) if self.zero_expressions.contains(id) => Counter::ZERO,
210-
_ => Counter::from_term(operand),
211-
};
212-
213208
self.function_coverage_info.expressions.iter().map(move |&Expression { lhs, op, rhs }| {
214209
CounterExpression {
215-
lhs: counter_from_operand(lhs),
210+
lhs: self.counter_for_term(lhs),
216211
kind: match op {
217212
Op::Add => ExprKind::Add,
218213
Op::Subtract => ExprKind::Subtract,
219214
},
220-
rhs: counter_from_operand(rhs),
215+
rhs: self.counter_for_term(rhs),
221216
}
222217
})
223218
}
@@ -227,34 +222,49 @@ impl<'tcx> FunctionCoverage<'tcx> {
227222
pub(crate) fn counter_regions(
228223
&self,
229224
) -> impl Iterator<Item = (Counter, &CodeRegion)> + ExactSizeIterator {
230-
// Historically, mappings were stored directly in counter/expression
231-
// statements in MIR, and MIR optimizations would sometimes remove them.
232-
// That's mostly no longer true, so now we detect cases where that would
233-
// have happened, and zero out the corresponding mappings here instead.
234-
let counter_for_term = move |term: CovTerm| {
235-
let force_to_zero = match term {
236-
CovTerm::Counter(id) => !self.counters_seen.contains(id),
237-
CovTerm::Expression(id) => self.zero_expressions.contains(id),
238-
CovTerm::Zero => false,
239-
};
240-
if force_to_zero { Counter::ZERO } else { Counter::from_term(term) }
241-
};
242-
243225
self.function_coverage_info.mappings.iter().map(move |mapping| {
244226
let &Mapping { term, ref code_region } = mapping;
245-
let counter = counter_for_term(term);
227+
let counter = self.counter_for_term(term);
246228
(counter, code_region)
247229
})
248230
}
231+
232+
fn counter_for_term(&self, term: CovTerm) -> Counter {
233+
if is_zero_term(&self.counters_seen, &self.zero_expressions, term) {
234+
Counter::ZERO
235+
} else {
236+
Counter::from_term(term)
237+
}
238+
}
249239
}
250240

251241
/// Set of expression IDs that are known to always evaluate to zero.
252242
/// Any mapping or expression operand that refers to these expressions can have
253243
/// that reference replaced with a constant zero value.
244+
#[derive(Default)]
254245
struct ZeroExpressions(FxIndexSet<ExpressionId>);
255246

256247
impl ZeroExpressions {
248+
fn insert(&mut self, id: ExpressionId) {
249+
self.0.insert(id);
250+
}
251+
257252
fn contains(&self, id: ExpressionId) -> bool {
258253
self.0.contains(&id)
259254
}
260255
}
256+
257+
/// Returns `true` if the given term is known to have a value of zero, taking
258+
/// into account knowledge of which counters are unused and which expressions
259+
/// are always zero.
260+
fn is_zero_term(
261+
counters_seen: &BitSet<CounterId>,
262+
zero_expressions: &ZeroExpressions,
263+
term: CovTerm,
264+
) -> bool {
265+
match term {
266+
CovTerm::Zero => true,
267+
CovTerm::Counter(id) => !counters_seen.contains(id),
268+
CovTerm::Expression(id) => zero_expressions.contains(id),
269+
}
270+
}

tests/coverage-map/fn_sig_into_try.cov-map

+15-15
Original file line numberDiff line numberDiff line change
@@ -7,47 +7,47 @@ Number of file 0 mappings: 1
77
- Code(Counter(0)) at (prev + 10, 1) to (start + 4, 2)
88

99
Function name: fn_sig_into_try::b
10-
Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 10, 01, 02, 0f, 00, 02, 0f, 00, 10, 02, 01, 05, 00, 0c, 07, 01, 01, 00, 02]
10+
Raw bytes (28): 0x[01, 01, 02, 01, 00, 00, 02, 04, 01, 10, 01, 02, 0f, 00, 02, 0f, 00, 10, 02, 01, 05, 00, 0c, 07, 01, 01, 00, 02]
1111
Number of files: 1
1212
- file 0 => global file 1
1313
Number of expressions: 2
14-
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
15-
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
14+
- expression 0 operands: lhs = Counter(0), rhs = Zero
15+
- expression 1 operands: lhs = Zero, rhs = Expression(0, Sub)
1616
Number of file 0 mappings: 4
1717
- Code(Counter(0)) at (prev + 16, 1) to (start + 2, 15)
1818
- Code(Zero) at (prev + 2, 15) to (start + 0, 16)
1919
- Code(Expression(0, Sub)) at (prev + 1, 5) to (start + 0, 12)
20-
= (c0 - c1)
20+
= (c0 - Zero)
2121
- Code(Expression(1, Add)) at (prev + 1, 1) to (start + 0, 2)
22-
= (c1 + (c0 - c1))
22+
= (Zero + (c0 - Zero))
2323

2424
Function name: fn_sig_into_try::c
25-
Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 16, 01, 02, 17, 00, 02, 17, 00, 18, 02, 01, 05, 00, 0c, 07, 01, 01, 00, 02]
25+
Raw bytes (28): 0x[01, 01, 02, 01, 00, 00, 02, 04, 01, 16, 01, 02, 17, 00, 02, 17, 00, 18, 02, 01, 05, 00, 0c, 07, 01, 01, 00, 02]
2626
Number of files: 1
2727
- file 0 => global file 1
2828
Number of expressions: 2
29-
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
30-
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
29+
- expression 0 operands: lhs = Counter(0), rhs = Zero
30+
- expression 1 operands: lhs = Zero, rhs = Expression(0, Sub)
3131
Number of file 0 mappings: 4
3232
- Code(Counter(0)) at (prev + 22, 1) to (start + 2, 23)
3333
- Code(Zero) at (prev + 2, 23) to (start + 0, 24)
3434
- Code(Expression(0, Sub)) at (prev + 1, 5) to (start + 0, 12)
35-
= (c0 - c1)
35+
= (c0 - Zero)
3636
- Code(Expression(1, Add)) at (prev + 1, 1) to (start + 0, 2)
37-
= (c1 + (c0 - c1))
37+
= (Zero + (c0 - Zero))
3838

3939
Function name: fn_sig_into_try::d
40-
Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 1c, 01, 03, 0f, 00, 03, 0f, 00, 10, 02, 01, 05, 00, 0c, 07, 01, 01, 00, 02]
40+
Raw bytes (28): 0x[01, 01, 02, 01, 00, 00, 02, 04, 01, 1c, 01, 03, 0f, 00, 03, 0f, 00, 10, 02, 01, 05, 00, 0c, 07, 01, 01, 00, 02]
4141
Number of files: 1
4242
- file 0 => global file 1
4343
Number of expressions: 2
44-
- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
45-
- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub)
44+
- expression 0 operands: lhs = Counter(0), rhs = Zero
45+
- expression 1 operands: lhs = Zero, rhs = Expression(0, Sub)
4646
Number of file 0 mappings: 4
4747
- Code(Counter(0)) at (prev + 28, 1) to (start + 3, 15)
4848
- Code(Zero) at (prev + 3, 15) to (start + 0, 16)
4949
- Code(Expression(0, Sub)) at (prev + 1, 5) to (start + 0, 12)
50-
= (c0 - c1)
50+
= (c0 - Zero)
5151
- Code(Expression(1, Add)) at (prev + 1, 1) to (start + 0, 2)
52-
= (c1 + (c0 - c1))
52+
= (Zero + (c0 - Zero))
5353

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
Function name: <bad_counter_ids::Foo as core::cmp::PartialEq>::eq
2+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0c, 11, 00, 1a]
3+
Number of files: 1
4+
- file 0 => global file 1
5+
Number of expressions: 0
6+
Number of file 0 mappings: 1
7+
- Code(Counter(0)) at (prev + 12, 17) to (start + 0, 26)
8+
9+
Function name: <bad_counter_ids::Foo as core::fmt::Debug>::fmt
10+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0c, 0a, 00, 0f]
11+
Number of files: 1
12+
- file 0 => global file 1
13+
Number of expressions: 0
14+
Number of file 0 mappings: 1
15+
- Code(Counter(0)) at (prev + 12, 10) to (start + 0, 15)
16+
17+
Function name: bad_counter_ids::eq_bad
18+
Raw bytes (14): 0x[01, 01, 00, 02, 01, 23, 01, 02, 1f, 00, 03, 01, 00, 02]
19+
Number of files: 1
20+
- file 0 => global file 1
21+
Number of expressions: 0
22+
Number of file 0 mappings: 2
23+
- Code(Counter(0)) at (prev + 35, 1) to (start + 2, 31)
24+
- Code(Zero) at (prev + 3, 1) to (start + 0, 2)
25+
26+
Function name: bad_counter_ids::eq_bad_message
27+
Raw bytes (21): 0x[01, 01, 01, 01, 00, 03, 01, 28, 01, 02, 0f, 02, 02, 20, 00, 2b, 00, 01, 01, 00, 02]
28+
Number of files: 1
29+
- file 0 => global file 1
30+
Number of expressions: 1
31+
- expression 0 operands: lhs = Counter(0), rhs = Zero
32+
Number of file 0 mappings: 3
33+
- Code(Counter(0)) at (prev + 40, 1) to (start + 2, 15)
34+
- Code(Expression(0, Sub)) at (prev + 2, 32) to (start + 0, 43)
35+
= (c0 - Zero)
36+
- Code(Zero) at (prev + 1, 1) to (start + 0, 2)
37+
38+
Function name: bad_counter_ids::eq_good
39+
Raw bytes (14): 0x[01, 01, 00, 02, 01, 0f, 01, 02, 1f, 05, 03, 01, 00, 02]
40+
Number of files: 1
41+
- file 0 => global file 1
42+
Number of expressions: 0
43+
Number of file 0 mappings: 2
44+
- Code(Counter(0)) at (prev + 15, 1) to (start + 2, 31)
45+
- Code(Counter(1)) at (prev + 3, 1) to (start + 0, 2)
46+
47+
Function name: bad_counter_ids::eq_good_message
48+
Raw bytes (19): 0x[01, 01, 00, 03, 01, 14, 01, 02, 0f, 00, 02, 20, 00, 2b, 05, 01, 01, 00, 02]
49+
Number of files: 1
50+
- file 0 => global file 1
51+
Number of expressions: 0
52+
Number of file 0 mappings: 3
53+
- Code(Counter(0)) at (prev + 20, 1) to (start + 2, 15)
54+
- Code(Zero) at (prev + 2, 32) to (start + 0, 43)
55+
- Code(Counter(1)) at (prev + 1, 1) to (start + 0, 2)
56+
57+
Function name: bad_counter_ids::ne_bad
58+
Raw bytes (14): 0x[01, 01, 00, 02, 01, 2d, 01, 02, 1f, 00, 03, 01, 00, 02]
59+
Number of files: 1
60+
- file 0 => global file 1
61+
Number of expressions: 0
62+
Number of file 0 mappings: 2
63+
- Code(Counter(0)) at (prev + 45, 1) to (start + 2, 31)
64+
- Code(Zero) at (prev + 3, 1) to (start + 0, 2)
65+
66+
Function name: bad_counter_ids::ne_bad_message
67+
Raw bytes (19): 0x[01, 01, 00, 03, 01, 32, 01, 02, 0f, 05, 02, 20, 00, 2b, 00, 01, 01, 00, 02]
68+
Number of files: 1
69+
- file 0 => global file 1
70+
Number of expressions: 0
71+
Number of file 0 mappings: 3
72+
- Code(Counter(0)) at (prev + 50, 1) to (start + 2, 15)
73+
- Code(Counter(1)) at (prev + 2, 32) to (start + 0, 43)
74+
- Code(Zero) at (prev + 1, 1) to (start + 0, 2)
75+
76+
Function name: bad_counter_ids::ne_good
77+
Raw bytes (16): 0x[01, 01, 01, 01, 00, 02, 01, 19, 01, 02, 1f, 02, 03, 01, 00, 02]
78+
Number of files: 1
79+
- file 0 => global file 1
80+
Number of expressions: 1
81+
- expression 0 operands: lhs = Counter(0), rhs = Zero
82+
Number of file 0 mappings: 2
83+
- Code(Counter(0)) at (prev + 25, 1) to (start + 2, 31)
84+
- Code(Expression(0, Sub)) at (prev + 3, 1) to (start + 0, 2)
85+
= (c0 - Zero)
86+
87+
Function name: bad_counter_ids::ne_good_message
88+
Raw bytes (21): 0x[01, 01, 01, 01, 00, 03, 01, 1e, 01, 02, 0f, 00, 02, 20, 00, 2b, 02, 01, 01, 00, 02]
89+
Number of files: 1
90+
- file 0 => global file 1
91+
Number of expressions: 1
92+
- expression 0 operands: lhs = Counter(0), rhs = Zero
93+
Number of file 0 mappings: 3
94+
- Code(Counter(0)) at (prev + 30, 1) to (start + 2, 15)
95+
- Code(Zero) at (prev + 2, 32) to (start + 0, 43)
96+
- Code(Expression(0, Sub)) at (prev + 1, 1) to (start + 0, 2)
97+
= (c0 - Zero)
98+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#![feature(coverage_attribute)]
2+
// compile-flags: --edition=2021 -Copt-level=0 -Zmir-opt-level=3
3+
4+
// Regression test for <https://github.com/rust-lang/rust/issues/117012>.
5+
//
6+
// If some coverage counters were removed by MIR optimizations, we need to take
7+
// care not to refer to those counter IDs in coverage mappings, and instead
8+
// replace them with a constant zero value. If we don't, `llvm-cov` might see
9+
// a too-large counter ID and silently discard the entire function from its
10+
// coverage reports.
11+
12+
#[derive(Debug, PartialEq, Eq)]
13+
struct Foo(u32);
14+
15+
fn eq_good() {
16+
println!("a");
17+
assert_eq!(Foo(1), Foo(1));
18+
}
19+
20+
fn eq_good_message() {
21+
println!("b");
22+
assert_eq!(Foo(1), Foo(1), "message b");
23+
}
24+
25+
fn ne_good() {
26+
println!("c");
27+
assert_ne!(Foo(1), Foo(3));
28+
}
29+
30+
fn ne_good_message() {
31+
println!("d");
32+
assert_ne!(Foo(1), Foo(3), "message d");
33+
}
34+
35+
fn eq_bad() {
36+
println!("e");
37+
assert_eq!(Foo(1), Foo(3));
38+
}
39+
40+
fn eq_bad_message() {
41+
println!("f");
42+
assert_eq!(Foo(1), Foo(3), "message f");
43+
}
44+
45+
fn ne_bad() {
46+
println!("g");
47+
assert_ne!(Foo(1), Foo(1));
48+
}
49+
50+
fn ne_bad_message() {
51+
println!("h");
52+
assert_ne!(Foo(1), Foo(1), "message h");
53+
}
54+
55+
#[coverage(off)]
56+
fn main() {
57+
eq_good();
58+
eq_good_message();
59+
ne_good();
60+
ne_good_message();
61+
62+
assert!(std::panic::catch_unwind(eq_bad).is_err());
63+
assert!(std::panic::catch_unwind(eq_bad_message).is_err());
64+
assert!(std::panic::catch_unwind(ne_bad).is_err());
65+
assert!(std::panic::catch_unwind(ne_bad_message).is_err());
66+
}

0 commit comments

Comments
 (0)