Skip to content

Commit a10d556

Browse files
authored
Unrolled build for rust-lang#118666
Rollup merge of rust-lang#118666 - Zalathar:body-closure, r=cjgillot coverage: Simplify the heuristic for ignoring `async fn` return spans The code for extracting coverage spans from MIR has a special heuristic for dealing with `async fn`, so that the function's closing brace does not have a confusing double count. The code implementing that heuristic is currently mixed in with the code for flushing remaining spans after the main refinement loop, making the refinement code harder to understand. We can solve that by hoisting the heuristic to an earlier stage, after the spans have been extracted and sorted but before they have been processed by the refinement loop. The coverage tests verify that the heuristic is still effective, so coverage mappings/reports for `async fn` have not changed. --- This PR also has the side-effect of fixing the `None some_prev` panic that started appearing after rust-lang#118525. The old code assumed that `prev` would always be present after the refinement loop. That was only true if the list of collected spans was non-empty, but prior to rust-lang#118525 that didn't seem to come up in practice. After that change, the list of collected spans could be empty in some specific circumstances, leading to panics. The new code uses an `if let` to inspect `prev`, which correctly does nothing if there is no span present.
2 parents c416699 + e01338a commit a10d556

File tree

5 files changed

+93
-31
lines changed

5 files changed

+93
-31
lines changed

compiler/rustc_mir_transform/src/coverage/spans.rs

+16-31
Original file line numberDiff line numberDiff line change
@@ -319,29 +319,16 @@ impl<'a> CoverageSpansGenerator<'a> {
319319
}
320320
}
321321

322-
let prev = self.take_prev();
323-
debug!(" AT END, adding last prev={prev:?}");
324-
325322
// Drain any remaining dups into the output.
326323
for dup in self.pending_dups.drain(..) {
327324
debug!(" ...adding at least one pending dup={:?}", dup);
328325
self.refined_spans.push(dup);
329326
}
330327

331-
// Async functions wrap a closure that implements the body to be executed. The enclosing
332-
// function is called and returns an `impl Future` without initially executing any of the
333-
// body. To avoid showing the return from the enclosing function as a "covered" return from
334-
// the closure, the enclosing function's `TerminatorKind::Return`s `CoverageSpan` is
335-
// excluded. The closure's `Return` is the only one that will be counted. This provides
336-
// adequate coverage, and more intuitive counts. (Avoids double-counting the closing brace
337-
// of the function body.)
338-
let body_ends_with_closure = if let Some(last_covspan) = self.refined_spans.last() {
339-
last_covspan.is_closure && last_covspan.span.hi() == self.body_span.hi()
340-
} else {
341-
false
342-
};
343-
344-
if !body_ends_with_closure {
328+
// There is usually a final span remaining in `prev` after the loop ends,
329+
// so add it to the output as well.
330+
if let Some(prev) = self.some_prev.take() {
331+
debug!(" AT END, adding last prev={prev:?}");
345332
self.refined_spans.push(prev);
346333
}
347334

@@ -398,38 +385,36 @@ impl<'a> CoverageSpansGenerator<'a> {
398385
self.refined_spans.push(macro_name_cov);
399386
}
400387

388+
#[track_caller]
401389
fn curr(&self) -> &CoverageSpan {
402-
self.some_curr
403-
.as_ref()
404-
.unwrap_or_else(|| bug!("invalid attempt to unwrap a None some_curr"))
390+
self.some_curr.as_ref().unwrap_or_else(|| bug!("some_curr is None (curr)"))
405391
}
406392

393+
#[track_caller]
407394
fn curr_mut(&mut self) -> &mut CoverageSpan {
408-
self.some_curr
409-
.as_mut()
410-
.unwrap_or_else(|| bug!("invalid attempt to unwrap a None some_curr"))
395+
self.some_curr.as_mut().unwrap_or_else(|| bug!("some_curr is None (curr_mut)"))
411396
}
412397

413398
/// If called, then the next call to `next_coverage_span()` will *not* update `prev` with the
414399
/// `curr` coverage span.
400+
#[track_caller]
415401
fn take_curr(&mut self) -> CoverageSpan {
416-
self.some_curr.take().unwrap_or_else(|| bug!("invalid attempt to unwrap a None some_curr"))
402+
self.some_curr.take().unwrap_or_else(|| bug!("some_curr is None (take_curr)"))
417403
}
418404

405+
#[track_caller]
419406
fn prev(&self) -> &CoverageSpan {
420-
self.some_prev
421-
.as_ref()
422-
.unwrap_or_else(|| bug!("invalid attempt to unwrap a None some_prev"))
407+
self.some_prev.as_ref().unwrap_or_else(|| bug!("some_prev is None (prev)"))
423408
}
424409

410+
#[track_caller]
425411
fn prev_mut(&mut self) -> &mut CoverageSpan {
426-
self.some_prev
427-
.as_mut()
428-
.unwrap_or_else(|| bug!("invalid attempt to unwrap a None some_prev"))
412+
self.some_prev.as_mut().unwrap_or_else(|| bug!("some_prev is None (prev_mut)"))
429413
}
430414

415+
#[track_caller]
431416
fn take_prev(&mut self) -> CoverageSpan {
432-
self.some_prev.take().unwrap_or_else(|| bug!("invalid attempt to unwrap a None some_prev"))
417+
self.some_prev.take().unwrap_or_else(|| bug!("some_prev is None (take_prev)"))
433418
}
434419

435420
/// If there are `pending_dups` but `prev` is not a matching dup (`prev.span` doesn't match the

compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs

+10
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ pub(super) fn mir_to_initial_sorted_coverage_spans(
4444
.then_with(|| Ord::cmp(&a.is_closure, &b.is_closure).reverse())
4545
});
4646

47+
// The desugaring of an async function includes a closure containing the
48+
// original function body, and a terminator that returns the `impl Future`.
49+
// That terminator will cause a confusing coverage count for the function's
50+
// closing brace, so discard everything after the body closure span.
51+
if let Some(body_closure_index) =
52+
initial_spans.iter().rposition(|covspan| covspan.is_closure && covspan.span == body_span)
53+
{
54+
initial_spans.truncate(body_closure_index + 1);
55+
}
56+
4757
initial_spans
4858
}
4959

tests/coverage/no_spans.cov-map

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Function name: no_spans::affected_function::{closure#0}
2+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 1b, 0c, 00, 0e]
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 + 27, 12) to (start + 0, 14)
8+

tests/coverage/no_spans.coverage

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
LL| |#![feature(coverage_attribute)]
2+
LL| |// edition: 2021
3+
LL| |
4+
LL| |// If the span extractor can't find any relevant spans for a function, the
5+
LL| |// refinement loop will terminate with nothing in its `prev` slot. If the
6+
LL| |// subsequent code tries to unwrap `prev`, it will panic.
7+
LL| |//
8+
LL| |// This scenario became more likely after #118525 started discarding spans that
9+
LL| |// can't be un-expanded back to within the function body.
10+
LL| |//
11+
LL| |// Regression test for "invalid attempt to unwrap a None some_prev", as seen
12+
LL| |// in issues such as #118643 and #118662.
13+
LL| |
14+
LL| |#[coverage(off)]
15+
LL| |fn main() {
16+
LL| | affected_function()();
17+
LL| |}
18+
LL| |
19+
LL| |macro_rules! macro_that_defines_a_function {
20+
LL| | (fn $name:ident () $body:tt) => {
21+
LL| | fn $name () -> impl Fn() $body
22+
LL| | }
23+
LL| |}
24+
LL| |
25+
LL| |macro_that_defines_a_function! {
26+
LL| | fn affected_function() {
27+
LL| 1| || ()
28+
LL| | }
29+
LL| |}
30+

tests/coverage/no_spans.rs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#![feature(coverage_attribute)]
2+
// edition: 2021
3+
4+
// If the span extractor can't find any relevant spans for a function, the
5+
// refinement loop will terminate with nothing in its `prev` slot. If the
6+
// subsequent code tries to unwrap `prev`, it will panic.
7+
//
8+
// This scenario became more likely after #118525 started discarding spans that
9+
// can't be un-expanded back to within the function body.
10+
//
11+
// Regression test for "invalid attempt to unwrap a None some_prev", as seen
12+
// in issues such as #118643 and #118662.
13+
14+
#[coverage(off)]
15+
fn main() {
16+
affected_function()();
17+
}
18+
19+
macro_rules! macro_that_defines_a_function {
20+
(fn $name:ident () $body:tt) => {
21+
fn $name () -> impl Fn() $body
22+
}
23+
}
24+
25+
macro_that_defines_a_function! {
26+
fn affected_function() {
27+
|| ()
28+
}
29+
}

0 commit comments

Comments
 (0)