Reland - Report coverage 0 of dead blocks

Fixes: #84018

With `-Z instrument-coverage`, coverage reporting of dead blocks
(for example, blocks dropped because a conditional branch is dropped,
based on const evaluation) is now supported.

Note, this PR relands an earlier, reverted PR that failed when compiling
generators. The prior issues with generators has been resolved and a new
test was added to prevent future regressions.

Check out the resulting changes to test coverage of dead blocks in the
test coverage reports in this PR.
This commit is contained in:
Rich Kadel 2021-05-01 14:56:48 -07:00
parent 7f9ab0300c
commit f4f76e60b3
24 changed files with 216 additions and 65 deletions

View file

@ -12,6 +12,7 @@
12| 1| if b {
13| 1| println!("non_async_func println in block");
14| 1| }
^0
15| 1|}
16| |
17| |

View file

@ -5,6 +5,7 @@
5| 1| if true {
6| 1| countdown = 10;
7| 1| }
^0
8| |
9| | const B: u32 = 100;
10| 1| let x = if countdown > 7 {
@ -24,6 +25,7 @@
24| 1| if true {
25| 1| countdown = 10;
26| 1| }
^0
27| |
28| 1| if countdown > 7 {
29| 1| countdown -= 4;
@ -42,6 +44,7 @@
41| 1| if true {
42| 1| countdown = 10;
43| 1| }
^0
44| |
45| 1| if countdown > 7 {
46| 1| countdown -= 4;
@ -54,13 +57,14 @@
53| | } else {
54| 0| return;
55| | }
56| | } // Note: closing brace shows uncovered (vs. `0` for implicit else) because condition literal
57| | // `true` was const-evaluated. The compiler knows the `if` block will be executed.
56| 0| }
57| |
58| |
59| 1| let mut countdown = 0;
60| 1| if true {
61| 1| countdown = 1;
62| 1| }
^0
63| |
64| 1| let z = if countdown > 7 {
^0

View file

@ -9,7 +9,7 @@
8| 1|//! assert_eq!(1, 1);
9| |//! } else {
10| |//! // this is not!
11| |//! assert_eq!(1, 2);
11| 0|//! assert_eq!(1, 2);
12| |//! }
13| 1|//! ```
14| |//!
@ -84,7 +84,7 @@
74| 1| if true {
75| 1| assert_eq!(1, 1);
76| | } else {
77| | assert_eq!(1, 2);
77| 0| assert_eq!(1, 2);
78| | }
79| 1|}
80| |

View file

@ -19,11 +19,11 @@
19| 1| if true {
20| 1| println!("Exiting with error...");
21| 1| return Err(1);
22| | }
23| |
24| | let _ = Firework { strength: 1000 };
25| |
26| | Ok(())
22| 0| }
23| 0|
24| 0| let _ = Firework { strength: 1000 };
25| 0|
26| 0| Ok(())
27| 1|}
28| |
29| |// Expected program output:

View file

@ -0,0 +1,32 @@
1| |#![feature(generators, generator_trait)]
2| |
3| |use std::ops::{Generator, GeneratorState};
4| |use std::pin::Pin;
5| |
6| |// The following implementation of a function called from a `yield` statement
7| |// (apparently requiring the Result and the `String` type or constructor)
8| |// creates conditions where the `generator::StateTransform` MIR transform will
9| |// drop all `Counter` `Coverage` statements from a MIR. `simplify.rs` has logic
10| |// to handle this condition, and still report dead block coverage.
11| 1|fn get_u32(val: bool) -> Result<u32, String> {
12| 1| if val { Ok(1) } else { Err(String::from("some error")) }
^0
13| 1|}
14| |
15| 1|fn main() {
16| 1| let is_true = std::env::args().len() == 1;
17| 1| let mut generator = || {
18| 1| yield get_u32(is_true);
19| 1| return "foo";
20| 1| };
21| |
22| 1| match Pin::new(&mut generator).resume(()) {
23| 1| GeneratorState::Yielded(Ok(1)) => {}
24| 0| _ => panic!("unexpected return from resume"),
25| | }
26| 1| match Pin::new(&mut generator).resume(()) {
27| 1| GeneratorState::Complete("foo") => {}
28| 0| _ => panic!("unexpected return from resume"),
29| | }
30| 1|}

View file

@ -52,15 +52,15 @@
30| 1| if true {
31| 1| println!("Exiting with error...");
32| 1| return Err(1);
33| | } // The remaining lines below have no coverage because `if true` (with the constant literal
34| | // `true`) is guaranteed to execute the `then` block, which is also guaranteed to `return`.
35| | // Thankfully, in the normal case, conditions are not guaranteed ahead of time, and as shown
36| | // in other tests, the lines below would have coverage (which would show they had `0`
37| | // executions, assuming the condition still evaluated to `true`).
38| |
39| | let _ = Firework { strength: 1000 };
40| |
41| | Ok(())
33| 0| }
34| 0|
35| 0|
36| 0|
37| 0|
38| 0|
39| 0| let _ = Firework { strength: 1000 };
40| 0|
41| 0| Ok(())
42| 1|}
43| |
44| |// Expected program output:

View file

@ -9,23 +9,23 @@
9| 1| fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
10| 1| if true {
11| 1| if false {
12| | while true {
13| | }
12| 0| while true {
13| 0| }
14| 1| }
15| 1| write!(f, "error")?;
^0
16| | } else {
17| | }
15| 1| write!(f, "cool")?;
^0
16| 0| } else {
17| 0| }
18| |
19| 10| for i in 0..10 {
20| 10| if true {
21| 10| if false {
22| | while true {}
22| 0| while true {}
23| 10| }
24| 10| write!(f, "error")?;
^0
25| | } else {
26| | }
24| 10| write!(f, "cool")?;
^0
25| 0| } else {
26| 0| }
27| | }
28| 1| Ok(())
29| 1| }
@ -36,21 +36,21 @@
34| |impl std::fmt::Display for DisplayTest {
35| 1| fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
36| 1| if false {
37| | } else {
37| 0| } else {
38| 1| if false {
39| | while true {}
39| 0| while true {}
40| 1| }
41| 1| write!(f, "error")?;
^0
41| 1| write!(f, "cool")?;
^0
42| | }
43| 10| for i in 0..10 {
44| 10| if false {
45| | } else {
45| 0| } else {
46| 10| if false {
47| | while true {}
47| 0| while true {}
48| 10| }
49| 10| write!(f, "error")?;
^0
49| 10| write!(f, "cool")?;
^0
50| | }
51| | }
52| 1| Ok(())

View file

@ -1,6 +1,6 @@
1| 1|fn main() {
2| 1| if false {
3| | loop {}
3| 0| loop {}
4| 1| }
5| 1|}

View file

@ -53,8 +53,8 @@ fn main() {
} else {
return;
}
} // Note: closing brace shows uncovered (vs. `0` for implicit else) because condition literal
// `true` was const-evaluated. The compiler knows the `if` block will be executed.
}
let mut countdown = 0;
if true {

View file

@ -0,0 +1,30 @@
#![feature(generators, generator_trait)]
use std::ops::{Generator, GeneratorState};
use std::pin::Pin;
// The following implementation of a function called from a `yield` statement
// (apparently requiring the Result and the `String` type or constructor)
// creates conditions where the `generator::StateTransform` MIR transform will
// drop all `Counter` `Coverage` statements from a MIR. `simplify.rs` has logic
// to handle this condition, and still report dead block coverage.
fn get_u32(val: bool) -> Result<u32, String> {
if val { Ok(1) } else { Err(String::from("some error")) }
}
fn main() {
let is_true = std::env::args().len() == 1;
let mut generator = || {
yield get_u32(is_true);
return "foo";
};
match Pin::new(&mut generator).resume(()) {
GeneratorState::Yielded(Ok(1)) => {}
_ => panic!("unexpected return from resume"),
}
match Pin::new(&mut generator).resume(()) {
GeneratorState::Complete("foo") => {}
_ => panic!("unexpected return from resume"),
}
}

View file

@ -30,11 +30,11 @@ fn main() -> Result<(),u8> {
if true {
println!("Exiting with error...");
return Err(1);
} // The remaining lines below have no coverage because `if true` (with the constant literal
// `true`) is guaranteed to execute the `then` block, which is also guaranteed to `return`.
// Thankfully, in the normal case, conditions are not guaranteed ahead of time, and as shown
// in other tests, the lines below would have coverage (which would show they had `0`
// executions, assuming the condition still evaluated to `true`).
}
let _ = Firework { strength: 1000 };

View file

@ -12,7 +12,7 @@ impl std::fmt::Debug for DebugTest {
while true {
}
}
write!(f, "error")?;
write!(f, "cool")?;
} else {
}
@ -21,7 +21,7 @@ impl std::fmt::Debug for DebugTest {
if false {
while true {}
}
write!(f, "error")?;
write!(f, "cool")?;
} else {
}
}
@ -38,7 +38,7 @@ impl std::fmt::Display for DisplayTest {
if false {
while true {}
}
write!(f, "error")?;
write!(f, "cool")?;
}
for i in 0..10 {
if false {
@ -46,7 +46,7 @@ impl std::fmt::Display for DisplayTest {
if false {
while true {}
}
write!(f, "error")?;
write!(f, "cool")?;
}
}
Ok(())