From fe420dc46ecf988eb13c34526feea18fd8daef6b Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 22 Jan 2024 13:56:37 +1100 Subject: [PATCH 1/5] coverage: Test for closure body that is a single bang-macro --- tests/coverage/macro_in_closure.cov-map | 16 ++++++++++++++++ tests/coverage/macro_in_closure.coverage | 18 ++++++++++++++++++ tests/coverage/macro_in_closure.rs | 17 +++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 tests/coverage/macro_in_closure.cov-map create mode 100644 tests/coverage/macro_in_closure.coverage create mode 100644 tests/coverage/macro_in_closure.rs diff --git a/tests/coverage/macro_in_closure.cov-map b/tests/coverage/macro_in_closure.cov-map new file mode 100644 index 00000000000..2feaab717b5 --- /dev/null +++ b/tests/coverage/macro_in_closure.cov-map @@ -0,0 +1,16 @@ +Function name: macro_in_closure::NO_BLOCK::{closure#0} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 07, 1c, 00, 2d] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 7, 28) to (start + 0, 45) + +Function name: macro_in_closure::WITH_BLOCK::{closure#0} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 09, 1e, 02, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 9, 30) to (start + 2, 2) + diff --git a/tests/coverage/macro_in_closure.coverage b/tests/coverage/macro_in_closure.coverage new file mode 100644 index 00000000000..7f6f873439d --- /dev/null +++ b/tests/coverage/macro_in_closure.coverage @@ -0,0 +1,18 @@ + LL| |#![feature(coverage_attribute)] + LL| |// edition: 2021 + LL| | + LL| |// If a closure body consists entirely of a single bang-macro invocation, the + LL| |// body span ends up inside the macro-expansion, so we need to un-expand it + LL| |// back to the declaration site. + LL| 1|static NO_BLOCK: fn() = || println!("hello"); + LL| | + LL| 1|static WITH_BLOCK: fn() = || { + LL| 1| println!("hello"); + LL| 1|}; + LL| | + LL| |#[coverage(off)] + LL| |fn main() { + LL| | NO_BLOCK(); + LL| | WITH_BLOCK(); + LL| |} + diff --git a/tests/coverage/macro_in_closure.rs b/tests/coverage/macro_in_closure.rs new file mode 100644 index 00000000000..6948c9079c0 --- /dev/null +++ b/tests/coverage/macro_in_closure.rs @@ -0,0 +1,17 @@ +#![feature(coverage_attribute)] +// edition: 2021 + +// If a closure body consists entirely of a single bang-macro invocation, the +// body span ends up inside the macro-expansion, so we need to un-expand it +// back to the declaration site. +static NO_BLOCK: fn() = || println!("hello"); + +static WITH_BLOCK: fn() = || { + println!("hello"); +}; + +#[coverage(off)] +fn main() { + NO_BLOCK(); + WITH_BLOCK(); +} From 8dd2b37462a2017207d4ecb01d3123881d8ec2f2 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 20 Jan 2024 18:23:49 +1100 Subject: [PATCH 2/5] coverage: Add a test for `#[coverage(..)]` on closures --- tests/coverage/coverage_attr_closure.cov-map | 34 +++++++++++++++ tests/coverage/coverage_attr_closure.coverage | 43 +++++++++++++++++++ tests/coverage/coverage_attr_closure.rs | 42 ++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 tests/coverage/coverage_attr_closure.cov-map create mode 100644 tests/coverage/coverage_attr_closure.coverage create mode 100644 tests/coverage/coverage_attr_closure.rs diff --git a/tests/coverage/coverage_attr_closure.cov-map b/tests/coverage/coverage_attr_closure.cov-map new file mode 100644 index 00000000000..2208b28fd41 --- /dev/null +++ b/tests/coverage/coverage_attr_closure.cov-map @@ -0,0 +1,34 @@ +Function name: coverage_attr_closure::GLOBAL_CLOSURE_ON::{closure#0} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 06, 0f, 02, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 6, 15) to (start + 2, 2) + +Function name: coverage_attr_closure::contains_closures_off::{closure#0} (unused) +Raw bytes (9): 0x[01, 01, 00, 01, 00, 1d, 13, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Zero) at (prev + 29, 19) to (start + 2, 6) + +Function name: coverage_attr_closure::contains_closures_on +Raw bytes (19): 0x[01, 01, 00, 03, 01, 0f, 01, 02, 05, 01, 04, 06, 02, 05, 01, 04, 06, 01, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 3 +- Code(Counter(0)) at (prev + 15, 1) to (start + 2, 5) +- Code(Counter(0)) at (prev + 4, 6) to (start + 2, 5) +- Code(Counter(0)) at (prev + 4, 6) to (start + 1, 2) + +Function name: coverage_attr_closure::contains_closures_on::{closure#0} (unused) +Raw bytes (9): 0x[01, 01, 00, 01, 00, 11, 13, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Zero) at (prev + 17, 19) to (start + 2, 6) + diff --git a/tests/coverage/coverage_attr_closure.coverage b/tests/coverage/coverage_attr_closure.coverage new file mode 100644 index 00000000000..32c75b40d83 --- /dev/null +++ b/tests/coverage/coverage_attr_closure.coverage @@ -0,0 +1,43 @@ + LL| |#![feature(coverage_attribute, stmt_expr_attributes)] + LL| |#![allow(dead_code)] + LL| |// edition: 2021 + LL| | + LL| |static GLOBAL_CLOSURE_ON: fn(&str) = #[coverage(on)] + LL| 0||input: &str| { + LL| 0| println!("{input}"); + LL| 0|}; + LL| |static GLOBAL_CLOSURE_OFF: fn(&str) = #[coverage(off)] + LL| ||input: &str| { + LL| | println!("{input}"); + LL| |}; + LL| | + LL| |#[coverage(on)] + LL| 1|fn contains_closures_on() { + LL| 1| let _local_closure_on = #[coverage(on)] + LL| 1| |input: &str| { + LL| 0| println!("{input}"); + LL| 1| }; + LL| 1| let _local_closure_off = #[coverage(off)] + LL| 1| |input: &str| { + LL| | println!("{input}"); + LL| 1| }; + LL| 1|} + LL| | + LL| |#[coverage(off)] + LL| |fn contains_closures_off() { + LL| | let _local_closure_on = #[coverage(on)] + LL| 0| |input: &str| { + LL| 0| println!("{input}"); + LL| 0| }; + LL| | let _local_closure_off = #[coverage(off)] + LL| | |input: &str| { + LL| | println!("{input}"); + LL| | }; + LL| |} + LL| | + LL| |#[coverage(off)] + LL| |fn main() { + LL| | contains_closures_on(); + LL| | contains_closures_off(); + LL| |} + diff --git a/tests/coverage/coverage_attr_closure.rs b/tests/coverage/coverage_attr_closure.rs new file mode 100644 index 00000000000..1904c89c920 --- /dev/null +++ b/tests/coverage/coverage_attr_closure.rs @@ -0,0 +1,42 @@ +#![feature(coverage_attribute, stmt_expr_attributes)] +#![allow(dead_code)] +// edition: 2021 + +static GLOBAL_CLOSURE_ON: fn(&str) = #[coverage(on)] +|input: &str| { + println!("{input}"); +}; +static GLOBAL_CLOSURE_OFF: fn(&str) = #[coverage(off)] +|input: &str| { + println!("{input}"); +}; + +#[coverage(on)] +fn contains_closures_on() { + let _local_closure_on = #[coverage(on)] + |input: &str| { + println!("{input}"); + }; + let _local_closure_off = #[coverage(off)] + |input: &str| { + println!("{input}"); + }; +} + +#[coverage(off)] +fn contains_closures_off() { + let _local_closure_on = #[coverage(on)] + |input: &str| { + println!("{input}"); + }; + let _local_closure_off = #[coverage(off)] + |input: &str| { + println!("{input}"); + }; +} + +#[coverage(off)] +fn main() { + contains_closures_on(); + contains_closures_off(); +} From dd6d7f27e46ae68ee183d8b41eb481b39cb881c6 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 22 Jan 2024 12:49:58 +1100 Subject: [PATCH 3/5] coverage: Make unexpansion of closure bodies more precise This improves the coverage instrumentation of closures declared in macros, as seen in `closure_macro.rs` and `closure_macro_async.rs`. --- .../rustc_mir_transform/src/coverage/mod.rs | 30 ++++++++----------- tests/coverage/closure_macro.cov-map | 25 +++++++++++----- tests/coverage/closure_macro.coverage | 17 +++++------ tests/coverage/closure_macro.rs | 3 +- tests/coverage/closure_macro_async.cov-map | 17 ++++++++--- tests/coverage/closure_macro_async.coverage | 16 +++++----- tests/coverage/closure_macro_async.rs | 2 +- 7 files changed, 60 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 5fb72fcf0cf..b8bce7c3fb6 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -408,7 +408,18 @@ fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> ExtractedHir let hir_body = tcx.hir().body(fn_body_id); let is_async_fn = hir_node.fn_sig().is_some_and(|fn_sig| fn_sig.header.is_async()); - let body_span = get_body_span(tcx, hir_body, def_id); + + let mut body_span = hir_body.value.span; + + use rustc_hir::{Closure, Expr, ExprKind, Node}; + // Unexpand a closure's body span back to the context of its declaration. + // This helps with closure bodies that consist of just a single bang-macro, + // and also with closure bodies produced by async desugaring. + if let Node::Expr(&Expr { kind: ExprKind::Closure(&Closure { fn_decl_span, .. }), .. }) = + hir_node + { + body_span = body_span.find_ancestor_in_same_ctxt(fn_decl_span).unwrap_or(body_span); + } // The actual signature span is only used if it has the same context and // filename as the body, and precedes the body. @@ -432,23 +443,6 @@ fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> ExtractedHir ExtractedHirInfo { function_source_hash, is_async_fn, fn_sig_span, body_span } } -fn get_body_span<'tcx>( - tcx: TyCtxt<'tcx>, - hir_body: &rustc_hir::Body<'tcx>, - def_id: LocalDefId, -) -> Span { - let mut body_span = hir_body.value.span; - - if tcx.is_closure_or_coroutine(def_id.to_def_id()) { - // If the current function is a closure, and its "body" span was created - // by macro expansion or compiler desugaring, try to walk backwards to - // the pre-expansion call site or body. - body_span = body_span.source_callsite(); - } - - body_span -} - fn hash_mir_source<'tcx>(tcx: TyCtxt<'tcx>, hir_body: &'tcx rustc_hir::Body<'tcx>) -> u64 { // FIXME(cjgillot) Stop hashing HIR manually here. let owner = hir_body.id().hir_id.owner; diff --git a/tests/coverage/closure_macro.cov-map b/tests/coverage/closure_macro.cov-map index 323d6e3c8aa..571e5564b65 100644 --- a/tests/coverage/closure_macro.cov-map +++ b/tests/coverage/closure_macro.cov-map @@ -1,20 +1,20 @@ Function name: closure_macro::load_configuration_files -Raw bytes (9): 0x[01, 01, 00, 01, 01, 1e, 01, 02, 02] +Raw bytes (9): 0x[01, 01, 00, 01, 01, 1d, 01, 02, 02] Number of files: 1 - file 0 => global file 1 Number of expressions: 0 Number of file 0 mappings: 1 -- Code(Counter(0)) at (prev + 30, 1) to (start + 2, 2) +- Code(Counter(0)) at (prev + 29, 1) to (start + 2, 2) Function name: closure_macro::main -Raw bytes (43): 0x[01, 01, 02, 01, 05, 05, 02, 07, 01, 22, 01, 01, 21, 02, 02, 09, 00, 0f, 05, 00, 12, 00, 13, 02, 00, 12, 00, 13, 05, 00, 54, 00, 55, 02, 02, 09, 02, 0b, 07, 03, 01, 00, 02] +Raw bytes (43): 0x[01, 01, 02, 01, 05, 05, 02, 07, 01, 21, 01, 01, 21, 02, 02, 09, 00, 0f, 05, 00, 12, 00, 13, 02, 00, 12, 00, 13, 05, 00, 54, 00, 55, 02, 02, 09, 02, 0b, 07, 03, 01, 00, 02] Number of files: 1 - file 0 => global file 1 Number of expressions: 2 - expression 0 operands: lhs = Counter(0), rhs = Counter(1) - expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub) Number of file 0 mappings: 7 -- Code(Counter(0)) at (prev + 34, 1) to (start + 1, 33) +- Code(Counter(0)) at (prev + 33, 1) to (start + 1, 33) - Code(Expression(0, Sub)) at (prev + 2, 9) to (start + 0, 15) = (c0 - c1) - Code(Counter(1)) at (prev + 0, 18) to (start + 0, 19) @@ -27,10 +27,19 @@ Number of file 0 mappings: 7 = (c1 + (c0 - c1)) Function name: closure_macro::main::{closure#0} -Raw bytes (9): 0x[01, 01, 00, 01, 01, 24, 12, 00, 54] +Raw bytes (35): 0x[01, 01, 03, 01, 05, 05, 0b, 09, 00, 05, 01, 10, 1c, 03, 21, 05, 04, 11, 01, 27, 02, 03, 11, 00, 16, 00, 00, 17, 00, 1e, 07, 02, 09, 00, 0a] Number of files: 1 - file 0 => global file 1 -Number of expressions: 0 -Number of file 0 mappings: 1 -- Code(Counter(0)) at (prev + 36, 18) to (start + 0, 84) +Number of expressions: 3 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(1), rhs = Expression(2, Add) +- expression 2 operands: lhs = Counter(2), rhs = Zero +Number of file 0 mappings: 5 +- Code(Counter(0)) at (prev + 16, 28) to (start + 3, 33) +- Code(Counter(1)) at (prev + 4, 17) to (start + 1, 39) +- Code(Expression(0, Sub)) at (prev + 3, 17) to (start + 0, 22) + = (c0 - c1) +- Code(Zero) at (prev + 0, 23) to (start + 0, 30) +- Code(Expression(1, Add)) at (prev + 2, 9) to (start + 0, 10) + = (c1 + (c2 + Zero)) diff --git a/tests/coverage/closure_macro.coverage b/tests/coverage/closure_macro.coverage index ab152a91356..716d75cb8d7 100644 --- a/tests/coverage/closure_macro.coverage +++ b/tests/coverage/closure_macro.coverage @@ -1,4 +1,3 @@ - LL| |#![feature(coverage_attribute)] LL| |// edition: 2018 LL| | LL| |macro_rules! bail { @@ -14,16 +13,16 @@ LL| | LL| |macro_rules! on_error { LL| | ($value:expr, $error_message:expr) => { - LL| | $value.or_else(|e| { - LL| | // FIXME(85000): no coverage in closure macros - LL| | let message = format!($error_message, e); - LL| | if message.len() > 0 { - LL| | println!("{}", message); - LL| | Ok(String::from("ok")) + LL| 0| $value.or_else(|e| { + LL| 0| // This closure, which is declared in a macro, should be instrumented. + LL| 0| let message = format!($error_message, e); + LL| 0| if message.len() > 0 { + LL| 0| println!("{}", message); + LL| 0| Ok(String::from("ok")) LL| | } else { - LL| | bail!("error"); + LL| 0| bail!("error"); LL| | } - LL| | }) + LL| 0| }) LL| | }; LL| |} LL| | diff --git a/tests/coverage/closure_macro.rs b/tests/coverage/closure_macro.rs index 38a81ea434b..6fe1212de8d 100644 --- a/tests/coverage/closure_macro.rs +++ b/tests/coverage/closure_macro.rs @@ -1,4 +1,3 @@ -#![feature(coverage_attribute)] // edition: 2018 macro_rules! bail { @@ -15,7 +14,7 @@ macro_rules! bail { macro_rules! on_error { ($value:expr, $error_message:expr) => { $value.or_else(|e| { - // FIXME(85000): no coverage in closure macros + // This closure, which is declared in a macro, should be instrumented. let message = format!($error_message, e); if message.len() > 0 { println!("{}", message); diff --git a/tests/coverage/closure_macro_async.cov-map b/tests/coverage/closure_macro_async.cov-map index 5f5e0644278..49ec767eab3 100644 --- a/tests/coverage/closure_macro_async.cov-map +++ b/tests/coverage/closure_macro_async.cov-map @@ -35,10 +35,19 @@ Number of file 0 mappings: 7 = (c1 + (c0 - c1)) Function name: closure_macro_async::test::{closure#0}::{closure#0} -Raw bytes (9): 0x[01, 01, 00, 01, 01, 25, 12, 00, 54] +Raw bytes (35): 0x[01, 01, 03, 01, 05, 05, 0b, 09, 00, 05, 01, 12, 1c, 03, 21, 05, 04, 11, 01, 27, 02, 03, 11, 00, 16, 00, 00, 17, 00, 1e, 07, 02, 09, 00, 0a] Number of files: 1 - file 0 => global file 1 -Number of expressions: 0 -Number of file 0 mappings: 1 -- Code(Counter(0)) at (prev + 37, 18) to (start + 0, 84) +Number of expressions: 3 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(1), rhs = Expression(2, Add) +- expression 2 operands: lhs = Counter(2), rhs = Zero +Number of file 0 mappings: 5 +- Code(Counter(0)) at (prev + 18, 28) to (start + 3, 33) +- Code(Counter(1)) at (prev + 4, 17) to (start + 1, 39) +- Code(Expression(0, Sub)) at (prev + 3, 17) to (start + 0, 22) + = (c0 - c1) +- Code(Zero) at (prev + 0, 23) to (start + 0, 30) +- Code(Expression(1, Add)) at (prev + 2, 9) to (start + 0, 10) + = (c1 + (c2 + Zero)) diff --git a/tests/coverage/closure_macro_async.coverage b/tests/coverage/closure_macro_async.coverage index d67c2ed524b..1032e027cd9 100644 --- a/tests/coverage/closure_macro_async.coverage +++ b/tests/coverage/closure_macro_async.coverage @@ -15,16 +15,16 @@ LL| | LL| |macro_rules! on_error { LL| | ($value:expr, $error_message:expr) => { - LL| | $value.or_else(|e| { - LL| | // FIXME(85000): no coverage in closure macros - LL| | let message = format!($error_message, e); - LL| | if message.len() > 0 { - LL| | println!("{}", message); - LL| | Ok(String::from("ok")) + LL| 0| $value.or_else(|e| { + LL| 0| // This closure, which is declared in a macro, should be instrumented. + LL| 0| let message = format!($error_message, e); + LL| 0| if message.len() > 0 { + LL| 0| println!("{}", message); + LL| 0| Ok(String::from("ok")) LL| | } else { - LL| | bail!("error"); + LL| 0| bail!("error"); LL| | } - LL| | }) + LL| 0| }) LL| | }; LL| |} LL| | diff --git a/tests/coverage/closure_macro_async.rs b/tests/coverage/closure_macro_async.rs index 116cb72b0f3..db656fca198 100644 --- a/tests/coverage/closure_macro_async.rs +++ b/tests/coverage/closure_macro_async.rs @@ -16,7 +16,7 @@ macro_rules! bail { macro_rules! on_error { ($value:expr, $error_message:expr) => { $value.or_else(|e| { - // FIXME(85000): no coverage in closure macros + // This closure, which is declared in a macro, should be instrumented. let message = format!($error_message, e); if message.len() > 0 { println!("{}", message); From fde1702db86a8de4849caca12787d74525d06344 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 16 Jan 2024 22:07:21 +1100 Subject: [PATCH 4/5] coverage: Hoist special handling of async function spans This sidesteps the normal span refinement code in cases where we know that we are only dealing with the special signature span that represents having called an async function. --- .../rustc_mir_transform/src/coverage/spans.rs | 28 +++++++++++++------ .../src/coverage/spans/from_mir.rs | 24 ++++++---------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index dee6a3b7143..6db16262a70 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -3,7 +3,7 @@ use rustc_middle::mir; use rustc_span::{BytePos, Span, DUMMY_SP}; -use super::graph::{BasicCoverageBlock, CoverageGraph}; +use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph, START_BCB}; use crate::coverage::ExtractedHirInfo; mod from_mir; @@ -46,13 +46,25 @@ pub(super) fn generate_coverage_spans( ) -> Option { let mut mappings = vec![]; - let sorted_spans = - from_mir::mir_to_initial_sorted_coverage_spans(mir_body, hir_info, basic_coverage_blocks); - let coverage_spans = SpansRefiner::refine_sorted_spans(basic_coverage_blocks, sorted_spans); - mappings.extend(coverage_spans.into_iter().map(|CoverageSpan { bcb, span, .. }| { - // Each span produced by the generator represents an ordinary code region. - BcbMapping { kind: BcbMappingKind::Code(bcb), span } - })); + if hir_info.is_async_fn { + // An async function desugars into a function that returns a future, + // with the user code wrapped in a closure. Any spans in the desugared + // outer function will be unhelpful, so just keep the signature span + // and ignore all of the spans in the MIR body. + let span = hir_info.fn_sig_span; + mappings.push(BcbMapping { kind: BcbMappingKind::Code(START_BCB), span }); + } else { + let sorted_spans = from_mir::mir_to_initial_sorted_coverage_spans( + mir_body, + hir_info, + basic_coverage_blocks, + ); + let coverage_spans = SpansRefiner::refine_sorted_spans(basic_coverage_blocks, sorted_spans); + mappings.extend(coverage_spans.into_iter().map(|CoverageSpan { bcb, span, .. }| { + // Each span produced by the generator represents an ordinary code region. + BcbMapping { kind: BcbMappingKind::Code(bcb), span } + })); + } if mappings.is_empty() { return None; diff --git a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs index 8d8e8e61327..63371a39f5a 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs @@ -23,25 +23,17 @@ pub(super) fn mir_to_initial_sorted_coverage_spans( hir_info: &ExtractedHirInfo, basic_coverage_blocks: &CoverageGraph, ) -> Vec { - let &ExtractedHirInfo { is_async_fn, fn_sig_span, body_span, .. } = hir_info; + let &ExtractedHirInfo { fn_sig_span, body_span, .. } = hir_info; - let mut initial_spans = vec![SpanFromMir::for_fn_sig(fn_sig_span)]; + let mut initial_spans = vec![]; - if is_async_fn { - // An async function desugars into a function that returns a future, - // with the user code wrapped in a closure. Any spans in the desugared - // outer function will be unhelpful, so just keep the signature span - // and ignore all of the spans in the MIR body. - } else { - for (bcb, bcb_data) in basic_coverage_blocks.iter_enumerated() { - initial_spans.extend(bcb_to_initial_coverage_spans(mir_body, body_span, bcb, bcb_data)); - } + for (bcb, bcb_data) in basic_coverage_blocks.iter_enumerated() { + initial_spans.extend(bcb_to_initial_coverage_spans(mir_body, body_span, bcb, bcb_data)); + } - // If no spans were extracted from the body, discard the signature span. - // FIXME: This preserves existing behavior; consider getting rid of it. - if initial_spans.len() == 1 { - initial_spans.clear(); - } + // Only add the signature span if we found at least one span in the body. + if !initial_spans.is_empty() { + initial_spans.push(SpanFromMir::for_fn_sig(fn_sig_span)); } initial_spans.sort_by(|a, b| basic_coverage_blocks.cmp_in_dominator_order(a.bcb, b.bcb)); From a246b6be1dcd4a8ea0ac468921af7e8022686849 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 22 Jan 2024 17:04:13 +1100 Subject: [PATCH 5/5] coverage: Make `fn_sig_span` optional, and note its quirks --- .../rustc_mir_transform/src/coverage/mod.rs | 17 +++++++++-------- .../rustc_mir_transform/src/coverage/spans.rs | 5 +++-- .../src/coverage/spans/from_mir.rs | 6 +++++- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index b8bce7c3fb6..3aa41250fd3 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -394,7 +394,9 @@ fn is_eligible_for_coverage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { struct ExtractedHirInfo { function_source_hash: u64, is_async_fn: bool, - fn_sig_span: Span, + /// The span of the function's signature, extended to the start of `body_span`. + /// Must have the same context and filename as the body span. + fn_sig_span_extended: Option, body_span: Span, } @@ -407,7 +409,8 @@ fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> ExtractedHir hir::map::associated_body(hir_node).expect("HIR node is a function with body"); let hir_body = tcx.hir().body(fn_body_id); - let is_async_fn = hir_node.fn_sig().is_some_and(|fn_sig| fn_sig.header.is_async()); + let maybe_fn_sig = hir_node.fn_sig(); + let is_async_fn = maybe_fn_sig.is_some_and(|fn_sig| fn_sig.header.is_async()); let mut body_span = hir_body.value.span; @@ -423,8 +426,8 @@ fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> ExtractedHir // The actual signature span is only used if it has the same context and // filename as the body, and precedes the body. - let maybe_fn_sig_span = hir_node.fn_sig().map(|fn_sig| fn_sig.span); - let fn_sig_span = maybe_fn_sig_span + let fn_sig_span_extended = maybe_fn_sig + .map(|fn_sig| fn_sig.span) .filter(|&fn_sig_span| { let source_map = tcx.sess.source_map(); let file_idx = |span: Span| source_map.lookup_source_file_idx(span.lo()); @@ -434,13 +437,11 @@ fn extract_hir_info<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> ExtractedHir && file_idx(fn_sig_span) == file_idx(body_span) }) // If so, extend it to the start of the body span. - .map(|fn_sig_span| fn_sig_span.with_hi(body_span.lo())) - // Otherwise, create a dummy signature span at the start of the body. - .unwrap_or_else(|| body_span.shrink_to_lo()); + .map(|fn_sig_span| fn_sig_span.with_hi(body_span.lo())); let function_source_hash = hash_mir_source(tcx, hir_body); - ExtractedHirInfo { function_source_hash, is_async_fn, fn_sig_span, body_span } + ExtractedHirInfo { function_source_hash, is_async_fn, fn_sig_span_extended, body_span } } fn hash_mir_source<'tcx>(tcx: TyCtxt<'tcx>, hir_body: &'tcx rustc_hir::Body<'tcx>) -> u64 { diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index 6db16262a70..d3d0c7bcc95 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -51,8 +51,9 @@ pub(super) fn generate_coverage_spans( // with the user code wrapped in a closure. Any spans in the desugared // outer function will be unhelpful, so just keep the signature span // and ignore all of the spans in the MIR body. - let span = hir_info.fn_sig_span; - mappings.push(BcbMapping { kind: BcbMappingKind::Code(START_BCB), span }); + if let Some(span) = hir_info.fn_sig_span_extended { + mappings.push(BcbMapping { kind: BcbMappingKind::Code(START_BCB), span }); + } } else { let sorted_spans = from_mir::mir_to_initial_sorted_coverage_spans( mir_body, diff --git a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs index 63371a39f5a..5b4d58836b4 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs @@ -23,7 +23,7 @@ pub(super) fn mir_to_initial_sorted_coverage_spans( hir_info: &ExtractedHirInfo, basic_coverage_blocks: &CoverageGraph, ) -> Vec { - let &ExtractedHirInfo { fn_sig_span, body_span, .. } = hir_info; + let &ExtractedHirInfo { body_span, .. } = hir_info; let mut initial_spans = vec![]; @@ -33,6 +33,10 @@ pub(super) fn mir_to_initial_sorted_coverage_spans( // Only add the signature span if we found at least one span in the body. if !initial_spans.is_empty() { + // If there is no usable signature span, add a fake one (before refinement) + // to avoid an ugly gap between the body start and the first real span. + // FIXME: Find a more principled way to solve this problem. + let fn_sig_span = hir_info.fn_sig_span_extended.unwrap_or_else(|| body_span.shrink_to_lo()); initial_spans.push(SpanFromMir::for_fn_sig(fn_sig_span)); }