Skip to content

Commit cb70221

Browse files
committed
Coverage instruments closure bodies in macros (not the macro body)
Fixes: #84884 This solution might be considered a compromise, but I think it is the better choice. The results in the `closure.rs` test correctly resolve all test cases broken as described in #84884. One test pattern (in both `closure_macro.rs` and `closure_macro_async.rs`) was also affected, and removes coverage statistics for the lines inside the closure, because the closure includes a macro. (The coverage remains at the callsite of the macro, so we lose some detail, but there isn't a perfect choice with macros. Often macro implementations are split across the macro and the callsite, and there doesn't appear to be a single "right choice" for which body should be covered. For the current implementation, we can't do both. The callsite is most likely to be the preferred site for coverage. I applied this fix to all `MacroKinds`, not just `Bang`. I'm trying to resolve an issue of lost coverage in a `MacroKind::Attr`-based, function-scoped macro. Instead of only searching for a body_span that is "not a function-like macro" (that is, MacroKind::Bang), I'm expanding this to all `MacroKind`s. Maybe I should expand this to `ExpnKind::Desugaring` and `ExpnKind::AstPass` (or subsets, depending on their sub-kinds) as well, but I'm not sure that's a good idea. I'd like to add a test of the `Attr` macro on functions, but I need time to figure out how to constract a good, simple example without external crate dependencies. For the moment, all tests still work as expected (no change), this new commit shouldn't have a negative affect, and more importantly, I believe it will have a positive effect. I will try to confirm this.
1 parent 603a42e commit cb70221

File tree

7 files changed

+192
-49
lines changed

7 files changed

+192
-49
lines changed

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

+24-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use rustc_middle::mir::{
3232
use rustc_middle::ty::TyCtxt;
3333
use rustc_span::def_id::DefId;
3434
use rustc_span::source_map::SourceMap;
35-
use rustc_span::{CharPos, Pos, SourceFile, Span, Symbol};
35+
use rustc_span::{CharPos, ExpnKind, Pos, SourceFile, Span, Symbol};
3636

3737
/// A simple error message wrapper for `coverage::Error`s.
3838
#[derive(Debug)]
@@ -113,8 +113,29 @@ struct Instrumentor<'a, 'tcx> {
113113
impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
114114
fn new(pass_name: &'a str, tcx: TyCtxt<'tcx>, mir_body: &'a mut mir::Body<'tcx>) -> Self {
115115
let source_map = tcx.sess.source_map();
116-
let (some_fn_sig, hir_body) = fn_sig_and_body(tcx, mir_body.source.def_id());
117-
let body_span = hir_body.value.span;
116+
let def_id = mir_body.source.def_id();
117+
let (some_fn_sig, hir_body) = fn_sig_and_body(tcx, def_id);
118+
119+
let mut body_span = hir_body.value.span;
120+
121+
if tcx.is_closure(def_id) {
122+
// If the MIR function is a closure, and if the closure body span
123+
// starts from a macro, but it's content is not in that macro, try
124+
// to find a non-macro callsite, and instrument the spans there
125+
// instead.
126+
loop {
127+
let expn_data = body_span.ctxt().outer_expn_data();
128+
if expn_data.is_root() {
129+
break;
130+
}
131+
if let ExpnKind::Macro(..) = expn_data.kind {
132+
body_span = expn_data.call_site;
133+
} else {
134+
break;
135+
}
136+
}
137+
}
138+
118139
let source_file = source_map.lookup_source_file(body_span.lo());
119140
let fn_sig_span = match some_fn_sig.filter(|fn_sig| {
120141
fn_sig.span.ctxt() == body_span.ctxt()

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

+84-22
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
37| 0| countdown = 10;
3838
38| 0| }
3939
39| 0| "alt string 2".to_owned()
40-
40| 1| };
40+
40| 0| };
4141
41| 1| println!(
4242
42| 1| "The string or alt: {}"
4343
43| 1| ,
@@ -125,36 +125,98 @@
125125
125| 0| countdown = 10;
126126
126| 0| }
127127
127| 0| "closure should be unused".to_owned()
128-
128| 1| };
129-
129| 1|
128+
128| 0| };
129+
129| |
130130
130| 1| let mut countdown = 10;
131131
131| 1| let _short_unused_closure = | _unused_arg: u8 | countdown += 1;
132132
^0
133-
132| 1|
134-
133| 1| // Macros can sometimes confuse the coverage results. Compare this next assignment, with an
135-
134| 1| // unused closure that invokes the `println!()` macro, with the closure assignment above, that
136-
135| 1| // does not use a macro. The closure above correctly shows `0` executions.
137-
136| 1| let _short_unused_closure = | _unused_arg: u8 | println!("not called");
138-
137| 1| // The closure assignment above is executed, with a line count of `1`, but the `println!()`
139-
138| 1| // could not have been called, and yet, there is no indication that it wasn't...
140-
139| 1|
141-
140| 1| // ...but adding block braces gives the expected result, showing the block was not executed.
133+
132| |
134+
133| |
135+
134| 1| let short_used_covered_closure_macro = | used_arg: u8 | println!("called");
136+
135| 1| let short_used_not_covered_closure_macro = | used_arg: u8 | println!("not called");
137+
^0
138+
136| 1| let _short_unused_closure_macro = | _unused_arg: u8 | println!("not called");
139+
^0
140+
137| |
141+
138| |
142+
139| |
143+
140| |
142144
141| 1| let _short_unused_closure_block = | _unused_arg: u8 | { println!("not called") };
143145
^0
144-
142| 1|
146+
142| |
145147
143| 1| let _shortish_unused_closure = | _unused_arg: u8 | {
146148
144| 0| println!("not called")
147-
145| 1| };
148-
146| 1|
149+
145| 0| };
150+
146| |
149151
147| 1| let _as_short_unused_closure = |
150152
148| | _unused_arg: u8
151-
149| 1| | { println!("not called") };
152-
^0
153-
150| 1|
153+
149| 0| | { println!("not called") };
154+
150| |
154155
151| 1| let _almost_as_short_unused_closure = |
155156
152| | _unused_arg: u8
156-
153| 1| | { println!("not called") }
157-
^0
158-
154| 1| ;
159-
155| 1|}
157+
153| 0| | { println!("not called") }
158+
154| | ;
159+
155| |
160+
156| |
161+
157| |
162+
158| |
163+
159| |
164+
160| 1| let _short_unused_closure_line_break_no_block = | _unused_arg: u8 |
165+
161| 0|println!("not called")
166+
162| | ;
167+
163| |
168+
164| 1| let _short_unused_closure_line_break_no_block2 =
169+
165| | | _unused_arg: u8 |
170+
166| 0| println!(
171+
167| 0| "not called"
172+
168| 0| )
173+
169| | ;
174+
170| |
175+
171| 1| let short_used_not_covered_closure_line_break_no_block_embedded_branch =
176+
172| 1| | _unused_arg: u8 |
177+
173| 0| println!(
178+
174| 0| "not called: {}",
179+
175| 0| if is_true { "check" } else { "me" }
180+
176| 0| )
181+
177| | ;
182+
178| |
183+
179| 1| let short_used_not_covered_closure_line_break_block_embedded_branch =
184+
180| 1| | _unused_arg: u8 |
185+
181| 0| {
186+
182| 0| println!(
187+
183| 0| "not called: {}",
188+
184| 0| if is_true { "check" } else { "me" }
189+
185| | )
190+
186| 0| }
191+
187| | ;
192+
188| |
193+
189| 1| let short_used_covered_closure_line_break_no_block_embedded_branch =
194+
190| 1| | _unused_arg: u8 |
195+
191| 1| println!(
196+
192| 1| "not called: {}",
197+
193| 1| if is_true { "check" } else { "me" }
198+
^0
199+
194| 1| )
200+
195| | ;
201+
196| |
202+
197| 1| let short_used_covered_closure_line_break_block_embedded_branch =
203+
198| 1| | _unused_arg: u8 |
204+
199| 1| {
205+
200| 1| println!(
206+
201| 1| "not called: {}",
207+
202| 1| if is_true { "check" } else { "me" }
208+
^0
209+
203| | )
210+
204| 1| }
211+
205| | ;
212+
206| |
213+
207| 1| if is_false {
214+
208| 0| short_used_not_covered_closure_macro(0);
215+
209| 0| short_used_not_covered_closure_line_break_no_block_embedded_branch(0);
216+
210| 0| short_used_not_covered_closure_line_break_block_embedded_branch(0);
217+
211| 1| }
218+
212| 1| short_used_covered_closure_macro(0);
219+
213| 1| short_used_covered_closure_line_break_no_block_embedded_branch(0);
220+
214| 1| short_used_covered_closure_line_break_block_embedded_branch(0);
221+
215| 1|}
160222

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
14| |
1515
15| |macro_rules! on_error {
1616
16| | ($value:expr, $error_message:expr) => {
17-
17| 0| $value.or_else(|e| {
18-
18| 0| let message = format!($error_message, e);
19-
19| 0| if message.len() > 0 {
20-
20| 0| println!("{}", message);
21-
21| 0| Ok(String::from("ok"))
17+
17| | $value.or_else(|e| { // FIXME(85000): no coverage in closure macros
18+
18| | let message = format!($error_message, e);
19+
19| | if message.len() > 0 {
20+
20| | println!("{}", message);
21+
21| | Ok(String::from("ok"))
2222
22| | } else {
23-
23| 0| bail!("error");
23+
23| | bail!("error");
2424
24| | }
25-
25| 0| })
25+
25| | })
2626
26| | };
2727
27| |}
2828
28| |

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
14| |
1515
15| |macro_rules! on_error {
1616
16| | ($value:expr, $error_message:expr) => {
17-
17| 0| $value.or_else(|e| {
18-
18| 0| let message = format!($error_message, e);
19-
19| 0| if message.len() > 0 {
20-
20| 0| println!("{}", message);
21-
21| 0| Ok(String::from("ok"))
17+
17| | $value.or_else(|e| { // FIXME(85000): no coverage in closure macros
18+
18| | let message = format!($error_message, e);
19+
19| | if message.len() > 0 {
20+
20| | println!("{}", message);
21+
21| | Ok(String::from("ok"))
2222
22| | } else {
23-
23| 0| bail!("error");
23+
23| | bail!("error");
2424
24| | }
25-
25| 0| })
25+
25| | })
2626
26| | };
2727
27| |}
2828
28| |

src/test/run-make-fulldeps/coverage/closure.rs

+68-8
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,14 @@ fn main() {
130130
let mut countdown = 10;
131131
let _short_unused_closure = | _unused_arg: u8 | countdown += 1;
132132

133-
// Macros can sometimes confuse the coverage results. Compare this next assignment, with an
134-
// unused closure that invokes the `println!()` macro, with the closure assignment above, that
135-
// does not use a macro. The closure above correctly shows `0` executions.
136-
let _short_unused_closure = | _unused_arg: u8 | println!("not called");
137-
// The closure assignment above is executed, with a line count of `1`, but the `println!()`
138-
// could not have been called, and yet, there is no indication that it wasn't...
139-
140-
// ...but adding block braces gives the expected result, showing the block was not executed.
133+
134+
let short_used_covered_closure_macro = | used_arg: u8 | println!("called");
135+
let short_used_not_covered_closure_macro = | used_arg: u8 | println!("not called");
136+
let _short_unused_closure_macro = | _unused_arg: u8 | println!("not called");
137+
138+
139+
140+
141141
let _short_unused_closure_block = | _unused_arg: u8 | { println!("not called") };
142142

143143
let _shortish_unused_closure = | _unused_arg: u8 | {
@@ -152,4 +152,64 @@ fn main() {
152152
_unused_arg: u8
153153
| { println!("not called") }
154154
;
155+
156+
157+
158+
159+
160+
let _short_unused_closure_line_break_no_block = | _unused_arg: u8 |
161+
println!("not called")
162+
;
163+
164+
let _short_unused_closure_line_break_no_block2 =
165+
| _unused_arg: u8 |
166+
println!(
167+
"not called"
168+
)
169+
;
170+
171+
let short_used_not_covered_closure_line_break_no_block_embedded_branch =
172+
| _unused_arg: u8 |
173+
println!(
174+
"not called: {}",
175+
if is_true { "check" } else { "me" }
176+
)
177+
;
178+
179+
let short_used_not_covered_closure_line_break_block_embedded_branch =
180+
| _unused_arg: u8 |
181+
{
182+
println!(
183+
"not called: {}",
184+
if is_true { "check" } else { "me" }
185+
)
186+
}
187+
;
188+
189+
let short_used_covered_closure_line_break_no_block_embedded_branch =
190+
| _unused_arg: u8 |
191+
println!(
192+
"not called: {}",
193+
if is_true { "check" } else { "me" }
194+
)
195+
;
196+
197+
let short_used_covered_closure_line_break_block_embedded_branch =
198+
| _unused_arg: u8 |
199+
{
200+
println!(
201+
"not called: {}",
202+
if is_true { "check" } else { "me" }
203+
)
204+
}
205+
;
206+
207+
if is_false {
208+
short_used_not_covered_closure_macro(0);
209+
short_used_not_covered_closure_line_break_no_block_embedded_branch(0);
210+
short_used_not_covered_closure_line_break_block_embedded_branch(0);
211+
}
212+
short_used_covered_closure_macro(0);
213+
short_used_covered_closure_line_break_no_block_embedded_branch(0);
214+
short_used_covered_closure_line_break_block_embedded_branch(0);
155215
}

src/test/run-make-fulldeps/coverage/closure_macro.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ macro_rules! bail {
1414

1515
macro_rules! on_error {
1616
($value:expr, $error_message:expr) => {
17-
$value.or_else(|e| {
17+
$value.or_else(|e| { // FIXME(85000): no coverage in closure macros
1818
let message = format!($error_message, e);
1919
if message.len() > 0 {
2020
println!("{}", message);

src/test/run-make-fulldeps/coverage/closure_macro_async.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ macro_rules! bail {
1414

1515
macro_rules! on_error {
1616
($value:expr, $error_message:expr) => {
17-
$value.or_else(|e| {
17+
$value.or_else(|e| { // FIXME(85000): no coverage in closure macros
1818
let message = format!($error_message, e);
1919
if message.len() > 0 {
2020
println!("{}", message);

0 commit comments

Comments
 (0)