Skip to content

Commit d96f351

Browse files
committed
Addressed feedback from 2020-12-01
Added one more test (two files) showing coverage of generics and unused functions across crates. Created and referenced new Issues, as requested. Added comments. Added a note about the possible effects of compiler options on LLVM coverage maps.
1 parent def932c commit d96f351

28 files changed

+1797
-366
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs

+19
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ fn add_unreachable_coverage<'tcx>(
262262
tcx: TyCtxt<'tcx>,
263263
function_coverage_map: &mut FxHashMap<Instance<'tcx>, FunctionCoverage<'tcx>>,
264264
) {
265+
// FIXME(#79622): Can this solution be simplified and/or improved? Are there other sources
266+
// of compiler state data that might help (or better sources that could be exposed, but
267+
// aren't yet)?
268+
265269
// Note: If the crate *only* defines generic functions, there are no codegenerated non-generic
266270
// functions to add any unreachable code to. In this case, the unreachable code regions will
267271
// have no coverage, instead of having coverage with zero executions.
@@ -359,6 +363,21 @@ fn add_unreachable_coverage<'tcx>(
359363
for def_id in
360364
unreachable_def_ids_by_file.remove(&covered_file_name).into_iter().flatten()
361365
{
366+
// Note, this loop adds an unreachable code regions for each MIR-derived region.
367+
// Alternatively, we could add a single code region for the maximum span of all
368+
// code regions here.
369+
//
370+
// Observed downsides of this approach are:
371+
//
372+
// 1. The coverage results will appear inconsistent compared with the same (or
373+
// similar) code in a function that is reached.
374+
// 2. If the function is unreachable from one crate but reachable when compiling
375+
// another referencing crate (such as a cross-crate reference to a
376+
// generic function or inlined function), actual coverage regions overlaid
377+
// on a single larger code span of `Zero` coverage can appear confusing or
378+
// wrong. Chaning the unreachable coverage from a `code_region` to a
379+
// `gap_region` can help, but still can look odd with `0` line counts for
380+
// lines between executed (> 0) lines (such as for blank lines or comments).
362381
for &region in tcx.covered_code_regions(def_id) {
363382
function_coverage.add_unreachable_region(region.clone());
364383
}

compiler/rustc_mir/src/transform/coverage/graph.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ impl CoverageGraph {
140140

141141
// The following `TerminatorKind`s are either not expected outside an unwind branch,
142142
// or they should not (under normal circumstances) branch. Coverage graphs are
143-
// simplified by assuring coverage results are accurate for well-behaved programs.
143+
// simplified by assuring coverage results are accurate for program executions that
144+
// don't panic.
145+
//
144146
// Programs that panic and unwind may record slightly inaccurate coverage results
145147
// for a coverage region containing the `Terminator` that began the panic. This
146148
// is as intended. (See Issue #78544 for a possible future option to support

compiler/rustc_mir/src/transform/coverage/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,8 @@ fn fn_sig_and_body<'tcx>(
499499
tcx: TyCtxt<'tcx>,
500500
def_id: DefId,
501501
) -> (Option<&'tcx rustc_hir::FnSig<'tcx>>, &'tcx rustc_hir::Body<'tcx>) {
502+
// FIXME(#79625): Consider improving MIR to provide the information needed, to avoid going back
503+
// to HIR for it.
502504
let hir_node = tcx.hir().get_if_local(def_id).expect("expected DefId is local");
503505
let fn_body_id = hir::map::associated_body(hir_node).expect("HIR node is a function with body");
504506
(hir::map::fn_sig(hir_node), tcx.hir().body(fn_body_id))

compiler/rustc_mir/src/transform/coverage/spans.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,12 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
359359
}
360360

361361
// Async functions wrap a closure that implements the body to be executed. The enclosing
362-
// function is initially called, posts the closure to the executor, and returns. To avoid
363-
// showing the return from the enclosing function as a "covered" return from the closure,
364-
// the enclosing function's `TerminatorKind::Return`s `CoverageSpan` is excluded. The
365-
// closure's `Return` is the only one that will be counted. This provides adequate
366-
// coverage, and more intuitive counts. (Avoids double-counting the closing brace of the
367-
// function body.)
362+
// function is called and returns an `impl Future` without initially executing any of the
363+
// body. To avoid showing the return from the enclosing function as a "covered" return from
364+
// the closure, the enclosing function's `TerminatorKind::Return`s `CoverageSpan` is
365+
// excluded. The closure's `Return` is the only one that will be counted. This provides
366+
// adequate coverage, and more intuitive counts. (Avoids double-counting the closing brace
367+
// of the function body.)
368368
let body_ends_with_closure = if let Some(last_covspan) = refined_spans.last() {
369369
last_covspan.is_closure && last_covspan.span.hi() == self.body_span.hi()
370370
} else {

src/doc/unstable-book/src/compiler-flags/source-based-code-coverage.md

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ $ RUSTC=$HOME/rust/build/x86_64-unknown-linux-gnu/stage1/bin/rustc \
7676
cargo build --example formatjson5
7777
```
7878

79+
Note that some compiler options, combined with `-Zinstrument-coverage`, can produce LLVM IR and/or linked binaries that are incompatible with LLVM coverage maps. For example, coverage requires references to actual functions in LLVM IR. If any covered function is optimized out, the coverage tools may not be able to process the coverage results. If you need to pass additional options, with coverage enabled, test them early, to confirm you will get the coverage results you expect.
80+
7981
## Running the instrumented binary to generate raw coverage profiling data
8082

8183
In the previous example, `cargo` generated the coverage-instrumented binary `formatjson5`:

src/test/run-make-fulldeps/coverage-reports/expected_export_coverage.async.json

+26-26
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,50 @@
66
"filename": "../coverage/async.rs",
77
"summary": {
88
"functions": {
9-
"count": 16,
10-
"covered": 15,
11-
"percent": 93.75
9+
"count": 17,
10+
"covered": 16,
11+
"percent": 94.11764705882352
1212
},
1313
"instantiations": {
14-
"count": 16,
15-
"covered": 15,
16-
"percent": 93.75
14+
"count": 17,
15+
"covered": 16,
16+
"percent": 94.11764705882352
1717
},
1818
"lines": {
19-
"count": 102,
20-
"covered": 75,
21-
"percent": 73.52941176470588
19+
"count": 105,
20+
"covered": 77,
21+
"percent": 73.33333333333333
2222
},
2323
"regions": {
24-
"count": 76,
25-
"covered": 35,
26-
"notcovered": 41,
27-
"percent": 46.05263157894737
24+
"count": 78,
25+
"covered": 36,
26+
"notcovered": 42,
27+
"percent": 46.15384615384615
2828
}
2929
}
3030
}
3131
],
3232
"totals": {
3333
"functions": {
34-
"count": 16,
35-
"covered": 15,
36-
"percent": 93.75
34+
"count": 17,
35+
"covered": 16,
36+
"percent": 94.11764705882352
3737
},
3838
"instantiations": {
39-
"count": 16,
40-
"covered": 15,
41-
"percent": 93.75
39+
"count": 17,
40+
"covered": 16,
41+
"percent": 94.11764705882352
4242
},
4343
"lines": {
44-
"count": 102,
45-
"covered": 75,
46-
"percent": 73.52941176470588
44+
"count": 105,
45+
"covered": 77,
46+
"percent": 73.33333333333333
4747
},
4848
"regions": {
49-
"count": 76,
50-
"covered": 35,
51-
"notcovered": 41,
52-
"percent": 46.05263157894737
49+
"count": 78,
50+
"covered": 36,
51+
"notcovered": 42,
52+
"percent": 46.15384615384615
5353
}
5454
}
5555
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
{
2+
"data": [
3+
{
4+
"files": [
5+
{
6+
"filename": "../coverage/used_crate/mod.rs",
7+
"summary": {
8+
"functions": {
9+
"count": 3,
10+
"covered": 3,
11+
"percent": 100
12+
},
13+
"instantiations": {
14+
"count": 4,
15+
"covered": 4,
16+
"percent": 100
17+
},
18+
"lines": {
19+
"count": 31,
20+
"covered": 14,
21+
"percent": 45.16129032258064
22+
},
23+
"regions": {
24+
"count": 16,
25+
"covered": 6,
26+
"notcovered": 10,
27+
"percent": 37.5
28+
}
29+
}
30+
},
31+
{
32+
"filename": "../coverage/uses_crate.rs",
33+
"summary": {
34+
"functions": {
35+
"count": 1,
36+
"covered": 1,
37+
"percent": 100
38+
},
39+
"instantiations": {
40+
"count": 1,
41+
"covered": 1,
42+
"percent": 100
43+
},
44+
"lines": {
45+
"count": 6,
46+
"covered": 6,
47+
"percent": 100
48+
},
49+
"regions": {
50+
"count": 1,
51+
"covered": 1,
52+
"notcovered": 0,
53+
"percent": 100
54+
}
55+
}
56+
}
57+
],
58+
"totals": {
59+
"functions": {
60+
"count": 4,
61+
"covered": 4,
62+
"percent": 100
63+
},
64+
"instantiations": {
65+
"count": 5,
66+
"covered": 5,
67+
"percent": 100
68+
},
69+
"lines": {
70+
"count": 37,
71+
"covered": 20,
72+
"percent": 54.054054054054056
73+
},
74+
"regions": {
75+
"count": 17,
76+
"covered": 7,
77+
"notcovered": 10,
78+
"percent": 41.17647058823529
79+
}
80+
}
81+
}
82+
],
83+
"type": "llvm.coverage.json.export",
84+
"version": "2.0.1"
85+
}

src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async.txt

+39-35
Original file line numberDiff line numberDiff line change
@@ -93,39 +93,43 @@
9393
88| | }
9494
89| 1|}
9595
90| |
96-
91| 1|fn main() {
97-
92| 1| let _ = g(10);
98-
93| 1| let _ = h(9);
99-
94| 1| let mut future = Box::pin(i(8));
100-
95| 1| j(7);
101-
96| 1| l(6);
102-
97| 1| executor::block_on(future.as_mut());
103-
98| 1|}
104-
99| |
105-
100| |mod executor {
106-
101| | use core::{
107-
102| | future::Future,
108-
103| | pin::Pin,
109-
104| | task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
110-
105| | };
111-
106| |
112-
107| 1| pub fn block_on<F: Future>(mut future: F) -> F::Output {
113-
108| 1| let mut future = unsafe { Pin::new_unchecked(&mut future) };
114-
109| 1|
115-
110| 1| static VTABLE: RawWakerVTable = RawWakerVTable::new(
116-
111| 1| |_| unimplemented!("clone"),
117-
112| 1| |_| unimplemented!("wake"),
118-
113| 1| |_| unimplemented!("wake_by_ref"),
119-
114| 1| |_| (),
120-
115| 1| );
121-
116| 1| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) };
122-
117| 1| let mut context = Context::from_waker(&waker);
123-
118| |
124-
119| | loop {
125-
120| 1| if let Poll::Ready(val) = future.as_mut().poll(&mut context) {
126-
121| 1| break val;
127-
122| 0| }
128-
123| | }
129-
124| 1| }
130-
125| |}
96+
91| 1|async fn m(x: u8) -> u8 { x - 1 }
97+
^0
98+
92| |
99+
93| 1|fn main() {
100+
94| 1| let _ = g(10);
101+
95| 1| let _ = h(9);
102+
96| 1| let mut future = Box::pin(i(8));
103+
97| 1| j(7);
104+
98| 1| l(6);
105+
99| 1| let _ = m(5);
106+
100| 1| executor::block_on(future.as_mut());
107+
101| 1|}
108+
102| |
109+
103| |mod executor {
110+
104| | use core::{
111+
105| | future::Future,
112+
106| | pin::Pin,
113+
107| | task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
114+
108| | };
115+
109| |
116+
110| 1| pub fn block_on<F: Future>(mut future: F) -> F::Output {
117+
111| 1| let mut future = unsafe { Pin::new_unchecked(&mut future) };
118+
112| 1|
119+
113| 1| static VTABLE: RawWakerVTable = RawWakerVTable::new(
120+
114| 1| |_| unimplemented!("clone"),
121+
115| 1| |_| unimplemented!("wake"),
122+
116| 1| |_| unimplemented!("wake_by_ref"),
123+
117| 1| |_| (),
124+
118| 1| );
125+
119| 1| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) };
126+
120| 1| let mut context = Context::from_waker(&waker);
127+
121| |
128+
122| | loop {
129+
123| 1| if let Poll::Ready(val) = future.as_mut().poll(&mut context) {
130+
124| 1| break val;
131+
125| 0| }
132+
126| | }
133+
127| 1| }
134+
128| |}
131135

src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt

+15
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,19 @@
4545
44| |`function_source_hash` without a code region, if necessary.
4646
45| |
4747
46| |*/
48+
47| |
49+
48| |// FIXME(#79626): The derived traits get coverage, which is great, but some of the traits appear
50+
49| |// to get two coverage execution counts at different positions:
51+
50| |//
52+
51| |// ```text
53+
52| |// 4| 2|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
54+
53| |// ^0 ^0 ^0 ^0 ^1 ^0 ^0^0
55+
54| |// ```text
56+
55| |//
57+
56| |// `PartialEq`, `PartialOrd`, and `Ord` (and possibly `Eq`, if the trait name was longer than 2
58+
57| |// characters) have counts at their first and last characters.
59+
58| |//
60+
59| |// Why is this? Why does `PartialOrd` have two values (1 and 0)? This must mean we are checking
61+
60| |// distinct coverages, so maybe we don't want to eliminate one of them. Should we merge them?
62+
61| |// If merged, do we lose some information?
4863

0 commit comments

Comments
 (0)