Rollup merge of #78963 - richkadel:llvm-coverage-counters-2.0.4, r=tmandry

Added some unit tests as requested

As discussed in PR #78267, for example:

* https://github.com/rust-lang/rust/pull/78267#discussion_r515404722
* https://github.com/rust-lang/rust/pull/78267#discussion_r515405958

r? ```````@tmandry```````
FYI: ```````@wesleywiser```````

This is pretty much self contained, but depending on feedback and timing, I may have a chance to add a few more unit tests requested against `counters.rs`. I'm looking at those now.
This commit is contained in:
Dylan DPC 2020-11-15 03:02:46 +01:00 committed by GitHub
commit 335a2554f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 788 additions and 25 deletions

View File

@ -721,6 +721,13 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a21fa21941700a3cd8fcb4091f361a6a712fac632f85d9f487cc892045d55c6"
[[package]]
name = "coverage_test_macros"
version = "0.0.0"
dependencies = [
"proc-macro2",
]
[[package]]
name = "cpuid-bool"
version = "0.1.2"
@ -3922,6 +3929,7 @@ dependencies = [
name = "rustc_mir"
version = "0.0.0"
dependencies = [
"coverage_test_macros",
"either",
"itertools 0.9.0",
"polonius-engine",

View File

@ -611,6 +611,18 @@ pub struct TyS<'tcx> {
outer_exclusive_binder: ty::DebruijnIndex,
}
impl<'tcx> TyS<'tcx> {
/// A constructor used only for internal testing.
#[allow(rustc::usage_of_ty_tykind)]
pub fn make_for_test(
kind: TyKind<'tcx>,
flags: TypeFlags,
outer_exclusive_binder: ty::DebruijnIndex,
) -> TyS<'tcx> {
TyS { kind, flags, outer_exclusive_binder }
}
}
// `TyS` is used a lot. Make sure it doesn't unintentionally get bigger.
#[cfg(target_arch = "x86_64")]
static_assert_size!(TyS<'_>, 32);

View File

@ -31,3 +31,6 @@ rustc_ast = { path = "../rustc_ast" }
rustc_span = { path = "../rustc_span" }
rustc_apfloat = { path = "../rustc_apfloat" }
smallvec = { version = "1.0", features = ["union", "may_dangle"] }
[dev-dependencies]
coverage_test_macros = { path = "src/transform/coverage/test_macros" }

View File

@ -14,7 +14,7 @@ use rustc_middle::mir::coverage::*;
/// Manages the counter and expression indexes/IDs to generate `CoverageKind` components for MIR
/// `Coverage` statements.
pub(crate) struct CoverageCounters {
pub(super) struct CoverageCounters {
function_source_hash: u64,
next_counter_id: u32,
num_expressions: u32,
@ -37,7 +37,7 @@ impl CoverageCounters {
self.debug_counters.enable();
}
/// Makes `CoverageKind` `Counter`s and `Expressions` for the `BasicCoverageBlocks` directly or
/// Makes `CoverageKind` `Counter`s and `Expressions` for the `BasicCoverageBlock`s directly or
/// indirectly associated with `CoverageSpans`, and returns additional `Expression`s
/// representing intermediate values.
pub fn make_bcb_counters(
@ -120,7 +120,6 @@ struct BcbCounters<'a> {
basic_coverage_blocks: &'a mut CoverageGraph,
}
// FIXME(richkadel): Add unit tests for `BcbCounters` functions/algorithms.
impl<'a> BcbCounters<'a> {
fn new(
coverage_counters: &'a mut CoverageCounters,

View File

@ -127,7 +127,7 @@ pub const NESTED_INDENT: &str = " ";
const RUSTC_COVERAGE_DEBUG_OPTIONS: &str = "RUSTC_COVERAGE_DEBUG_OPTIONS";
pub(crate) fn debug_options<'a>() -> &'a DebugOptions {
pub(super) fn debug_options<'a>() -> &'a DebugOptions {
static DEBUG_OPTIONS: SyncOnceCell<DebugOptions> = SyncOnceCell::new();
&DEBUG_OPTIONS.get_or_init(|| DebugOptions::from_env())
@ -136,7 +136,7 @@ pub(crate) fn debug_options<'a>() -> &'a DebugOptions {
/// Parses and maintains coverage-specific debug options captured from the environment variable
/// "RUSTC_COVERAGE_DEBUG_OPTIONS", if set.
#[derive(Debug, Clone)]
pub(crate) struct DebugOptions {
pub(super) struct DebugOptions {
pub allow_unused_expressions: bool,
counter_format: ExpressionFormat,
}
@ -250,7 +250,7 @@ impl Default for ExpressionFormat {
///
/// `DebugCounters` supports a recursive rendering of `Expression` counters, so they can be
/// presented as nested expressions such as `(bcb3 - (bcb0 + bcb1))`.
pub(crate) struct DebugCounters {
pub(super) struct DebugCounters {
some_counters: Option<FxHashMap<ExpressionOperandId, DebugCounter>>,
}
@ -386,7 +386,7 @@ impl DebugCounter {
/// If enabled, this data structure captures additional debugging information used when generating
/// a Graphviz (.dot file) representation of the `CoverageGraph`, for debugging purposes.
pub(crate) struct GraphvizData {
pub(super) struct GraphvizData {
some_bcb_to_coverage_spans_with_counters:
Option<FxHashMap<BasicCoverageBlock, Vec<(CoverageSpan, CoverageKind)>>>,
some_bcb_to_dependency_counters: Option<FxHashMap<BasicCoverageBlock, Vec<CoverageKind>>>,
@ -496,7 +496,7 @@ impl GraphvizData {
/// directly or indirectly, to compute the coverage counts for all `CoverageSpan`s, and any that are
/// _not_ used are retained in the `unused_expressions` Vec, to be included in debug output (logs
/// and/or a `CoverageGraph` graphviz output).
pub(crate) struct UsedExpressions {
pub(super) struct UsedExpressions {
some_used_expression_operands:
Option<FxHashMap<ExpressionOperandId, Vec<InjectedExpressionId>>>,
some_unused_expressions:
@ -626,7 +626,7 @@ impl UsedExpressions {
}
/// Generates the MIR pass `CoverageSpan`-specific spanview dump file.
pub(crate) fn dump_coverage_spanview(
pub(super) fn dump_coverage_spanview(
tcx: TyCtxt<'tcx>,
mir_body: &mir::Body<'tcx>,
basic_coverage_blocks: &CoverageGraph,
@ -666,7 +666,7 @@ fn span_viewables(
}
/// Generates the MIR pass coverage-specific graphviz dump file.
pub(crate) fn dump_coverage_graphviz(
pub(super) fn dump_coverage_graphviz(
tcx: TyCtxt<'tcx>,
mir_body: &mir::Body<'tcx>,
pass_name: &str,
@ -815,7 +815,7 @@ fn bcb_to_string_sections(
/// Returns a simple string representation of a `TerminatorKind` variant, indenpendent of any
/// values it might hold.
pub(crate) fn term_type(kind: &TerminatorKind<'tcx>) -> &'static str {
pub(super) fn term_type(kind: &TerminatorKind<'tcx>) -> &'static str {
match kind {
TerminatorKind::Goto { .. } => "Goto",
TerminatorKind::SwitchInt { .. } => "SwitchInt",

View File

@ -17,7 +17,8 @@ const ID_SEPARATOR: &str = ",";
/// `CoverageKind` counter (to be added by `CoverageCounters::make_bcb_counters`), and an optional
/// set of additional counters--if needed--to count incoming edges, if there are more than one.
/// (These "edge counters" are eventually converted into new MIR `BasicBlock`s.)
pub(crate) struct CoverageGraph {
#[derive(Debug)]
pub(super) struct CoverageGraph {
bcbs: IndexVec<BasicCoverageBlock, BasicCoverageBlockData>,
bb_to_bcb: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,
pub successors: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
@ -275,7 +276,7 @@ impl graph::WithPredecessors for CoverageGraph {
rustc_index::newtype_index! {
/// A node in the [control-flow graph][CFG] of CoverageGraph.
pub(crate) struct BasicCoverageBlock {
pub(super) struct BasicCoverageBlock {
DEBUG_FORMAT = "bcb{}",
}
}
@ -305,7 +306,7 @@ rustc_index::newtype_index! {
/// queries (`is_dominated_by()`, `predecessors`, `successors`, etc.) have branch (control flow)
/// significance.
#[derive(Debug, Clone)]
pub(crate) struct BasicCoverageBlockData {
pub(super) struct BasicCoverageBlockData {
pub basic_blocks: Vec<BasicBlock>,
pub counter_kind: Option<CoverageKind>,
edge_from_bcbs: Option<FxHashMap<BasicCoverageBlock, CoverageKind>>,
@ -431,7 +432,7 @@ impl BasicCoverageBlockData {
/// the specific branching BCB, representing the edge between the two. The latter case
/// distinguishes this incoming edge from other incoming edges to the same `target_bcb`.
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) struct BcbBranch {
pub(super) struct BcbBranch {
pub edge_from_bcb: Option<BasicCoverageBlock>,
pub target_bcb: BasicCoverageBlock,
}
@ -498,9 +499,8 @@ fn bcb_filtered_successors<'a, 'tcx>(
/// Maintains separate worklists for each loop in the BasicCoverageBlock CFG, plus one for the
/// CoverageGraph outside all loops. This supports traversing the BCB CFG in a way that
/// ensures a loop is completely traversed before processing Blocks after the end of the loop.
// FIXME(richkadel): Add unit tests for TraversalContext.
#[derive(Debug)]
pub(crate) struct TraversalContext {
pub(super) struct TraversalContext {
/// From one or more backedges returning to a loop header.
pub loop_backedges: Option<(Vec<BasicCoverageBlock>, BasicCoverageBlock)>,
@ -510,7 +510,7 @@ pub(crate) struct TraversalContext {
pub worklist: Vec<BasicCoverageBlock>,
}
pub(crate) struct TraverseCoverageGraphWithLoops {
pub(super) struct TraverseCoverageGraphWithLoops {
pub backedges: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
pub context_stack: Vec<TraversalContext>,
visited: BitSet<BasicCoverageBlock>,
@ -642,7 +642,7 @@ impl TraverseCoverageGraphWithLoops {
}
}
fn find_loop_backedges(
pub(super) fn find_loop_backedges(
basic_coverage_blocks: &CoverageGraph,
) -> IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>> {
let num_bcbs = basic_coverage_blocks.num_nodes();

View File

@ -5,6 +5,9 @@ mod debug;
mod graph;
mod spans;
#[cfg(test)]
mod tests;
use counters::CoverageCounters;
use graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph};
use spans::{CoverageSpan, CoverageSpans};
@ -31,7 +34,7 @@ use rustc_span::{CharPos, Pos, SourceFile, Span, Symbol};
/// A simple error message wrapper for `coverage::Error`s.
#[derive(Debug)]
pub(crate) struct Error {
struct Error {
message: String,
}

View File

@ -17,7 +17,7 @@ use rustc_span::{BytePos, Span, SyntaxContext};
use std::cmp::Ordering;
#[derive(Debug, Copy, Clone)]
pub(crate) enum CoverageStatement {
pub(super) enum CoverageStatement {
Statement(BasicBlock, Span, usize),
Terminator(BasicBlock, Span),
}
@ -66,7 +66,7 @@ impl CoverageStatement {
/// or is subsumed by the `Span` associated with this `CoverageSpan`, and it's `BasicBlock`
/// `is_dominated_by()` the `BasicBlock`s in this `CoverageSpan`.
#[derive(Debug, Clone)]
pub(crate) struct CoverageSpan {
pub(super) struct CoverageSpan {
pub span: Span,
pub bcb: BasicCoverageBlock,
pub coverage_statements: Vec<CoverageStatement>,
@ -214,7 +214,7 @@ pub struct CoverageSpans<'a, 'tcx> {
}
impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
pub(crate) fn generate_coverage_spans(
pub(super) fn generate_coverage_spans(
mir_body: &'a mir::Body<'tcx>,
body_span: Span,
basic_coverage_blocks: &'a CoverageGraph,
@ -645,7 +645,10 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
}
}
fn filtered_statement_span(statement: &'a Statement<'tcx>, body_span: Span) -> Option<Span> {
pub(super) fn filtered_statement_span(
statement: &'a Statement<'tcx>,
body_span: Span,
) -> Option<Span> {
match statement.kind {
// These statements have spans that are often outside the scope of the executed source code
// for their parent `BasicBlock`.
@ -686,7 +689,10 @@ fn filtered_statement_span(statement: &'a Statement<'tcx>, body_span: Span) -> O
}
}
fn filtered_terminator_span(terminator: &'a Terminator<'tcx>, body_span: Span) -> Option<Span> {
pub(super) fn filtered_terminator_span(
terminator: &'a Terminator<'tcx>,
body_span: Span,
) -> Option<Span> {
match terminator.kind {
// These terminators have spans that don't positively contribute to computing a reasonable
// span of actually executed source code. (For example, SwitchInt terminators extracted from

View File

@ -0,0 +1,12 @@
[package]
authors = ["The Rust Project Developers"]
name = "coverage_test_macros"
version = "0.0.0"
edition = "2018"
[lib]
proc-macro = true
doctest = false
[dependencies]
proc-macro2 = "1"

View File

@ -0,0 +1,6 @@
use proc_macro::TokenStream;
#[proc_macro]
pub fn let_bcb(item: TokenStream) -> TokenStream {
format!("let bcb{} = graph::BasicCoverageBlock::from_usize({});", item, item).parse().unwrap()
}

View File

@ -0,0 +1,714 @@
//! This crate hosts a selection of "unit tests" for components of the `InstrumentCoverage` MIR
//! pass.
//!
//! The tests construct a few "mock" objects, as needed, to support the `InstrumentCoverage`
//! functions and algorithms. Mocked objects include instances of `mir::Body`; including
//! `Terminator`s of various `kind`s, and `Span` objects. Some functions used by or used on
//! real, runtime versions of these mocked-up objects have constraints (such as cross-thread
//! limitations) and deep dependencies on other elements of the full Rust compiler (which is
//! *not* constructed or mocked for these tests).
//!
//! Of particular note, attempting to simply print elements of the `mir::Body` with default
//! `Debug` formatting can fail because some `Debug` format implementations require the
//! `TyCtxt`, obtained via a static global variable that is *not* set for these tests.
//! Initializing the global type context is prohibitively complex for the scope and scale of these
//! tests (essentially requiring initializing the entire compiler).
//!
//! Also note, some basic features of `Span` also rely on the `Span`s own "session globals", which
//! are unrelated to the `TyCtxt` global. Without initializing the `Span` session globals, some
//! basic, coverage-specific features would be impossible to test, but thankfully initializing these
//! globals is comparitively simpler. The easiest way is to wrap the test in a closure argument
//! to: `rustc_span::with_default_session_globals(|| { test_here(); })`.
use super::counters;
use super::debug;
use super::graph;
use super::spans;
use coverage_test_macros::let_bcb;
use rustc_data_structures::graph::WithNumNodes;
use rustc_data_structures::graph::WithSuccessors;
use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::mir::coverage::CoverageKind;
use rustc_middle::mir::*;
use rustc_middle::ty::{self, DebruijnIndex, TyS, TypeFlags};
use rustc_span::{self, BytePos, Pos, Span, DUMMY_SP};
// All `TEMP_BLOCK` targets should be replaced before calling `to_body() -> mir::Body`.
const TEMP_BLOCK: BasicBlock = BasicBlock::MAX;
fn dummy_ty() -> &'static TyS<'static> {
thread_local! {
static DUMMY_TYS: &'static TyS<'static> = Box::leak(box TyS::make_for_test(
ty::Bool,
TypeFlags::empty(),
DebruijnIndex::from_usize(0),
));
}
&DUMMY_TYS.with(|tys| *tys)
}
struct MockBlocks<'tcx> {
blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>,
dummy_place: Place<'tcx>,
next_local: usize,
}
impl<'tcx> MockBlocks<'tcx> {
fn new() -> Self {
Self {
blocks: IndexVec::new(),
dummy_place: Place { local: RETURN_PLACE, projection: ty::List::empty() },
next_local: 0,
}
}
fn new_temp(&mut self) -> Local {
let index = self.next_local;
self.next_local += 1;
Local::new(index)
}
fn push(&mut self, kind: TerminatorKind<'tcx>) -> BasicBlock {
let next_lo = if let Some(last) = self.blocks.last() {
self.blocks[last].terminator().source_info.span.hi()
} else {
BytePos(1)
};
let next_hi = next_lo + BytePos(1);
self.blocks.push(BasicBlockData {
statements: vec![],
terminator: Some(Terminator {
source_info: SourceInfo::outermost(Span::with_root_ctxt(next_lo, next_hi)),
kind,
}),
is_cleanup: false,
})
}
fn link(&mut self, from_block: BasicBlock, to_block: BasicBlock) {
match self.blocks[from_block].terminator_mut().kind {
TerminatorKind::Assert { ref mut target, .. }
| TerminatorKind::Call { destination: Some((_, ref mut target)), .. }
| TerminatorKind::Drop { ref mut target, .. }
| TerminatorKind::DropAndReplace { ref mut target, .. }
| TerminatorKind::FalseEdge { real_target: ref mut target, .. }
| TerminatorKind::FalseUnwind { real_target: ref mut target, .. }
| TerminatorKind::Goto { ref mut target }
| TerminatorKind::InlineAsm { destination: Some(ref mut target), .. }
| TerminatorKind::Yield { resume: ref mut target, .. } => *target = to_block,
ref invalid => bug!("Invalid from_block: {:?}", invalid),
}
}
fn add_block_from(
&mut self,
some_from_block: Option<BasicBlock>,
to_kind: TerminatorKind<'tcx>,
) -> BasicBlock {
let new_block = self.push(to_kind);
if let Some(from_block) = some_from_block {
self.link(from_block, new_block);
}
new_block
}
fn set_branch(&mut self, switchint: BasicBlock, branch_index: usize, to_block: BasicBlock) {
match self.blocks[switchint].terminator_mut().kind {
TerminatorKind::SwitchInt { ref mut targets, .. } => {
let mut branches = targets.iter().collect::<Vec<_>>();
let otherwise = if branch_index == branches.len() {
to_block
} else {
let old_otherwise = targets.otherwise();
if branch_index > branches.len() {
branches.push((branches.len() as u128, old_otherwise));
while branches.len() < branch_index {
branches.push((branches.len() as u128, TEMP_BLOCK));
}
to_block
} else {
branches[branch_index] = (branch_index as u128, to_block);
old_otherwise
}
};
*targets = SwitchTargets::new(branches.into_iter(), otherwise);
}
ref invalid => bug!("Invalid BasicBlock kind or no to_block: {:?}", invalid),
}
}
fn call(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
self.add_block_from(
some_from_block,
TerminatorKind::Call {
func: Operand::Copy(self.dummy_place.clone()),
args: vec![],
destination: Some((self.dummy_place.clone(), TEMP_BLOCK)),
cleanup: None,
from_hir_call: false,
fn_span: DUMMY_SP,
},
)
}
fn goto(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
self.add_block_from(some_from_block, TerminatorKind::Goto { target: TEMP_BLOCK })
}
fn switchint(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
let switchint_kind = TerminatorKind::SwitchInt {
discr: Operand::Move(Place::from(self.new_temp())),
switch_ty: dummy_ty(),
targets: SwitchTargets::static_if(0, TEMP_BLOCK, TEMP_BLOCK),
};
self.add_block_from(some_from_block, switchint_kind)
}
fn return_(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
self.add_block_from(some_from_block, TerminatorKind::Return)
}
fn to_body(self) -> Body<'tcx> {
Body::new_cfg_only(self.blocks)
}
}
fn debug_basic_blocks(mir_body: &Body<'tcx>) -> String {
format!(
"{:?}",
mir_body
.basic_blocks()
.iter_enumerated()
.map(|(bb, data)| {
let term = &data.terminator();
let kind = &term.kind;
let span = term.source_info.span;
let sp = format!("(span:{},{})", span.lo().to_u32(), span.hi().to_u32());
match kind {
TerminatorKind::Assert { target, .. }
| TerminatorKind::Call { destination: Some((_, target)), .. }
| TerminatorKind::Drop { target, .. }
| TerminatorKind::DropAndReplace { target, .. }
| TerminatorKind::FalseEdge { real_target: target, .. }
| TerminatorKind::FalseUnwind { real_target: target, .. }
| TerminatorKind::Goto { target }
| TerminatorKind::InlineAsm { destination: Some(target), .. }
| TerminatorKind::Yield { resume: target, .. } => {
format!("{}{:?}:{} -> {:?}", sp, bb, debug::term_type(kind), target)
}
TerminatorKind::SwitchInt { targets, .. } => {
format!("{}{:?}:{} -> {:?}", sp, bb, debug::term_type(kind), targets)
}
_ => format!("{}{:?}:{}", sp, bb, debug::term_type(kind)),
}
})
.collect::<Vec<_>>()
)
}
static PRINT_GRAPHS: bool = false;
fn print_mir_graphviz(name: &str, mir_body: &Body<'_>) {
if PRINT_GRAPHS {
println!(
"digraph {} {{\n{}\n}}",
name,
mir_body
.basic_blocks()
.iter_enumerated()
.map(|(bb, data)| {
format!(
" {:?} [label=\"{:?}: {}\"];\n{}",
bb,
bb,
debug::term_type(&data.terminator().kind),
mir_body
.successors(bb)
.map(|successor| { format!(" {:?} -> {:?};", bb, successor) })
.collect::<Vec<_>>()
.join("\n")
)
})
.collect::<Vec<_>>()
.join("\n")
);
}
}
fn print_coverage_graphviz(
name: &str,
mir_body: &Body<'_>,
basic_coverage_blocks: &graph::CoverageGraph,
) {
if PRINT_GRAPHS {
println!(
"digraph {} {{\n{}\n}}",
name,
basic_coverage_blocks
.iter_enumerated()
.map(|(bcb, bcb_data)| {
format!(
" {:?} [label=\"{:?}: {}\"];\n{}",
bcb,
bcb,
debug::term_type(&bcb_data.terminator(mir_body).kind),
basic_coverage_blocks
.successors(bcb)
.map(|successor| { format!(" {:?} -> {:?};", bcb, successor) })
.collect::<Vec<_>>()
.join("\n")
)
})
.collect::<Vec<_>>()
.join("\n")
);
}
}
/// Create a mock `Body` with a simple flow.
fn goto_switchint() -> Body<'a> {
let mut blocks = MockBlocks::new();
let start = blocks.call(None);
let goto = blocks.goto(Some(start));
let switchint = blocks.switchint(Some(goto));
let then_call = blocks.call(None);
let else_call = blocks.call(None);
blocks.set_branch(switchint, 0, then_call);
blocks.set_branch(switchint, 1, else_call);
blocks.return_(Some(then_call));
blocks.return_(Some(else_call));
let mir_body = blocks.to_body();
print_mir_graphviz("mir_goto_switchint", &mir_body);
/* Graphviz character plots created using: `graph-easy --as=boxart`:
bb0: Call
bb1: Goto
bb4: Call bb2: SwitchInt
bb6: Return bb3: Call
bb5: Return
*/
mir_body
}
macro_rules! assert_successors {
($basic_coverage_blocks:ident, $i:ident, [$($successor:ident),*]) => {
let mut successors = $basic_coverage_blocks.successors[$i].clone();
successors.sort_unstable();
assert_eq!(successors, vec![$($successor),*]);
}
}
#[test]
fn test_covgraph_goto_switchint() {
let mir_body = goto_switchint();
if false {
println!("basic_blocks = {}", debug_basic_blocks(&mir_body));
}
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
print_coverage_graphviz("covgraph_goto_switchint ", &mir_body, &basic_coverage_blocks);
/*
bcb2: Return bcb0: SwitchInt
bcb1: Return
*/
assert_eq!(
basic_coverage_blocks.num_nodes(),
3,
"basic_coverage_blocks: {:?}",
basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
);
let_bcb!(0);
let_bcb!(1);
let_bcb!(2);
assert_successors!(basic_coverage_blocks, bcb0, [bcb1, bcb2]);
assert_successors!(basic_coverage_blocks, bcb1, []);
assert_successors!(basic_coverage_blocks, bcb2, []);
}
/// Create a mock `Body` with a loop.
fn switchint_then_loop_else_return() -> Body<'a> {
let mut blocks = MockBlocks::new();
let start = blocks.call(None);
let switchint = blocks.switchint(Some(start));
let then_call = blocks.call(None);
blocks.set_branch(switchint, 0, then_call);
let backedge_goto = blocks.goto(Some(then_call));
blocks.link(backedge_goto, switchint);
let else_return = blocks.return_(None);
blocks.set_branch(switchint, 1, else_return);
let mir_body = blocks.to_body();
print_mir_graphviz("mir_switchint_then_loop_else_return", &mir_body);
/*
bb0: Call
bb4: Return bb1: SwitchInt
bb2: Call
bb3: Goto
*/
mir_body
}
#[test]
fn test_covgraph_switchint_then_loop_else_return() {
let mir_body = switchint_then_loop_else_return();
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
print_coverage_graphviz(
"covgraph_switchint_then_loop_else_return",
&mir_body,
&basic_coverage_blocks,
);
/*
bcb0: Call
bcb3: Goto bcb1: SwitchInt
bcb2: Return
*/
assert_eq!(
basic_coverage_blocks.num_nodes(),
4,
"basic_coverage_blocks: {:?}",
basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
);
let_bcb!(0);
let_bcb!(1);
let_bcb!(2);
let_bcb!(3);
assert_successors!(basic_coverage_blocks, bcb0, [bcb1]);
assert_successors!(basic_coverage_blocks, bcb1, [bcb2, bcb3]);
assert_successors!(basic_coverage_blocks, bcb2, []);
assert_successors!(basic_coverage_blocks, bcb3, [bcb1]);
}
/// Create a mock `Body` with nested loops.
fn switchint_loop_then_inner_loop_else_break() -> Body<'a> {
let mut blocks = MockBlocks::new();
let start = blocks.call(None);
let switchint = blocks.switchint(Some(start));
let then_call = blocks.call(None);
blocks.set_branch(switchint, 0, then_call);
let else_return = blocks.return_(None);
blocks.set_branch(switchint, 1, else_return);
let inner_start = blocks.call(Some(then_call));
let inner_switchint = blocks.switchint(Some(inner_start));
let inner_then_call = blocks.call(None);
blocks.set_branch(inner_switchint, 0, inner_then_call);
let inner_backedge_goto = blocks.goto(Some(inner_then_call));
blocks.link(inner_backedge_goto, inner_switchint);
let inner_else_break_goto = blocks.goto(None);
blocks.set_branch(inner_switchint, 1, inner_else_break_goto);
let backedge_goto = blocks.goto(Some(inner_else_break_goto));
blocks.link(backedge_goto, switchint);
let mir_body = blocks.to_body();
print_mir_graphviz("mir_switchint_loop_then_inner_loop_else_break", &mir_body);
/*
bb0: Call
bb3: Return bb1: SwitchInt
bb2: Call
bb4: Call
bb8: Goto bb5: SwitchInt
bb9: Goto bb6: Call
bb7: Goto
*/
mir_body
}
#[test]
fn test_covgraph_switchint_loop_then_inner_loop_else_break() {
let mir_body = switchint_loop_then_inner_loop_else_break();
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
print_coverage_graphviz(
"covgraph_switchint_loop_then_inner_loop_else_break",
&mir_body,
&basic_coverage_blocks,
);
/*
bcb0: Call
bcb2: Return bcb1: SwitchInt
bcb3: Call
bcb6: Goto bcb4: SwitchInt
bcb5: Goto
*/
assert_eq!(
basic_coverage_blocks.num_nodes(),
7,
"basic_coverage_blocks: {:?}",
basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
);
let_bcb!(0);
let_bcb!(1);
let_bcb!(2);
let_bcb!(3);
let_bcb!(4);
let_bcb!(5);
let_bcb!(6);
assert_successors!(basic_coverage_blocks, bcb0, [bcb1]);
assert_successors!(basic_coverage_blocks, bcb1, [bcb2, bcb3]);
assert_successors!(basic_coverage_blocks, bcb2, []);
assert_successors!(basic_coverage_blocks, bcb3, [bcb4]);
assert_successors!(basic_coverage_blocks, bcb4, [bcb5, bcb6]);
assert_successors!(basic_coverage_blocks, bcb5, [bcb1]);
assert_successors!(basic_coverage_blocks, bcb6, [bcb4]);
}
#[test]
fn test_find_loop_backedges_none() {
let mir_body = goto_switchint();
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
if false {
println!(
"basic_coverage_blocks = {:?}",
basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
);
println!("successors = {:?}", basic_coverage_blocks.successors);
}
let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
assert_eq!(
backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
0,
"backedges: {:?}",
backedges
);
}
#[test]
fn test_find_loop_backedges_one() {
let mir_body = switchint_then_loop_else_return();
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
assert_eq!(
backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
1,
"backedges: {:?}",
backedges
);
let_bcb!(1);
let_bcb!(3);
assert_eq!(backedges[bcb1], vec![bcb3]);
}
#[test]
fn test_find_loop_backedges_two() {
let mir_body = switchint_loop_then_inner_loop_else_break();
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
assert_eq!(
backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
2,
"backedges: {:?}",
backedges
);
let_bcb!(1);
let_bcb!(4);
let_bcb!(5);
let_bcb!(6);
assert_eq!(backedges[bcb1], vec![bcb5]);
assert_eq!(backedges[bcb4], vec![bcb6]);
}
#[test]
fn test_traverse_coverage_with_loops() {
let mir_body = switchint_loop_then_inner_loop_else_break();
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
let mut traversed_in_order = Vec::new();
let mut traversal = graph::TraverseCoverageGraphWithLoops::new(&basic_coverage_blocks);
while let Some(bcb) = traversal.next(&basic_coverage_blocks) {
traversed_in_order.push(bcb);
}
let_bcb!(6);
// bcb0 is visited first. Then bcb1 starts the first loop, and all remaining nodes, *except*
// bcb6 are inside the first loop.
assert_eq!(
*traversed_in_order.last().expect("should have elements"),
bcb6,
"bcb6 should not be visited until all nodes inside the first loop have been visited"
);
}
fn synthesize_body_span_from_terminators(mir_body: &Body<'_>) -> Span {
let mut some_span: Option<Span> = None;
for (_, data) in mir_body.basic_blocks().iter_enumerated() {
let term_span = data.terminator().source_info.span;
if let Some(span) = some_span.as_mut() {
*span = span.to(term_span);
} else {
some_span = Some(term_span)
}
}
some_span.expect("body must have at least one BasicBlock")
}
#[test]
fn test_make_bcb_counters() {
rustc_span::with_default_session_globals(|| {
let mir_body = goto_switchint();
let body_span = synthesize_body_span_from_terminators(&mir_body);
let mut basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
let mut coverage_spans = Vec::new();
for (bcb, data) in basic_coverage_blocks.iter_enumerated() {
if let Some(span) =
spans::filtered_terminator_span(data.terminator(&mir_body), body_span)
{
coverage_spans.push(spans::CoverageSpan::for_terminator(span, bcb, data.last_bb()));
}
}
let mut coverage_counters = counters::CoverageCounters::new(0);
let intermediate_expressions = coverage_counters
.make_bcb_counters(&mut basic_coverage_blocks, &coverage_spans)
.expect("should be Ok");
assert_eq!(intermediate_expressions.len(), 0);
let_bcb!(1);
assert_eq!(
1, // coincidentally, bcb1 has a `Counter` with id = 1
match basic_coverage_blocks[bcb1].counter().expect("should have a counter") {
CoverageKind::Counter { id, .. } => id,
_ => panic!("expected a Counter"),
}
.as_u32()
);
let_bcb!(2);
assert_eq!(
2, // coincidentally, bcb2 has a `Counter` with id = 2
match basic_coverage_blocks[bcb2].counter().expect("should have a counter") {
CoverageKind::Counter { id, .. } => id,
_ => panic!("expected a Counter"),
}
.as_u32()
);
});
}