Auto merge of #78267 - richkadel:llvm-coverage-counters-2.0.3r1, r=tmandry
Working expression optimization, and some improvements to branch-level source coverage This replaces PR #78040 after reorganizing the original commits (by request) into a more logical sequence of major changes. Most of the work is in the MIR `transform/coverage/` directory (originally, `transform/instrument_coverage.rs`). Note this PR includes some significant additional debugging capabilities, to help myself and any future developer working on coverage improvements or issues. In particular, there's a new Graphviz (.dot file) output for the coverage graph (the `BasicCoverageBlock` control flow graph) that provides ways to get some very good insight into the relationships between the MIR, the coverage graph BCBs, coverage spans, and counters. (There are also some cool debugging options, available via environment variable, to alter how some data in the graph appears.) And the code for this Graphviz view is actually generic... it can be used by any implementation of the Rust `Graph` traits. Finally (for now), I also now output information from `llvm-cov` that shows the actual counters and spans it found in the coverage map, and their counts (from the `--debug` flag). I found this to be enormously helpful in debugging some coverage issues, so I kept it in the test results as well for additional context. `@tmandry` `@wesleywiser` r? `@tmandry` Here's an example of the new coverage graph: * Within each `BasicCoverageBlock` (BCB), you can see each `CoverageSpan` and its contributing statements (MIR `Statement`s and/or `Terminator`s) * Each `CoverageSpan` has a `Counter` or and `Expression`, and `Expression`s show their Add/Subtract operation with nested operations. (This can be changed to show the Counter and Expression IDs instead, or in addition to, the BCB.) * The terminators of all MIR `BasicBlock`s in the BCB, including one final `Terminator` * If an "edge counter" is required (because we need to count an edge between blocks, in some cases) the edge's Counter or Expression is shown next to its label. (Not shown in the example below.) (FYI, Edge Counters are converted into a new MIR `BasicBlock` with `Goto`) <img width="1116" alt="Screen Shot 2020-10-17 at 12 23 29 AM" src="https://user-images.githubusercontent.com/3827298/96331095-616cb480-100f-11eb-8212-60f2d433e2d8.png"> r? `@tmandry` FYI: `@wesleywiser`
This commit is contained in:
commit
8532e742fc
234 changed files with 14075 additions and 2124 deletions
|
@ -129,7 +129,7 @@ impl CoverageMapGenerator {
|
|||
let (filenames_index, _) = self.filenames.insert_full(c_filename);
|
||||
virtual_file_mapping.push(filenames_index as u32);
|
||||
}
|
||||
debug!("Adding counter {:?} to map for {:?}", counter, region,);
|
||||
debug!("Adding counter {:?} to map for {:?}", counter, region);
|
||||
mapping_regions.push(CounterMappingRegion::code_region(
|
||||
counter,
|
||||
current_file_id,
|
||||
|
|
|
@ -12,7 +12,7 @@ use rustc_codegen_ssa::traits::{
|
|||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_llvm::RustString;
|
||||
use rustc_middle::mir::coverage::{
|
||||
CodeRegion, CounterValueReference, ExpressionOperandId, InjectedExpressionIndex, Op,
|
||||
CodeRegion, CounterValueReference, ExpressionOperandId, InjectedExpressionId, Op,
|
||||
};
|
||||
use rustc_middle::ty::Instance;
|
||||
|
||||
|
@ -27,8 +27,8 @@ const COVMAP_VAR_ALIGN_BYTES: usize = 8;
|
|||
|
||||
/// A context object for maintaining all state needed by the coverageinfo module.
|
||||
pub struct CrateCoverageContext<'tcx> {
|
||||
// Coverage region data for each instrumented function identified by DefId.
|
||||
pub(crate) function_coverage_map: RefCell<FxHashMap<Instance<'tcx>, FunctionCoverage>>,
|
||||
// Coverage data for each instrumented function identified by DefId.
|
||||
pub(crate) function_coverage_map: RefCell<FxHashMap<Instance<'tcx>, FunctionCoverage<'tcx>>>,
|
||||
}
|
||||
|
||||
impl<'tcx> CrateCoverageContext<'tcx> {
|
||||
|
@ -36,7 +36,7 @@ impl<'tcx> CrateCoverageContext<'tcx> {
|
|||
Self { function_coverage_map: Default::default() }
|
||||
}
|
||||
|
||||
pub fn take_function_coverage_map(&self) -> FxHashMap<Instance<'tcx>, FunctionCoverage> {
|
||||
pub fn take_function_coverage_map(&self) -> FxHashMap<Instance<'tcx>, FunctionCoverage<'tcx>> {
|
||||
self.function_coverage_map.replace(FxHashMap::default())
|
||||
}
|
||||
}
|
||||
|
@ -58,47 +58,66 @@ impl CoverageInfoBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
unsafe { llvm::LLVMRustCoverageCreatePGOFuncNameVar(llfn, mangled_fn_name.as_ptr()) }
|
||||
}
|
||||
|
||||
fn add_counter_region(
|
||||
fn set_function_source_hash(
|
||||
&mut self,
|
||||
instance: Instance<'tcx>,
|
||||
function_source_hash: u64,
|
||||
id: CounterValueReference,
|
||||
region: CodeRegion,
|
||||
) -> bool {
|
||||
if let Some(coverage_context) = self.coverage_context() {
|
||||
debug!(
|
||||
"adding counter to coverage_regions: instance={:?}, function_source_hash={}, id={:?}, \
|
||||
at {:?}",
|
||||
instance, function_source_hash, id, region,
|
||||
"ensuring function source hash is set for instance={:?}; function_source_hash={}",
|
||||
instance, function_source_hash,
|
||||
);
|
||||
let mut coverage_regions = coverage_context.function_coverage_map.borrow_mut();
|
||||
coverage_regions
|
||||
let mut coverage_map = coverage_context.function_coverage_map.borrow_mut();
|
||||
coverage_map
|
||||
.entry(instance)
|
||||
.or_insert_with(|| FunctionCoverage::new(self.tcx, instance))
|
||||
.add_counter(function_source_hash, id, region);
|
||||
.set_function_source_hash(function_source_hash);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn add_counter_expression_region(
|
||||
fn add_coverage_counter(
|
||||
&mut self,
|
||||
instance: Instance<'tcx>,
|
||||
id: InjectedExpressionIndex,
|
||||
lhs: ExpressionOperandId,
|
||||
op: Op,
|
||||
rhs: ExpressionOperandId,
|
||||
id: CounterValueReference,
|
||||
region: CodeRegion,
|
||||
) -> bool {
|
||||
if let Some(coverage_context) = self.coverage_context() {
|
||||
debug!(
|
||||
"adding counter expression to coverage_regions: instance={:?}, id={:?}, {:?} {:?} {:?}, \
|
||||
at {:?}",
|
||||
"adding counter to coverage_map: instance={:?}, id={:?}, region={:?}",
|
||||
instance, id, region,
|
||||
);
|
||||
let mut coverage_map = coverage_context.function_coverage_map.borrow_mut();
|
||||
coverage_map
|
||||
.entry(instance)
|
||||
.or_insert_with(|| FunctionCoverage::new(self.tcx, instance))
|
||||
.add_counter(id, region);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn add_coverage_counter_expression(
|
||||
&mut self,
|
||||
instance: Instance<'tcx>,
|
||||
id: InjectedExpressionId,
|
||||
lhs: ExpressionOperandId,
|
||||
op: Op,
|
||||
rhs: ExpressionOperandId,
|
||||
region: Option<CodeRegion>,
|
||||
) -> bool {
|
||||
if let Some(coverage_context) = self.coverage_context() {
|
||||
debug!(
|
||||
"adding counter expression to coverage_map: instance={:?}, id={:?}, {:?} {:?} {:?}; \
|
||||
region: {:?}",
|
||||
instance, id, lhs, op, rhs, region,
|
||||
);
|
||||
let mut coverage_regions = coverage_context.function_coverage_map.borrow_mut();
|
||||
coverage_regions
|
||||
let mut coverage_map = coverage_context.function_coverage_map.borrow_mut();
|
||||
coverage_map
|
||||
.entry(instance)
|
||||
.or_insert_with(|| FunctionCoverage::new(self.tcx, instance))
|
||||
.add_counter_expression(id, lhs, op, rhs, region);
|
||||
|
@ -108,14 +127,14 @@ impl CoverageInfoBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_unreachable_region(&mut self, instance: Instance<'tcx>, region: CodeRegion) -> bool {
|
||||
fn add_coverage_unreachable(&mut self, instance: Instance<'tcx>, region: CodeRegion) -> bool {
|
||||
if let Some(coverage_context) = self.coverage_context() {
|
||||
debug!(
|
||||
"adding unreachable code to coverage_regions: instance={:?}, at {:?}",
|
||||
"adding unreachable code to coverage_map: instance={:?}, at {:?}",
|
||||
instance, region,
|
||||
);
|
||||
let mut coverage_regions = coverage_context.function_coverage_map.borrow_mut();
|
||||
coverage_regions
|
||||
let mut coverage_map = coverage_context.function_coverage_map.borrow_mut();
|
||||
coverage_map
|
||||
.entry(instance)
|
||||
.or_insert_with(|| FunctionCoverage::new(self.tcx, instance))
|
||||
.add_unreachable_region(region);
|
||||
|
|
|
@ -3,7 +3,7 @@ use rustc_middle::mir::coverage::{CounterValueReference, MappedExpressionIndex};
|
|||
/// Aligns with [llvm::coverage::Counter::CounterKind](https://github.com/rust-lang/llvm-project/blob/rustc/10.0-2020-05-05/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h#L91)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
enum CounterKind {
|
||||
pub enum CounterKind {
|
||||
Zero = 0,
|
||||
CounterValueReference = 1,
|
||||
Expression = 2,
|
||||
|
@ -23,8 +23,8 @@ enum CounterKind {
|
|||
#[repr(C)]
|
||||
pub struct Counter {
|
||||
// Important: The layout (order and types of fields) must match its C++ counterpart.
|
||||
kind: CounterKind,
|
||||
id: u32,
|
||||
pub kind: CounterKind,
|
||||
pub id: u32,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
|
@ -55,9 +55,9 @@ pub enum ExprKind {
|
|||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct CounterExpression {
|
||||
kind: ExprKind,
|
||||
lhs: Counter,
|
||||
rhs: Counter,
|
||||
pub kind: ExprKind,
|
||||
pub lhs: Counter,
|
||||
pub rhs: Counter,
|
||||
}
|
||||
|
||||
impl CounterExpression {
|
||||
|
|
|
@ -2,18 +2,18 @@ pub use super::ffi::*;
|
|||
|
||||
use rustc_index::vec::IndexVec;
|
||||
use rustc_middle::mir::coverage::{
|
||||
CodeRegion, CounterValueReference, ExpressionOperandId, InjectedExpressionIndex,
|
||||
MappedExpressionIndex, Op,
|
||||
CodeRegion, CounterValueReference, ExpressionOperandId, InjectedExpressionId,
|
||||
InjectedExpressionIndex, MappedExpressionIndex, Op,
|
||||
};
|
||||
use rustc_middle::ty::Instance;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExpressionRegion {
|
||||
pub struct Expression {
|
||||
lhs: ExpressionOperandId,
|
||||
op: Op,
|
||||
rhs: ExpressionOperandId,
|
||||
region: CodeRegion,
|
||||
region: Option<CodeRegion>,
|
||||
}
|
||||
|
||||
/// Collects all of the coverage regions associated with (a) injected counters, (b) counter
|
||||
|
@ -28,17 +28,23 @@ pub struct ExpressionRegion {
|
|||
/// only whitespace or comments). According to LLVM Code Coverage Mapping documentation, "A count
|
||||
/// for a gap area is only used as the line execution count if there are no other regions on a
|
||||
/// line."
|
||||
pub struct FunctionCoverage {
|
||||
pub struct FunctionCoverage<'tcx> {
|
||||
instance: Instance<'tcx>,
|
||||
source_hash: u64,
|
||||
counters: IndexVec<CounterValueReference, Option<CodeRegion>>,
|
||||
expressions: IndexVec<InjectedExpressionIndex, Option<ExpressionRegion>>,
|
||||
expressions: IndexVec<InjectedExpressionIndex, Option<Expression>>,
|
||||
unreachable_regions: Vec<CodeRegion>,
|
||||
}
|
||||
|
||||
impl FunctionCoverage {
|
||||
pub fn new<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Self {
|
||||
impl<'tcx> FunctionCoverage<'tcx> {
|
||||
pub fn new(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Self {
|
||||
let coverageinfo = tcx.coverageinfo(instance.def_id());
|
||||
debug!(
|
||||
"FunctionCoverage::new(instance={:?}) has coverageinfo={:?}",
|
||||
instance, coverageinfo
|
||||
);
|
||||
Self {
|
||||
instance,
|
||||
source_hash: 0, // will be set with the first `add_counter()`
|
||||
counters: IndexVec::from_elem_n(None, coverageinfo.num_counters as usize),
|
||||
expressions: IndexVec::from_elem_n(None, coverageinfo.num_expressions as usize),
|
||||
|
@ -46,15 +52,18 @@ impl FunctionCoverage {
|
|||
}
|
||||
}
|
||||
|
||||
/// Adds a code region to be counted by an injected counter intrinsic.
|
||||
/// The source_hash (computed during coverage instrumentation) should also be provided, and
|
||||
/// should be the same for all counters in a given function.
|
||||
pub fn add_counter(&mut self, source_hash: u64, id: CounterValueReference, region: CodeRegion) {
|
||||
/// Sets the function source hash value. If called multiple times for the same function, all
|
||||
/// calls should have the same hash value.
|
||||
pub fn set_function_source_hash(&mut self, source_hash: u64) {
|
||||
if self.source_hash == 0 {
|
||||
self.source_hash = source_hash;
|
||||
} else {
|
||||
debug_assert_eq!(source_hash, self.source_hash);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a code region to be counted by an injected counter intrinsic.
|
||||
pub fn add_counter(&mut self, id: CounterValueReference, region: CodeRegion) {
|
||||
self.counters[id].replace(region).expect_none("add_counter called with duplicate `id`");
|
||||
}
|
||||
|
||||
|
@ -74,15 +83,19 @@ impl FunctionCoverage {
|
|||
/// counters and expressions have been added.
|
||||
pub fn add_counter_expression(
|
||||
&mut self,
|
||||
expression_id: InjectedExpressionIndex,
|
||||
expression_id: InjectedExpressionId,
|
||||
lhs: ExpressionOperandId,
|
||||
op: Op,
|
||||
rhs: ExpressionOperandId,
|
||||
region: CodeRegion,
|
||||
region: Option<CodeRegion>,
|
||||
) {
|
||||
debug!(
|
||||
"add_counter_expression({:?}, lhs={:?}, op={:?}, rhs={:?} at {:?}",
|
||||
expression_id, lhs, op, rhs, region
|
||||
);
|
||||
let expression_index = self.expression_index(u32::from(expression_id));
|
||||
self.expressions[expression_index]
|
||||
.replace(ExpressionRegion { lhs, op, rhs, region })
|
||||
.replace(Expression { lhs, op, rhs, region })
|
||||
.expect_none("add_counter_expression called with duplicate `id_descending_from_max`");
|
||||
}
|
||||
|
||||
|
@ -103,7 +116,11 @@ impl FunctionCoverage {
|
|||
pub fn get_expressions_and_counter_regions<'a>(
|
||||
&'a self,
|
||||
) -> (Vec<CounterExpression>, impl Iterator<Item = (Counter, &'a CodeRegion)>) {
|
||||
assert!(self.source_hash != 0);
|
||||
assert!(
|
||||
self.source_hash != 0,
|
||||
"No counters provided the source_hash for function: {:?}",
|
||||
self.instance
|
||||
);
|
||||
|
||||
let counter_regions = self.counter_regions();
|
||||
let (counter_expressions, expression_regions) = self.expressions_with_regions();
|
||||
|
@ -129,54 +146,78 @@ impl FunctionCoverage {
|
|||
) -> (Vec<CounterExpression>, impl Iterator<Item = (Counter, &'a CodeRegion)>) {
|
||||
let mut counter_expressions = Vec::with_capacity(self.expressions.len());
|
||||
let mut expression_regions = Vec::with_capacity(self.expressions.len());
|
||||
let mut new_indexes =
|
||||
IndexVec::from_elem_n(MappedExpressionIndex::from(u32::MAX), self.expressions.len());
|
||||
// Note, the initial value shouldn't matter since every index in use in `self.expressions`
|
||||
// will be set, and after that, `new_indexes` will only be accessed using those same
|
||||
// indexes.
|
||||
let mut new_indexes = IndexVec::from_elem_n(None, self.expressions.len());
|
||||
|
||||
// Note that an `ExpressionRegion`s at any given index can include other expressions as
|
||||
// This closure converts any `Expression` operand (`lhs` or `rhs` of the `Op::Add` or
|
||||
// `Op::Subtract` operation) into its native `llvm::coverage::Counter::CounterKind` type
|
||||
// and value. Operand ID value `0` maps to `CounterKind::Zero`; values in the known range
|
||||
// of injected LLVM counters map to `CounterKind::CounterValueReference` (and the value
|
||||
// matches the injected counter index); and any other value is converted into a
|
||||
// `CounterKind::Expression` with the expression's `new_index`.
|
||||
//
|
||||
// Expressions will be returned from this function in a sequential vector (array) of
|
||||
// `CounterExpression`, so the expression IDs must be mapped from their original,
|
||||
// potentially sparse set of indexes, originally in reverse order from `u32::MAX`.
|
||||
//
|
||||
// An `Expression` as an operand will have already been encountered as an `Expression` with
|
||||
// operands, so its new_index will already have been generated (as a 1-up index value).
|
||||
// (If an `Expression` as an operand does not have a corresponding new_index, it was
|
||||
// probably optimized out, after the expression was injected into the MIR, so it will
|
||||
// get a `CounterKind::Zero` instead.)
|
||||
//
|
||||
// In other words, an `Expression`s at any given index can include other expressions as
|
||||
// operands, but expression operands can only come from the subset of expressions having
|
||||
// `expression_index`s lower than the referencing `ExpressionRegion`. Therefore, it is
|
||||
// `expression_index`s lower than the referencing `Expression`. Therefore, it is
|
||||
// reasonable to look up the new index of an expression operand while the `new_indexes`
|
||||
// vector is only complete up to the current `ExpressionIndex`.
|
||||
let id_to_counter =
|
||||
|new_indexes: &IndexVec<InjectedExpressionIndex, MappedExpressionIndex>,
|
||||
|new_indexes: &IndexVec<InjectedExpressionIndex, Option<MappedExpressionIndex>>,
|
||||
id: ExpressionOperandId| {
|
||||
if id == ExpressionOperandId::ZERO {
|
||||
Some(Counter::zero())
|
||||
} else if id.index() < self.counters.len() {
|
||||
// Note: Some codegen-injected Counters may be only referenced by `Expression`s,
|
||||
// and may not have their own `CodeRegion`s,
|
||||
let index = CounterValueReference::from(id.index());
|
||||
self.counters
|
||||
.get(index)
|
||||
.unwrap() // pre-validated
|
||||
.as_ref()
|
||||
.map(|_| Counter::counter_value_reference(index))
|
||||
Some(Counter::counter_value_reference(index))
|
||||
} else {
|
||||
let index = self.expression_index(u32::from(id));
|
||||
self.expressions
|
||||
.get(index)
|
||||
.expect("expression id is out of range")
|
||||
.as_ref()
|
||||
.map(|_| Counter::expression(new_indexes[index]))
|
||||
// If an expression was optimized out, assume it would have produced a count
|
||||
// of zero. This ensures that expressions dependent on optimized-out
|
||||
// expressions are still valid.
|
||||
.map_or(Some(Counter::zero()), |_| {
|
||||
new_indexes[index].map(|new_index| Counter::expression(new_index))
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
for (original_index, expression_region) in
|
||||
for (original_index, expression) in
|
||||
self.expressions.iter_enumerated().filter_map(|(original_index, entry)| {
|
||||
// Option::map() will return None to filter out missing expressions. This may happen
|
||||
// if, for example, a MIR-instrumented expression is removed during an optimization.
|
||||
entry.as_ref().map(|region| (original_index, region))
|
||||
entry.as_ref().map(|expression| (original_index, expression))
|
||||
})
|
||||
{
|
||||
let region = &expression_region.region;
|
||||
let ExpressionRegion { lhs, op, rhs, .. } = *expression_region;
|
||||
let optional_region = &expression.region;
|
||||
let Expression { lhs, op, rhs, .. } = *expression;
|
||||
|
||||
if let Some(Some((lhs_counter, rhs_counter))) =
|
||||
id_to_counter(&new_indexes, lhs).map(|lhs_counter| {
|
||||
id_to_counter(&new_indexes, rhs).map(|rhs_counter| (lhs_counter, rhs_counter))
|
||||
})
|
||||
{
|
||||
debug_assert!(
|
||||
(lhs_counter.id as usize)
|
||||
< usize::max(self.counters.len(), self.expressions.len())
|
||||
);
|
||||
debug_assert!(
|
||||
(rhs_counter.id as usize)
|
||||
< usize::max(self.counters.len(), self.expressions.len())
|
||||
);
|
||||
// Both operands exist. `Expression` operands exist in `self.expressions` and have
|
||||
// been assigned a `new_index`.
|
||||
let mapped_expression_index =
|
||||
|
@ -190,13 +231,21 @@ impl FunctionCoverage {
|
|||
rhs_counter,
|
||||
);
|
||||
debug!(
|
||||
"Adding expression {:?} = {:?} at {:?}",
|
||||
mapped_expression_index, expression, region
|
||||
"Adding expression {:?} = {:?}, region: {:?}",
|
||||
mapped_expression_index, expression, optional_region
|
||||
);
|
||||
counter_expressions.push(expression);
|
||||
new_indexes[original_index] = mapped_expression_index;
|
||||
new_indexes[original_index] = Some(mapped_expression_index);
|
||||
if let Some(region) = optional_region {
|
||||
expression_regions.push((Counter::expression(mapped_expression_index), region));
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"Ignoring expression with one or more missing operands: \
|
||||
original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}",
|
||||
original_index, lhs, op, rhs, optional_region,
|
||||
)
|
||||
}
|
||||
}
|
||||
(counter_expressions, expression_regions.into_iter())
|
||||
}
|
||||
|
|
|
@ -10,25 +10,37 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
let Coverage { kind, code_region } = coverage;
|
||||
match kind {
|
||||
CoverageKind::Counter { function_source_hash, id } => {
|
||||
if bx.add_counter_region(self.instance, function_source_hash, id, code_region) {
|
||||
if bx.set_function_source_hash(self.instance, function_source_hash) {
|
||||
// If `set_function_source_hash()` returned true, the coverage map is enabled,
|
||||
// so continue adding the counter.
|
||||
if let Some(code_region) = code_region {
|
||||
// Note: Some counters do not have code regions, but may still be referenced
|
||||
// from expressions. In that case, don't add the counter to the coverage map,
|
||||
// but do inject the counter intrinsic.
|
||||
bx.add_coverage_counter(self.instance, id, code_region);
|
||||
}
|
||||
|
||||
let coverageinfo = bx.tcx().coverageinfo(self.instance.def_id());
|
||||
|
||||
let fn_name = bx.create_pgo_func_name_var(self.instance);
|
||||
let hash = bx.const_u64(function_source_hash);
|
||||
let num_counters = bx.const_u32(coverageinfo.num_counters);
|
||||
let id = bx.const_u32(u32::from(id));
|
||||
let index = bx.const_u32(u32::from(id));
|
||||
debug!(
|
||||
"codegen intrinsic instrprof.increment(fn_name={:?}, hash={:?}, num_counters={:?}, index={:?})",
|
||||
fn_name, hash, num_counters, id,
|
||||
fn_name, hash, num_counters, index,
|
||||
);
|
||||
bx.instrprof_increment(fn_name, hash, num_counters, id);
|
||||
bx.instrprof_increment(fn_name, hash, num_counters, index);
|
||||
}
|
||||
}
|
||||
CoverageKind::Expression { id, lhs, op, rhs } => {
|
||||
bx.add_counter_expression_region(self.instance, id, lhs, op, rhs, code_region);
|
||||
bx.add_coverage_counter_expression(self.instance, id, lhs, op, rhs, code_region);
|
||||
}
|
||||
CoverageKind::Unreachable => {
|
||||
bx.add_unreachable_region(self.instance, code_region);
|
||||
bx.add_coverage_unreachable(
|
||||
self.instance,
|
||||
code_region.expect("unreachable regions always have code regions"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,29 +9,37 @@ pub trait CoverageInfoMethods: BackendTypes {
|
|||
pub trait CoverageInfoBuilderMethods<'tcx>: BackendTypes {
|
||||
fn create_pgo_func_name_var(&self, instance: Instance<'tcx>) -> Self::Value;
|
||||
|
||||
/// Returns true if the counter was added to the coverage map; false if `-Z instrument-coverage`
|
||||
/// is not enabled (a coverage map is not being generated).
|
||||
fn add_counter_region(
|
||||
/// Returns true if the function source hash was added to the coverage map (even if it had
|
||||
/// already been added, for this instance). Returns false *only* if `-Z instrument-coverage` is
|
||||
/// not enabled (a coverage map is not being generated).
|
||||
fn set_function_source_hash(
|
||||
&mut self,
|
||||
instance: Instance<'tcx>,
|
||||
function_source_hash: u64,
|
||||
id: CounterValueReference,
|
||||
) -> bool;
|
||||
|
||||
/// Returns true if the counter was added to the coverage map; false if `-Z instrument-coverage`
|
||||
/// is not enabled (a coverage map is not being generated).
|
||||
fn add_coverage_counter(
|
||||
&mut self,
|
||||
instance: Instance<'tcx>,
|
||||
index: CounterValueReference,
|
||||
region: CodeRegion,
|
||||
) -> bool;
|
||||
|
||||
/// Returns true if the expression was added to the coverage map; false if
|
||||
/// `-Z instrument-coverage` is not enabled (a coverage map is not being generated).
|
||||
fn add_counter_expression_region(
|
||||
fn add_coverage_counter_expression(
|
||||
&mut self,
|
||||
instance: Instance<'tcx>,
|
||||
id: InjectedExpressionIndex,
|
||||
id: InjectedExpressionId,
|
||||
lhs: ExpressionOperandId,
|
||||
op: Op,
|
||||
rhs: ExpressionOperandId,
|
||||
region: CodeRegion,
|
||||
region: Option<CodeRegion>,
|
||||
) -> bool;
|
||||
|
||||
/// Returns true if the region was added to the coverage map; false if `-Z instrument-coverage`
|
||||
/// is not enabled (a coverage map is not being generated).
|
||||
fn add_unreachable_region(&mut self, instance: Instance<'tcx>, region: CodeRegion) -> bool;
|
||||
fn add_coverage_unreachable(&mut self, instance: Instance<'tcx>, region: CodeRegion) -> bool;
|
||||
}
|
||||
|
|
|
@ -643,6 +643,7 @@ where
|
|||
}
|
||||
if options.contains(&RenderOption::DarkTheme) {
|
||||
graph_attrs.push(r#"bgcolor="black""#);
|
||||
graph_attrs.push(r#"fontcolor="white""#);
|
||||
content_attrs.push(r#"color="white""#);
|
||||
content_attrs.push(r#"fontcolor="white""#);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@ use std::cmp::Ord;
|
|||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
/// An ExpressionOperandId value is assigned directly from either a
|
||||
/// CounterValueReference.as_u32() (which ascend from 1) or an ExpressionOperandId.as_u32()
|
||||
/// (which _*descend*_ from u32::MAX). Id value `0` (zero) represents a virtual counter with a
|
||||
/// constant value of `0`.
|
||||
pub struct ExpressionOperandId {
|
||||
derive [HashStable]
|
||||
DEBUG_FORMAT = "ExpressionOperandId({})",
|
||||
|
@ -42,6 +46,20 @@ impl CounterValueReference {
|
|||
}
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
/// InjectedExpressionId.as_u32() converts to ExpressionOperandId.as_u32()
|
||||
///
|
||||
/// Values descend from u32::MAX.
|
||||
pub struct InjectedExpressionId {
|
||||
derive [HashStable]
|
||||
DEBUG_FORMAT = "InjectedExpressionId({})",
|
||||
MAX = 0xFFFF_FFFF,
|
||||
}
|
||||
}
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
/// InjectedExpressionIndex.as_u32() translates to u32::MAX - ExpressionOperandId.as_u32()
|
||||
///
|
||||
/// Values ascend from 0.
|
||||
pub struct InjectedExpressionIndex {
|
||||
derive [HashStable]
|
||||
DEBUG_FORMAT = "InjectedExpressionIndex({})",
|
||||
|
@ -50,6 +68,9 @@ rustc_index::newtype_index! {
|
|||
}
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
/// MappedExpressionIndex values ascend from zero, and are recalculated indexes based on their
|
||||
/// array position in the LLVM coverage map "Expressions" array, which is assembled during the
|
||||
/// "mapgen" process. They cannot be computed algorithmically, from the other `newtype_index`s.
|
||||
pub struct MappedExpressionIndex {
|
||||
derive [HashStable]
|
||||
DEBUG_FORMAT = "MappedExpressionIndex({})",
|
||||
|
@ -64,21 +85,21 @@ impl From<CounterValueReference> for ExpressionOperandId {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<InjectedExpressionIndex> for ExpressionOperandId {
|
||||
impl From<InjectedExpressionId> for ExpressionOperandId {
|
||||
#[inline]
|
||||
fn from(v: InjectedExpressionIndex) -> ExpressionOperandId {
|
||||
fn from(v: InjectedExpressionId) -> ExpressionOperandId {
|
||||
ExpressionOperandId::from(v.as_u32())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
|
||||
#[derive(Clone, PartialEq, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
|
||||
pub enum CoverageKind {
|
||||
Counter {
|
||||
function_source_hash: u64,
|
||||
id: CounterValueReference,
|
||||
},
|
||||
Expression {
|
||||
id: InjectedExpressionIndex,
|
||||
id: InjectedExpressionId,
|
||||
lhs: ExpressionOperandId,
|
||||
op: Op,
|
||||
rhs: ExpressionOperandId,
|
||||
|
@ -88,13 +109,48 @@ pub enum CoverageKind {
|
|||
|
||||
impl CoverageKind {
|
||||
pub fn as_operand_id(&self) -> ExpressionOperandId {
|
||||
use CoverageKind::*;
|
||||
match *self {
|
||||
CoverageKind::Counter { id, .. } => ExpressionOperandId::from(id),
|
||||
CoverageKind::Expression { id, .. } => ExpressionOperandId::from(id),
|
||||
CoverageKind::Unreachable => {
|
||||
bug!("Unreachable coverage cannot be part of an expression")
|
||||
Counter { id, .. } => ExpressionOperandId::from(id),
|
||||
Expression { id, .. } => ExpressionOperandId::from(id),
|
||||
Unreachable => bug!("Unreachable coverage cannot be part of an expression"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_counter(&self) -> bool {
|
||||
match self {
|
||||
Self::Counter { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_expression(&self) -> bool {
|
||||
match self {
|
||||
Self::Expression { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_unreachable(&self) -> bool {
|
||||
*self == Self::Unreachable
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for CoverageKind {
|
||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||
use CoverageKind::*;
|
||||
match self {
|
||||
Counter { id, .. } => write!(fmt, "Counter({:?})", id.index()),
|
||||
Expression { id, lhs, op, rhs } => write!(
|
||||
fmt,
|
||||
"Expression({:?}) = {} {} {}",
|
||||
id.index(),
|
||||
lhs.index(),
|
||||
if *op == Op::Add { "+" } else { "-" },
|
||||
rhs.index(),
|
||||
),
|
||||
Unreachable => write!(fmt, "Unreachable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1585,21 +1585,10 @@ impl Debug for Statement<'_> {
|
|||
write!(fmt, "AscribeUserType({:?}, {:?}, {:?})", place, variance, c_ty)
|
||||
}
|
||||
Coverage(box ref coverage) => {
|
||||
let rgn = &coverage.code_region;
|
||||
match coverage.kind {
|
||||
CoverageKind::Counter { id, .. } => {
|
||||
write!(fmt, "Coverage::Counter({:?}) for {:?}", id.index(), rgn)
|
||||
}
|
||||
CoverageKind::Expression { id, lhs, op, rhs } => write!(
|
||||
fmt,
|
||||
"Coverage::Expression({:?}) = {} {} {} for {:?}",
|
||||
id.index(),
|
||||
lhs.index(),
|
||||
if op == coverage::Op::Add { "+" } else { "-" },
|
||||
rhs.index(),
|
||||
rgn
|
||||
),
|
||||
CoverageKind::Unreachable => write!(fmt, "Coverage::Unreachable for {:?}", rgn),
|
||||
if let Some(rgn) = &coverage.code_region {
|
||||
write!(fmt, "Coverage::{:?} for {:?}", coverage.kind, rgn)
|
||||
} else {
|
||||
write!(fmt, "Coverage::{:?}", coverage.kind)
|
||||
}
|
||||
}
|
||||
Nop => write!(fmt, "nop"),
|
||||
|
@ -1610,7 +1599,7 @@ impl Debug for Statement<'_> {
|
|||
#[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
|
||||
pub struct Coverage {
|
||||
pub kind: CoverageKind,
|
||||
pub code_region: CodeRegion,
|
||||
pub code_region: Option<CodeRegion>,
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -300,6 +300,7 @@ CloneTypeFoldableAndLiftImpls! {
|
|||
::rustc_target::spec::abi::Abi,
|
||||
crate::mir::coverage::ExpressionOperandId,
|
||||
crate::mir::coverage::CounterValueReference,
|
||||
crate::mir::coverage::InjectedExpressionId,
|
||||
crate::mir::coverage::InjectedExpressionIndex,
|
||||
crate::mir::coverage::MappedExpressionIndex,
|
||||
crate::mir::Local,
|
||||
|
|
615
compiler/rustc_mir/src/transform/coverage/counters.rs
Normal file
615
compiler/rustc_mir/src/transform/coverage/counters.rs
Normal file
|
@ -0,0 +1,615 @@
|
|||
use super::Error;
|
||||
|
||||
use super::debug;
|
||||
use super::graph;
|
||||
use super::spans;
|
||||
|
||||
use debug::{DebugCounters, NESTED_INDENT};
|
||||
use graph::{BasicCoverageBlock, BcbBranch, CoverageGraph, TraverseCoverageGraphWithLoops};
|
||||
use spans::CoverageSpan;
|
||||
|
||||
use rustc_data_structures::graph::WithNumNodes;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir::coverage::*;
|
||||
|
||||
/// Manages the counter and expression indexes/IDs to generate `CoverageKind` components for MIR
|
||||
/// `Coverage` statements.
|
||||
pub(crate) struct CoverageCounters {
|
||||
function_source_hash: u64,
|
||||
next_counter_id: u32,
|
||||
num_expressions: u32,
|
||||
pub debug_counters: DebugCounters,
|
||||
}
|
||||
|
||||
impl CoverageCounters {
|
||||
pub fn new(function_source_hash: u64) -> Self {
|
||||
Self {
|
||||
function_source_hash,
|
||||
next_counter_id: CounterValueReference::START.as_u32(),
|
||||
num_expressions: 0,
|
||||
debug_counters: DebugCounters::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Activate the `DebugCounters` data structures, to provide additional debug formatting
|
||||
/// features when formating `CoverageKind` (counter) values.
|
||||
pub fn enable_debug(&mut self) {
|
||||
self.debug_counters.enable();
|
||||
}
|
||||
|
||||
/// Makes `CoverageKind` `Counter`s and `Expressions` for the `BasicCoverageBlocks` directly or
|
||||
/// indirectly associated with `CoverageSpans`, and returns additional `Expression`s
|
||||
/// representing intermediate values.
|
||||
pub fn make_bcb_counters(
|
||||
&mut self,
|
||||
basic_coverage_blocks: &mut CoverageGraph,
|
||||
coverage_spans: &Vec<CoverageSpan>,
|
||||
) -> Result<Vec<CoverageKind>, Error> {
|
||||
let mut bcb_counters = BcbCounters::new(self, basic_coverage_blocks);
|
||||
bcb_counters.make_bcb_counters(coverage_spans)
|
||||
}
|
||||
|
||||
fn make_counter<F>(&mut self, debug_block_label_fn: F) -> CoverageKind
|
||||
where
|
||||
F: Fn() -> Option<String>,
|
||||
{
|
||||
let counter = CoverageKind::Counter {
|
||||
function_source_hash: self.function_source_hash,
|
||||
id: self.next_counter(),
|
||||
};
|
||||
if self.debug_counters.is_enabled() {
|
||||
self.debug_counters.add_counter(&counter, (debug_block_label_fn)());
|
||||
}
|
||||
counter
|
||||
}
|
||||
|
||||
fn make_expression<F>(
|
||||
&mut self,
|
||||
lhs: ExpressionOperandId,
|
||||
op: Op,
|
||||
rhs: ExpressionOperandId,
|
||||
debug_block_label_fn: F,
|
||||
) -> CoverageKind
|
||||
where
|
||||
F: Fn() -> Option<String>,
|
||||
{
|
||||
let id = self.next_expression();
|
||||
let expression = CoverageKind::Expression { id, lhs, op, rhs };
|
||||
if self.debug_counters.is_enabled() {
|
||||
self.debug_counters.add_counter(&expression, (debug_block_label_fn)());
|
||||
}
|
||||
expression
|
||||
}
|
||||
|
||||
pub fn make_identity_counter(&mut self, counter_operand: ExpressionOperandId) -> CoverageKind {
|
||||
let some_debug_block_label = if self.debug_counters.is_enabled() {
|
||||
self.debug_counters.some_block_label(counter_operand).cloned()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.make_expression(counter_operand, Op::Add, ExpressionOperandId::ZERO, || {
|
||||
some_debug_block_label.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Counter IDs start from one and go up.
|
||||
fn next_counter(&mut self) -> CounterValueReference {
|
||||
assert!(self.next_counter_id < u32::MAX - self.num_expressions);
|
||||
let next = self.next_counter_id;
|
||||
self.next_counter_id += 1;
|
||||
CounterValueReference::from(next)
|
||||
}
|
||||
|
||||
/// Expression IDs start from u32::MAX and go down because a Expression can reference
|
||||
/// (add or subtract counts) of both Counter regions and Expression regions. The counter
|
||||
/// expression operand IDs must be unique across both types.
|
||||
fn next_expression(&mut self) -> InjectedExpressionId {
|
||||
assert!(self.next_counter_id < u32::MAX - self.num_expressions);
|
||||
let next = u32::MAX - self.num_expressions;
|
||||
self.num_expressions += 1;
|
||||
InjectedExpressionId::from(next)
|
||||
}
|
||||
}
|
||||
|
||||
/// Traverse the `CoverageGraph` and add either a `Counter` or `Expression` to every BCB, to be
|
||||
/// injected with `CoverageSpan`s. `Expressions` have no runtime overhead, so if a viable expression
|
||||
/// (adding or subtracting two other counters or expressions) can compute the same result as an
|
||||
/// embedded counter, an `Expression` should be used.
|
||||
struct BcbCounters<'a> {
|
||||
coverage_counters: &'a mut CoverageCounters,
|
||||
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,
|
||||
basic_coverage_blocks: &'a mut CoverageGraph,
|
||||
) -> Self {
|
||||
Self { coverage_counters, basic_coverage_blocks }
|
||||
}
|
||||
|
||||
/// If two `BasicCoverageBlock`s branch from another `BasicCoverageBlock`, one of the branches
|
||||
/// can be counted by `Expression` by subtracting the other branch from the branching
|
||||
/// block. Otherwise, the `BasicCoverageBlock` executed the least should have the `Counter`.
|
||||
/// One way to predict which branch executes the least is by considering loops. A loop is exited
|
||||
/// at a branch, so the branch that jumps to a `BasicCoverageBlock` outside the loop is almost
|
||||
/// always executed less than the branch that does not exit the loop.
|
||||
///
|
||||
/// Returns any non-code-span expressions created to represent intermediate values (such as to
|
||||
/// add two counters so the result can be subtracted from another counter), or an Error with
|
||||
/// message for subsequent debugging.
|
||||
fn make_bcb_counters(
|
||||
&mut self,
|
||||
coverage_spans: &Vec<CoverageSpan>,
|
||||
) -> Result<Vec<CoverageKind>, Error> {
|
||||
debug!("make_bcb_counters(): adding a counter or expression to each BasicCoverageBlock");
|
||||
let num_bcbs = self.basic_coverage_blocks.num_nodes();
|
||||
let mut collect_intermediate_expressions = Vec::with_capacity(num_bcbs);
|
||||
|
||||
let mut bcbs_with_coverage = BitSet::new_empty(num_bcbs);
|
||||
for covspan in coverage_spans {
|
||||
bcbs_with_coverage.insert(covspan.bcb);
|
||||
}
|
||||
|
||||
// Walk the `CoverageGraph`. For each `BasicCoverageBlock` node with an associated
|
||||
// `CoverageSpan`, add a counter. If the `BasicCoverageBlock` branches, add a counter or
|
||||
// expression to each branch `BasicCoverageBlock` (if the branch BCB has only one incoming
|
||||
// edge) or edge from the branching BCB to the branch BCB (if the branch BCB has multiple
|
||||
// incoming edges).
|
||||
//
|
||||
// The `TraverseCoverageGraphWithLoops` traversal ensures that, when a loop is encountered,
|
||||
// all `BasicCoverageBlock` nodes in the loop are visited before visiting any node outside
|
||||
// the loop. The `traversal` state includes a `context_stack`, providing a way to know if
|
||||
// the current BCB is in one or more nested loops or not.
|
||||
let mut traversal = TraverseCoverageGraphWithLoops::new(&self.basic_coverage_blocks);
|
||||
while let Some(bcb) = traversal.next(self.basic_coverage_blocks) {
|
||||
if bcbs_with_coverage.contains(bcb) {
|
||||
debug!("{:?} has at least one `CoverageSpan`. Get or make its counter", bcb);
|
||||
let branching_counter_operand =
|
||||
self.get_or_make_counter_operand(bcb, &mut collect_intermediate_expressions)?;
|
||||
|
||||
if self.bcb_needs_branch_counters(bcb) {
|
||||
self.make_branch_counters(
|
||||
&mut traversal,
|
||||
bcb,
|
||||
branching_counter_operand,
|
||||
&mut collect_intermediate_expressions,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"{:?} does not have any `CoverageSpan`s. A counter will only be added if \
|
||||
and when a covered BCB has an expression dependency.",
|
||||
bcb,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if traversal.is_complete() {
|
||||
Ok(collect_intermediate_expressions)
|
||||
} else {
|
||||
Error::from_string(format!(
|
||||
"`TraverseCoverageGraphWithLoops` missed some `BasicCoverageBlock`s: {:?}",
|
||||
traversal.unvisited(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn make_branch_counters(
|
||||
&mut self,
|
||||
traversal: &mut TraverseCoverageGraphWithLoops,
|
||||
branching_bcb: BasicCoverageBlock,
|
||||
branching_counter_operand: ExpressionOperandId,
|
||||
collect_intermediate_expressions: &mut Vec<CoverageKind>,
|
||||
) -> Result<(), Error> {
|
||||
let branches = self.bcb_branches(branching_bcb);
|
||||
debug!(
|
||||
"{:?} has some branch(es) without counters:\n {}",
|
||||
branching_bcb,
|
||||
branches
|
||||
.iter()
|
||||
.map(|branch| {
|
||||
format!("{:?}: {:?}", branch, branch.counter(&self.basic_coverage_blocks))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n "),
|
||||
);
|
||||
|
||||
// Use the `traversal` state to decide if a subset of the branches exit a loop, making it
|
||||
// likely that branch is executed less than branches that do not exit the same loop. In this
|
||||
// case, any branch that does not exit the loop (and has not already been assigned a
|
||||
// counter) should be counted by expression, if possible. (If a preferred expression branch
|
||||
// is not selected based on the loop context, select any branch without an existing
|
||||
// counter.)
|
||||
let expression_branch = self.choose_preferred_expression_branch(traversal, &branches);
|
||||
|
||||
// Assign a Counter or Expression to each branch, plus additional `Expression`s, as needed,
|
||||
// to sum up intermediate results.
|
||||
let mut some_sumup_counter_operand = None;
|
||||
for branch in branches {
|
||||
// Skip the selected `expression_branch`, if any. It's expression will be assigned after
|
||||
// all others.
|
||||
if branch != expression_branch {
|
||||
let branch_counter_operand = if branch.is_only_path_to_target() {
|
||||
debug!(
|
||||
" {:?} has only one incoming edge (from {:?}), so adding a \
|
||||
counter",
|
||||
branch, branching_bcb
|
||||
);
|
||||
self.get_or_make_counter_operand(
|
||||
branch.target_bcb,
|
||||
collect_intermediate_expressions,
|
||||
)?
|
||||
} else {
|
||||
debug!(" {:?} has multiple incoming edges, so adding an edge counter", branch);
|
||||
self.get_or_make_edge_counter_operand(
|
||||
branching_bcb,
|
||||
branch.target_bcb,
|
||||
collect_intermediate_expressions,
|
||||
)?
|
||||
};
|
||||
if let Some(sumup_counter_operand) =
|
||||
some_sumup_counter_operand.replace(branch_counter_operand)
|
||||
{
|
||||
let intermediate_expression = self.coverage_counters.make_expression(
|
||||
branch_counter_operand,
|
||||
Op::Add,
|
||||
sumup_counter_operand,
|
||||
|| None,
|
||||
);
|
||||
debug!(
|
||||
" [new intermediate expression: {}]",
|
||||
self.format_counter(&intermediate_expression)
|
||||
);
|
||||
let intermediate_expression_operand = intermediate_expression.as_operand_id();
|
||||
collect_intermediate_expressions.push(intermediate_expression);
|
||||
some_sumup_counter_operand.replace(intermediate_expression_operand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign the final expression to the `expression_branch` by subtracting the total of all
|
||||
// other branches from the counter of the branching BCB.
|
||||
let sumup_counter_operand =
|
||||
some_sumup_counter_operand.expect("sumup_counter_operand should have a value");
|
||||
debug!(
|
||||
"Making an expression for the selected expression_branch: {:?} \
|
||||
(expression_branch predecessors: {:?})",
|
||||
expression_branch,
|
||||
self.bcb_predecessors(expression_branch.target_bcb),
|
||||
);
|
||||
let expression = self.coverage_counters.make_expression(
|
||||
branching_counter_operand,
|
||||
Op::Subtract,
|
||||
sumup_counter_operand,
|
||||
|| Some(format!("{:?}", expression_branch)),
|
||||
);
|
||||
debug!("{:?} gets an expression: {}", expression_branch, self.format_counter(&expression));
|
||||
let bcb = expression_branch.target_bcb;
|
||||
if expression_branch.is_only_path_to_target() {
|
||||
self.basic_coverage_blocks[bcb].set_counter(expression)?;
|
||||
} else {
|
||||
self.basic_coverage_blocks[bcb].set_edge_counter_from(branching_bcb, expression)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_or_make_counter_operand(
|
||||
&mut self,
|
||||
bcb: BasicCoverageBlock,
|
||||
collect_intermediate_expressions: &mut Vec<CoverageKind>,
|
||||
) -> Result<ExpressionOperandId, Error> {
|
||||
self.recursive_get_or_make_counter_operand(bcb, collect_intermediate_expressions, 1)
|
||||
}
|
||||
|
||||
fn recursive_get_or_make_counter_operand(
|
||||
&mut self,
|
||||
bcb: BasicCoverageBlock,
|
||||
collect_intermediate_expressions: &mut Vec<CoverageKind>,
|
||||
debug_indent_level: usize,
|
||||
) -> Result<ExpressionOperandId, Error> {
|
||||
// If the BCB already has a counter, return it.
|
||||
if let Some(counter_kind) = self.basic_coverage_blocks[bcb].counter() {
|
||||
debug!(
|
||||
"{}{:?} already has a counter: {}",
|
||||
NESTED_INDENT.repeat(debug_indent_level),
|
||||
bcb,
|
||||
self.format_counter(counter_kind),
|
||||
);
|
||||
return Ok(counter_kind.as_operand_id());
|
||||
}
|
||||
|
||||
// A BCB with only one incoming edge gets a simple `Counter` (via `make_counter()`).
|
||||
// Also, a BCB that loops back to itself gets a simple `Counter`. This may indicate the
|
||||
// program results in a tight infinite loop, but it should still compile.
|
||||
let one_path_to_target = self.bcb_has_one_path_to_target(bcb);
|
||||
if one_path_to_target || self.bcb_predecessors(bcb).contains(&bcb) {
|
||||
let counter_kind = self.coverage_counters.make_counter(|| Some(format!("{:?}", bcb)));
|
||||
if one_path_to_target {
|
||||
debug!(
|
||||
"{}{:?} gets a new counter: {}",
|
||||
NESTED_INDENT.repeat(debug_indent_level),
|
||||
bcb,
|
||||
self.format_counter(&counter_kind),
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"{}{:?} has itself as its own predecessor. It can't be part of its own \
|
||||
Expression sum, so it will get its own new counter: {}. (Note, the compiled \
|
||||
code will generate an infinite loop.)",
|
||||
NESTED_INDENT.repeat(debug_indent_level),
|
||||
bcb,
|
||||
self.format_counter(&counter_kind),
|
||||
);
|
||||
}
|
||||
return self.basic_coverage_blocks[bcb].set_counter(counter_kind);
|
||||
}
|
||||
|
||||
// A BCB with multiple incoming edges can compute its count by `Expression`, summing up the
|
||||
// counters and/or expressions of its incoming edges. This will recursively get or create
|
||||
// counters for those incoming edges first, then call `make_expression()` to sum them up,
|
||||
// with additional intermediate expressions as needed.
|
||||
let mut predecessors = self.bcb_predecessors(bcb).clone().into_iter();
|
||||
debug!(
|
||||
"{}{:?} has multiple incoming edges and will get an expression that sums them up...",
|
||||
NESTED_INDENT.repeat(debug_indent_level),
|
||||
bcb,
|
||||
);
|
||||
let first_edge_counter_operand = self.recursive_get_or_make_edge_counter_operand(
|
||||
predecessors.next().unwrap(),
|
||||
bcb,
|
||||
collect_intermediate_expressions,
|
||||
debug_indent_level + 1,
|
||||
)?;
|
||||
let mut some_sumup_edge_counter_operand = None;
|
||||
for predecessor in predecessors {
|
||||
let edge_counter_operand = self.recursive_get_or_make_edge_counter_operand(
|
||||
predecessor,
|
||||
bcb,
|
||||
collect_intermediate_expressions,
|
||||
debug_indent_level + 1,
|
||||
)?;
|
||||
if let Some(sumup_edge_counter_operand) =
|
||||
some_sumup_edge_counter_operand.replace(edge_counter_operand)
|
||||
{
|
||||
let intermediate_expression = self.coverage_counters.make_expression(
|
||||
sumup_edge_counter_operand,
|
||||
Op::Add,
|
||||
edge_counter_operand,
|
||||
|| None,
|
||||
);
|
||||
debug!(
|
||||
"{}new intermediate expression: {}",
|
||||
NESTED_INDENT.repeat(debug_indent_level),
|
||||
self.format_counter(&intermediate_expression)
|
||||
);
|
||||
let intermediate_expression_operand = intermediate_expression.as_operand_id();
|
||||
collect_intermediate_expressions.push(intermediate_expression);
|
||||
some_sumup_edge_counter_operand.replace(intermediate_expression_operand);
|
||||
}
|
||||
}
|
||||
let counter_kind = self.coverage_counters.make_expression(
|
||||
first_edge_counter_operand,
|
||||
Op::Add,
|
||||
some_sumup_edge_counter_operand.unwrap(),
|
||||
|| Some(format!("{:?}", bcb)),
|
||||
);
|
||||
debug!(
|
||||
"{}{:?} gets a new counter (sum of predecessor counters): {}",
|
||||
NESTED_INDENT.repeat(debug_indent_level),
|
||||
bcb,
|
||||
self.format_counter(&counter_kind)
|
||||
);
|
||||
self.basic_coverage_blocks[bcb].set_counter(counter_kind)
|
||||
}
|
||||
|
||||
fn get_or_make_edge_counter_operand(
|
||||
&mut self,
|
||||
from_bcb: BasicCoverageBlock,
|
||||
to_bcb: BasicCoverageBlock,
|
||||
collect_intermediate_expressions: &mut Vec<CoverageKind>,
|
||||
) -> Result<ExpressionOperandId, Error> {
|
||||
self.recursive_get_or_make_edge_counter_operand(
|
||||
from_bcb,
|
||||
to_bcb,
|
||||
collect_intermediate_expressions,
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
fn recursive_get_or_make_edge_counter_operand(
|
||||
&mut self,
|
||||
from_bcb: BasicCoverageBlock,
|
||||
to_bcb: BasicCoverageBlock,
|
||||
collect_intermediate_expressions: &mut Vec<CoverageKind>,
|
||||
debug_indent_level: usize,
|
||||
) -> Result<ExpressionOperandId, Error> {
|
||||
// If the source BCB has only one successor (assumed to be the given target), an edge
|
||||
// counter is unnecessary. Just get or make a counter for the source BCB.
|
||||
let successors = self.bcb_successors(from_bcb).iter();
|
||||
if successors.len() == 1 {
|
||||
return self.recursive_get_or_make_counter_operand(
|
||||
from_bcb,
|
||||
collect_intermediate_expressions,
|
||||
debug_indent_level + 1,
|
||||
);
|
||||
}
|
||||
|
||||
// If the edge already has a counter, return it.
|
||||
if let Some(counter_kind) = self.basic_coverage_blocks[to_bcb].edge_counter_from(from_bcb) {
|
||||
debug!(
|
||||
"{}Edge {:?}->{:?} already has a counter: {}",
|
||||
NESTED_INDENT.repeat(debug_indent_level),
|
||||
from_bcb,
|
||||
to_bcb,
|
||||
self.format_counter(counter_kind)
|
||||
);
|
||||
return Ok(counter_kind.as_operand_id());
|
||||
}
|
||||
|
||||
// Make a new counter to count this edge.
|
||||
let counter_kind =
|
||||
self.coverage_counters.make_counter(|| Some(format!("{:?}->{:?}", from_bcb, to_bcb)));
|
||||
debug!(
|
||||
"{}Edge {:?}->{:?} gets a new counter: {}",
|
||||
NESTED_INDENT.repeat(debug_indent_level),
|
||||
from_bcb,
|
||||
to_bcb,
|
||||
self.format_counter(&counter_kind)
|
||||
);
|
||||
self.basic_coverage_blocks[to_bcb].set_edge_counter_from(from_bcb, counter_kind)
|
||||
}
|
||||
|
||||
/// Select a branch for the expression, either the recommended `reloop_branch`, or if none was
|
||||
/// found, select any branch.
|
||||
fn choose_preferred_expression_branch(
|
||||
&self,
|
||||
traversal: &TraverseCoverageGraphWithLoops,
|
||||
branches: &Vec<BcbBranch>,
|
||||
) -> BcbBranch {
|
||||
let branch_needs_a_counter =
|
||||
|branch: &BcbBranch| branch.counter(&self.basic_coverage_blocks).is_none();
|
||||
|
||||
let some_reloop_branch = self.find_some_reloop_branch(traversal, &branches);
|
||||
if let Some(reloop_branch_without_counter) =
|
||||
some_reloop_branch.filter(branch_needs_a_counter)
|
||||
{
|
||||
debug!(
|
||||
"Selecting reloop_branch={:?} that still needs a counter, to get the \
|
||||
`Expression`",
|
||||
reloop_branch_without_counter
|
||||
);
|
||||
reloop_branch_without_counter
|
||||
} else {
|
||||
let &branch_without_counter = branches
|
||||
.iter()
|
||||
.find(|&&branch| branch.counter(&self.basic_coverage_blocks).is_none())
|
||||
.expect(
|
||||
"needs_branch_counters was `true` so there should be at least one \
|
||||
branch",
|
||||
);
|
||||
debug!(
|
||||
"Selecting any branch={:?} that still needs a counter, to get the \
|
||||
`Expression` because there was no `reloop_branch`, or it already had a \
|
||||
counter",
|
||||
branch_without_counter
|
||||
);
|
||||
branch_without_counter
|
||||
}
|
||||
}
|
||||
|
||||
/// At most, one of the branches (or its edge, from the branching_bcb, if the branch has
|
||||
/// multiple incoming edges) can have a counter computed by expression.
|
||||
///
|
||||
/// If at least one of the branches leads outside of a loop (`found_loop_exit` is
|
||||
/// true), and at least one other branch does not exit the loop (the first of which
|
||||
/// is captured in `some_reloop_branch`), it's likely any reloop branch will be
|
||||
/// executed far more often than loop exit branch, making the reloop branch a better
|
||||
/// candidate for an expression.
|
||||
fn find_some_reloop_branch(
|
||||
&self,
|
||||
traversal: &TraverseCoverageGraphWithLoops,
|
||||
branches: &Vec<BcbBranch>,
|
||||
) -> Option<BcbBranch> {
|
||||
let branch_needs_a_counter =
|
||||
|branch: &BcbBranch| branch.counter(&self.basic_coverage_blocks).is_none();
|
||||
|
||||
let mut some_reloop_branch: Option<BcbBranch> = None;
|
||||
for context in traversal.context_stack.iter().rev() {
|
||||
if let Some((backedge_from_bcbs, _)) = &context.loop_backedges {
|
||||
let mut found_loop_exit = false;
|
||||
for &branch in branches.iter() {
|
||||
if backedge_from_bcbs.iter().any(|&backedge_from_bcb| {
|
||||
self.bcb_is_dominated_by(backedge_from_bcb, branch.target_bcb)
|
||||
}) {
|
||||
if let Some(reloop_branch) = some_reloop_branch {
|
||||
if reloop_branch.counter(&self.basic_coverage_blocks).is_none() {
|
||||
// we already found a candidate reloop_branch that still
|
||||
// needs a counter
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// The path from branch leads back to the top of the loop. Set this
|
||||
// branch as the `reloop_branch`. If this branch already has a
|
||||
// counter, and we find another reloop branch that doesn't have a
|
||||
// counter yet, that branch will be selected as the `reloop_branch`
|
||||
// instead.
|
||||
some_reloop_branch = Some(branch);
|
||||
} else {
|
||||
// The path from branch leads outside this loop
|
||||
found_loop_exit = true;
|
||||
}
|
||||
if found_loop_exit
|
||||
&& some_reloop_branch.filter(branch_needs_a_counter).is_some()
|
||||
{
|
||||
// Found both a branch that exits the loop and a branch that returns
|
||||
// to the top of the loop (`reloop_branch`), and the `reloop_branch`
|
||||
// doesn't already have a counter.
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found_loop_exit {
|
||||
debug!(
|
||||
"No branches exit the loop, so any branch without an existing \
|
||||
counter can have the `Expression`."
|
||||
);
|
||||
break;
|
||||
}
|
||||
if some_reloop_branch.is_some() {
|
||||
debug!(
|
||||
"Found a branch that exits the loop and a branch the loops back to \
|
||||
the top of the loop (`reloop_branch`). The `reloop_branch` will \
|
||||
get the `Expression`, as long as it still needs a counter."
|
||||
);
|
||||
break;
|
||||
}
|
||||
// else all branches exited this loop context, so run the same checks with
|
||||
// the outer loop(s)
|
||||
}
|
||||
}
|
||||
some_reloop_branch
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bcb_predecessors(&self, bcb: BasicCoverageBlock) -> &Vec<BasicCoverageBlock> {
|
||||
&self.basic_coverage_blocks.predecessors[bcb]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bcb_successors(&self, bcb: BasicCoverageBlock) -> &Vec<BasicCoverageBlock> {
|
||||
&self.basic_coverage_blocks.successors[bcb]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bcb_branches(&self, from_bcb: BasicCoverageBlock) -> Vec<BcbBranch> {
|
||||
self.bcb_successors(from_bcb)
|
||||
.iter()
|
||||
.map(|&to_bcb| BcbBranch::from_to(from_bcb, to_bcb, &self.basic_coverage_blocks))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn bcb_needs_branch_counters(&self, bcb: BasicCoverageBlock) -> bool {
|
||||
let branch_needs_a_counter =
|
||||
|branch: &BcbBranch| branch.counter(&self.basic_coverage_blocks).is_none();
|
||||
let branches = self.bcb_branches(bcb);
|
||||
branches.len() > 1 && branches.iter().any(branch_needs_a_counter)
|
||||
}
|
||||
|
||||
/// Returns true if the BasicCoverageBlock has zero or one incoming edge. (If zero, it should be
|
||||
/// the entry point for the function.)
|
||||
#[inline]
|
||||
fn bcb_has_one_path_to_target(&self, bcb: BasicCoverageBlock) -> bool {
|
||||
self.bcb_predecessors(bcb).len() <= 1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bcb_is_dominated_by(&self, node: BasicCoverageBlock, dom: BasicCoverageBlock) -> bool {
|
||||
self.basic_coverage_blocks.is_dominated_by(node, dom)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format_counter(&self, counter_kind: &CoverageKind) -> String {
|
||||
self.coverage_counters.debug_counters.format_counter(counter_kind)
|
||||
}
|
||||
}
|
836
compiler/rustc_mir/src/transform/coverage/debug.rs
Normal file
836
compiler/rustc_mir/src/transform/coverage/debug.rs
Normal file
|
@ -0,0 +1,836 @@
|
|||
//! The `InstrumentCoverage` MIR pass implementation includes debugging tools and options
|
||||
//! to help developers understand and/or improve the analysis and instrumentation of a MIR.
|
||||
//!
|
||||
//! To enable coverage, include the rustc command line option:
|
||||
//!
|
||||
//! * `-Z instrument-coverage`
|
||||
//!
|
||||
//! MIR Dump Files, with additional `CoverageGraph` graphviz and `CoverageSpan` spanview
|
||||
//! ------------------------------------------------------------------------------------
|
||||
//!
|
||||
//! Additional debugging options include:
|
||||
//!
|
||||
//! * `-Z dump-mir=InstrumentCoverage` - Generate `.mir` files showing the state of the MIR,
|
||||
//! before and after the `InstrumentCoverage` pass, for each compiled function.
|
||||
//!
|
||||
//! * `-Z dump-mir-graphviz` - If `-Z dump-mir` is also enabled for the current MIR node path,
|
||||
//! each MIR dump is accompanied by a before-and-after graphical view of the MIR, in Graphviz
|
||||
//! `.dot` file format (which can be visually rendered as a graph using any of a number of free
|
||||
//! Graphviz viewers and IDE extensions).
|
||||
//!
|
||||
//! For the `InstrumentCoverage` pass, this option also enables generation of an additional
|
||||
//! Graphviz `.dot` file for each function, rendering the `CoverageGraph`: the control flow
|
||||
//! graph (CFG) of `BasicCoverageBlocks` (BCBs), as nodes, internally labeled to show the
|
||||
//! `CoverageSpan`-based MIR elements each BCB represents (`BasicBlock`s, `Statement`s and
|
||||
//! `Terminator`s), assigned coverage counters and/or expressions, and edge counters, as needed.
|
||||
//!
|
||||
//! (Note the additional option, `-Z graphviz-dark-mode`, can be added, to change the rendered
|
||||
//! output from its default black-on-white background to a dark color theme, if desired.)
|
||||
//!
|
||||
//! * `-Z dump-mir-spanview` - If `-Z dump-mir` is also enabled for the current MIR node path,
|
||||
//! each MIR dump is accompanied by a before-and-after `.html` document showing the function's
|
||||
//! original source code, highlighted by it's MIR spans, at the `statement`-level (by default),
|
||||
//! `terminator` only, or encompassing span for the `Terminator` plus all `Statement`s, in each
|
||||
//! `block` (`BasicBlock`).
|
||||
//!
|
||||
//! For the `InstrumentCoverage` pass, this option also enables generation of an additional
|
||||
//! spanview `.html` file for each function, showing the aggregated `CoverageSpan`s that will
|
||||
//! require counters (or counter expressions) for accurate coverage analysis.
|
||||
//!
|
||||
//! Debug Logging
|
||||
//! -------------
|
||||
//!
|
||||
//! The `InstrumentCoverage` pass includes debug logging messages at various phases and decision
|
||||
//! points, which can be enabled via environment variable:
|
||||
//!
|
||||
//! ```shell
|
||||
//! RUSTC_LOG=rustc_mir::transform::coverage=debug
|
||||
//! ```
|
||||
//!
|
||||
//! Other module paths with coverage-related debug logs may also be of interest, particularly for
|
||||
//! debugging the coverage map data, injected as global variables in the LLVM IR (during rustc's
|
||||
//! code generation pass). For example:
|
||||
//!
|
||||
//! ```shell
|
||||
//! RUSTC_LOG=rustc_mir::transform::coverage,rustc_codegen_ssa::coverageinfo,rustc_codegen_llvm::coverageinfo=debug
|
||||
//! ```
|
||||
//!
|
||||
//! Coverage Debug Options
|
||||
//! ---------------------------------
|
||||
//!
|
||||
//! Additional debugging options can be enabled using the environment variable:
|
||||
//!
|
||||
//! ```shell
|
||||
//! RUSTC_COVERAGE_DEBUG_OPTIONS=<options>
|
||||
//! ```
|
||||
//!
|
||||
//! These options are comma-separated, and specified in the format `option-name=value`. For example:
|
||||
//!
|
||||
//! ```shell
|
||||
//! $ RUSTC_COVERAGE_DEBUG_OPTIONS=counter-format=id+operation,allow-unused-expressions=yes cargo build
|
||||
//! ```
|
||||
//!
|
||||
//! Coverage debug options include:
|
||||
//!
|
||||
//! * `allow-unused-expressions=yes` or `no` (default: `no`)
|
||||
//!
|
||||
//! The `InstrumentCoverage` algorithms _should_ only create and assign expressions to a
|
||||
//! `BasicCoverageBlock`, or an incoming edge, if that expression is either (a) required to
|
||||
//! count a `CoverageSpan`, or (b) a dependency of some other required counter expression.
|
||||
//!
|
||||
//! If an expression is generated that does not map to a `CoverageSpan` or dependency, this
|
||||
//! probably indicates there was a bug in the algorithm that creates and assigns counters
|
||||
//! and expressions.
|
||||
//!
|
||||
//! When this kind of bug is encountered, the rustc compiler will panic by default. Setting:
|
||||
//! `allow-unused-expressions=yes` will log a warning message instead of panicking (effectively
|
||||
//! ignoring the unused expressions), which may be helpful when debugging the root cause of
|
||||
//! the problem.
|
||||
//!
|
||||
//! * `counter-format=<choices>`, where `<choices>` can be any plus-separated combination of `id`,
|
||||
//! `block`, and/or `operation` (default: `block+operation`)
|
||||
//!
|
||||
//! This option effects both the `CoverageGraph` (graphviz `.dot` files) and debug logging, when
|
||||
//! generating labels for counters and expressions.
|
||||
//!
|
||||
//! Depending on the values and combinations, counters can be labeled by:
|
||||
//!
|
||||
//! * `id` - counter or expression ID (ascending counter IDs, starting at 1, or descending
|
||||
//! expression IDs, starting at `u32:MAX`)
|
||||
//! * `block` - the `BasicCoverageBlock` label (for example, `bcb0`) or edge label (for
|
||||
//! example `bcb0->bcb1`), for counters or expressions assigned to count a
|
||||
//! `BasicCoverageBlock` or edge. Intermediate expressions (not directly associated with
|
||||
//! a BCB or edge) will be labeled by their expression ID, unless `operation` is also
|
||||
//! specified.
|
||||
//! * `operation` - applied to expressions only, labels include the left-hand-side counter
|
||||
//! or expression label (lhs operand), the operator (`+` or `-`), and the right-hand-side
|
||||
//! counter or expression (rhs operand). Expression operand labels are generated
|
||||
//! recursively, generating labels with nested operations, enclosed in parentheses
|
||||
//! (for example: `bcb2 + (bcb0 - bcb1)`).
|
||||
|
||||
use super::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph};
|
||||
use super::spans::CoverageSpan;
|
||||
|
||||
use crate::util::generic_graphviz::GraphvizWriter;
|
||||
use crate::util::pretty;
|
||||
use crate::util::spanview::{self, SpanViewable};
|
||||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_index::vec::Idx;
|
||||
use rustc_middle::mir::coverage::*;
|
||||
use rustc_middle::mir::{self, BasicBlock, TerminatorKind};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
use std::lazy::SyncOnceCell;
|
||||
|
||||
pub const NESTED_INDENT: &str = " ";
|
||||
|
||||
const RUSTC_COVERAGE_DEBUG_OPTIONS: &str = "RUSTC_COVERAGE_DEBUG_OPTIONS";
|
||||
|
||||
pub(crate) fn debug_options<'a>() -> &'a DebugOptions {
|
||||
static DEBUG_OPTIONS: SyncOnceCell<DebugOptions> = SyncOnceCell::new();
|
||||
|
||||
&DEBUG_OPTIONS.get_or_init(|| DebugOptions::from_env())
|
||||
}
|
||||
|
||||
/// 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 allow_unused_expressions: bool,
|
||||
counter_format: ExpressionFormat,
|
||||
}
|
||||
|
||||
impl DebugOptions {
|
||||
fn from_env() -> Self {
|
||||
let mut allow_unused_expressions = true;
|
||||
let mut counter_format = ExpressionFormat::default();
|
||||
|
||||
if let Ok(env_debug_options) = std::env::var(RUSTC_COVERAGE_DEBUG_OPTIONS) {
|
||||
for setting_str in env_debug_options.replace(" ", "").replace("-", "_").split(",") {
|
||||
let mut setting = setting_str.splitn(2, "=");
|
||||
match setting.next() {
|
||||
Some(option) if option == "allow_unused_expressions" => {
|
||||
allow_unused_expressions = bool_option_val(option, setting.next());
|
||||
debug!(
|
||||
"{} env option `allow_unused_expressions` is set to {}",
|
||||
RUSTC_COVERAGE_DEBUG_OPTIONS, allow_unused_expressions
|
||||
);
|
||||
}
|
||||
Some(option) if option == "counter_format" => {
|
||||
if let Some(strval) = setting.next() {
|
||||
counter_format = counter_format_option_val(strval);
|
||||
debug!(
|
||||
"{} env option `counter_format` is set to {:?}",
|
||||
RUSTC_COVERAGE_DEBUG_OPTIONS, counter_format
|
||||
);
|
||||
} else {
|
||||
bug!(
|
||||
"`{}` option in environment variable {} requires one or more \
|
||||
plus-separated choices (a non-empty subset of \
|
||||
`id+block+operation`)",
|
||||
option,
|
||||
RUSTC_COVERAGE_DEBUG_OPTIONS
|
||||
);
|
||||
}
|
||||
}
|
||||
Some("") => {}
|
||||
Some(invalid) => bug!(
|
||||
"Unsupported setting `{}` in environment variable {}",
|
||||
invalid,
|
||||
RUSTC_COVERAGE_DEBUG_OPTIONS
|
||||
),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self { allow_unused_expressions, counter_format }
|
||||
}
|
||||
}
|
||||
|
||||
fn bool_option_val(option: &str, some_strval: Option<&str>) -> bool {
|
||||
if let Some(val) = some_strval {
|
||||
if vec!["yes", "y", "on", "true"].contains(&val) {
|
||||
true
|
||||
} else if vec!["no", "n", "off", "false"].contains(&val) {
|
||||
false
|
||||
} else {
|
||||
bug!(
|
||||
"Unsupported value `{}` for option `{}` in environment variable {}",
|
||||
option,
|
||||
val,
|
||||
RUSTC_COVERAGE_DEBUG_OPTIONS
|
||||
)
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn counter_format_option_val(strval: &str) -> ExpressionFormat {
|
||||
let mut counter_format = ExpressionFormat { id: false, block: false, operation: false };
|
||||
let components = strval.splitn(3, "+");
|
||||
for component in components {
|
||||
match component {
|
||||
"id" => counter_format.id = true,
|
||||
"block" => counter_format.block = true,
|
||||
"operation" => counter_format.operation = true,
|
||||
_ => bug!(
|
||||
"Unsupported counter_format choice `{}` in environment variable {}",
|
||||
component,
|
||||
RUSTC_COVERAGE_DEBUG_OPTIONS
|
||||
),
|
||||
}
|
||||
}
|
||||
counter_format
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ExpressionFormat {
|
||||
id: bool,
|
||||
block: bool,
|
||||
operation: bool,
|
||||
}
|
||||
|
||||
impl Default for ExpressionFormat {
|
||||
fn default() -> Self {
|
||||
Self { id: false, block: true, operation: true }
|
||||
}
|
||||
}
|
||||
|
||||
/// If enabled, this struct maintains a map from `CoverageKind` IDs (as `ExpressionOperandId`) to
|
||||
/// the `CoverageKind` data and optional label (normally, the counter's associated
|
||||
/// `BasicCoverageBlock` format string, if any).
|
||||
///
|
||||
/// Use `format_counter` to convert one of these `CoverageKind` counters to a debug output string,
|
||||
/// as directed by the `DebugOptions`. This allows the format of counter labels in logs and dump
|
||||
/// files (including the `CoverageGraph` graphviz file) to be changed at runtime, via environment
|
||||
/// variable.
|
||||
///
|
||||
/// `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 {
|
||||
some_counters: Option<FxHashMap<ExpressionOperandId, DebugCounter>>,
|
||||
}
|
||||
|
||||
impl DebugCounters {
|
||||
pub fn new() -> Self {
|
||||
Self { some_counters: None }
|
||||
}
|
||||
|
||||
pub fn enable(&mut self) {
|
||||
debug_assert!(!self.is_enabled());
|
||||
self.some_counters.replace(FxHashMap::default());
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.some_counters.is_some()
|
||||
}
|
||||
|
||||
pub fn add_counter(&mut self, counter_kind: &CoverageKind, some_block_label: Option<String>) {
|
||||
if let Some(counters) = &mut self.some_counters {
|
||||
let id: ExpressionOperandId = match *counter_kind {
|
||||
CoverageKind::Counter { id, .. } => id.into(),
|
||||
CoverageKind::Expression { id, .. } => id.into(),
|
||||
_ => bug!(
|
||||
"the given `CoverageKind` is not an counter or expression: {:?}",
|
||||
counter_kind
|
||||
),
|
||||
};
|
||||
counters
|
||||
.insert(id.into(), DebugCounter::new(counter_kind.clone(), some_block_label))
|
||||
.expect_none(
|
||||
"attempt to add the same counter_kind to DebugCounters more than once",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn some_block_label(&self, operand: ExpressionOperandId) -> Option<&String> {
|
||||
self.some_counters.as_ref().map_or(None, |counters| {
|
||||
counters
|
||||
.get(&operand)
|
||||
.map_or(None, |debug_counter| debug_counter.some_block_label.as_ref())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn format_counter(&self, counter_kind: &CoverageKind) -> String {
|
||||
match *counter_kind {
|
||||
CoverageKind::Counter { .. } => {
|
||||
format!("Counter({})", self.format_counter_kind(counter_kind))
|
||||
}
|
||||
CoverageKind::Expression { .. } => {
|
||||
format!("Expression({})", self.format_counter_kind(counter_kind))
|
||||
}
|
||||
CoverageKind::Unreachable { .. } => "Unreachable".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_counter_kind(&self, counter_kind: &CoverageKind) -> String {
|
||||
let counter_format = &debug_options().counter_format;
|
||||
if let CoverageKind::Expression { id, lhs, op, rhs } = *counter_kind {
|
||||
if counter_format.operation {
|
||||
return format!(
|
||||
"{}{} {} {}",
|
||||
if counter_format.id || self.some_counters.is_none() {
|
||||
format!("#{} = ", id.index())
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
self.format_operand(lhs),
|
||||
if op == Op::Add { "+" } else { "-" },
|
||||
self.format_operand(rhs),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let id: ExpressionOperandId = match *counter_kind {
|
||||
CoverageKind::Counter { id, .. } => id.into(),
|
||||
CoverageKind::Expression { id, .. } => id.into(),
|
||||
_ => {
|
||||
bug!("the given `CoverageKind` is not an counter or expression: {:?}", counter_kind)
|
||||
}
|
||||
};
|
||||
if self.some_counters.is_some() && (counter_format.block || !counter_format.id) {
|
||||
let counters = self.some_counters.as_ref().unwrap();
|
||||
if let Some(DebugCounter { some_block_label: Some(block_label), .. }) =
|
||||
counters.get(&id.into())
|
||||
{
|
||||
return if counter_format.id {
|
||||
format!("{}#{}", block_label, id.index())
|
||||
} else {
|
||||
format!("{}", block_label)
|
||||
};
|
||||
}
|
||||
}
|
||||
format!("#{}", id.index())
|
||||
}
|
||||
|
||||
fn format_operand(&self, operand: ExpressionOperandId) -> String {
|
||||
if operand.index() == 0 {
|
||||
return String::from("0");
|
||||
}
|
||||
if let Some(counters) = &self.some_counters {
|
||||
if let Some(DebugCounter { counter_kind, some_block_label }) = counters.get(&operand) {
|
||||
if let CoverageKind::Expression { .. } = counter_kind {
|
||||
if let Some(block_label) = some_block_label {
|
||||
if debug_options().counter_format.block {
|
||||
return format!(
|
||||
"{}:({})",
|
||||
block_label,
|
||||
self.format_counter_kind(counter_kind)
|
||||
);
|
||||
}
|
||||
}
|
||||
return format!("({})", self.format_counter_kind(counter_kind));
|
||||
}
|
||||
return format!("{}", self.format_counter_kind(counter_kind));
|
||||
}
|
||||
}
|
||||
format!("#{}", operand.index().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// A non-public support class to `DebugCounters`.
|
||||
#[derive(Debug)]
|
||||
struct DebugCounter {
|
||||
counter_kind: CoverageKind,
|
||||
some_block_label: Option<String>,
|
||||
}
|
||||
|
||||
impl DebugCounter {
|
||||
fn new(counter_kind: CoverageKind, some_block_label: Option<String>) -> Self {
|
||||
Self { counter_kind, some_block_label }
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
some_bcb_to_coverage_spans_with_counters:
|
||||
Option<FxHashMap<BasicCoverageBlock, Vec<(CoverageSpan, CoverageKind)>>>,
|
||||
some_bcb_to_dependency_counters: Option<FxHashMap<BasicCoverageBlock, Vec<CoverageKind>>>,
|
||||
some_edge_to_counter: Option<FxHashMap<(BasicCoverageBlock, BasicBlock), CoverageKind>>,
|
||||
}
|
||||
|
||||
impl GraphvizData {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
some_bcb_to_coverage_spans_with_counters: None,
|
||||
some_bcb_to_dependency_counters: None,
|
||||
some_edge_to_counter: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable(&mut self) {
|
||||
debug_assert!(!self.is_enabled());
|
||||
self.some_bcb_to_coverage_spans_with_counters = Some(FxHashMap::default());
|
||||
self.some_bcb_to_dependency_counters = Some(FxHashMap::default());
|
||||
self.some_edge_to_counter = Some(FxHashMap::default());
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.some_bcb_to_coverage_spans_with_counters.is_some()
|
||||
}
|
||||
|
||||
pub fn add_bcb_coverage_span_with_counter(
|
||||
&mut self,
|
||||
bcb: BasicCoverageBlock,
|
||||
coverage_span: &CoverageSpan,
|
||||
counter_kind: &CoverageKind,
|
||||
) {
|
||||
if let Some(bcb_to_coverage_spans_with_counters) =
|
||||
self.some_bcb_to_coverage_spans_with_counters.as_mut()
|
||||
{
|
||||
bcb_to_coverage_spans_with_counters
|
||||
.entry(bcb)
|
||||
.or_insert_with(|| Vec::new())
|
||||
.push((coverage_span.clone(), counter_kind.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_bcb_coverage_spans_with_counters(
|
||||
&self,
|
||||
bcb: BasicCoverageBlock,
|
||||
) -> Option<&Vec<(CoverageSpan, CoverageKind)>> {
|
||||
if let Some(bcb_to_coverage_spans_with_counters) =
|
||||
self.some_bcb_to_coverage_spans_with_counters.as_ref()
|
||||
{
|
||||
bcb_to_coverage_spans_with_counters.get(&bcb)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_bcb_dependency_counter(
|
||||
&mut self,
|
||||
bcb: BasicCoverageBlock,
|
||||
counter_kind: &CoverageKind,
|
||||
) {
|
||||
if let Some(bcb_to_dependency_counters) = self.some_bcb_to_dependency_counters.as_mut() {
|
||||
bcb_to_dependency_counters
|
||||
.entry(bcb)
|
||||
.or_insert_with(|| Vec::new())
|
||||
.push(counter_kind.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_bcb_dependency_counters(
|
||||
&self,
|
||||
bcb: BasicCoverageBlock,
|
||||
) -> Option<&Vec<CoverageKind>> {
|
||||
if let Some(bcb_to_dependency_counters) = self.some_bcb_to_dependency_counters.as_ref() {
|
||||
bcb_to_dependency_counters.get(&bcb)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_edge_counter(
|
||||
&mut self,
|
||||
from_bcb: BasicCoverageBlock,
|
||||
to_bb: BasicBlock,
|
||||
counter_kind: &CoverageKind,
|
||||
) {
|
||||
if let Some(edge_to_counter) = self.some_edge_to_counter.as_mut() {
|
||||
edge_to_counter.insert((from_bcb, to_bb), counter_kind.clone()).expect_none(
|
||||
"invalid attempt to insert more than one edge counter for the same edge",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_edge_counter(
|
||||
&self,
|
||||
from_bcb: BasicCoverageBlock,
|
||||
to_bb: BasicBlock,
|
||||
) -> Option<&CoverageKind> {
|
||||
if let Some(edge_to_counter) = self.some_edge_to_counter.as_ref() {
|
||||
edge_to_counter.get(&(from_bcb, to_bb))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If enabled, this struct captures additional data used to track whether expressions were used,
|
||||
/// 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 {
|
||||
some_used_expression_operands:
|
||||
Option<FxHashMap<ExpressionOperandId, Vec<InjectedExpressionId>>>,
|
||||
some_unused_expressions:
|
||||
Option<Vec<(CoverageKind, Option<BasicCoverageBlock>, BasicCoverageBlock)>>,
|
||||
}
|
||||
|
||||
impl UsedExpressions {
|
||||
pub fn new() -> Self {
|
||||
Self { some_used_expression_operands: None, some_unused_expressions: None }
|
||||
}
|
||||
|
||||
pub fn enable(&mut self) {
|
||||
debug_assert!(!self.is_enabled());
|
||||
self.some_used_expression_operands = Some(FxHashMap::default());
|
||||
self.some_unused_expressions = Some(Vec::new());
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.some_used_expression_operands.is_some()
|
||||
}
|
||||
|
||||
pub fn add_expression_operands(&mut self, expression: &CoverageKind) {
|
||||
if let Some(used_expression_operands) = self.some_used_expression_operands.as_mut() {
|
||||
if let CoverageKind::Expression { id, lhs, rhs, .. } = *expression {
|
||||
used_expression_operands.entry(lhs).or_insert_with(|| Vec::new()).push(id);
|
||||
used_expression_operands.entry(rhs).or_insert_with(|| Vec::new()).push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expression_is_used(&self, expression: &CoverageKind) -> bool {
|
||||
if let Some(used_expression_operands) = self.some_used_expression_operands.as_ref() {
|
||||
used_expression_operands.contains_key(&expression.as_operand_id())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_unused_expression_if_not_found(
|
||||
&mut self,
|
||||
expression: &CoverageKind,
|
||||
edge_from_bcb: Option<BasicCoverageBlock>,
|
||||
target_bcb: BasicCoverageBlock,
|
||||
) {
|
||||
if let Some(used_expression_operands) = self.some_used_expression_operands.as_ref() {
|
||||
if !used_expression_operands.contains_key(&expression.as_operand_id()) {
|
||||
self.some_unused_expressions.as_mut().unwrap().push((
|
||||
expression.clone(),
|
||||
edge_from_bcb,
|
||||
target_bcb,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the list of unused counters (if any) as a tuple with the counter (`CoverageKind`),
|
||||
/// optional `from_bcb` (if it was an edge counter), and `target_bcb`.
|
||||
pub fn get_unused_expressions(
|
||||
&self,
|
||||
) -> Vec<(CoverageKind, Option<BasicCoverageBlock>, BasicCoverageBlock)> {
|
||||
if let Some(unused_expressions) = self.some_unused_expressions.as_ref() {
|
||||
unused_expressions.clone()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// If enabled, validate that every BCB or edge counter not directly associated with a coverage
|
||||
/// span is at least indirectly associated (it is a dependency of a BCB counter that _is_
|
||||
/// associated with a coverage span).
|
||||
pub fn validate(
|
||||
&mut self,
|
||||
bcb_counters_without_direct_coverage_spans: &Vec<(
|
||||
Option<BasicCoverageBlock>,
|
||||
BasicCoverageBlock,
|
||||
CoverageKind,
|
||||
)>,
|
||||
) {
|
||||
if self.is_enabled() {
|
||||
let mut not_validated = bcb_counters_without_direct_coverage_spans
|
||||
.iter()
|
||||
.map(|(_, _, counter_kind)| counter_kind)
|
||||
.collect::<Vec<_>>();
|
||||
let mut validating_count = 0;
|
||||
while not_validated.len() != validating_count {
|
||||
let to_validate = not_validated.split_off(0);
|
||||
validating_count = to_validate.len();
|
||||
for counter_kind in to_validate {
|
||||
if self.expression_is_used(counter_kind) {
|
||||
self.add_expression_operands(counter_kind);
|
||||
} else {
|
||||
not_validated.push(counter_kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alert_on_unused_expressions(&self, debug_counters: &DebugCounters) {
|
||||
if let Some(unused_expressions) = self.some_unused_expressions.as_ref() {
|
||||
for (counter_kind, edge_from_bcb, target_bcb) in unused_expressions {
|
||||
let unused_counter_message = if let Some(from_bcb) = edge_from_bcb.as_ref() {
|
||||
format!(
|
||||
"non-coverage edge counter found without a dependent expression, in \
|
||||
{:?}->{:?}; counter={}",
|
||||
from_bcb,
|
||||
target_bcb,
|
||||
debug_counters.format_counter(&counter_kind),
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"non-coverage counter found without a dependent expression, in {:?}; \
|
||||
counter={}",
|
||||
target_bcb,
|
||||
debug_counters.format_counter(&counter_kind),
|
||||
)
|
||||
};
|
||||
|
||||
if debug_options().allow_unused_expressions {
|
||||
debug!("WARNING: {}", unused_counter_message);
|
||||
} else {
|
||||
bug!("{}", unused_counter_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the MIR pass `CoverageSpan`-specific spanview dump file.
|
||||
pub(crate) fn dump_coverage_spanview(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mir_body: &mir::Body<'tcx>,
|
||||
basic_coverage_blocks: &CoverageGraph,
|
||||
pass_name: &str,
|
||||
coverage_spans: &Vec<CoverageSpan>,
|
||||
) {
|
||||
let mir_source = mir_body.source;
|
||||
let def_id = mir_source.def_id();
|
||||
|
||||
let span_viewables = span_viewables(tcx, mir_body, basic_coverage_blocks, &coverage_spans);
|
||||
let mut file = pretty::create_dump_file(tcx, "html", None, pass_name, &0, mir_source)
|
||||
.expect("Unexpected error creating MIR spanview HTML file");
|
||||
let crate_name = tcx.crate_name(def_id.krate);
|
||||
let item_name = tcx.def_path(def_id).to_filename_friendly_no_crate();
|
||||
let title = format!("{}.{} - Coverage Spans", crate_name, item_name);
|
||||
spanview::write_document(tcx, def_id, span_viewables, &title, &mut file)
|
||||
.expect("Unexpected IO error dumping coverage spans as HTML");
|
||||
}
|
||||
|
||||
/// Converts the computed `BasicCoverageBlockData`s into `SpanViewable`s.
|
||||
fn span_viewables(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mir_body: &mir::Body<'tcx>,
|
||||
basic_coverage_blocks: &CoverageGraph,
|
||||
coverage_spans: &Vec<CoverageSpan>,
|
||||
) -> Vec<SpanViewable> {
|
||||
let mut span_viewables = Vec::new();
|
||||
for coverage_span in coverage_spans {
|
||||
let tooltip = coverage_span.format_coverage_statements(tcx, mir_body);
|
||||
let CoverageSpan { span, bcb, .. } = coverage_span;
|
||||
let bcb_data = &basic_coverage_blocks[*bcb];
|
||||
let id = bcb_data.id();
|
||||
let leader_bb = bcb_data.leader_bb();
|
||||
span_viewables.push(SpanViewable { bb: leader_bb, span: *span, id, tooltip });
|
||||
}
|
||||
span_viewables
|
||||
}
|
||||
|
||||
/// Generates the MIR pass coverage-specific graphviz dump file.
|
||||
pub(crate) fn dump_coverage_graphviz(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mir_body: &mir::Body<'tcx>,
|
||||
pass_name: &str,
|
||||
basic_coverage_blocks: &CoverageGraph,
|
||||
debug_counters: &DebugCounters,
|
||||
graphviz_data: &GraphvizData,
|
||||
intermediate_expressions: &Vec<CoverageKind>,
|
||||
debug_used_expressions: &UsedExpressions,
|
||||
) {
|
||||
let mir_source = mir_body.source;
|
||||
let def_id = mir_source.def_id();
|
||||
let node_content = |bcb| {
|
||||
bcb_to_string_sections(
|
||||
tcx,
|
||||
mir_body,
|
||||
debug_counters,
|
||||
&basic_coverage_blocks[bcb],
|
||||
graphviz_data.get_bcb_coverage_spans_with_counters(bcb),
|
||||
graphviz_data.get_bcb_dependency_counters(bcb),
|
||||
// intermediate_expressions are injected into the mir::START_BLOCK, so
|
||||
// include them in the first BCB.
|
||||
if bcb.index() == 0 { Some(&intermediate_expressions) } else { None },
|
||||
)
|
||||
};
|
||||
let edge_labels = |from_bcb| {
|
||||
let from_bcb_data = &basic_coverage_blocks[from_bcb];
|
||||
let from_terminator = from_bcb_data.terminator(mir_body);
|
||||
let mut edge_labels = from_terminator.kind.fmt_successor_labels();
|
||||
edge_labels.retain(|label| label.to_string() != "unreachable");
|
||||
let edge_counters = from_terminator
|
||||
.successors()
|
||||
.map(|&successor_bb| graphviz_data.get_edge_counter(from_bcb, successor_bb));
|
||||
edge_labels
|
||||
.iter()
|
||||
.zip(edge_counters)
|
||||
.map(|(label, some_counter)| {
|
||||
if let Some(counter) = some_counter {
|
||||
format!("{}\n{}", label, debug_counters.format_counter(counter))
|
||||
} else {
|
||||
label.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let graphviz_name = format!("Cov_{}_{}", def_id.krate.index(), def_id.index.index());
|
||||
let mut graphviz_writer =
|
||||
GraphvizWriter::new(basic_coverage_blocks, &graphviz_name, node_content, edge_labels);
|
||||
let unused_expressions = debug_used_expressions.get_unused_expressions();
|
||||
if unused_expressions.len() > 0 {
|
||||
graphviz_writer.set_graph_label(&format!(
|
||||
"Unused expressions:\n {}",
|
||||
unused_expressions
|
||||
.as_slice()
|
||||
.iter()
|
||||
.map(|(counter_kind, edge_from_bcb, target_bcb)| {
|
||||
if let Some(from_bcb) = edge_from_bcb.as_ref() {
|
||||
format!(
|
||||
"{:?}->{:?}: {}",
|
||||
from_bcb,
|
||||
target_bcb,
|
||||
debug_counters.format_counter(&counter_kind),
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{:?}: {}",
|
||||
target_bcb,
|
||||
debug_counters.format_counter(&counter_kind),
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ")
|
||||
));
|
||||
}
|
||||
let mut file = pretty::create_dump_file(tcx, "dot", None, pass_name, &0, mir_source)
|
||||
.expect("Unexpected error creating BasicCoverageBlock graphviz DOT file");
|
||||
graphviz_writer
|
||||
.write_graphviz(tcx, &mut file)
|
||||
.expect("Unexpected error writing BasicCoverageBlock graphviz DOT file");
|
||||
}
|
||||
|
||||
fn bcb_to_string_sections(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mir_body: &mir::Body<'tcx>,
|
||||
debug_counters: &DebugCounters,
|
||||
bcb_data: &BasicCoverageBlockData,
|
||||
some_coverage_spans_with_counters: Option<&Vec<(CoverageSpan, CoverageKind)>>,
|
||||
some_dependency_counters: Option<&Vec<CoverageKind>>,
|
||||
some_intermediate_expressions: Option<&Vec<CoverageKind>>,
|
||||
) -> Vec<String> {
|
||||
let len = bcb_data.basic_blocks.len();
|
||||
let mut sections = Vec::new();
|
||||
if let Some(collect_intermediate_expressions) = some_intermediate_expressions {
|
||||
sections.push(
|
||||
collect_intermediate_expressions
|
||||
.iter()
|
||||
.map(|expression| {
|
||||
format!("Intermediate {}", debug_counters.format_counter(expression))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
);
|
||||
}
|
||||
if let Some(coverage_spans_with_counters) = some_coverage_spans_with_counters {
|
||||
sections.push(
|
||||
coverage_spans_with_counters
|
||||
.iter()
|
||||
.map(|(covspan, counter)| {
|
||||
format!(
|
||||
"{} at {}",
|
||||
debug_counters.format_counter(counter),
|
||||
covspan.format(tcx, mir_body)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
);
|
||||
}
|
||||
if let Some(dependency_counters) = some_dependency_counters {
|
||||
sections.push(format!(
|
||||
"Non-coverage counters:\n {}",
|
||||
dependency_counters
|
||||
.iter()
|
||||
.map(|counter| debug_counters.format_counter(counter))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" \n"),
|
||||
));
|
||||
}
|
||||
if let Some(counter_kind) = &bcb_data.counter_kind {
|
||||
sections.push(format!("{:?}", counter_kind));
|
||||
}
|
||||
let non_term_blocks = bcb_data.basic_blocks[0..len - 1]
|
||||
.iter()
|
||||
.map(|&bb| format!("{:?}: {}", bb, term_type(&mir_body[bb].terminator().kind)))
|
||||
.collect::<Vec<_>>();
|
||||
if non_term_blocks.len() > 0 {
|
||||
sections.push(non_term_blocks.join("\n"));
|
||||
}
|
||||
sections.push(format!(
|
||||
"{:?}: {}",
|
||||
bcb_data.basic_blocks.last().unwrap(),
|
||||
term_type(&bcb_data.terminator(mir_body).kind)
|
||||
));
|
||||
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 {
|
||||
match kind {
|
||||
TerminatorKind::Goto { .. } => "Goto",
|
||||
TerminatorKind::SwitchInt { .. } => "SwitchInt",
|
||||
TerminatorKind::Resume => "Resume",
|
||||
TerminatorKind::Abort => "Abort",
|
||||
TerminatorKind::Return => "Return",
|
||||
TerminatorKind::Unreachable => "Unreachable",
|
||||
TerminatorKind::Drop { .. } => "Drop",
|
||||
TerminatorKind::DropAndReplace { .. } => "DropAndReplace",
|
||||
TerminatorKind::Call { .. } => "Call",
|
||||
TerminatorKind::Assert { .. } => "Assert",
|
||||
TerminatorKind::Yield { .. } => "Yield",
|
||||
TerminatorKind::GeneratorDrop => "GeneratorDrop",
|
||||
TerminatorKind::FalseEdge { .. } => "FalseEdge",
|
||||
TerminatorKind::FalseUnwind { .. } => "FalseUnwind",
|
||||
TerminatorKind::InlineAsm { .. } => "InlineAsm",
|
||||
}
|
||||
}
|
759
compiler/rustc_mir/src/transform/coverage/graph.rs
Normal file
759
compiler/rustc_mir/src/transform/coverage/graph.rs
Normal file
|
@ -0,0 +1,759 @@
|
|||
use super::Error;
|
||||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::graph::dominators::{self, Dominators};
|
||||
use rustc_data_structures::graph::{self, GraphSuccessors, WithNumNodes, WithStartNode};
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_index::vec::IndexVec;
|
||||
use rustc_middle::mir::coverage::*;
|
||||
use rustc_middle::mir::{self, BasicBlock, BasicBlockData, Terminator, TerminatorKind};
|
||||
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
const ID_SEPARATOR: &str = ",";
|
||||
|
||||
/// A coverage-specific simplification of the MIR control flow graph (CFG). The `CoverageGraph`s
|
||||
/// nodes are `BasicCoverageBlock`s, which encompass one or more MIR `BasicBlock`s, plus a
|
||||
/// `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 {
|
||||
bcbs: IndexVec<BasicCoverageBlock, BasicCoverageBlockData>,
|
||||
bb_to_bcb: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,
|
||||
pub successors: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
|
||||
pub predecessors: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
|
||||
dominators: Option<Dominators<BasicCoverageBlock>>,
|
||||
}
|
||||
|
||||
impl CoverageGraph {
|
||||
pub fn from_mir(mir_body: &mir::Body<'tcx>) -> Self {
|
||||
let (bcbs, bb_to_bcb) = Self::compute_basic_coverage_blocks(mir_body);
|
||||
|
||||
// Pre-transform MIR `BasicBlock` successors and predecessors into the BasicCoverageBlock
|
||||
// equivalents. Note that since the BasicCoverageBlock graph has been fully simplified, the
|
||||
// each predecessor of a BCB leader_bb should be in a unique BCB, and each successor of a
|
||||
// BCB last_bb should bin in its own unique BCB. Therefore, collecting the BCBs using
|
||||
// `bb_to_bcb` should work without requiring a deduplication step.
|
||||
|
||||
let successors = IndexVec::from_fn_n(
|
||||
|bcb| {
|
||||
let bcb_data = &bcbs[bcb];
|
||||
let bcb_successors =
|
||||
bcb_filtered_successors(&mir_body, &bcb_data.terminator(mir_body).kind)
|
||||
.filter_map(|&successor_bb| bb_to_bcb[successor_bb])
|
||||
.collect::<Vec<_>>();
|
||||
debug_assert!({
|
||||
let mut sorted = bcb_successors.clone();
|
||||
sorted.sort_unstable();
|
||||
let initial_len = sorted.len();
|
||||
sorted.dedup();
|
||||
sorted.len() == initial_len
|
||||
});
|
||||
bcb_successors
|
||||
},
|
||||
bcbs.len(),
|
||||
);
|
||||
|
||||
let mut predecessors = IndexVec::from_elem_n(Vec::new(), bcbs.len());
|
||||
for (bcb, bcb_successors) in successors.iter_enumerated() {
|
||||
for &successor in bcb_successors {
|
||||
predecessors[successor].push(bcb);
|
||||
}
|
||||
}
|
||||
|
||||
let mut basic_coverage_blocks =
|
||||
Self { bcbs, bb_to_bcb, successors, predecessors, dominators: None };
|
||||
let dominators = dominators::dominators(&basic_coverage_blocks);
|
||||
basic_coverage_blocks.dominators = Some(dominators);
|
||||
basic_coverage_blocks
|
||||
}
|
||||
|
||||
fn compute_basic_coverage_blocks(
|
||||
mir_body: &mir::Body<'tcx>,
|
||||
) -> (
|
||||
IndexVec<BasicCoverageBlock, BasicCoverageBlockData>,
|
||||
IndexVec<BasicBlock, Option<BasicCoverageBlock>>,
|
||||
) {
|
||||
let num_basic_blocks = mir_body.num_nodes();
|
||||
let mut bcbs = IndexVec::with_capacity(num_basic_blocks);
|
||||
let mut bb_to_bcb = IndexVec::from_elem_n(None, num_basic_blocks);
|
||||
|
||||
// Walk the MIR CFG using a Preorder traversal, which starts from `START_BLOCK` and follows
|
||||
// each block terminator's `successors()`. Coverage spans must map to actual source code,
|
||||
// so compiler generated blocks and paths can be ignored. To that end, the CFG traversal
|
||||
// intentionally omits unwind paths.
|
||||
// FIXME(#78544): MIR InstrumentCoverage: Improve coverage of `#[should_panic]` tests and
|
||||
// `catch_unwind()` handlers.
|
||||
let mir_cfg_without_unwind = ShortCircuitPreorder::new(&mir_body, bcb_filtered_successors);
|
||||
|
||||
let mut basic_blocks = Vec::new();
|
||||
for (bb, data) in mir_cfg_without_unwind {
|
||||
if let Some(last) = basic_blocks.last() {
|
||||
let predecessors = &mir_body.predecessors()[bb];
|
||||
if predecessors.len() > 1 || !predecessors.contains(last) {
|
||||
// The `bb` has more than one _incoming_ edge, and should start its own
|
||||
// `BasicCoverageBlockData`. (Note, the `basic_blocks` vector does not yet
|
||||
// include `bb`; it contains a sequence of one or more sequential basic_blocks
|
||||
// with no intermediate branches in or out. Save these as a new
|
||||
// `BasicCoverageBlockData` before starting the new one.)
|
||||
Self::add_basic_coverage_block(
|
||||
&mut bcbs,
|
||||
&mut bb_to_bcb,
|
||||
basic_blocks.split_off(0),
|
||||
);
|
||||
debug!(
|
||||
" because {}",
|
||||
if predecessors.len() > 1 {
|
||||
"predecessors.len() > 1".to_owned()
|
||||
} else {
|
||||
format!("bb {} is not in precessors: {:?}", bb.index(), predecessors)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
basic_blocks.push(bb);
|
||||
|
||||
let term = data.terminator();
|
||||
|
||||
match term.kind {
|
||||
TerminatorKind::Return { .. }
|
||||
// FIXME(richkadel): Add test(s) for `Abort` coverage.
|
||||
| TerminatorKind::Abort
|
||||
// FIXME(richkadel): Add test(s) for `Assert` coverage.
|
||||
// Should `Assert` be handled like `FalseUnwind` instead? Since we filter out unwind
|
||||
// branches when creating the BCB CFG, aren't `Assert`s (without unwinds) just like
|
||||
// `FalseUnwinds` (which are kind of like `Goto`s)?
|
||||
| TerminatorKind::Assert { .. }
|
||||
// FIXME(richkadel): Add test(s) for `Yield` coverage, and confirm coverage is
|
||||
// sensible for code using the `yield` keyword.
|
||||
| TerminatorKind::Yield { .. }
|
||||
// FIXME(richkadel): Also add coverage tests using async/await, and threading.
|
||||
|
||||
| TerminatorKind::SwitchInt { .. } => {
|
||||
// The `bb` has more than one _outgoing_ edge, or exits the function. Save the
|
||||
// current sequence of `basic_blocks` gathered to this point, as a new
|
||||
// `BasicCoverageBlockData`.
|
||||
Self::add_basic_coverage_block(
|
||||
&mut bcbs,
|
||||
&mut bb_to_bcb,
|
||||
basic_blocks.split_off(0),
|
||||
);
|
||||
debug!(" because term.kind = {:?}", term.kind);
|
||||
// Note that this condition is based on `TerminatorKind`, even though it
|
||||
// theoretically boils down to `successors().len() != 1`; that is, either zero
|
||||
// (e.g., `Return`, `Abort`) or multiple successors (e.g., `SwitchInt`), but
|
||||
// since the BCB CFG ignores things like unwind branches (which exist in the
|
||||
// `Terminator`s `successors()` list) checking the number of successors won't
|
||||
// work.
|
||||
}
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Drop { .. }
|
||||
| TerminatorKind::DropAndReplace { .. }
|
||||
| TerminatorKind::Call { .. }
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::FalseEdge { .. }
|
||||
| TerminatorKind::FalseUnwind { .. }
|
||||
| TerminatorKind::InlineAsm { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !basic_blocks.is_empty() {
|
||||
// process any remaining basic_blocks into a final `BasicCoverageBlockData`
|
||||
Self::add_basic_coverage_block(&mut bcbs, &mut bb_to_bcb, basic_blocks.split_off(0));
|
||||
debug!(" because the end of the MIR CFG was reached while traversing");
|
||||
}
|
||||
|
||||
(bcbs, bb_to_bcb)
|
||||
}
|
||||
|
||||
fn add_basic_coverage_block(
|
||||
bcbs: &mut IndexVec<BasicCoverageBlock, BasicCoverageBlockData>,
|
||||
bb_to_bcb: &mut IndexVec<BasicBlock, Option<BasicCoverageBlock>>,
|
||||
basic_blocks: Vec<BasicBlock>,
|
||||
) {
|
||||
let bcb = BasicCoverageBlock::from_usize(bcbs.len());
|
||||
for &bb in basic_blocks.iter() {
|
||||
bb_to_bcb[bb] = Some(bcb);
|
||||
}
|
||||
let bcb_data = BasicCoverageBlockData::from(basic_blocks);
|
||||
debug!("adding bcb{}: {:?}", bcb.index(), bcb_data);
|
||||
bcbs.push(bcb_data);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn iter_enumerated(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (BasicCoverageBlock, &BasicCoverageBlockData)> {
|
||||
self.bcbs.iter_enumerated()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn iter_enumerated_mut(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = (BasicCoverageBlock, &mut BasicCoverageBlockData)> {
|
||||
self.bcbs.iter_enumerated_mut()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bcb_from_bb(&self, bb: BasicBlock) -> Option<BasicCoverageBlock> {
|
||||
if bb.index() < self.bb_to_bcb.len() { self.bb_to_bcb[bb] } else { None }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_dominated_by(&self, node: BasicCoverageBlock, dom: BasicCoverageBlock) -> bool {
|
||||
self.dominators.as_ref().unwrap().is_dominated_by(node, dom)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn dominators(&self) -> &Dominators<BasicCoverageBlock> {
|
||||
self.dominators.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<BasicCoverageBlock> for CoverageGraph {
|
||||
type Output = BasicCoverageBlockData;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: BasicCoverageBlock) -> &BasicCoverageBlockData {
|
||||
&self.bcbs[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<BasicCoverageBlock> for CoverageGraph {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, index: BasicCoverageBlock) -> &mut BasicCoverageBlockData {
|
||||
&mut self.bcbs[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl graph::DirectedGraph for CoverageGraph {
|
||||
type Node = BasicCoverageBlock;
|
||||
}
|
||||
|
||||
impl graph::WithNumNodes for CoverageGraph {
|
||||
#[inline]
|
||||
fn num_nodes(&self) -> usize {
|
||||
self.bcbs.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl graph::WithStartNode for CoverageGraph {
|
||||
#[inline]
|
||||
fn start_node(&self) -> Self::Node {
|
||||
self.bcb_from_bb(mir::START_BLOCK)
|
||||
.expect("mir::START_BLOCK should be in a BasicCoverageBlock")
|
||||
}
|
||||
}
|
||||
|
||||
type BcbSuccessors<'graph> = std::slice::Iter<'graph, BasicCoverageBlock>;
|
||||
|
||||
impl<'graph> graph::GraphSuccessors<'graph> for CoverageGraph {
|
||||
type Item = BasicCoverageBlock;
|
||||
type Iter = std::iter::Cloned<BcbSuccessors<'graph>>;
|
||||
}
|
||||
|
||||
impl graph::WithSuccessors for CoverageGraph {
|
||||
#[inline]
|
||||
fn successors(&self, node: Self::Node) -> <Self as GraphSuccessors<'_>>::Iter {
|
||||
self.successors[node].iter().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl graph::GraphPredecessors<'graph> for CoverageGraph {
|
||||
type Item = BasicCoverageBlock;
|
||||
type Iter = std::vec::IntoIter<BasicCoverageBlock>;
|
||||
}
|
||||
|
||||
impl graph::WithPredecessors for CoverageGraph {
|
||||
#[inline]
|
||||
fn predecessors(&self, node: Self::Node) -> <Self as graph::GraphPredecessors<'_>>::Iter {
|
||||
self.predecessors[node].clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
/// A node in the [control-flow graph][CFG] of CoverageGraph.
|
||||
pub(crate) struct BasicCoverageBlock {
|
||||
DEBUG_FORMAT = "bcb{}",
|
||||
}
|
||||
}
|
||||
|
||||
/// A BasicCoverageBlockData (BCB) represents the maximal-length sequence of MIR BasicBlocks without
|
||||
/// conditional branches, and form a new, simplified, coverage-specific Control Flow Graph, without
|
||||
/// altering the original MIR CFG.
|
||||
///
|
||||
/// Note that running the MIR `SimplifyCfg` transform is not sufficient (and therefore not
|
||||
/// necessary). The BCB-based CFG is a more aggressive simplification. For example:
|
||||
///
|
||||
/// * The BCB CFG ignores (trims) branches not relevant to coverage, such as unwind-related code,
|
||||
/// that is injected by the Rust compiler but has no physical source code to count. This also
|
||||
/// means a BasicBlock with a `Call` terminator can be merged into its primary successor target
|
||||
/// block, in the same BCB. (But, note: Issue #78544: "MIR InstrumentCoverage: Improve coverage
|
||||
/// of `#[should_panic]` tests and `catch_unwind()` handlers")
|
||||
/// * Some BasicBlock terminators support Rust-specific concerns--like borrow-checking--that are
|
||||
/// not relevant to coverage analysis. `FalseUnwind`, for example, can be treated the same as
|
||||
/// a `Goto`, and merged with its successor into the same BCB.
|
||||
///
|
||||
/// Each BCB with at least one computed `CoverageSpan` will have no more than one `Counter`.
|
||||
/// In some cases, a BCB's execution count can be computed by `Expression`. Additional
|
||||
/// disjoint `CoverageSpan`s in a BCB can also be counted by `Expression` (by adding `ZERO`
|
||||
/// to the BCB's primary counter or expression).
|
||||
///
|
||||
/// The BCB CFG is critical to simplifying the coverage analysis by ensuring graph path-based
|
||||
/// queries (`is_dominated_by()`, `predecessors`, `successors`, etc.) have branch (control flow)
|
||||
/// significance.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct BasicCoverageBlockData {
|
||||
pub basic_blocks: Vec<BasicBlock>,
|
||||
pub counter_kind: Option<CoverageKind>,
|
||||
edge_from_bcbs: Option<FxHashMap<BasicCoverageBlock, CoverageKind>>,
|
||||
}
|
||||
|
||||
impl BasicCoverageBlockData {
|
||||
pub fn from(basic_blocks: Vec<BasicBlock>) -> Self {
|
||||
assert!(basic_blocks.len() > 0);
|
||||
Self { basic_blocks, counter_kind: None, edge_from_bcbs: None }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn leader_bb(&self) -> BasicBlock {
|
||||
self.basic_blocks[0]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn last_bb(&self) -> BasicBlock {
|
||||
*self.basic_blocks.last().unwrap()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn terminator<'a, 'tcx>(&self, mir_body: &'a mir::Body<'tcx>) -> &'a Terminator<'tcx> {
|
||||
&mir_body[self.last_bb()].terminator()
|
||||
}
|
||||
|
||||
pub fn set_counter(
|
||||
&mut self,
|
||||
counter_kind: CoverageKind,
|
||||
) -> Result<ExpressionOperandId, Error> {
|
||||
debug_assert!(
|
||||
// If the BCB has an edge counter (to be injected into a new `BasicBlock`), it can also
|
||||
// have an expression (to be injected into an existing `BasicBlock` represented by this
|
||||
// `BasicCoverageBlock`).
|
||||
self.edge_from_bcbs.is_none() || counter_kind.is_expression(),
|
||||
"attempt to add a `Counter` to a BCB target with existing incoming edge counters"
|
||||
);
|
||||
let operand = counter_kind.as_operand_id();
|
||||
if let Some(replaced) = self.counter_kind.replace(counter_kind) {
|
||||
Error::from_string(format!(
|
||||
"attempt to set a BasicCoverageBlock coverage counter more than once; \
|
||||
{:?} already had counter {:?}",
|
||||
self, replaced,
|
||||
))
|
||||
} else {
|
||||
Ok(operand)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn counter(&self) -> Option<&CoverageKind> {
|
||||
self.counter_kind.as_ref()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn take_counter(&mut self) -> Option<CoverageKind> {
|
||||
self.counter_kind.take()
|
||||
}
|
||||
|
||||
pub fn set_edge_counter_from(
|
||||
&mut self,
|
||||
from_bcb: BasicCoverageBlock,
|
||||
counter_kind: CoverageKind,
|
||||
) -> Result<ExpressionOperandId, Error> {
|
||||
if level_enabled!(tracing::Level::DEBUG) {
|
||||
// If the BCB has an edge counter (to be injected into a new `BasicBlock`), it can also
|
||||
// have an expression (to be injected into an existing `BasicBlock` represented by this
|
||||
// `BasicCoverageBlock`).
|
||||
if !self.counter_kind.as_ref().map_or(true, |c| c.is_expression()) {
|
||||
return Error::from_string(format!(
|
||||
"attempt to add an incoming edge counter from {:?} when the target BCB already \
|
||||
has a `Counter`",
|
||||
from_bcb
|
||||
));
|
||||
}
|
||||
}
|
||||
let operand = counter_kind.as_operand_id();
|
||||
if let Some(replaced) = self
|
||||
.edge_from_bcbs
|
||||
.get_or_insert_with(|| FxHashMap::default())
|
||||
.insert(from_bcb, counter_kind)
|
||||
{
|
||||
Error::from_string(format!(
|
||||
"attempt to set an edge counter more than once; from_bcb: \
|
||||
{:?} already had counter {:?}",
|
||||
from_bcb, replaced,
|
||||
))
|
||||
} else {
|
||||
Ok(operand)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn edge_counter_from(&self, from_bcb: BasicCoverageBlock) -> Option<&CoverageKind> {
|
||||
if let Some(edge_from_bcbs) = &self.edge_from_bcbs {
|
||||
edge_from_bcbs.get(&from_bcb)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_edge_counters(
|
||||
&mut self,
|
||||
) -> Option<impl Iterator<Item = (BasicCoverageBlock, CoverageKind)>> {
|
||||
self.edge_from_bcbs.take().map_or(None, |m| Some(m.into_iter()))
|
||||
}
|
||||
|
||||
pub fn id(&self) -> String {
|
||||
format!(
|
||||
"@{}",
|
||||
self.basic_blocks
|
||||
.iter()
|
||||
.map(|bb| bb.index().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(ID_SEPARATOR)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a successor from a branching BasicCoverageBlock (such as the arms of a `SwitchInt`)
|
||||
/// as either the successor BCB itself, if it has only one incoming edge, or the successor _plus_
|
||||
/// 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 edge_from_bcb: Option<BasicCoverageBlock>,
|
||||
pub target_bcb: BasicCoverageBlock,
|
||||
}
|
||||
|
||||
impl BcbBranch {
|
||||
pub fn from_to(
|
||||
from_bcb: BasicCoverageBlock,
|
||||
to_bcb: BasicCoverageBlock,
|
||||
basic_coverage_blocks: &CoverageGraph,
|
||||
) -> Self {
|
||||
let edge_from_bcb = if basic_coverage_blocks.predecessors[to_bcb].len() > 1 {
|
||||
Some(from_bcb)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self { edge_from_bcb, target_bcb: to_bcb }
|
||||
}
|
||||
|
||||
pub fn counter<'a>(
|
||||
&self,
|
||||
basic_coverage_blocks: &'a CoverageGraph,
|
||||
) -> Option<&'a CoverageKind> {
|
||||
if let Some(from_bcb) = self.edge_from_bcb {
|
||||
basic_coverage_blocks[self.target_bcb].edge_counter_from(from_bcb)
|
||||
} else {
|
||||
basic_coverage_blocks[self.target_bcb].counter()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_only_path_to_target(&self) -> bool {
|
||||
self.edge_from_bcb.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BcbBranch {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(from_bcb) = self.edge_from_bcb {
|
||||
write!(fmt, "{:?}->{:?}", from_bcb, self.target_bcb)
|
||||
} else {
|
||||
write!(fmt, "{:?}", self.target_bcb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the `Terminator`s non-unwind successors.
|
||||
// FIXME(#78544): MIR InstrumentCoverage: Improve coverage of `#[should_panic]` tests and
|
||||
// `catch_unwind()` handlers.
|
||||
fn bcb_filtered_successors<'a, 'tcx>(
|
||||
body: &'tcx &'a mir::Body<'tcx>,
|
||||
term_kind: &'tcx TerminatorKind<'tcx>,
|
||||
) -> Box<dyn Iterator<Item = &'a BasicBlock> + 'a> {
|
||||
let mut successors = term_kind.successors();
|
||||
box match &term_kind {
|
||||
// SwitchInt successors are never unwind, and all of them should be traversed.
|
||||
TerminatorKind::SwitchInt { .. } => successors,
|
||||
// For all other kinds, return only the first successor, if any, and ignore unwinds.
|
||||
// NOTE: `chain(&[])` is required to coerce the `option::iter` (from
|
||||
// `next().into_iter()`) into the `mir::Successors` aliased type.
|
||||
_ => successors.next().into_iter().chain(&[]),
|
||||
}
|
||||
.filter(move |&&successor| body[successor].terminator().kind != TerminatorKind::Unreachable)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// From one or more backedges returning to a loop header.
|
||||
pub loop_backedges: Option<(Vec<BasicCoverageBlock>, BasicCoverageBlock)>,
|
||||
|
||||
/// worklist, to be traversed, of CoverageGraph in the loop with the given loop
|
||||
/// backedges, such that the loop is the inner inner-most loop containing these
|
||||
/// CoverageGraph
|
||||
pub worklist: Vec<BasicCoverageBlock>,
|
||||
}
|
||||
|
||||
pub(crate) struct TraverseCoverageGraphWithLoops {
|
||||
pub backedges: IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>>,
|
||||
pub context_stack: Vec<TraversalContext>,
|
||||
visited: BitSet<BasicCoverageBlock>,
|
||||
}
|
||||
|
||||
impl TraverseCoverageGraphWithLoops {
|
||||
pub fn new(basic_coverage_blocks: &CoverageGraph) -> Self {
|
||||
let start_bcb = basic_coverage_blocks.start_node();
|
||||
let backedges = find_loop_backedges(basic_coverage_blocks);
|
||||
let mut context_stack = Vec::new();
|
||||
context_stack.push(TraversalContext { loop_backedges: None, worklist: vec![start_bcb] });
|
||||
// `context_stack` starts with a `TraversalContext` for the main function context (beginning
|
||||
// with the `start` BasicCoverageBlock of the function). New worklists are pushed to the top
|
||||
// of the stack as loops are entered, and popped off of the stack when a loop's worklist is
|
||||
// exhausted.
|
||||
let visited = BitSet::new_empty(basic_coverage_blocks.num_nodes());
|
||||
Self { backedges, context_stack, visited }
|
||||
}
|
||||
|
||||
pub fn next(&mut self, basic_coverage_blocks: &CoverageGraph) -> Option<BasicCoverageBlock> {
|
||||
debug!(
|
||||
"TraverseCoverageGraphWithLoops::next - context_stack: {:?}",
|
||||
self.context_stack.iter().rev().collect::<Vec<_>>()
|
||||
);
|
||||
while let Some(next_bcb) = {
|
||||
// Strip contexts with empty worklists from the top of the stack
|
||||
while self.context_stack.last().map_or(false, |context| context.worklist.is_empty()) {
|
||||
self.context_stack.pop();
|
||||
}
|
||||
// Pop the next bcb off of the current context_stack. If none, all BCBs were visited.
|
||||
self.context_stack.last_mut().map_or(None, |context| context.worklist.pop())
|
||||
} {
|
||||
if !self.visited.insert(next_bcb) {
|
||||
debug!("Already visited: {:?}", next_bcb);
|
||||
continue;
|
||||
}
|
||||
debug!("Visiting {:?}", next_bcb);
|
||||
if self.backedges[next_bcb].len() > 0 {
|
||||
debug!("{:?} is a loop header! Start a new TraversalContext...", next_bcb);
|
||||
self.context_stack.push(TraversalContext {
|
||||
loop_backedges: Some((self.backedges[next_bcb].clone(), next_bcb)),
|
||||
worklist: Vec::new(),
|
||||
});
|
||||
}
|
||||
self.extend_worklist(basic_coverage_blocks, next_bcb);
|
||||
return Some(next_bcb);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn extend_worklist(
|
||||
&mut self,
|
||||
basic_coverage_blocks: &CoverageGraph,
|
||||
bcb: BasicCoverageBlock,
|
||||
) {
|
||||
let successors = &basic_coverage_blocks.successors[bcb];
|
||||
debug!("{:?} has {} successors:", bcb, successors.len());
|
||||
for &successor in successors {
|
||||
if successor == bcb {
|
||||
debug!(
|
||||
"{:?} has itself as its own successor. (Note, the compiled code will \
|
||||
generate an infinite loop.)",
|
||||
bcb
|
||||
);
|
||||
// Don't re-add this successor to the worklist. We are already processing it.
|
||||
break;
|
||||
}
|
||||
for context in self.context_stack.iter_mut().rev() {
|
||||
// Add successors of the current BCB to the appropriate context. Successors that
|
||||
// stay within a loop are added to the BCBs context worklist. Successors that
|
||||
// exit the loop (they are not dominated by the loop header) must be reachable
|
||||
// from other BCBs outside the loop, and they will be added to a different
|
||||
// worklist.
|
||||
//
|
||||
// Branching blocks (with more than one successor) must be processed before
|
||||
// blocks with only one successor, to prevent unnecessarily complicating
|
||||
// `Expression`s by creating a Counter in a `BasicCoverageBlock` that the
|
||||
// branching block would have given an `Expression` (or vice versa).
|
||||
let (some_successor_to_add, some_loop_header) =
|
||||
if let Some((_, loop_header)) = context.loop_backedges {
|
||||
if basic_coverage_blocks.is_dominated_by(successor, loop_header) {
|
||||
(Some(successor), Some(loop_header))
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
} else {
|
||||
(Some(successor), None)
|
||||
};
|
||||
if let Some(successor_to_add) = some_successor_to_add {
|
||||
if basic_coverage_blocks.successors[successor_to_add].len() > 1 {
|
||||
debug!(
|
||||
"{:?} successor is branching. Prioritize it at the beginning of \
|
||||
the {}",
|
||||
successor_to_add,
|
||||
if let Some(loop_header) = some_loop_header {
|
||||
format!("worklist for the loop headed by {:?}", loop_header)
|
||||
} else {
|
||||
String::from("non-loop worklist")
|
||||
},
|
||||
);
|
||||
context.worklist.insert(0, successor_to_add);
|
||||
} else {
|
||||
debug!(
|
||||
"{:?} successor is non-branching. Defer it to the end of the {}",
|
||||
successor_to_add,
|
||||
if let Some(loop_header) = some_loop_header {
|
||||
format!("worklist for the loop headed by {:?}", loop_header)
|
||||
} else {
|
||||
String::from("non-loop worklist")
|
||||
},
|
||||
);
|
||||
context.worklist.push(successor_to_add);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.visited.count() == self.visited.domain_size()
|
||||
}
|
||||
|
||||
pub fn unvisited(&self) -> Vec<BasicCoverageBlock> {
|
||||
let mut unvisited_set: BitSet<BasicCoverageBlock> =
|
||||
BitSet::new_filled(self.visited.domain_size());
|
||||
unvisited_set.subtract(&self.visited);
|
||||
unvisited_set.iter().collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
fn find_loop_backedges(
|
||||
basic_coverage_blocks: &CoverageGraph,
|
||||
) -> IndexVec<BasicCoverageBlock, Vec<BasicCoverageBlock>> {
|
||||
let num_bcbs = basic_coverage_blocks.num_nodes();
|
||||
let mut backedges = IndexVec::from_elem_n(Vec::<BasicCoverageBlock>::new(), num_bcbs);
|
||||
|
||||
// Identify loops by their backedges.
|
||||
//
|
||||
// The computational complexity is bounded by: n(s) x d where `n` is the number of
|
||||
// `BasicCoverageBlock` nodes (the simplified/reduced representation of the CFG derived from the
|
||||
// MIR); `s` is the average number of successors per node (which is most likely less than 2, and
|
||||
// independent of the size of the function, so it can be treated as a constant);
|
||||
// and `d` is the average number of dominators per node.
|
||||
//
|
||||
// The average number of dominators depends on the size and complexity of the function, and
|
||||
// nodes near the start of the function's control flow graph typically have less dominators
|
||||
// than nodes near the end of the CFG. Without doing a detailed mathematical analysis, I
|
||||
// think the resulting complexity has the characteristics of O(n log n).
|
||||
//
|
||||
// The overall complexity appears to be comparable to many other MIR transform algorithms, and I
|
||||
// don't expect that this function is creating a performance hot spot, but if this becomes an
|
||||
// issue, there may be ways to optimize the `is_dominated_by` algorithm (as indicated by an
|
||||
// existing `FIXME` comment in that code), or possibly ways to optimize it's usage here, perhaps
|
||||
// by keeping track of results for visited `BasicCoverageBlock`s if they can be used to short
|
||||
// circuit downstream `is_dominated_by` checks.
|
||||
//
|
||||
// For now, that kind of optimization seems unnecessarily complicated.
|
||||
for (bcb, _) in basic_coverage_blocks.iter_enumerated() {
|
||||
for &successor in &basic_coverage_blocks.successors[bcb] {
|
||||
if basic_coverage_blocks.is_dominated_by(bcb, successor) {
|
||||
let loop_header = successor;
|
||||
let backedge_from_bcb = bcb;
|
||||
debug!(
|
||||
"Found BCB backedge: {:?} -> loop_header: {:?}",
|
||||
backedge_from_bcb, loop_header
|
||||
);
|
||||
backedges[loop_header].push(backedge_from_bcb);
|
||||
}
|
||||
}
|
||||
}
|
||||
backedges
|
||||
}
|
||||
|
||||
pub struct ShortCircuitPreorder<
|
||||
'a,
|
||||
'tcx,
|
||||
F: Fn(
|
||||
&'tcx &'a mir::Body<'tcx>,
|
||||
&'tcx TerminatorKind<'tcx>,
|
||||
) -> Box<dyn Iterator<Item = &'a BasicBlock> + 'a>,
|
||||
> {
|
||||
body: &'tcx &'a mir::Body<'tcx>,
|
||||
visited: BitSet<BasicBlock>,
|
||||
worklist: Vec<BasicBlock>,
|
||||
filtered_successors: F,
|
||||
}
|
||||
|
||||
impl<
|
||||
'a,
|
||||
'tcx,
|
||||
F: Fn(
|
||||
&'tcx &'a mir::Body<'tcx>,
|
||||
&'tcx TerminatorKind<'tcx>,
|
||||
) -> Box<dyn Iterator<Item = &'a BasicBlock> + 'a>,
|
||||
> ShortCircuitPreorder<'a, 'tcx, F>
|
||||
{
|
||||
pub fn new(
|
||||
body: &'tcx &'a mir::Body<'tcx>,
|
||||
filtered_successors: F,
|
||||
) -> ShortCircuitPreorder<'a, 'tcx, F> {
|
||||
let worklist = vec![mir::START_BLOCK];
|
||||
|
||||
ShortCircuitPreorder {
|
||||
body,
|
||||
visited: BitSet::new_empty(body.basic_blocks().len()),
|
||||
worklist,
|
||||
filtered_successors,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
'a: 'tcx,
|
||||
'tcx,
|
||||
F: Fn(
|
||||
&'tcx &'a mir::Body<'tcx>,
|
||||
&'tcx TerminatorKind<'tcx>,
|
||||
) -> Box<dyn Iterator<Item = &'a BasicBlock> + 'a>,
|
||||
> Iterator for ShortCircuitPreorder<'a, 'tcx, F>
|
||||
{
|
||||
type Item = (BasicBlock, &'a BasicBlockData<'tcx>);
|
||||
|
||||
fn next(&mut self) -> Option<(BasicBlock, &'a BasicBlockData<'tcx>)> {
|
||||
while let Some(idx) = self.worklist.pop() {
|
||||
if !self.visited.insert(idx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let data = &self.body[idx];
|
||||
|
||||
if let Some(ref term) = data.terminator {
|
||||
self.worklist.extend((self.filtered_successors)(&self.body, &term.kind));
|
||||
}
|
||||
|
||||
return Some((idx, data));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let size = self.body.basic_blocks().len() - self.visited.count();
|
||||
(size, Some(size))
|
||||
}
|
||||
}
|
539
compiler/rustc_mir/src/transform/coverage/mod.rs
Normal file
539
compiler/rustc_mir/src/transform/coverage/mod.rs
Normal file
|
@ -0,0 +1,539 @@
|
|||
pub mod query;
|
||||
|
||||
mod counters;
|
||||
mod debug;
|
||||
mod graph;
|
||||
mod spans;
|
||||
|
||||
use counters::CoverageCounters;
|
||||
use graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph};
|
||||
use spans::{CoverageSpan, CoverageSpans};
|
||||
|
||||
use crate::transform::MirPass;
|
||||
use crate::util::pretty;
|
||||
|
||||
use rustc_data_structures::fingerprint::Fingerprint;
|
||||
use rustc_data_structures::graph::WithNumNodes;
|
||||
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_index::vec::IndexVec;
|
||||
use rustc_middle::hir;
|
||||
use rustc_middle::hir::map::blocks::FnLikeNode;
|
||||
use rustc_middle::ich::StableHashingContext;
|
||||
use rustc_middle::mir::coverage::*;
|
||||
use rustc_middle::mir::{
|
||||
self, BasicBlock, BasicBlockData, Coverage, SourceInfo, Statement, StatementKind, Terminator,
|
||||
TerminatorKind,
|
||||
};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::{CharPos, Pos, SourceFile, Span, Symbol};
|
||||
|
||||
/// A simple error message wrapper for `coverage::Error`s.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Error {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn from_string<T>(message: String) -> Result<T, Error> {
|
||||
Err(Self { message })
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts `StatementKind::Coverage` statements that either instrument the binary with injected
|
||||
/// counters, via intrinsic `llvm.instrprof.increment`, and/or inject metadata used during codegen
|
||||
/// to construct the coverage map.
|
||||
pub struct InstrumentCoverage;
|
||||
|
||||
impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
|
||||
fn run_pass(&self, tcx: TyCtxt<'tcx>, mir_body: &mut mir::Body<'tcx>) {
|
||||
let mir_source = mir_body.source;
|
||||
|
||||
// If the InstrumentCoverage pass is called on promoted MIRs, skip them.
|
||||
// See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601
|
||||
if mir_source.promoted.is_some() {
|
||||
trace!(
|
||||
"InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",
|
||||
mir_source.def_id()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());
|
||||
let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();
|
||||
|
||||
// Only instrument functions, methods, and closures (not constants since they are evaluated
|
||||
// at compile time by Miri).
|
||||
// FIXME(#73156): Handle source code coverage in const eval, but note, if and when const
|
||||
// expressions get coverage spans, we will probably have to "carve out" space for const
|
||||
// expressions from coverage spans in enclosing MIR's, like we do for closures. (That might
|
||||
// be tricky if const expressions have no corresponding statements in the enclosing MIR.
|
||||
// Closures are carved out by their initial `Assign` statement.)
|
||||
if !is_fn_like {
|
||||
trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());
|
||||
return;
|
||||
}
|
||||
|
||||
trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());
|
||||
Instrumentor::new(&self.name(), tcx, mir_body).inject_counters();
|
||||
trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());
|
||||
}
|
||||
}
|
||||
|
||||
struct Instrumentor<'a, 'tcx> {
|
||||
pass_name: &'a str,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mir_body: &'a mut mir::Body<'tcx>,
|
||||
body_span: Span,
|
||||
basic_coverage_blocks: CoverageGraph,
|
||||
coverage_counters: CoverageCounters,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
fn new(pass_name: &'a str, tcx: TyCtxt<'tcx>, mir_body: &'a mut mir::Body<'tcx>) -> Self {
|
||||
let hir_body = hir_body(tcx, mir_body.source.def_id());
|
||||
let body_span = hir_body.value.span;
|
||||
let function_source_hash = hash_mir_source(tcx, hir_body);
|
||||
let basic_coverage_blocks = CoverageGraph::from_mir(mir_body);
|
||||
Self {
|
||||
pass_name,
|
||||
tcx,
|
||||
mir_body,
|
||||
body_span,
|
||||
basic_coverage_blocks,
|
||||
coverage_counters: CoverageCounters::new(function_source_hash),
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_counters(&'a mut self) {
|
||||
let tcx = self.tcx;
|
||||
let source_map = tcx.sess.source_map();
|
||||
let mir_source = self.mir_body.source;
|
||||
let def_id = mir_source.def_id();
|
||||
let body_span = self.body_span;
|
||||
|
||||
debug!("instrumenting {:?}, span: {}", def_id, source_map.span_to_string(body_span));
|
||||
|
||||
let mut graphviz_data = debug::GraphvizData::new();
|
||||
let mut debug_used_expressions = debug::UsedExpressions::new();
|
||||
|
||||
let dump_mir = pretty::dump_enabled(tcx, self.pass_name, def_id);
|
||||
let dump_graphviz = dump_mir && tcx.sess.opts.debugging_opts.dump_mir_graphviz;
|
||||
let dump_spanview = dump_mir && tcx.sess.opts.debugging_opts.dump_mir_spanview.is_some();
|
||||
|
||||
if dump_graphviz {
|
||||
graphviz_data.enable();
|
||||
self.coverage_counters.enable_debug();
|
||||
}
|
||||
|
||||
if dump_graphviz || level_enabled!(tracing::Level::DEBUG) {
|
||||
debug_used_expressions.enable();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Compute `CoverageSpan`s from the `CoverageGraph`.
|
||||
let coverage_spans = CoverageSpans::generate_coverage_spans(
|
||||
&self.mir_body,
|
||||
body_span,
|
||||
&self.basic_coverage_blocks,
|
||||
);
|
||||
|
||||
if dump_spanview {
|
||||
debug::dump_coverage_spanview(
|
||||
tcx,
|
||||
self.mir_body,
|
||||
&self.basic_coverage_blocks,
|
||||
self.pass_name,
|
||||
&coverage_spans,
|
||||
);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Create an optimized mix of `Counter`s and `Expression`s for the `CoverageGraph`. Ensure
|
||||
// every `CoverageSpan` has a `Counter` or `Expression` assigned to its `BasicCoverageBlock`
|
||||
// and all `Expression` dependencies (operands) are also generated, for any other
|
||||
// `BasicCoverageBlock`s not already associated with a `CoverageSpan`.
|
||||
//
|
||||
// Intermediate expressions (used to compute other `Expression` values), which have no
|
||||
// direct associate to any `BasicCoverageBlock`, are returned in the method `Result`.
|
||||
let intermediate_expressions_or_error = self
|
||||
.coverage_counters
|
||||
.make_bcb_counters(&mut self.basic_coverage_blocks, &coverage_spans);
|
||||
|
||||
let (result, intermediate_expressions) = match intermediate_expressions_or_error {
|
||||
Ok(intermediate_expressions) => {
|
||||
// If debugging, add any intermediate expressions (which are not associated with any
|
||||
// BCB) to the `debug_used_expressions` map.
|
||||
if debug_used_expressions.is_enabled() {
|
||||
for intermediate_expression in &intermediate_expressions {
|
||||
debug_used_expressions.add_expression_operands(intermediate_expression);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Remove the counter or edge counter from of each `CoverageSpan`s associated
|
||||
// `BasicCoverageBlock`, and inject a `Coverage` statement into the MIR.
|
||||
//
|
||||
// `Coverage` statements injected from `CoverageSpan`s will include the code regions
|
||||
// (source code start and end positions) to be counted by the associated counter.
|
||||
//
|
||||
// These `CoverageSpan`-associated counters are removed from their associated
|
||||
// `BasicCoverageBlock`s so that the only remaining counters in the `CoverageGraph`
|
||||
// are indirect counters (to be injected next, without associated code regions).
|
||||
self.inject_coverage_span_counters(
|
||||
coverage_spans,
|
||||
&mut graphviz_data,
|
||||
&mut debug_used_expressions,
|
||||
);
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// For any remaining `BasicCoverageBlock` counters (that were not associated with
|
||||
// any `CoverageSpan`), inject `Coverage` statements (_without_ code region `Span`s)
|
||||
// to ensure `BasicCoverageBlock` counters that other `Expression`s may depend on
|
||||
// are in fact counted, even though they don't directly contribute to counting
|
||||
// their own independent code region's coverage.
|
||||
self.inject_indirect_counters(&mut graphviz_data, &mut debug_used_expressions);
|
||||
|
||||
// Intermediate expressions will be injected as the final step, after generating
|
||||
// debug output, if any.
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
(Ok(()), intermediate_expressions)
|
||||
}
|
||||
Err(e) => (Err(e), Vec::new()),
|
||||
};
|
||||
|
||||
if graphviz_data.is_enabled() {
|
||||
// Even if there was an error, a partial CoverageGraph can still generate a useful
|
||||
// graphviz output.
|
||||
debug::dump_coverage_graphviz(
|
||||
tcx,
|
||||
self.mir_body,
|
||||
self.pass_name,
|
||||
&self.basic_coverage_blocks,
|
||||
&self.coverage_counters.debug_counters,
|
||||
&graphviz_data,
|
||||
&intermediate_expressions,
|
||||
&debug_used_expressions,
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(e) = result {
|
||||
bug!("Error processing: {:?}: {:?}", self.mir_body.source.def_id(), e)
|
||||
};
|
||||
|
||||
// Depending on current `debug_options()`, `alert_on_unused_expressions()` could panic, so
|
||||
// this check is performed as late as possible, to allow other debug output (logs and dump
|
||||
// files), which might be helpful in analyzing unused expressions, to still be generated.
|
||||
debug_used_expressions.alert_on_unused_expressions(&self.coverage_counters.debug_counters);
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Finally, inject the intermediate expressions collected along the way.
|
||||
for intermediate_expression in intermediate_expressions {
|
||||
inject_intermediate_expression(self.mir_body, intermediate_expression);
|
||||
}
|
||||
}
|
||||
|
||||
/// Inject a counter for each `CoverageSpan`. There can be multiple `CoverageSpan`s for a given
|
||||
/// BCB, but only one actual counter needs to be incremented per BCB. `bb_counters` maps each
|
||||
/// `bcb` to its `Counter`, when injected. Subsequent `CoverageSpan`s for a BCB that already has
|
||||
/// a `Counter` will inject an `Expression` instead, and compute its value by adding `ZERO` to
|
||||
/// the BCB `Counter` value.
|
||||
///
|
||||
/// If debugging, add every BCB `Expression` associated with a `CoverageSpan`s to the
|
||||
/// `used_expression_operands` map.
|
||||
fn inject_coverage_span_counters(
|
||||
&mut self,
|
||||
coverage_spans: Vec<CoverageSpan>,
|
||||
graphviz_data: &mut debug::GraphvizData,
|
||||
debug_used_expressions: &mut debug::UsedExpressions,
|
||||
) {
|
||||
let tcx = self.tcx;
|
||||
let source_map = tcx.sess.source_map();
|
||||
let body_span = self.body_span;
|
||||
let source_file = source_map.lookup_source_file(body_span.lo());
|
||||
let file_name = Symbol::intern(&source_file.name.to_string());
|
||||
|
||||
let mut bcb_counters = IndexVec::from_elem_n(None, self.basic_coverage_blocks.num_nodes());
|
||||
for covspan in coverage_spans {
|
||||
let bcb = covspan.bcb;
|
||||
let span = covspan.span;
|
||||
let counter_kind = if let Some(&counter_operand) = bcb_counters[bcb].as_ref() {
|
||||
self.coverage_counters.make_identity_counter(counter_operand)
|
||||
} else if let Some(counter_kind) = self.bcb_data_mut(bcb).take_counter() {
|
||||
bcb_counters[bcb] = Some(counter_kind.as_operand_id());
|
||||
debug_used_expressions.add_expression_operands(&counter_kind);
|
||||
counter_kind
|
||||
} else {
|
||||
bug!("Every BasicCoverageBlock should have a Counter or Expression");
|
||||
};
|
||||
graphviz_data.add_bcb_coverage_span_with_counter(bcb, &covspan, &counter_kind);
|
||||
// FIXME(#78542): Can spans for `TerminatorKind::Goto` be improved to avoid special
|
||||
// cases?
|
||||
let some_code_region = if self.is_code_region_redundant(bcb, span, body_span) {
|
||||
None
|
||||
} else {
|
||||
Some(make_code_region(file_name, &source_file, span, body_span))
|
||||
};
|
||||
inject_statement(self.mir_body, counter_kind, self.bcb_last_bb(bcb), some_code_region);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the type of `BasicCoverageBlock` (specifically, it's `BasicBlock`s
|
||||
/// `TerminatorKind`) with the given `Span` (relative to the `body_span`) is known to produce
|
||||
/// a redundant coverage count.
|
||||
///
|
||||
/// There is at least one case for this, and if it's not handled, the last line in a function
|
||||
/// will be double-counted.
|
||||
///
|
||||
/// If this method returns `true`, the counter (which other `Expressions` may depend on) is
|
||||
/// still injected, but without an associated code region.
|
||||
// FIXME(#78542): Can spans for `TerminatorKind::Goto` be improved to avoid special cases?
|
||||
fn is_code_region_redundant(
|
||||
&self,
|
||||
bcb: BasicCoverageBlock,
|
||||
span: Span,
|
||||
body_span: Span,
|
||||
) -> bool {
|
||||
if span.hi() == body_span.hi() {
|
||||
// All functions execute a `Return`-terminated `BasicBlock`, regardless of how the
|
||||
// function returns; but only some functions also _can_ return after a `Goto` block
|
||||
// that ends on the closing brace of the function (with the `Return`). When this
|
||||
// happens, the last character is counted 2 (or possibly more) times, when we know
|
||||
// the function returned only once (of course). By giving all `Goto` terminators at
|
||||
// the end of a function a `non-reportable` code region, they are still counted
|
||||
// if appropriate, but they don't increment the line counter, as long as their is
|
||||
// also a `Return` on that last line.
|
||||
if let TerminatorKind::Goto { .. } = self.bcb_terminator(bcb).kind {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// `inject_coverage_span_counters()` looped through the `CoverageSpan`s and injected the
|
||||
/// counter from the `CoverageSpan`s `BasicCoverageBlock`, removing it from the BCB in the
|
||||
/// process (via `take_counter()`).
|
||||
///
|
||||
/// Any other counter associated with a `BasicCoverageBlock`, or its incoming edge, but not
|
||||
/// associated with a `CoverageSpan`, should only exist if the counter is a `Expression`
|
||||
/// dependency (one of the expression operands). Collect them, and inject the additional
|
||||
/// counters into the MIR, without a reportable coverage span.
|
||||
fn inject_indirect_counters(
|
||||
&mut self,
|
||||
graphviz_data: &mut debug::GraphvizData,
|
||||
debug_used_expressions: &mut debug::UsedExpressions,
|
||||
) {
|
||||
let mut bcb_counters_without_direct_coverage_spans = Vec::new();
|
||||
for (target_bcb, target_bcb_data) in self.basic_coverage_blocks.iter_enumerated_mut() {
|
||||
if let Some(counter_kind) = target_bcb_data.take_counter() {
|
||||
bcb_counters_without_direct_coverage_spans.push((None, target_bcb, counter_kind));
|
||||
}
|
||||
if let Some(edge_counters) = target_bcb_data.take_edge_counters() {
|
||||
for (from_bcb, counter_kind) in edge_counters {
|
||||
bcb_counters_without_direct_coverage_spans.push((
|
||||
Some(from_bcb),
|
||||
target_bcb,
|
||||
counter_kind,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If debug is enabled, validate that every BCB or edge counter not directly associated
|
||||
// with a coverage span is at least indirectly associated (it is a dependency of a BCB
|
||||
// counter that _is_ associated with a coverage span).
|
||||
debug_used_expressions.validate(&bcb_counters_without_direct_coverage_spans);
|
||||
|
||||
for (edge_from_bcb, target_bcb, counter_kind) in bcb_counters_without_direct_coverage_spans
|
||||
{
|
||||
debug_used_expressions.add_unused_expression_if_not_found(
|
||||
&counter_kind,
|
||||
edge_from_bcb,
|
||||
target_bcb,
|
||||
);
|
||||
|
||||
match counter_kind {
|
||||
CoverageKind::Counter { .. } => {
|
||||
let inject_to_bb = if let Some(from_bcb) = edge_from_bcb {
|
||||
// The MIR edge starts `from_bb` (the outgoing / last BasicBlock in
|
||||
// `from_bcb`) and ends at `to_bb` (the incoming / first BasicBlock in the
|
||||
// `target_bcb`; also called the `leader_bb`).
|
||||
let from_bb = self.bcb_last_bb(from_bcb);
|
||||
let to_bb = self.bcb_leader_bb(target_bcb);
|
||||
|
||||
let new_bb = inject_edge_counter_basic_block(self.mir_body, from_bb, to_bb);
|
||||
graphviz_data.set_edge_counter(from_bcb, new_bb, &counter_kind);
|
||||
debug!(
|
||||
"Edge {:?} (last {:?}) -> {:?} (leader {:?}) requires a new MIR \
|
||||
BasicBlock {:?}, for unclaimed edge counter {}",
|
||||
edge_from_bcb,
|
||||
from_bb,
|
||||
target_bcb,
|
||||
to_bb,
|
||||
new_bb,
|
||||
self.format_counter(&counter_kind),
|
||||
);
|
||||
new_bb
|
||||
} else {
|
||||
let target_bb = self.bcb_last_bb(target_bcb);
|
||||
graphviz_data.add_bcb_dependency_counter(target_bcb, &counter_kind);
|
||||
debug!(
|
||||
"{:?} ({:?}) gets a new Coverage statement for unclaimed counter {}",
|
||||
target_bcb,
|
||||
target_bb,
|
||||
self.format_counter(&counter_kind),
|
||||
);
|
||||
target_bb
|
||||
};
|
||||
|
||||
inject_statement(self.mir_body, counter_kind, inject_to_bb, None);
|
||||
}
|
||||
CoverageKind::Expression { .. } => {
|
||||
inject_intermediate_expression(self.mir_body, counter_kind)
|
||||
}
|
||||
_ => bug!("CoverageKind should be a counter"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bcb_leader_bb(&self, bcb: BasicCoverageBlock) -> BasicBlock {
|
||||
self.bcb_data(bcb).leader_bb()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bcb_last_bb(&self, bcb: BasicCoverageBlock) -> BasicBlock {
|
||||
self.bcb_data(bcb).last_bb()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bcb_terminator(&self, bcb: BasicCoverageBlock) -> &Terminator<'tcx> {
|
||||
self.bcb_data(bcb).terminator(self.mir_body)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bcb_data(&self, bcb: BasicCoverageBlock) -> &BasicCoverageBlockData {
|
||||
&self.basic_coverage_blocks[bcb]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bcb_data_mut(&mut self, bcb: BasicCoverageBlock) -> &mut BasicCoverageBlockData {
|
||||
&mut self.basic_coverage_blocks[bcb]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format_counter(&self, counter_kind: &CoverageKind) -> String {
|
||||
self.coverage_counters.debug_counters.format_counter(counter_kind)
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_edge_counter_basic_block(
|
||||
mir_body: &mut mir::Body<'tcx>,
|
||||
from_bb: BasicBlock,
|
||||
to_bb: BasicBlock,
|
||||
) -> BasicBlock {
|
||||
let span = mir_body[from_bb].terminator().source_info.span.shrink_to_hi();
|
||||
let new_bb = mir_body.basic_blocks_mut().push(BasicBlockData {
|
||||
statements: vec![], // counter will be injected here
|
||||
terminator: Some(Terminator {
|
||||
source_info: SourceInfo::outermost(span),
|
||||
kind: TerminatorKind::Goto { target: to_bb },
|
||||
}),
|
||||
is_cleanup: false,
|
||||
});
|
||||
let edge_ref = mir_body[from_bb]
|
||||
.terminator_mut()
|
||||
.successors_mut()
|
||||
.find(|successor| **successor == to_bb)
|
||||
.expect("from_bb should have a successor for to_bb");
|
||||
*edge_ref = new_bb;
|
||||
new_bb
|
||||
}
|
||||
|
||||
fn inject_statement(
|
||||
mir_body: &mut mir::Body<'tcx>,
|
||||
counter_kind: CoverageKind,
|
||||
bb: BasicBlock,
|
||||
some_code_region: Option<CodeRegion>,
|
||||
) {
|
||||
debug!(
|
||||
" injecting statement {:?} for {:?} at code region: {:?}",
|
||||
counter_kind, bb, some_code_region
|
||||
);
|
||||
let data = &mut mir_body[bb];
|
||||
let source_info = data.terminator().source_info;
|
||||
let statement = Statement {
|
||||
source_info,
|
||||
kind: StatementKind::Coverage(box Coverage {
|
||||
kind: counter_kind,
|
||||
code_region: some_code_region,
|
||||
}),
|
||||
};
|
||||
data.statements.push(statement);
|
||||
}
|
||||
|
||||
// Non-code expressions are injected into the coverage map, without generating executable code.
|
||||
fn inject_intermediate_expression(mir_body: &mut mir::Body<'tcx>, expression: CoverageKind) {
|
||||
debug_assert!(if let CoverageKind::Expression { .. } = expression { true } else { false });
|
||||
debug!(" injecting non-code expression {:?}", expression);
|
||||
let inject_in_bb = mir::START_BLOCK;
|
||||
let data = &mut mir_body[inject_in_bb];
|
||||
let source_info = data.terminator().source_info;
|
||||
let statement = Statement {
|
||||
source_info,
|
||||
kind: StatementKind::Coverage(box Coverage { kind: expression, code_region: None }),
|
||||
};
|
||||
data.statements.push(statement);
|
||||
}
|
||||
|
||||
/// Convert the Span into its file name, start line and column, and end line and column
|
||||
fn make_code_region(
|
||||
file_name: Symbol,
|
||||
source_file: &Lrc<SourceFile>,
|
||||
span: Span,
|
||||
body_span: Span,
|
||||
) -> CodeRegion {
|
||||
let (start_line, mut start_col) = source_file.lookup_file_pos(span.lo());
|
||||
let (end_line, end_col) = if span.hi() == span.lo() {
|
||||
let (end_line, mut end_col) = (start_line, start_col);
|
||||
// Extend an empty span by one character so the region will be counted.
|
||||
let CharPos(char_pos) = start_col;
|
||||
if span.hi() == body_span.hi() {
|
||||
start_col = CharPos(char_pos - 1);
|
||||
} else {
|
||||
end_col = CharPos(char_pos + 1);
|
||||
}
|
||||
(end_line, end_col)
|
||||
} else {
|
||||
source_file.lookup_file_pos(span.hi())
|
||||
};
|
||||
CodeRegion {
|
||||
file_name,
|
||||
start_line: start_line as u32,
|
||||
start_col: start_col.to_u32() + 1,
|
||||
end_line: end_line as u32,
|
||||
end_col: end_col.to_u32() + 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn hir_body<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &'tcx rustc_hir::Body<'tcx> {
|
||||
let hir_node = tcx.hir().get_if_local(def_id).expect("expected DefId is local");
|
||||
let fn_body_id = hir::map::associated_body(hir_node).expect("HIR node is a function with body");
|
||||
tcx.hir().body(fn_body_id)
|
||||
}
|
||||
|
||||
fn hash_mir_source<'tcx>(tcx: TyCtxt<'tcx>, hir_body: &'tcx rustc_hir::Body<'tcx>) -> u64 {
|
||||
let mut hcx = tcx.create_no_span_stable_hashing_context();
|
||||
hash(&mut hcx, &hir_body.value).to_smaller_hash()
|
||||
}
|
||||
|
||||
fn hash(
|
||||
hcx: &mut StableHashingContext<'tcx>,
|
||||
node: &impl HashStable<StableHashingContext<'tcx>>,
|
||||
) -> Fingerprint {
|
||||
let mut stable_hasher = StableHasher::new();
|
||||
node.hash_stable(hcx, &mut stable_hasher);
|
||||
stable_hasher.finish()
|
||||
}
|
125
compiler/rustc_mir/src/transform/coverage/query.rs
Normal file
125
compiler/rustc_mir/src/transform/coverage/query.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
use rustc_middle::mir::coverage::*;
|
||||
use rustc_middle::mir::visit::Visitor;
|
||||
use rustc_middle::mir::{Coverage, CoverageInfo, Location};
|
||||
use rustc_middle::ty::query::Providers;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_span::def_id::DefId;
|
||||
|
||||
/// The `query` provider for `CoverageInfo`, requested by `codegen_coverage()` (to inject each
|
||||
/// counter) and `FunctionCoverage::new()` (to extract the coverage map metadata from the MIR).
|
||||
pub(crate) fn provide(providers: &mut Providers) {
|
||||
providers.coverageinfo = |tcx, def_id| coverageinfo_from_mir(tcx, def_id);
|
||||
}
|
||||
|
||||
/// The `num_counters` argument to `llvm.instrprof.increment` is the max counter_id + 1, or in
|
||||
/// other words, the number of counter value references injected into the MIR (plus 1 for the
|
||||
/// reserved `ZERO` counter, which uses counter ID `0` when included in an expression). Injected
|
||||
/// counters have a counter ID from `1..num_counters-1`.
|
||||
///
|
||||
/// `num_expressions` is the number of counter expressions added to the MIR body.
|
||||
///
|
||||
/// Both `num_counters` and `num_expressions` are used to initialize new vectors, during backend
|
||||
/// code generate, to lookup counters and expressions by simple u32 indexes.
|
||||
///
|
||||
/// MIR optimization may split and duplicate some BasicBlock sequences, or optimize out some code
|
||||
/// including injected counters. (It is OK if some counters are optimized out, but those counters
|
||||
/// are still included in the total `num_counters` or `num_expressions`.) Simply counting the
|
||||
/// calls may not work; but computing the number of counters or expressions by adding `1` to the
|
||||
/// highest ID (for a given instrumented function) is valid.
|
||||
///
|
||||
/// This visitor runs twice, first with `add_missing_operands` set to `false`, to find the maximum
|
||||
/// counter ID and maximum expression ID based on their enum variant `id` fields; then, as a
|
||||
/// safeguard, with `add_missing_operands` set to `true`, to find any other counter or expression
|
||||
/// IDs referenced by expression operands, if not already seen.
|
||||
///
|
||||
/// Ideally, each operand ID in a MIR `CoverageKind::Expression` will have a separate MIR `Coverage`
|
||||
/// statement for the `Counter` or `Expression` with the referenced ID. but since current or future
|
||||
/// MIR optimizations can theoretically optimize out segments of a MIR, it may not be possible to
|
||||
/// guarantee this, so the second pass ensures the `CoverageInfo` counts include all referenced IDs.
|
||||
struct CoverageVisitor {
|
||||
info: CoverageInfo,
|
||||
add_missing_operands: bool,
|
||||
}
|
||||
|
||||
impl CoverageVisitor {
|
||||
/// Updates `num_counters` to the maximum encountered zero-based counter_id plus 1. Note the
|
||||
/// final computed number of counters should be the number of all `CoverageKind::Counter`
|
||||
/// statements in the MIR *plus one* for the implicit `ZERO` counter.
|
||||
#[inline(always)]
|
||||
fn update_num_counters(&mut self, counter_id: u32) {
|
||||
self.info.num_counters = std::cmp::max(self.info.num_counters, counter_id + 1);
|
||||
}
|
||||
|
||||
/// Computes an expression index for each expression ID, and updates `num_expressions` to the
|
||||
/// maximum encountered index plus 1.
|
||||
#[inline(always)]
|
||||
fn update_num_expressions(&mut self, expression_id: u32) {
|
||||
let expression_index = u32::MAX - expression_id;
|
||||
self.info.num_expressions = std::cmp::max(self.info.num_expressions, expression_index + 1);
|
||||
}
|
||||
|
||||
fn update_from_expression_operand(&mut self, operand_id: u32) {
|
||||
if operand_id >= self.info.num_counters {
|
||||
let operand_as_expression_index = u32::MAX - operand_id;
|
||||
if operand_as_expression_index >= self.info.num_expressions {
|
||||
// The operand ID is outside the known range of counter IDs and also outside the
|
||||
// known range of expression IDs. In either case, the result of a missing operand
|
||||
// (if and when used in an expression) will be zero, so from a computation
|
||||
// perspective, it doesn't matter whether it is interepretted as a counter or an
|
||||
// expression.
|
||||
//
|
||||
// However, the `num_counters` and `num_expressions` query results are used to
|
||||
// allocate arrays when generating the coverage map (during codegen), so choose
|
||||
// the type that grows either `num_counters` or `num_expressions` the least.
|
||||
if operand_id - self.info.num_counters
|
||||
< operand_as_expression_index - self.info.num_expressions
|
||||
{
|
||||
self.update_num_counters(operand_id)
|
||||
} else {
|
||||
self.update_num_expressions(operand_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Visitor<'_> for CoverageVisitor {
|
||||
fn visit_coverage(&mut self, coverage: &Coverage, _location: Location) {
|
||||
if self.add_missing_operands {
|
||||
match coverage.kind {
|
||||
CoverageKind::Expression { lhs, rhs, .. } => {
|
||||
self.update_from_expression_operand(u32::from(lhs));
|
||||
self.update_from_expression_operand(u32::from(rhs));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
match coverage.kind {
|
||||
CoverageKind::Counter { id, .. } => {
|
||||
self.update_num_counters(u32::from(id));
|
||||
}
|
||||
CoverageKind::Expression { id, .. } => {
|
||||
self.update_num_expressions(u32::from(id));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn coverageinfo_from_mir<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> CoverageInfo {
|
||||
let mir_body = tcx.optimized_mir(def_id);
|
||||
|
||||
let mut coverage_visitor = CoverageVisitor {
|
||||
// num_counters always has at least the `ZERO` counter.
|
||||
info: CoverageInfo { num_counters: 1, num_expressions: 0 },
|
||||
add_missing_operands: false,
|
||||
};
|
||||
|
||||
coverage_visitor.visit_body(mir_body);
|
||||
|
||||
coverage_visitor.add_missing_operands = true;
|
||||
coverage_visitor.visit_body(mir_body);
|
||||
|
||||
coverage_visitor.info
|
||||
}
|
753
compiler/rustc_mir/src/transform/coverage/spans.rs
Normal file
753
compiler/rustc_mir/src/transform/coverage/spans.rs
Normal file
|
@ -0,0 +1,753 @@
|
|||
use super::debug::term_type;
|
||||
use super::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph};
|
||||
|
||||
use crate::util::spanview::source_range_no_file;
|
||||
|
||||
use rustc_data_structures::graph::WithNumNodes;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir::{
|
||||
self, AggregateKind, BasicBlock, FakeReadCause, Rvalue, Statement, StatementKind, Terminator,
|
||||
TerminatorKind,
|
||||
};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
use rustc_span::source_map::original_sp;
|
||||
use rustc_span::{BytePos, Span, SyntaxContext};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum CoverageStatement {
|
||||
Statement(BasicBlock, Span, usize),
|
||||
Terminator(BasicBlock, Span),
|
||||
}
|
||||
|
||||
impl CoverageStatement {
|
||||
pub fn format(&self, tcx: TyCtxt<'tcx>, mir_body: &'a mir::Body<'tcx>) -> String {
|
||||
match *self {
|
||||
Self::Statement(bb, span, stmt_index) => {
|
||||
let stmt = &mir_body[bb].statements[stmt_index];
|
||||
format!(
|
||||
"{}: @{}[{}]: {:?}",
|
||||
source_range_no_file(tcx, &span),
|
||||
bb.index(),
|
||||
stmt_index,
|
||||
stmt
|
||||
)
|
||||
}
|
||||
Self::Terminator(bb, span) => {
|
||||
let term = mir_body[bb].terminator();
|
||||
format!(
|
||||
"{}: @{}.{}: {:?}",
|
||||
source_range_no_file(tcx, &span),
|
||||
bb.index(),
|
||||
term_type(&term.kind),
|
||||
term.kind
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn span(&self) -> &Span {
|
||||
match self {
|
||||
Self::Statement(_, span, _) | Self::Terminator(_, span) => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A BCB is deconstructed into one or more `Span`s. Each `Span` maps to a `CoverageSpan` that
|
||||
/// references the originating BCB and one or more MIR `Statement`s and/or `Terminator`s.
|
||||
/// Initially, the `Span`s come from the `Statement`s and `Terminator`s, but subsequent
|
||||
/// transforms can combine adjacent `Span`s and `CoverageSpan` from the same BCB, merging the
|
||||
/// `CoverageStatement` vectors, and the `Span`s to cover the extent of the combined `Span`s.
|
||||
///
|
||||
/// Note: A `CoverageStatement` merged into another CoverageSpan may come from a `BasicBlock` that
|
||||
/// is not part of the `CoverageSpan` bcb if the statement was included because it's `Span` matches
|
||||
/// 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 span: Span,
|
||||
pub bcb: BasicCoverageBlock,
|
||||
pub coverage_statements: Vec<CoverageStatement>,
|
||||
pub is_closure: bool,
|
||||
}
|
||||
|
||||
impl CoverageSpan {
|
||||
pub fn for_statement(
|
||||
statement: &Statement<'tcx>,
|
||||
span: Span,
|
||||
bcb: BasicCoverageBlock,
|
||||
bb: BasicBlock,
|
||||
stmt_index: usize,
|
||||
) -> Self {
|
||||
let is_closure = match statement.kind {
|
||||
StatementKind::Assign(box (
|
||||
_,
|
||||
Rvalue::Aggregate(box AggregateKind::Closure(_, _), _),
|
||||
)) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
Self {
|
||||
span,
|
||||
bcb,
|
||||
coverage_statements: vec![CoverageStatement::Statement(bb, span, stmt_index)],
|
||||
is_closure,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_terminator(span: Span, bcb: BasicCoverageBlock, bb: BasicBlock) -> Self {
|
||||
Self {
|
||||
span,
|
||||
bcb,
|
||||
coverage_statements: vec![CoverageStatement::Terminator(bb, span)],
|
||||
is_closure: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge_from(&mut self, mut other: CoverageSpan) {
|
||||
debug_assert!(self.is_mergeable(&other));
|
||||
self.span = self.span.to(other.span);
|
||||
if other.is_closure {
|
||||
self.is_closure = true;
|
||||
}
|
||||
self.coverage_statements.append(&mut other.coverage_statements);
|
||||
}
|
||||
|
||||
pub fn cutoff_statements_at(&mut self, cutoff_pos: BytePos) {
|
||||
self.coverage_statements.retain(|covstmt| covstmt.span().hi() <= cutoff_pos);
|
||||
if let Some(highest_covstmt) =
|
||||
self.coverage_statements.iter().max_by_key(|covstmt| covstmt.span().hi())
|
||||
{
|
||||
self.span = self.span.with_hi(highest_covstmt.span().hi());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_mergeable(&self, other: &Self) -> bool {
|
||||
self.is_in_same_bcb(other) && !(self.is_closure || other.is_closure)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_in_same_bcb(&self, other: &Self) -> bool {
|
||||
self.bcb == other.bcb
|
||||
}
|
||||
|
||||
pub fn format(&self, tcx: TyCtxt<'tcx>, mir_body: &'a mir::Body<'tcx>) -> String {
|
||||
format!(
|
||||
"{}\n {}",
|
||||
source_range_no_file(tcx, &self.span),
|
||||
self.format_coverage_statements(tcx, mir_body).replace("\n", "\n "),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn format_coverage_statements(
|
||||
&self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mir_body: &'a mir::Body<'tcx>,
|
||||
) -> String {
|
||||
let mut sorted_coverage_statements = self.coverage_statements.clone();
|
||||
sorted_coverage_statements.sort_unstable_by_key(|covstmt| match *covstmt {
|
||||
CoverageStatement::Statement(bb, _, index) => (bb, index),
|
||||
CoverageStatement::Terminator(bb, _) => (bb, usize::MAX),
|
||||
});
|
||||
sorted_coverage_statements
|
||||
.iter()
|
||||
.map(|covstmt| covstmt.format(tcx, mir_body))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the initial set of `CoverageSpan`s (one per MIR `Statement` or `Terminator`) into a
|
||||
/// minimal set of `CoverageSpan`s, using the BCB CFG to determine where it is safe and useful to:
|
||||
///
|
||||
/// * Remove duplicate source code coverage regions
|
||||
/// * Merge spans that represent continuous (both in source code and control flow), non-branching
|
||||
/// execution
|
||||
/// * Carve out (leave uncovered) any span that will be counted by another MIR (notably, closures)
|
||||
pub struct CoverageSpans<'a, 'tcx> {
|
||||
/// The MIR, used to look up `BasicBlockData`.
|
||||
mir_body: &'a mir::Body<'tcx>,
|
||||
|
||||
/// A `Span` covering the function body of the MIR (typically from left curly brace to right
|
||||
/// curly brace).
|
||||
body_span: Span,
|
||||
|
||||
/// The BasicCoverageBlock Control Flow Graph (BCB CFG).
|
||||
basic_coverage_blocks: &'a CoverageGraph,
|
||||
|
||||
/// The initial set of `CoverageSpan`s, sorted by `Span` (`lo` and `hi`) and by relative
|
||||
/// dominance between the `BasicCoverageBlock`s of equal `Span`s.
|
||||
sorted_spans_iter: Option<std::vec::IntoIter<CoverageSpan>>,
|
||||
|
||||
/// The current `CoverageSpan` to compare to its `prev`, to possibly merge, discard, force the
|
||||
/// discard of the `prev` (and or `pending_dups`), or keep both (with `prev` moved to
|
||||
/// `pending_dups`). If `curr` is not discarded or merged, it becomes `prev` for the next
|
||||
/// iteration.
|
||||
some_curr: Option<CoverageSpan>,
|
||||
|
||||
/// The original `span` for `curr`, in case the `curr` span is modified.
|
||||
curr_original_span: Span,
|
||||
|
||||
/// The CoverageSpan from a prior iteration; typically assigned from that iteration's `curr`.
|
||||
/// If that `curr` was discarded, `prev` retains its value from the previous iteration.
|
||||
some_prev: Option<CoverageSpan>,
|
||||
|
||||
/// Assigned from `curr_original_span` from the previous iteration.
|
||||
prev_original_span: Span,
|
||||
|
||||
/// One or more `CoverageSpan`s with the same `Span` but different `BasicCoverageBlock`s, and
|
||||
/// no `BasicCoverageBlock` in this list dominates another `BasicCoverageBlock` in the list.
|
||||
/// If a new `curr` span also fits this criteria (compared to an existing list of
|
||||
/// `pending_dups`), that `curr` `CoverageSpan` moves to `prev` before possibly being added to
|
||||
/// the `pending_dups` list, on the next iteration. As a result, if `prev` and `pending_dups`
|
||||
/// have the same `Span`, the criteria for `pending_dups` holds for `prev` as well: a `prev`
|
||||
/// with a matching `Span` does not dominate any `pending_dup` and no `pending_dup` dominates a
|
||||
/// `prev` with a matching `Span`)
|
||||
pending_dups: Vec<CoverageSpan>,
|
||||
|
||||
/// The final `CoverageSpan`s to add to the coverage map. A `Counter` or `Expression`
|
||||
/// will also be injected into the MIR for each `CoverageSpan`.
|
||||
refined_spans: Vec<CoverageSpan>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
|
||||
pub(crate) fn generate_coverage_spans(
|
||||
mir_body: &'a mir::Body<'tcx>,
|
||||
body_span: Span,
|
||||
basic_coverage_blocks: &'a CoverageGraph,
|
||||
) -> Vec<CoverageSpan> {
|
||||
let mut coverage_spans = CoverageSpans {
|
||||
mir_body,
|
||||
body_span,
|
||||
basic_coverage_blocks,
|
||||
sorted_spans_iter: None,
|
||||
refined_spans: Vec::with_capacity(basic_coverage_blocks.num_nodes() * 2),
|
||||
some_curr: None,
|
||||
curr_original_span: Span::with_root_ctxt(BytePos(0), BytePos(0)),
|
||||
some_prev: None,
|
||||
prev_original_span: Span::with_root_ctxt(BytePos(0), BytePos(0)),
|
||||
pending_dups: Vec::new(),
|
||||
};
|
||||
|
||||
let sorted_spans = coverage_spans.mir_to_initial_sorted_coverage_spans();
|
||||
|
||||
coverage_spans.sorted_spans_iter = Some(sorted_spans.into_iter());
|
||||
coverage_spans.some_prev = coverage_spans.sorted_spans_iter.as_mut().unwrap().next();
|
||||
coverage_spans.prev_original_span =
|
||||
coverage_spans.some_prev.as_ref().expect("at least one span").span;
|
||||
|
||||
coverage_spans.to_refined_spans()
|
||||
}
|
||||
|
||||
/// Generate a minimal set of `CoverageSpan`s, each representing a contiguous code region to be
|
||||
/// counted.
|
||||
///
|
||||
/// The basic steps are:
|
||||
///
|
||||
/// 1. Extract an initial set of spans from the `Statement`s and `Terminator`s of each
|
||||
/// `BasicCoverageBlockData`.
|
||||
/// 2. Sort the spans by span.lo() (starting position). Spans that start at the same position
|
||||
/// are sorted with longer spans before shorter spans; and equal spans are sorted
|
||||
/// (deterministically) based on "dominator" relationship (if any).
|
||||
/// 3. Traverse the spans in sorted order to identify spans that can be dropped (for instance,
|
||||
/// if another span or spans are already counting the same code region), or should be merged
|
||||
/// into a broader combined span (because it represents a contiguous, non-branching, and
|
||||
/// uninterrupted region of source code).
|
||||
///
|
||||
/// Closures are exposed in their enclosing functions as `Assign` `Rvalue`s, and since
|
||||
/// closures have their own MIR, their `Span` in their enclosing function should be left
|
||||
/// "uncovered".
|
||||
///
|
||||
/// Note the resulting vector of `CoverageSpan`s does may not be fully sorted (and does not need
|
||||
/// to be).
|
||||
fn mir_to_initial_sorted_coverage_spans(&self) -> Vec<CoverageSpan> {
|
||||
let mut initial_spans = Vec::<CoverageSpan>::with_capacity(self.mir_body.num_nodes() * 2);
|
||||
for (bcb, bcb_data) in self.basic_coverage_blocks.iter_enumerated() {
|
||||
for coverage_span in self.bcb_to_initial_coverage_spans(bcb, bcb_data) {
|
||||
initial_spans.push(coverage_span);
|
||||
}
|
||||
}
|
||||
|
||||
if initial_spans.is_empty() {
|
||||
// This can happen if, for example, the function is unreachable (contains only a
|
||||
// `BasicBlock`(s) with an `Unreachable` terminator).
|
||||
return initial_spans;
|
||||
}
|
||||
|
||||
initial_spans.sort_unstable_by(|a, b| {
|
||||
if a.span.lo() == b.span.lo() {
|
||||
if a.span.hi() == b.span.hi() {
|
||||
if a.is_in_same_bcb(b) {
|
||||
Some(Ordering::Equal)
|
||||
} else {
|
||||
// Sort equal spans by dominator relationship, in reverse order (so
|
||||
// dominators always come after the dominated equal spans). When later
|
||||
// comparing two spans in order, the first will either dominate the second,
|
||||
// or they will have no dominator relationship.
|
||||
self.basic_coverage_blocks.dominators().rank_partial_cmp(b.bcb, a.bcb)
|
||||
}
|
||||
} else {
|
||||
// Sort hi() in reverse order so shorter spans are attempted after longer spans.
|
||||
// This guarantees that, if a `prev` span overlaps, and is not equal to, a
|
||||
// `curr` span, the prev span either extends further left of the curr span, or
|
||||
// they start at the same position and the prev span extends further right of
|
||||
// the end of the curr span.
|
||||
b.span.hi().partial_cmp(&a.span.hi())
|
||||
}
|
||||
} else {
|
||||
a.span.lo().partial_cmp(&b.span.lo())
|
||||
}
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
initial_spans
|
||||
}
|
||||
|
||||
/// Iterate through the sorted `CoverageSpan`s, and return the refined list of merged and
|
||||
/// de-duplicated `CoverageSpan`s.
|
||||
fn to_refined_spans(mut self) -> Vec<CoverageSpan> {
|
||||
while self.next_coverage_span() {
|
||||
if self.curr().is_mergeable(self.prev()) {
|
||||
debug!(" same bcb (and neither is a closure), merge with prev={:?}", self.prev());
|
||||
let prev = self.take_prev();
|
||||
self.curr_mut().merge_from(prev);
|
||||
// Note that curr.span may now differ from curr_original_span
|
||||
} else if self.prev_ends_before_curr() {
|
||||
debug!(
|
||||
" different bcbs and disjoint spans, so keep curr for next iter, and add \
|
||||
prev={:?}",
|
||||
self.prev()
|
||||
);
|
||||
let prev = self.take_prev();
|
||||
self.refined_spans.push(prev);
|
||||
} else if self.prev().is_closure {
|
||||
// drop any equal or overlapping span (`curr`) and keep `prev` to test again in the
|
||||
// next iter
|
||||
debug!(
|
||||
" curr overlaps a closure (prev). Drop curr and keep prev for next iter. \
|
||||
prev={:?}",
|
||||
self.prev()
|
||||
);
|
||||
self.discard_curr();
|
||||
} else if self.curr().is_closure {
|
||||
self.carve_out_span_for_closure();
|
||||
} else if self.prev_original_span == self.curr().span {
|
||||
// Note that this compares the new span to `prev_original_span`, which may not
|
||||
// be the full `prev.span` (if merged during the previous iteration).
|
||||
self.hold_pending_dups_unless_dominated();
|
||||
} else {
|
||||
self.cutoff_prev_at_overlapping_curr();
|
||||
}
|
||||
}
|
||||
|
||||
debug!(" AT END, adding last prev={:?}", self.prev());
|
||||
let prev = self.take_prev();
|
||||
let CoverageSpans {
|
||||
mir_body, basic_coverage_blocks, pending_dups, mut refined_spans, ..
|
||||
} = self;
|
||||
for dup in pending_dups {
|
||||
debug!(" ...adding at least one pending dup={:?}", dup);
|
||||
refined_spans.push(dup);
|
||||
}
|
||||
refined_spans.push(prev);
|
||||
|
||||
// Remove `CoverageSpan`s with empty spans ONLY if the empty `CoverageSpan`s BCB also has at
|
||||
// least one other non-empty `CoverageSpan`.
|
||||
let mut has_coverage = BitSet::new_empty(basic_coverage_blocks.num_nodes());
|
||||
for covspan in &refined_spans {
|
||||
if !covspan.span.is_empty() {
|
||||
has_coverage.insert(covspan.bcb);
|
||||
}
|
||||
}
|
||||
refined_spans.retain(|covspan| {
|
||||
!(covspan.span.is_empty()
|
||||
&& is_goto(&basic_coverage_blocks[covspan.bcb].terminator(mir_body).kind)
|
||||
&& has_coverage.contains(covspan.bcb))
|
||||
});
|
||||
|
||||
// Remove `CoverageSpan`s derived from closures, originally added to ensure the coverage
|
||||
// regions for the current function leave room for the closure's own coverage regions
|
||||
// (injected separately, from the closure's own MIR).
|
||||
refined_spans.retain(|covspan| !covspan.is_closure);
|
||||
refined_spans
|
||||
}
|
||||
|
||||
// Generate a set of `CoverageSpan`s from the filtered set of `Statement`s and `Terminator`s of
|
||||
// the `BasicBlock`(s) in the given `BasicCoverageBlockData`. One `CoverageSpan` is generated
|
||||
// for each `Statement` and `Terminator`. (Note that subsequent stages of coverage analysis will
|
||||
// merge some `CoverageSpan`s, at which point a `CoverageSpan` may represent multiple
|
||||
// `Statement`s and/or `Terminator`s.)
|
||||
fn bcb_to_initial_coverage_spans(
|
||||
&self,
|
||||
bcb: BasicCoverageBlock,
|
||||
bcb_data: &'a BasicCoverageBlockData,
|
||||
) -> Vec<CoverageSpan> {
|
||||
bcb_data
|
||||
.basic_blocks
|
||||
.iter()
|
||||
.flat_map(|&bb| {
|
||||
let data = &self.mir_body[bb];
|
||||
data.statements
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(move |(index, statement)| {
|
||||
filtered_statement_span(statement, self.body_span).map(|span| {
|
||||
CoverageSpan::for_statement(statement, span, bcb, bb, index)
|
||||
})
|
||||
})
|
||||
.chain(
|
||||
filtered_terminator_span(data.terminator(), self.body_span)
|
||||
.map(|span| CoverageSpan::for_terminator(span, bcb, bb)),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn curr(&self) -> &CoverageSpan {
|
||||
self.some_curr
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| bug!("invalid attempt to unwrap a None some_curr"))
|
||||
}
|
||||
|
||||
fn curr_mut(&mut self) -> &mut CoverageSpan {
|
||||
self.some_curr
|
||||
.as_mut()
|
||||
.unwrap_or_else(|| bug!("invalid attempt to unwrap a None some_curr"))
|
||||
}
|
||||
|
||||
fn prev(&self) -> &CoverageSpan {
|
||||
self.some_prev
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| bug!("invalid attempt to unwrap a None some_prev"))
|
||||
}
|
||||
|
||||
fn prev_mut(&mut self) -> &mut CoverageSpan {
|
||||
self.some_prev
|
||||
.as_mut()
|
||||
.unwrap_or_else(|| bug!("invalid attempt to unwrap a None some_prev"))
|
||||
}
|
||||
|
||||
fn take_prev(&mut self) -> CoverageSpan {
|
||||
self.some_prev.take().unwrap_or_else(|| bug!("invalid attempt to unwrap a None some_prev"))
|
||||
}
|
||||
|
||||
/// If there are `pending_dups` but `prev` is not a matching dup (`prev.span` doesn't match the
|
||||
/// `pending_dups` spans), then one of the following two things happened during the previous
|
||||
/// iteration:
|
||||
/// * the previous `curr` span (which is now `prev`) was not a duplicate of the pending_dups
|
||||
/// (in which case there should be at least two spans in `pending_dups`); or
|
||||
/// * the `span` of `prev` was modified by `curr_mut().merge_from(prev)` (in which case
|
||||
/// `pending_dups` could have as few as one span)
|
||||
/// In either case, no more spans will match the span of `pending_dups`, so
|
||||
/// add the `pending_dups` if they don't overlap `curr`, and clear the list.
|
||||
fn check_pending_dups(&mut self) {
|
||||
if let Some(dup) = self.pending_dups.last() {
|
||||
if dup.span != self.prev().span {
|
||||
debug!(
|
||||
" SAME spans, but pending_dups are NOT THE SAME, so BCBs matched on \
|
||||
previous iteration, or prev started a new disjoint span"
|
||||
);
|
||||
if dup.span.hi() <= self.curr().span.lo() {
|
||||
let pending_dups = self.pending_dups.split_off(0);
|
||||
for dup in pending_dups.into_iter() {
|
||||
debug!(" ...adding at least one pending={:?}", dup);
|
||||
self.refined_spans.push(dup);
|
||||
}
|
||||
} else {
|
||||
self.pending_dups.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance `prev` to `curr` (if any), and `curr` to the next `CoverageSpan` in sorted order.
|
||||
fn next_coverage_span(&mut self) -> bool {
|
||||
if let Some(curr) = self.some_curr.take() {
|
||||
self.some_prev = Some(curr);
|
||||
self.prev_original_span = self.curr_original_span;
|
||||
}
|
||||
while let Some(curr) = self.sorted_spans_iter.as_mut().unwrap().next() {
|
||||
debug!("FOR curr={:?}", curr);
|
||||
if self.prev_starts_after_next(&curr) {
|
||||
debug!(
|
||||
" prev.span starts after curr.span, so curr will be dropped (skipping past \
|
||||
closure?); prev={:?}",
|
||||
self.prev()
|
||||
);
|
||||
} else {
|
||||
// Save a copy of the original span for `curr` in case the `CoverageSpan` is changed
|
||||
// by `self.curr_mut().merge_from(prev)`.
|
||||
self.curr_original_span = curr.span;
|
||||
self.some_curr.replace(curr);
|
||||
self.check_pending_dups();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// If called, then the next call to `next_coverage_span()` will *not* update `prev` with the
|
||||
/// `curr` coverage span.
|
||||
fn discard_curr(&mut self) {
|
||||
self.some_curr = None;
|
||||
}
|
||||
|
||||
/// Returns true if the curr span should be skipped because prev has already advanced beyond the
|
||||
/// end of curr. This can only happen if a prior iteration updated `prev` to skip past a region
|
||||
/// of code, such as skipping past a closure.
|
||||
fn prev_starts_after_next(&self, next_curr: &CoverageSpan) -> bool {
|
||||
self.prev().span.lo() > next_curr.span.lo()
|
||||
}
|
||||
|
||||
/// Returns true if the curr span starts past the end of the prev span, which means they don't
|
||||
/// overlap, so we now know the prev can be added to the refined coverage spans.
|
||||
fn prev_ends_before_curr(&self) -> bool {
|
||||
self.prev().span.hi() <= self.curr().span.lo()
|
||||
}
|
||||
|
||||
/// If `prev`s span extends left of the closure (`curr`), carve out the closure's
|
||||
/// span from `prev`'s span. (The closure's coverage counters will be injected when
|
||||
/// processing the closure's own MIR.) Add the portion of the span to the left of the
|
||||
/// closure; and if the span extends to the right of the closure, update `prev` to
|
||||
/// that portion of the span. For any `pending_dups`, repeat the same process.
|
||||
fn carve_out_span_for_closure(&mut self) {
|
||||
let curr_span = self.curr().span;
|
||||
let left_cutoff = curr_span.lo();
|
||||
let right_cutoff = curr_span.hi();
|
||||
let has_pre_closure_span = self.prev().span.lo() < right_cutoff;
|
||||
let has_post_closure_span = self.prev().span.hi() > right_cutoff;
|
||||
let mut pending_dups = self.pending_dups.split_off(0);
|
||||
if has_pre_closure_span {
|
||||
let mut pre_closure = self.prev().clone();
|
||||
pre_closure.span = pre_closure.span.with_hi(left_cutoff);
|
||||
debug!(" prev overlaps a closure. Adding span for pre_closure={:?}", pre_closure);
|
||||
if !pending_dups.is_empty() {
|
||||
for mut dup in pending_dups.iter().cloned() {
|
||||
dup.span = dup.span.with_hi(left_cutoff);
|
||||
debug!(" ...and at least one pre_closure dup={:?}", dup);
|
||||
self.refined_spans.push(dup);
|
||||
}
|
||||
}
|
||||
self.refined_spans.push(pre_closure);
|
||||
}
|
||||
if has_post_closure_span {
|
||||
// Update prev.span to start after the closure (and discard curr)
|
||||
self.prev_mut().span = self.prev().span.with_lo(right_cutoff);
|
||||
self.prev_original_span = self.prev().span;
|
||||
for dup in pending_dups.iter_mut() {
|
||||
dup.span = dup.span.with_lo(right_cutoff);
|
||||
}
|
||||
self.pending_dups.append(&mut pending_dups);
|
||||
self.discard_curr(); // since self.prev() was already updated
|
||||
} else {
|
||||
pending_dups.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Called if `curr.span` equals `prev_original_span` (and potentially equal to all
|
||||
/// `pending_dups` spans, if any); but keep in mind, `prev.span` may start at a `Span.lo()` that
|
||||
/// is less than (further left of) `prev_original_span.lo()`.
|
||||
///
|
||||
/// When two `CoverageSpan`s have the same `Span`, dominated spans can be discarded; but if
|
||||
/// neither `CoverageSpan` dominates the other, both (or possibly more than two) are held,
|
||||
/// until their disposition is determined. In this latter case, the `prev` dup is moved into
|
||||
/// `pending_dups` so the new `curr` dup can be moved to `prev` for the next iteration.
|
||||
fn hold_pending_dups_unless_dominated(&mut self) {
|
||||
// Equal coverage spans are ordered by dominators before dominated (if any), so it should be
|
||||
// impossible for `curr` to dominate any previous `CoverageSpan`.
|
||||
debug_assert!(!self.span_bcb_is_dominated_by(self.prev(), self.curr()));
|
||||
|
||||
let initial_pending_count = self.pending_dups.len();
|
||||
if initial_pending_count > 0 {
|
||||
let mut pending_dups = self.pending_dups.split_off(0);
|
||||
pending_dups.retain(|dup| !self.span_bcb_is_dominated_by(self.curr(), dup));
|
||||
self.pending_dups.append(&mut pending_dups);
|
||||
if self.pending_dups.len() < initial_pending_count {
|
||||
debug!(
|
||||
" discarded {} of {} pending_dups that dominated curr",
|
||||
initial_pending_count - self.pending_dups.len(),
|
||||
initial_pending_count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if self.span_bcb_is_dominated_by(self.curr(), self.prev()) {
|
||||
debug!(
|
||||
" different bcbs but SAME spans, and prev dominates curr. Discard prev={:?}",
|
||||
self.prev()
|
||||
);
|
||||
self.cutoff_prev_at_overlapping_curr();
|
||||
// If one span dominates the other, assocate the span with the code from the dominated
|
||||
// block only (`curr`), and discard the overlapping portion of the `prev` span. (Note
|
||||
// that if `prev.span` is wider than `prev_original_span`, a `CoverageSpan` will still
|
||||
// be created for `prev`s block, for the non-overlapping portion, left of `curr.span`.)
|
||||
//
|
||||
// For example:
|
||||
// match somenum {
|
||||
// x if x < 1 => { ... }
|
||||
// }...
|
||||
//
|
||||
// The span for the first `x` is referenced by both the pattern block (every time it is
|
||||
// evaluated) and the arm code (only when matched). The counter will be applied only to
|
||||
// the dominated block. This allows coverage to track and highlight things like the
|
||||
// assignment of `x` above, if the branch is matched, making `x` available to the arm
|
||||
// code; and to track and highlight the question mark `?` "try" operator at the end of
|
||||
// a function call returning a `Result`, so the `?` is covered when the function returns
|
||||
// an `Err`, and not counted as covered if the function always returns `Ok`.
|
||||
} else {
|
||||
// Save `prev` in `pending_dups`. (`curr` will become `prev` in the next iteration.)
|
||||
// If the `curr` CoverageSpan is later discarded, `pending_dups` can be discarded as
|
||||
// well; but if `curr` is added to refined_spans, the `pending_dups` will also be added.
|
||||
debug!(
|
||||
" different bcbs but SAME spans, and neither dominates, so keep curr for \
|
||||
next iter, and, pending upcoming spans (unless overlapping) add prev={:?}",
|
||||
self.prev()
|
||||
);
|
||||
let prev = self.take_prev();
|
||||
self.pending_dups.push(prev);
|
||||
}
|
||||
}
|
||||
|
||||
/// `curr` overlaps `prev`. If `prev`s span extends left of `curr`s span, keep _only_
|
||||
/// statements that end before `curr.lo()` (if any), and add the portion of the
|
||||
/// combined span for those statements. Any other statements have overlapping spans
|
||||
/// that can be ignored because `curr` and/or other upcoming statements/spans inside
|
||||
/// the overlap area will produce their own counters. This disambiguation process
|
||||
/// avoids injecting multiple counters for overlapping spans, and the potential for
|
||||
/// double-counting.
|
||||
fn cutoff_prev_at_overlapping_curr(&mut self) {
|
||||
debug!(
|
||||
" different bcbs, overlapping spans, so ignore/drop pending and only add prev \
|
||||
if it has statements that end before curr; prev={:?}",
|
||||
self.prev()
|
||||
);
|
||||
if self.pending_dups.is_empty() {
|
||||
let curr_span = self.curr().span;
|
||||
self.prev_mut().cutoff_statements_at(curr_span.lo());
|
||||
if self.prev().coverage_statements.is_empty() {
|
||||
debug!(" ... no non-overlapping statements to add");
|
||||
} else {
|
||||
debug!(" ... adding modified prev={:?}", self.prev());
|
||||
let prev = self.take_prev();
|
||||
self.refined_spans.push(prev);
|
||||
}
|
||||
} else {
|
||||
// with `pending_dups`, `prev` cannot have any statements that don't overlap
|
||||
self.pending_dups.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn span_bcb_is_dominated_by(&self, covspan: &CoverageSpan, dom_covspan: &CoverageSpan) -> bool {
|
||||
self.basic_coverage_blocks.is_dominated_by(covspan.bcb, dom_covspan.bcb)
|
||||
}
|
||||
}
|
||||
|
||||
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`.
|
||||
StatementKind::StorageLive(_)
|
||||
| StatementKind::StorageDead(_)
|
||||
// Coverage should not be encountered, but don't inject coverage coverage
|
||||
| StatementKind::Coverage(_)
|
||||
// Ignore `Nop`s
|
||||
| StatementKind::Nop => None,
|
||||
|
||||
// FIXME(#78546): MIR InstrumentCoverage - Can the source_info.span for `FakeRead`
|
||||
// statements be more consistent?
|
||||
//
|
||||
// FakeReadCause::ForGuardBinding, in this example:
|
||||
// match somenum {
|
||||
// x if x < 1 => { ... }
|
||||
// }...
|
||||
// The BasicBlock within the match arm code included one of these statements, but the span
|
||||
// for it covered the `1` in this source. The actual statements have nothing to do with that
|
||||
// source span:
|
||||
// FakeRead(ForGuardBinding, _4);
|
||||
// where `_4` is:
|
||||
// _4 = &_1; (at the span for the first `x`)
|
||||
// and `_1` is the `Place` for `somenum`.
|
||||
//
|
||||
// If and when the Issue is resolved, remove this special case match pattern:
|
||||
StatementKind::FakeRead(cause, _) if cause == FakeReadCause::ForGuardBinding => None,
|
||||
|
||||
// Retain spans from all other statements
|
||||
StatementKind::FakeRead(_, _) // Not including `ForGuardBinding`
|
||||
| StatementKind::Assign(_)
|
||||
| StatementKind::SetDiscriminant { .. }
|
||||
| StatementKind::LlvmInlineAsm(_)
|
||||
| StatementKind::Retag(_, _)
|
||||
| StatementKind::AscribeUserType(_, _) => {
|
||||
Some(function_source_span(statement.source_info.span, body_span))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// an `if condition { block }` has a span that includes the executed block, if true,
|
||||
// but for coverage, the code region executed, up to *and* through the SwitchInt,
|
||||
// actually stops before the if's block.)
|
||||
TerminatorKind::Unreachable // Unreachable blocks are not connected to the MIR CFG
|
||||
| TerminatorKind::Assert { .. }
|
||||
| TerminatorKind::Drop { .. }
|
||||
| TerminatorKind::DropAndReplace { .. }
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
// For `FalseEdge`, only the `real` branch is taken, so it is similar to a `Goto`.
|
||||
// FIXME(richkadel): Note that `Goto` was moved to it's own match arm, for the reasons
|
||||
// described below. Add tests to confirm whether or not similar cases also apply to
|
||||
// `FalseEdge`.
|
||||
| TerminatorKind::FalseEdge { .. } => None,
|
||||
|
||||
// FIXME(#78542): Can spans for `TerminatorKind::Goto` be improved to avoid special cases?
|
||||
//
|
||||
// `Goto`s are often the targets of `SwitchInt` branches, and certain important
|
||||
// optimizations to replace some `Counter`s with `Expression`s require a separate
|
||||
// `BasicCoverageBlock` for each branch, to support the `Counter`, when needed.
|
||||
//
|
||||
// Also, some test cases showed that `Goto` terminators, and to some degree their `Span`s,
|
||||
// provided useful context for coverage, such as to count and show when `if` blocks
|
||||
// _without_ `else` blocks execute the `false` case (counting when the body of the `if`
|
||||
// was _not_ taken). In these cases, the `Goto` span is ultimately given a `CoverageSpan`
|
||||
// of 1 character, at the end of it's original `Span`.
|
||||
//
|
||||
// However, in other cases, a visible `CoverageSpan` is not wanted, but the `Goto`
|
||||
// block must still be counted (for example, to contribute its count to an `Expression`
|
||||
// that reports the execution count for some other block). In these cases, the code region
|
||||
// is set to `None`. (See `Instrumentor::is_code_region_redundant()`.)
|
||||
TerminatorKind::Goto { .. } => {
|
||||
Some(function_source_span(terminator.source_info.span.shrink_to_hi(), body_span))
|
||||
}
|
||||
|
||||
// Retain spans from all other terminators
|
||||
TerminatorKind::Resume
|
||||
| TerminatorKind::Abort
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Call { .. }
|
||||
| TerminatorKind::Yield { .. }
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::FalseUnwind { .. }
|
||||
| TerminatorKind::InlineAsm { .. } => {
|
||||
Some(function_source_span(terminator.source_info.span, body_span))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn function_source_span(span: Span, body_span: Span) -> Span {
|
||||
let span = original_sp(span, body_span).with_ctxt(SyntaxContext::root());
|
||||
if body_span.contains(span) { span } else { body_span }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_goto(term_kind: &TerminatorKind<'tcx>) -> bool {
|
||||
match term_kind {
|
||||
TerminatorKind::Goto { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -22,6 +22,7 @@ pub mod check_packed_ref;
|
|||
pub mod check_unsafety;
|
||||
pub mod cleanup_post_borrowck;
|
||||
pub mod const_prop;
|
||||
pub mod coverage;
|
||||
pub mod deaggregator;
|
||||
pub mod dest_prop;
|
||||
pub mod dump_mir;
|
||||
|
@ -31,7 +32,6 @@ pub mod function_item_references;
|
|||
pub mod generator;
|
||||
pub mod inline;
|
||||
pub mod instcombine;
|
||||
pub mod instrument_coverage;
|
||||
pub mod match_branches;
|
||||
pub mod multiple_return_terminators;
|
||||
pub mod no_landing_pads;
|
||||
|
@ -85,7 +85,7 @@ pub(crate) fn provide(providers: &mut Providers) {
|
|||
},
|
||||
..*providers
|
||||
};
|
||||
instrument_coverage::provide(providers);
|
||||
coverage::query::provide(providers);
|
||||
}
|
||||
|
||||
fn is_mir_available(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
|
||||
|
@ -306,7 +306,7 @@ fn mir_promoted(
|
|||
];
|
||||
|
||||
let opt_coverage: &[&dyn MirPass<'tcx>] = if tcx.sess.opts.debugging_opts.instrument_coverage {
|
||||
&[&instrument_coverage::InstrumentCoverage]
|
||||
&[&coverage::InstrumentCoverage]
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
|
185
compiler/rustc_mir/src/util/generic_graphviz.rs
Normal file
185
compiler/rustc_mir/src/util/generic_graphviz.rs
Normal file
|
@ -0,0 +1,185 @@
|
|||
use rustc_data_structures::graph::{self, iterate};
|
||||
use rustc_graphviz as dot;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub struct GraphvizWriter<
|
||||
'a,
|
||||
G: graph::DirectedGraph + graph::WithSuccessors + graph::WithStartNode + graph::WithNumNodes,
|
||||
NodeContentFn: Fn(<G as rustc_data_structures::graph::DirectedGraph>::Node) -> Vec<String>,
|
||||
EdgeLabelsFn: Fn(<G as rustc_data_structures::graph::DirectedGraph>::Node) -> Vec<String>,
|
||||
> {
|
||||
graph: &'a G,
|
||||
is_subgraph: bool,
|
||||
graphviz_name: String,
|
||||
graph_label: Option<String>,
|
||||
node_content_fn: NodeContentFn,
|
||||
edge_labels_fn: EdgeLabelsFn,
|
||||
}
|
||||
|
||||
impl<
|
||||
'a,
|
||||
G: graph::DirectedGraph + graph::WithSuccessors + graph::WithStartNode + graph::WithNumNodes,
|
||||
NodeContentFn: Fn(<G as rustc_data_structures::graph::DirectedGraph>::Node) -> Vec<String>,
|
||||
EdgeLabelsFn: Fn(<G as rustc_data_structures::graph::DirectedGraph>::Node) -> Vec<String>,
|
||||
> GraphvizWriter<'a, G, NodeContentFn, EdgeLabelsFn>
|
||||
{
|
||||
pub fn new(
|
||||
graph: &'a G,
|
||||
graphviz_name: &str,
|
||||
node_content_fn: NodeContentFn,
|
||||
edge_labels_fn: EdgeLabelsFn,
|
||||
) -> Self {
|
||||
Self {
|
||||
graph,
|
||||
is_subgraph: false,
|
||||
graphviz_name: graphviz_name.to_owned(),
|
||||
graph_label: None,
|
||||
node_content_fn,
|
||||
edge_labels_fn,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_subgraph(
|
||||
graph: &'a G,
|
||||
graphviz_name: &str,
|
||||
node_content_fn: NodeContentFn,
|
||||
edge_labels_fn: EdgeLabelsFn,
|
||||
) -> Self {
|
||||
Self {
|
||||
graph,
|
||||
is_subgraph: true,
|
||||
graphviz_name: graphviz_name.to_owned(),
|
||||
graph_label: None,
|
||||
node_content_fn,
|
||||
edge_labels_fn,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_graph_label(&mut self, graph_label: &str) {
|
||||
self.graph_label = Some(graph_label.to_owned());
|
||||
}
|
||||
|
||||
/// Write a graphviz DOT of the graph
|
||||
pub fn write_graphviz<'tcx, W>(&self, tcx: TyCtxt<'tcx>, w: &mut W) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let kind = if self.is_subgraph { "subgraph" } else { "digraph" };
|
||||
let cluster = if self.is_subgraph { "cluster_" } else { "" }; // Print border around graph
|
||||
// FIXME(richkadel): If/when migrating the MIR graphviz to this generic implementation,
|
||||
// prepend "Mir_" to the graphviz_safe_def_name(def_id)
|
||||
writeln!(w, "{} {}{} {{", kind, cluster, self.graphviz_name)?;
|
||||
|
||||
// Global graph properties
|
||||
let font = format!(r#"fontname="{}""#, tcx.sess.opts.debugging_opts.graphviz_font);
|
||||
let mut graph_attrs = vec![&font[..]];
|
||||
let mut content_attrs = vec![&font[..]];
|
||||
|
||||
let dark_mode = tcx.sess.opts.debugging_opts.graphviz_dark_mode;
|
||||
if dark_mode {
|
||||
graph_attrs.push(r#"bgcolor="black""#);
|
||||
graph_attrs.push(r#"fontcolor="white""#);
|
||||
content_attrs.push(r#"color="white""#);
|
||||
content_attrs.push(r#"fontcolor="white""#);
|
||||
}
|
||||
|
||||
writeln!(w, r#" graph [{}];"#, graph_attrs.join(" "))?;
|
||||
let content_attrs_str = content_attrs.join(" ");
|
||||
writeln!(w, r#" node [{}];"#, content_attrs_str)?;
|
||||
writeln!(w, r#" edge [{}];"#, content_attrs_str)?;
|
||||
|
||||
// Graph label
|
||||
if let Some(graph_label) = &self.graph_label {
|
||||
self.write_graph_label(graph_label, w)?;
|
||||
}
|
||||
|
||||
// Nodes
|
||||
for node in iterate::post_order_from(self.graph, self.graph.start_node()) {
|
||||
self.write_node(node, dark_mode, w)?;
|
||||
}
|
||||
|
||||
// Edges
|
||||
for source in iterate::post_order_from(self.graph, self.graph.start_node()) {
|
||||
self.write_edges(source, w)?;
|
||||
}
|
||||
writeln!(w, "}}")
|
||||
}
|
||||
|
||||
/// Write a graphviz DOT node for the given node.
|
||||
pub fn write_node<W>(&self, node: G::Node, dark_mode: bool, w: &mut W) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
// Start a new node with the label to follow, in one of DOT's pseudo-HTML tables.
|
||||
write!(w, r#" {} [shape="none", label=<"#, self.node(node))?;
|
||||
|
||||
write!(w, r#"<table border="0" cellborder="1" cellspacing="0">"#)?;
|
||||
|
||||
// FIXME(richkadel): Need generic way to know if node header should have a different color
|
||||
// let (blk, bgcolor) = if data.is_cleanup {
|
||||
// (format!("{:?} (cleanup)", node), "lightblue")
|
||||
// } else {
|
||||
// let color = if dark_mode { "dimgray" } else { "gray" };
|
||||
// (format!("{:?}", node), color)
|
||||
// };
|
||||
let color = if dark_mode { "dimgray" } else { "gray" };
|
||||
let (blk, bgcolor) = (format!("{:?}", node), color);
|
||||
write!(
|
||||
w,
|
||||
r#"<tr><td bgcolor="{bgcolor}" {attrs} colspan="{colspan}">{blk}</td></tr>"#,
|
||||
attrs = r#"align="center""#,
|
||||
colspan = 1,
|
||||
blk = blk,
|
||||
bgcolor = bgcolor
|
||||
)?;
|
||||
|
||||
for section in (self.node_content_fn)(node) {
|
||||
write!(
|
||||
w,
|
||||
r#"<tr><td align="left" balign="left">{}</td></tr>"#,
|
||||
dot::escape_html(§ion).replace("\n", "<br/>")
|
||||
)?;
|
||||
}
|
||||
|
||||
// Close the table
|
||||
write!(w, "</table>")?;
|
||||
|
||||
// Close the node label and the node itself.
|
||||
writeln!(w, ">];")
|
||||
}
|
||||
|
||||
/// Write graphviz DOT edges with labels between the given node and all of its successors.
|
||||
fn write_edges<W>(&self, source: G::Node, w: &mut W) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let edge_labels = (self.edge_labels_fn)(source);
|
||||
for (index, target) in self.graph.successors(source).enumerate() {
|
||||
let src = self.node(source);
|
||||
let trg = self.node(target);
|
||||
let escaped_edge_label = if let Some(edge_label) = edge_labels.get(index) {
|
||||
dot::escape_html(edge_label).replace("\n", r#"<br align="left"/>"#)
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
writeln!(w, r#" {} -> {} [label=<{}>];"#, src, trg, escaped_edge_label)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write the graphviz DOT label for the overall graph. This is essentially a block of text that
|
||||
/// will appear below the graph.
|
||||
fn write_graph_label<W>(&self, label: &str, w: &mut W) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let lines = label.split("\n").map(|s| dot::escape_html(s)).collect::<Vec<_>>();
|
||||
let escaped_label = lines.join(r#"<br align="left"/>"#);
|
||||
writeln!(w, r#" label=<<br/><br/>{}<br align="left"/><br/><br/><br/>>;"#, escaped_label)
|
||||
}
|
||||
|
||||
fn node(&self, node: G::Node) -> String {
|
||||
format!("{:?}__{}", node, self.graphviz_name)
|
||||
}
|
||||
}
|
|
@ -62,6 +62,7 @@ where
|
|||
let dark_mode = tcx.sess.opts.debugging_opts.graphviz_dark_mode;
|
||||
if dark_mode {
|
||||
graph_attrs.push(r#"bgcolor="black""#);
|
||||
graph_attrs.push(r#"fontcolor="white""#);
|
||||
content_attrs.push(r#"color="white""#);
|
||||
content_attrs.push(r#"fontcolor="white""#);
|
||||
}
|
||||
|
@ -112,7 +113,8 @@ where
|
|||
|
||||
// Basic block number at the top.
|
||||
let (blk, bgcolor) = if data.is_cleanup {
|
||||
(format!("{} (cleanup)", block.index()), "lightblue")
|
||||
let color = if dark_mode { "royalblue" } else { "lightblue" };
|
||||
(format!("{} (cleanup)", block.index()), color)
|
||||
} else {
|
||||
let color = if dark_mode { "dimgray" } else { "gray" };
|
||||
(format!("{}", block.index()), color)
|
||||
|
|
|
@ -7,6 +7,7 @@ pub mod storage;
|
|||
mod alignment;
|
||||
pub mod collect_writes;
|
||||
mod find_self_call;
|
||||
pub(crate) mod generic_graphviz;
|
||||
mod graphviz;
|
||||
pub(crate) mod pretty;
|
||||
pub(crate) mod spanview;
|
||||
|
|
|
@ -887,12 +887,15 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
|
|||
dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED],
|
||||
"exclude the pass number when dumping MIR (used in tests) (default: no)"),
|
||||
dump_mir_graphviz: bool = (false, parse_bool, [UNTRACKED],
|
||||
"in addition to `.mir` files, create graphviz `.dot` files (default: no)"),
|
||||
"in addition to `.mir` files, create graphviz `.dot` files (and with \
|
||||
`-Z instrument-coverage`, also create a `.dot` file for the MIR-derived \
|
||||
coverage graph) (default: no)"),
|
||||
dump_mir_spanview: Option<MirSpanview> = (None, parse_mir_spanview, [UNTRACKED],
|
||||
"in addition to `.mir` files, create `.html` files to view spans for \
|
||||
all `statement`s (including terminators), only `terminator` spans, or \
|
||||
computed `block` spans (one span encompassing a block's terminator and \
|
||||
all statements)."),
|
||||
all statements). If `-Z instrument-coverage` is also enabled, create \
|
||||
an additional `.html` file showing the computed coverage spans."),
|
||||
emit_future_incompat_report: bool = (false, parse_bool, [UNTRACKED],
|
||||
"emits a future-incompatibility report for lints (RFC 2834)"),
|
||||
emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED],
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
digraph Cov_0_4 {
|
||||
graph [fontname="Courier, monospace"];
|
||||
node [fontname="Courier, monospace"];
|
||||
edge [fontname="Courier, monospace"];
|
||||
bcb0__Cov_0_4 [shape="none", label=<<table border="0" cellborder="1" cellspacing="0"><tr><td bgcolor="gray" align="center" colspan="1">bcb0</td></tr><tr><td align="left" balign="left"></td></tr><tr><td align="left" balign="left">Counter(bcb0) at 19:5-20:2<br/> 19:5-19:9: @0[0]: _0 = const true<br/> 20:2-20:2: @0.Return: return</td></tr><tr><td align="left" balign="left">bb0: Return</td></tr></table>>];
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
digraph Cov_0_3 {
|
||||
graph [fontname="Courier, monospace"];
|
||||
node [fontname="Courier, monospace"];
|
||||
edge [fontname="Courier, monospace"];
|
||||
bcb2__Cov_0_3 [shape="none", label=<<table border="0" cellborder="1" cellspacing="0"><tr><td bgcolor="gray" align="center" colspan="1">bcb2</td></tr><tr><td align="left" balign="left">Expression(bcb0 - bcb1) at 14:6-14:6<br/> 14:6-14:6: @4.Goto: goto -> bb0</td></tr><tr><td align="left" balign="left">bb4: Goto</td></tr></table>>];
|
||||
bcb1__Cov_0_3 [shape="none", label=<<table border="0" cellborder="1" cellspacing="0"><tr><td bgcolor="gray" align="center" colspan="1">bcb1</td></tr><tr><td align="left" balign="left">Counter(bcb1) at 12:13-12:18<br/> 12:13-12:18: @5[0]: _0 = const ()<br/>Expression(bcb1 + 0) at 15:2-15:2<br/> 15:2-15:2: @5.Return: return</td></tr><tr><td align="left" balign="left">bb3: FalseEdge</td></tr><tr><td align="left" balign="left">bb5: Return</td></tr></table>>];
|
||||
bcb0__Cov_0_3 [shape="none", label=<<table border="0" cellborder="1" cellspacing="0"><tr><td bgcolor="gray" align="center" colspan="1">bcb0</td></tr><tr><td align="left" balign="left"></td></tr><tr><td align="left" balign="left">Counter(bcb0) at 11:12-11:17<br/> 11:12-11:17: @1.Call: _2 = bar() -> [return: bb2, unwind: bb6]<br/> 11:12-11:17: @2[0]: FakeRead(ForMatchedPlace, _2)</td></tr><tr><td align="left" balign="left">bb0: FalseUnwind<br/>bb1: Call</td></tr><tr><td align="left" balign="left">bb2: SwitchInt</td></tr></table>>];
|
||||
bcb2__Cov_0_3 -> bcb0__Cov_0_3 [label=<>];
|
||||
bcb0__Cov_0_3 -> bcb2__Cov_0_3 [label=<false>];
|
||||
bcb0__Cov_0_3 -> bcb1__Cov_0_3 [label=<otherwise>];
|
||||
}
|
20
src/test/mir-opt/coverage_graphviz.rs
Normal file
20
src/test/mir-opt/coverage_graphviz.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Test that `-Z instrument-coverage` with `-Z dump-mir-graphviz` generates a graphviz (.dot file)
|
||||
// rendering of the `BasicCoverageBlock` coverage control flow graph, with counters and
|
||||
// expressions.
|
||||
|
||||
// needs-profiler-support
|
||||
// compile-flags: -Z instrument-coverage -Z dump-mir-graphviz
|
||||
// EMIT_MIR coverage_graphviz.main.InstrumentCoverage.0.dot
|
||||
// EMIT_MIR coverage_graphviz.bar.InstrumentCoverage.0.dot
|
||||
fn main() {
|
||||
loop {
|
||||
if bar() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn bar() -> bool {
|
||||
true
|
||||
}
|
|
@ -8,7 +8,6 @@
|
|||
let mut _3: !; // in scope 0 at /the/src/instrument_coverage.rs:12:18: 14:10
|
||||
|
||||
bb0: {
|
||||
+ Coverage::Counter(1) for /the/src/instrument_coverage.rs:12:12 - 12:17; // scope 0 at /the/src/instrument_coverage.rs:11:5: 15:6
|
||||
falseUnwind -> [real: bb1, cleanup: bb6]; // scope 0 at /the/src/instrument_coverage.rs:11:5: 15:6
|
||||
}
|
||||
|
||||
|
@ -22,23 +21,26 @@
|
|||
|
||||
bb2: {
|
||||
FakeRead(ForMatchedPlace, _2); // scope 0 at /the/src/instrument_coverage.rs:12:12: 12:17
|
||||
+ Coverage::Counter(1) for /the/src/instrument_coverage.rs:12:12 - 12:17; // scope 0 at /the/src/instrument_coverage.rs:12:9: 14:10
|
||||
switchInt(_2) -> [false: bb4, otherwise: bb3]; // scope 0 at /the/src/instrument_coverage.rs:12:9: 14:10
|
||||
}
|
||||
|
||||
bb3: {
|
||||
+ Coverage::Counter(2) for /the/src/instrument_coverage.rs:13:13 - 16:2; // scope 0 at /the/src/instrument_coverage.rs:12:9: 14:10
|
||||
falseEdge -> [real: bb5, imaginary: bb4]; // scope 0 at /the/src/instrument_coverage.rs:12:9: 14:10
|
||||
}
|
||||
|
||||
bb4: {
|
||||
_1 = const (); // scope 0 at /the/src/instrument_coverage.rs:12:9: 14:10
|
||||
StorageDead(_2); // scope 0 at /the/src/instrument_coverage.rs:15:5: 15:6
|
||||
+ Coverage::Expression(4294967295) = 1 - 2 for /the/src/instrument_coverage.rs:15:6 - 15:7; // scope 0 at /the/src/instrument_coverage.rs:11:5: 15:6
|
||||
goto -> bb0; // scope 0 at /the/src/instrument_coverage.rs:11:5: 15:6
|
||||
}
|
||||
|
||||
bb5: {
|
||||
_0 = const (); // scope 0 at /the/src/instrument_coverage.rs:13:13: 13:18
|
||||
StorageDead(_2); // scope 0 at /the/src/instrument_coverage.rs:15:5: 15:6
|
||||
+ Coverage::Counter(2) for /the/src/instrument_coverage.rs:13:13 - 13:18; // scope 0 at /the/src/instrument_coverage.rs:16:2: 16:2
|
||||
+ Coverage::Expression(4294967294) = 2 + 0 for /the/src/instrument_coverage.rs:16:1 - 16:2; // scope 0 at /the/src/instrument_coverage.rs:16:2: 16:2
|
||||
return; // scope 0 at /the/src/instrument_coverage.rs:16:2: 16:2
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// Test that the initial version of Rust coverage injects Coverage statements at the top of each
|
||||
// function. The Coverage Counter statements are later converted into LLVM instrprof.increment
|
||||
// intrinsics, during codegen.
|
||||
// Test that `-Z instrument-coverage` injects Coverage statements. The Coverage Counter statements
|
||||
// are later converted into LLVM instrprof.increment intrinsics, during codegen.
|
||||
|
||||
// needs-profiler-support
|
||||
// ignore-windows
|
||||
// compile-flags: -Zinstrument-coverage --remap-path-prefix={{src-base}}=/the/src
|
||||
// compile-flags: -Z instrument-coverage --remap-path-prefix={{src-base}}=/the/src
|
||||
|
||||
// EMIT_MIR instrument_coverage.main.InstrumentCoverage.diff
|
||||
// EMIT_MIR instrument_coverage.bar.InstrumentCoverage.diff
|
||||
fn main() {
|
||||
|
|
|
@ -34,7 +34,7 @@ CHECK-SAME: section "llvm.metadata"
|
|||
|
||||
CHECK: [[DEFINE_INTERNAL]] { {{.*}} } @_R{{[a-zA-Z0-9_]+}}testprog14will_be_called() unnamed_addr #{{[0-9]+}} {
|
||||
CHECK-NEXT: start:
|
||||
CHECK-NOT: bb{{[0-9]+}}:
|
||||
CHECK-NOT: [[DEFINE_INTERNAL]]
|
||||
CHECK: %pgocount = load i64, i64* getelementptr inbounds
|
||||
CHECK-SAME: * @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called,
|
||||
|
||||
|
|
|
@ -13,6 +13,25 @@
|
|||
BASEDIR=../coverage-reports-base
|
||||
SOURCEDIR=../coverage
|
||||
|
||||
ifeq ($(UNAME),Darwin)
|
||||
# FIXME(richkadel): It appears that --debug is not available on MacOS even when not running
|
||||
# under CI.
|
||||
NO_LLVM_ASSERTIONS=1
|
||||
endif
|
||||
|
||||
# The `llvm-cov show` flag `--debug`, used to generate the `counters` output files, is only enabled
|
||||
# if LLVM assertions are enabled. Some CI builds disable debug assertions.
|
||||
ifndef NO_LLVM_ASSERTIONS
|
||||
DEBUG_FLAG=--debug
|
||||
endif
|
||||
|
||||
# When generating `expected_*` results (using `x.py test --bless`), the `--debug` flag is forced.
|
||||
# If assertions are disabled, the command will fail with an error, rather than attempt to generate
|
||||
# only partial results.
|
||||
ifdef RUSTC_BLESS_TEST
|
||||
DEBUG_FLAG=--debug
|
||||
endif
|
||||
|
||||
all: $(patsubst $(SOURCEDIR)/%.rs,%,$(wildcard $(SOURCEDIR)/*.rs))
|
||||
|
||||
# Ensure there are no `expected` results for tests that may have been removed or renamed
|
||||
|
@ -21,6 +40,7 @@ clear_expected_if_blessed:
|
|||
ifdef RUSTC_BLESS_TEST
|
||||
rm -f expected_export_coverage.*.json
|
||||
rm -f expected_show_coverage.*.txt
|
||||
rm -f expected_show_coverage_counters.*.txt
|
||||
endif
|
||||
|
||||
-include clear_expected_if_blessed
|
||||
|
@ -49,19 +69,33 @@ endif
|
|||
"$(TMPDIR)"/$@.profraw \
|
||||
-o "$(TMPDIR)"/$@.profdata
|
||||
|
||||
# Generate a coverage report using `llvm-cov show`. The output ordering
|
||||
# can be non-deterministic, so ignore the return status. If the test fails
|
||||
# when comparing the JSON `export`, the `show` output may be useful when
|
||||
# debugging.
|
||||
# Generate a coverage report using `llvm-cov show`.
|
||||
"$(LLVM_BIN_DIR)"/llvm-cov show \
|
||||
$(DEBUG_FLAG) \
|
||||
--Xdemangler="$(RUST_DEMANGLER)" \
|
||||
--show-line-counts-or-regions \
|
||||
--instr-profile="$(TMPDIR)"/$@.profdata \
|
||||
$(call BIN,"$(TMPDIR)"/$@) \
|
||||
> "$(TMPDIR)"/actual_show_coverage.$@.txt
|
||||
> "$(TMPDIR)"/actual_show_coverage.$@.txt \
|
||||
2> "$(TMPDIR)"/show_coverage_stderr.$@.txt || \
|
||||
( status=$$? ; \
|
||||
>&2 cat "$(TMPDIR)"/show_coverage_stderr.$@.txt ; \
|
||||
exit $$status \
|
||||
)
|
||||
|
||||
ifdef DEBUG_FLAG
|
||||
# The first line (beginning with "Args:" contains hard-coded, build-specific
|
||||
# file paths. Strip that line and keep the remaining lines with counter debug
|
||||
# data.
|
||||
tail -n +2 "$(TMPDIR)"/show_coverage_stderr.$@.txt \
|
||||
> "$(TMPDIR)"/actual_show_coverage_counters.$@.txt
|
||||
endif
|
||||
|
||||
ifdef RUSTC_BLESS_TEST
|
||||
cp "$(TMPDIR)"/actual_show_coverage.$@.txt expected_show_coverage.$@.txt
|
||||
cp "$(TMPDIR)"/actual_show_coverage.$@.txt \
|
||||
expected_show_coverage.$@.txt
|
||||
cp "$(TMPDIR)"/actual_show_coverage_counters.$@.txt \
|
||||
expected_show_coverage_counters.$@.txt
|
||||
else
|
||||
# Compare the show coverage output (`--bless` refreshes `typical` files)
|
||||
# Note `llvm-cov show` output for some programs can vary, but can be ignored
|
||||
|
@ -75,6 +109,21 @@ else
|
|||
false \
|
||||
)
|
||||
|
||||
ifdef DEBUG_FLAG
|
||||
$(DIFF) expected_show_coverage_counters.$@.txt "$(TMPDIR)"/actual_show_coverage_counters.$@.txt || \
|
||||
( grep -q '^\/\/ ignore-llvm-cov-show-diffs' $(SOURCEDIR)/$@.rs && \
|
||||
>&2 echo 'diff failed, but suppressed with `// ignore-llvm-cov-show-diffs` in $(SOURCEDIR)/$@.rs' \
|
||||
) || \
|
||||
( >&2 echo 'diff failed, and not suppressed without `// ignore-llvm-cov-show-diffs` in $(SOURCEDIR)/$@.rs'; \
|
||||
>&2 echo '(Ignore anyway until mangled function names in "counters" files are demangled.)' \
|
||||
)
|
||||
|
||||
# FIXME(richkadel): Apply the demangler to the `*_show_coverage_counters.*.txt` files,
|
||||
# so the crate disambiguator differences will be stripped away. At that point, these files
|
||||
# will be less likely to vary, and the last `echo` above (starting with "Ignore anyway")
|
||||
# can be replaced with `false` to fail the test.
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
# Generate a coverage report in JSON, using `llvm-cov export`, and fail if
|
||||
|
|
|
@ -17,14 +17,14 @@
|
|||
},
|
||||
"lines": {
|
||||
"count": 91,
|
||||
"covered": 75,
|
||||
"percent": 82.41758241758241
|
||||
"covered": 77,
|
||||
"percent": 84.61538461538461
|
||||
},
|
||||
"regions": {
|
||||
"count": 21,
|
||||
"covered": 11,
|
||||
"notcovered": 10,
|
||||
"percent": 52.38095238095239
|
||||
"count": 25,
|
||||
"covered": 13,
|
||||
"notcovered": 12,
|
||||
"percent": 52
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,14 +42,14 @@
|
|||
},
|
||||
"lines": {
|
||||
"count": 91,
|
||||
"covered": 75,
|
||||
"percent": 82.41758241758241
|
||||
"covered": 77,
|
||||
"percent": 84.61538461538461
|
||||
},
|
||||
"regions": {
|
||||
"count": 21,
|
||||
"covered": 11,
|
||||
"notcovered": 10,
|
||||
"percent": 52.38095238095239
|
||||
"count": 25,
|
||||
"covered": 13,
|
||||
"notcovered": 12,
|
||||
"percent": 52
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/various_conditions.rs",
|
||||
"filename": "../coverage/conditions.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
|
@ -21,10 +21,10 @@
|
|||
"percent": 46.93877551020408
|
||||
},
|
||||
"regions": {
|
||||
"count": 51,
|
||||
"covered": 19,
|
||||
"notcovered": 32,
|
||||
"percent": 37.254901960784316
|
||||
"count": 69,
|
||||
"covered": 18,
|
||||
"notcovered": 51,
|
||||
"percent": 26.08695652173913
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,10 +46,10 @@
|
|||
"percent": 46.93877551020408
|
||||
},
|
||||
"regions": {
|
||||
"count": 51,
|
||||
"covered": 19,
|
||||
"notcovered": 32,
|
||||
"percent": 37.254901960784316
|
||||
"count": 69,
|
||||
"covered": 18,
|
||||
"notcovered": 51,
|
||||
"percent": 26.08695652173913
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,10 +21,10 @@
|
|||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 4,
|
||||
"count": 5,
|
||||
"covered": 4,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"notcovered": 1,
|
||||
"percent": 80
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,10 +46,10 @@
|
|||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 4,
|
||||
"count": 5,
|
||||
"covered": 4,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"notcovered": 1,
|
||||
"percent": 80
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@
|
|||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 13,
|
||||
"count": 15,
|
||||
"covered": 13,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"notcovered": 2,
|
||||
"percent": 86.66666666666667
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,10 +46,10 @@
|
|||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 13,
|
||||
"count": 15,
|
||||
"covered": 13,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"notcovered": 2,
|
||||
"percent": 86.66666666666667
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 21,
|
||||
"covered": 19,
|
||||
"percent": 90.47619047619048
|
||||
"count": 40,
|
||||
"covered": 30,
|
||||
"percent": 75
|
||||
},
|
||||
"regions": {
|
||||
"count": 16,
|
||||
"covered": 14,
|
||||
"notcovered": 2,
|
||||
"percent": 87.5
|
||||
"count": 37,
|
||||
"covered": 26,
|
||||
"notcovered": 11,
|
||||
"percent": 70.27027027027027
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,15 +41,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 21,
|
||||
"covered": 19,
|
||||
"percent": 90.47619047619048
|
||||
"count": 40,
|
||||
"covered": 30,
|
||||
"percent": 75
|
||||
},
|
||||
"regions": {
|
||||
"count": 16,
|
||||
"covered": 14,
|
||||
"notcovered": 2,
|
||||
"percent": 87.5
|
||||
"count": 37,
|
||||
"covered": 26,
|
||||
"notcovered": 11,
|
||||
"percent": 70.27027027027027
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/loops_branches.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 11,
|
||||
"covered": 11,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
||||
"covered": 8,
|
||||
"notcovered": 1,
|
||||
"percent": 88.88888888888889
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"functions": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 11,
|
||||
"covered": 11,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
||||
"covered": 8,
|
||||
"notcovered": 1,
|
||||
"percent": 88.88888888888889
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "llvm.coverage.json.export",
|
||||
"version": "2.0.1"
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/nested_loops.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 21,
|
||||
"covered": 16,
|
||||
"percent": 76.19047619047619
|
||||
},
|
||||
"regions": {
|
||||
"count": 18,
|
||||
"covered": 14,
|
||||
"notcovered": 4,
|
||||
"percent": 77.77777777777779
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 21,
|
||||
"covered": 16,
|
||||
"percent": 76.19047619047619
|
||||
},
|
||||
"regions": {
|
||||
"count": 18,
|
||||
"covered": 14,
|
||||
"notcovered": 4,
|
||||
"percent": 77.77777777777779
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "llvm.coverage.json.export",
|
||||
"version": "2.0.1"
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/partial_eq.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 5,
|
||||
"covered": 5,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 5,
|
||||
"covered": 5,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 15,
|
||||
"covered": 15,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 6,
|
||||
"covered": 6,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"functions": {
|
||||
"count": 5,
|
||||
"covered": 5,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 5,
|
||||
"covered": 5,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 15,
|
||||
"covered": 15,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 6,
|
||||
"covered": 6,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "llvm.coverage.json.export",
|
||||
"version": "2.0.1"
|
||||
}
|
|
@ -16,15 +16,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 18,
|
||||
"covered": 18,
|
||||
"count": 19,
|
||||
"covered": 19,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 7,
|
||||
"covered": 7,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"count": 9,
|
||||
"covered": 8,
|
||||
"notcovered": 1,
|
||||
"percent": 88.88888888888889
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,15 +41,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 18,
|
||||
"covered": 18,
|
||||
"count": 19,
|
||||
"covered": 19,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 7,
|
||||
"covered": 7,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"count": 9,
|
||||
"covered": 8,
|
||||
"notcovered": 1,
|
||||
"percent": 88.88888888888889
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 26,
|
||||
"covered": 26,
|
||||
"count": 24,
|
||||
"covered": 24,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
||||
"covered": 9,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"count": 15,
|
||||
"covered": 14,
|
||||
"notcovered": 1,
|
||||
"percent": 93.33333333333333
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,15 +41,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 26,
|
||||
"covered": 26,
|
||||
"count": 24,
|
||||
"covered": 24,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
||||
"covered": 9,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"count": 15,
|
||||
"covered": 14,
|
||||
"notcovered": 1,
|
||||
"percent": 93.33333333333333
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/tight_inf_loop.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "llvm.coverage.json.export",
|
||||
"version": "2.0.1"
|
||||
}
|
|
@ -16,15 +16,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 16,
|
||||
"covered": 15,
|
||||
"percent": 93.75
|
||||
"count": 20,
|
||||
"covered": 18,
|
||||
"percent": 90
|
||||
},
|
||||
"regions": {
|
||||
"count": 13,
|
||||
"covered": 12,
|
||||
"notcovered": 1,
|
||||
"percent": 92.3076923076923
|
||||
"count": 19,
|
||||
"covered": 14,
|
||||
"notcovered": 5,
|
||||
"percent": 73.68421052631578
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,15 +41,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 16,
|
||||
"covered": 15,
|
||||
"percent": 93.75
|
||||
"count": 20,
|
||||
"covered": 18,
|
||||
"percent": 90
|
||||
},
|
||||
"regions": {
|
||||
"count": 13,
|
||||
"covered": 12,
|
||||
"notcovered": 1,
|
||||
"percent": 92.3076923076923
|
||||
"count": 19,
|
||||
"covered": 14,
|
||||
"notcovered": 5,
|
||||
"percent": 73.68421052631578
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/while.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 4,
|
||||
"covered": 3,
|
||||
"percent": 75
|
||||
},
|
||||
"regions": {
|
||||
"count": 4,
|
||||
"covered": 3,
|
||||
"notcovered": 1,
|
||||
"percent": 75
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 4,
|
||||
"covered": 3,
|
||||
"percent": 75
|
||||
},
|
||||
"regions": {
|
||||
"count": 4,
|
||||
"covered": 3,
|
||||
"notcovered": 1,
|
||||
"percent": 75
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "llvm.coverage.json.export",
|
||||
"version": "2.0.1"
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/while_early_return.rs",
|
||||
"filename": "../coverage/while_early_ret.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
|
@ -16,9 +16,9 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 18,
|
||||
"covered": 16,
|
||||
"percent": 88.88888888888889
|
||||
"count": 17,
|
||||
"covered": 15,
|
||||
"percent": 88.23529411764706
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
||||
|
@ -41,9 +41,9 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 18,
|
||||
"covered": 16,
|
||||
"percent": 88.88888888888889
|
||||
"count": 17,
|
||||
"covered": 15,
|
||||
"percent": 88.23529411764706
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
|
@ -62,7 +62,7 @@
|
|||
62| 1| let mut countdown = 0;
|
||||
63| 1| if is_false {
|
||||
64| 0| countdown = 10;
|
||||
65| 0| }
|
||||
65| 1| }
|
||||
66| 1| "alt string 3".to_owned()
|
||||
67| 1| }
|
||||
68| 1| )
|
||||
|
@ -77,7 +77,7 @@
|
|||
77| 1| let mut countdown = 0;
|
||||
78| 1| if is_false {
|
||||
79| 0| countdown = 10;
|
||||
80| 0| }
|
||||
80| 1| }
|
||||
81| 1| "alt string 4".to_owned()
|
||||
82| 1| };
|
||||
83| 1| println!(
|
||||
|
|
|
@ -25,5 +25,6 @@
|
|||
25| 1| 10
|
||||
26| 1| ;
|
||||
27| 1| }
|
||||
^0
|
||||
28| 1|}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
10| 1| if is_true {
|
||||
11| 1| countdown = 10;
|
||||
12| 1| }
|
||||
^0
|
||||
13| |
|
||||
14| | mod in_mod {
|
||||
15| | const IN_MOD_CONST: u32 = 1000;
|
||||
|
@ -48,6 +49,7 @@
|
|||
48| 1| if is_true {
|
||||
49| 1| in_func(countdown);
|
||||
50| 1| }
|
||||
^0
|
||||
51| |
|
||||
52| 1| let mut val = InStruct {
|
||||
53| 1| in_struct_field: 101,
|
||||
|
|
|
@ -12,12 +12,14 @@
|
|||
12| 1| b = 10;
|
||||
13| 1| c = 100;
|
||||
14| 1| }
|
||||
^0
|
||||
15| | let
|
||||
16| 1| somebool
|
||||
17| | =
|
||||
18| 1| a < b
|
||||
19| | ||
|
||||
20| 0| b < c
|
||||
20| 1| b < c
|
||||
^0
|
||||
21| | ;
|
||||
22| | let
|
||||
23| 1| somebool
|
||||
|
@ -26,19 +28,38 @@
|
|||
26| | ||
|
||||
27| 1| b < c
|
||||
28| | ;
|
||||
29| | let
|
||||
30| 1| somebool
|
||||
31| | =
|
||||
32| 1| a < b
|
||||
33| | &&
|
||||
34| 1| b < c
|
||||
35| | ;
|
||||
36| | let
|
||||
37| 1| somebool
|
||||
38| | =
|
||||
39| 1| b < a
|
||||
40| | &&
|
||||
41| 0| b < c
|
||||
42| | ;
|
||||
43| 1|}
|
||||
29| 1| let somebool = a < b && b < c;
|
||||
30| 1| let somebool = b < a && b < c;
|
||||
^0
|
||||
31| |
|
||||
32| | if
|
||||
33| 1| !
|
||||
34| 1| is_true
|
||||
35| 0| {
|
||||
36| 0| a = 2
|
||||
37| 0| ;
|
||||
38| 1| }
|
||||
39| |
|
||||
40| | if
|
||||
41| 1| is_true
|
||||
42| 1| {
|
||||
43| 1| b = 30
|
||||
44| 1| ;
|
||||
45| 1| }
|
||||
46| | else
|
||||
47| 0| {
|
||||
48| 0| c = 400
|
||||
49| 0| ;
|
||||
50| 0| }
|
||||
51| |
|
||||
52| 1| if !is_true {
|
||||
53| 0| a = 2;
|
||||
54| 1| }
|
||||
55| |
|
||||
56| 1| if is_true {
|
||||
57| 1| b = 30;
|
||||
58| 1| } else {
|
||||
59| 0| c = 400;
|
||||
60| 0| }
|
||||
61| 1|}
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
1| |#![allow(unused_assignments)]
|
||||
2| |
|
||||
3| |// This test confirms an earlier problem was resolved, supporting the MIR graph generated by the
|
||||
4| |// structure of this `fmt` function.
|
||||
5| |
|
||||
6| |struct DebugTest;
|
||||
7| |
|
||||
8| |impl std::fmt::Debug for DebugTest {
|
||||
9| | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
10| 1| if true {
|
||||
11| 1| if false {
|
||||
12| | while true {
|
||||
13| | }
|
||||
14| 1| }
|
||||
15| 1| write!(f, "error")?;
|
||||
^0
|
||||
16| | } else {
|
||||
17| 1| }
|
||||
18| 1| Ok(())
|
||||
19| 1| }
|
||||
20| |}
|
||||
21| |
|
||||
22| 1|fn main() {
|
||||
23| 1| let debug_test = DebugTest;
|
||||
24| 1| println!("{:?}", debug_test);
|
||||
25| 1|}
|
||||
26| |
|
||||
27| |/*
|
||||
28| |
|
||||
29| |This is the error message generated, before the issue was fixed:
|
||||
30| |
|
||||
31| |error: internal compiler error: compiler/rustc_mir/src/transform/coverage/mod.rs:374:42:
|
||||
32| |Error processing: DefId(0:6 ~ bug_incomplete_cov_graph_traversal_simplified[317d]::{impl#0}::fmt):
|
||||
33| |Error { message: "`TraverseCoverageGraphWithLoops` missed some `BasicCoverageBlock`s:
|
||||
34| |[bcb6, bcb7, bcb9]" }
|
||||
35| |
|
||||
36| |*/
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
1| |fn main() {
|
||||
2| 1| let is_true = std::env::args().len() == 1;
|
||||
3| 1| let mut countdown = 10;
|
||||
4| |
|
||||
5| 1| 'outer: while countdown > 0 {
|
||||
6| 1| let mut a = 100;
|
||||
7| 1| let mut b = 100;
|
||||
8| 3| for _ in 0..50 {
|
||||
9| 3| if a < 30 {
|
||||
10| 0| break;
|
||||
11| | }
|
||||
12| 3| a -= 5;
|
||||
13| 3| b -= 5;
|
||||
14| 3| if b < 90 {
|
||||
15| 1| a -= 10;
|
||||
16| 1| if is_true {
|
||||
17| 1| break 'outer;
|
||||
18| | } else {
|
||||
19| 0| a -= 2;
|
||||
20| 0| }
|
||||
21| 2| }
|
||||
22| 2| }
|
||||
23| 0| countdown -= 1;
|
||||
24| 0| }
|
||||
25| 1|}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
1| |// This test confirms an earlier problem was resolved, supporting the MIR graph generated by the
|
||||
2| |// structure of this test.
|
||||
3| |
|
||||
4| 2|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
^1 ^1
|
||||
5| |pub struct Version {
|
||||
6| | major: usize,
|
||||
7| 1| minor: usize,
|
||||
8| | patch: usize,
|
||||
9| |}
|
||||
10| |
|
||||
11| |impl Version {
|
||||
12| | pub fn new(major: usize, minor: usize, patch: usize) -> Self {
|
||||
13| 2| Self {
|
||||
14| 2| major,
|
||||
15| 2| minor,
|
||||
16| 2| patch,
|
||||
17| 2| }
|
||||
18| 2| }
|
||||
19| |}
|
||||
20| |
|
||||
21| 1|fn main() {
|
||||
22| 1| let version_3_2_1 = Version::new(3, 2, 1);
|
||||
23| 1| let version_3_3_0 = Version::new(3, 3, 0);
|
||||
24| 1|
|
||||
25| 1| println!("{:?} < {:?} = {}", version_3_2_1, version_3_3_0, version_3_2_1 < version_3_3_0);
|
||||
26| 1|}
|
||||
27| |
|
||||
28| |/*
|
||||
29| |
|
||||
30| |This test verifies a bug was fixed that otherwise generated this error:
|
||||
31| |
|
||||
32| |thread 'rustc' panicked at 'No counters provided the source_hash for function:
|
||||
33| | Instance {
|
||||
34| | def: Item(WithOptConstParam {
|
||||
35| | did: DefId(0:101 ~ autocfg[c44a]::version::{impl#2}::partial_cmp),
|
||||
36| | const_param_did: None
|
||||
37| | }),
|
||||
38| | substs: []
|
||||
39| | }'
|
||||
40| |The `PartialOrd` derived by `Version` happened to generate a MIR that generated coverage
|
||||
41| |without a code region associated with any `Counter`. Code regions were associated with at least
|
||||
42| |one expression, which is allowed, but the `function_source_hash` was only passed to the codegen
|
||||
43| |(coverage mapgen) phase from a `Counter`s code region. A new method was added to pass the
|
||||
44| |`function_source_hash` without a code region, if necessary.
|
||||
45| |
|
||||
46| |*/
|
||||
47| |
|
||||
48| |// FIXME(richkadel): It may be worth investigating why the coverage report for this test produces
|
||||
49| |// the following results:
|
||||
50| |
|
||||
51| |/*
|
||||
52| |
|
||||
53| |1. Why are their two counts below different characters (first and last) of `PartialOrd`, on line 17?
|
||||
54| |
|
||||
55| |2. Line 17 is counted twice, but the `::lt` instance shows a line count of 1? Is there a missing
|
||||
56| | line count with a different instance? Or was it really only called once?
|
||||
57| |
|
||||
58| |3. Line 20 shows another line count (of 1) for a line within a `struct` declaration (on only one of
|
||||
59| | its 3 fields). I doubt the specific field (`minor`) is relevant, but rather I suspect there's a
|
||||
60| | problem computing the file position here, for some reason.
|
||||
61| |
|
||||
62| |<snip>
|
||||
63| | 16| |
|
||||
64| | 17| 2|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
65| | ^1 ^1
|
||||
66| |------------------
|
||||
67| ||Unexecuted instantiation: <partial_eq_counter_without_region::Version as core::cmp::PartialOrd>::gt
|
||||
68| |------------------
|
||||
69| ||Unexecuted instantiation: <partial_eq_counter_without_region::Version as core::cmp::PartialOrd>::le
|
||||
70| |------------------
|
||||
71| ||Unexecuted instantiation: <partial_eq_counter_without_region::Version as core::cmp::PartialOrd>::ge
|
||||
72| |------------------
|
||||
73| ||<partial_eq_counter_without_region::Version as core::cmp::PartialOrd>::lt:
|
||||
74| || 17| 1|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
75| |------------------
|
||||
76| | 18| |pub struct Version {
|
||||
77| | 19| | major: usize,
|
||||
78| | 20| 1| minor: usize,
|
||||
79| | 21| | patch: usize,
|
||||
80| | 22| |}
|
||||
81| | 23| |
|
||||
82| | 24| |impl Version {
|
||||
83| | 25| | pub fn new(major: usize, minor: usize, patch: usize) -> Self {
|
||||
84| | 26| 2| Version {
|
||||
85| | 27| 2| major,
|
||||
86| | 28| 2| minor,
|
||||
87| | 29| 2| patch,
|
||||
88| | 30| 2| }
|
||||
89| | 31| 2| }
|
||||
90| | 32| |}
|
||||
91| | 33| |
|
||||
92| | 34| 1|fn main() {
|
||||
93| | 35| 1| let version_3_2_1 = Version::new(3, 2, 1);
|
||||
94| | 36| 1| let version_3_3_0 = Version::new(3, 3, 0);
|
||||
95| | 37| 1|
|
||||
96| | 38| 1| println!("{:?} < {:?} = {}", version_3_2_1, version_3_3_0, version_3_2_1 < version
|
||||
97| |_3_3_0);
|
||||
98| | 39| 1|}
|
||||
99| |*/
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
16| 1| 10
|
||||
17| 1| ;
|
||||
18| 1| }
|
||||
^0
|
||||
19| |
|
||||
20| | loop
|
||||
21| | {
|
||||
|
@ -31,6 +32,6 @@
|
|||
31| 10| -=
|
||||
32| 10| 1
|
||||
33| | ;
|
||||
34| | }
|
||||
34| 1| }
|
||||
35| 1|}
|
||||
|
||||
|
|
|
@ -10,22 +10,24 @@
|
|||
10| 1| if is_true {
|
||||
11| 1| countdown = 0;
|
||||
12| 1| }
|
||||
^0
|
||||
13| |
|
||||
14| 3| for
|
||||
15| 3| _
|
||||
14| | for
|
||||
15| 2| _
|
||||
16| | in
|
||||
17| 1| 0..2
|
||||
17| 3| 0..2
|
||||
18| | {
|
||||
19| | let z
|
||||
20| | ;
|
||||
21| | match
|
||||
22| 2| countdown
|
||||
23| 2| {
|
||||
24| 2| x
|
||||
25| 2| if
|
||||
23| | {
|
||||
24| 1| x
|
||||
25| | if
|
||||
26| 2| x
|
||||
27| 2| <
|
||||
28| 2| 1
|
||||
^1
|
||||
29| | =>
|
||||
30| 1| {
|
||||
31| 1| z = countdown
|
||||
|
@ -39,6 +41,6 @@
|
|||
39| | =>
|
||||
40| 1| {}
|
||||
41| | }
|
||||
42| | }
|
||||
42| 3| }
|
||||
43| 1|}
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
1| |fn main() {
|
||||
2| 1| if false {
|
||||
3| | loop {}
|
||||
4| | }
|
||||
5| 1|}
|
||||
|
|
@ -13,24 +13,26 @@
|
|||
13| 1| let mut
|
||||
14| 1| countdown = 10
|
||||
15| | ;
|
||||
16| 6| for
|
||||
16| | for
|
||||
17| 6| _
|
||||
18| | in
|
||||
19| 1| 0..10
|
||||
19| 6| 0..10
|
||||
20| | {
|
||||
21| 6| countdown
|
||||
22| 6| -= 1
|
||||
23| | ;
|
||||
24| | if
|
||||
23| 6| ;
|
||||
24| 6| if
|
||||
25| 6| countdown < 5
|
||||
26| | {
|
||||
27| 1| call(/*return_error=*/ true)?;
|
||||
28| | }
|
||||
29| | else
|
||||
30| | {
|
||||
31| 5| call(/*return_error=*/ false)?;
|
||||
32| | }
|
||||
33| | }
|
||||
34| 0| Ok(())
|
||||
35| 1|}
|
||||
28| 0| call(/*return_error=*/ false)?;
|
||||
29| | }
|
||||
30| | else
|
||||
31| | {
|
||||
32| 5| call(/*return_error=*/ false)?;
|
||||
^0
|
||||
33| 5| }
|
||||
34| 5| }
|
||||
35| 0| Ok(())
|
||||
36| 1|}
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
1| |fn main() {
|
||||
2| 1| let num = 9;
|
||||
3| 1| while num >= 10 {
|
||||
4| 0| }
|
||||
5| 1|}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
3| |
|
||||
4| |fn main() -> Result<(),u8> {
|
||||
5| 1| let mut countdown = 10;
|
||||
6| 7| while
|
||||
6| | while
|
||||
7| 7| countdown
|
||||
8| 7| >
|
||||
9| 7| 0
|
|
@ -0,0 +1,94 @@
|
|||
Counter in file 0 20:21 -> 20:38, #1
|
||||
Counter in file 0 21:20 -> 21:28, (#1 + 0)
|
||||
Counter in file 0 21:29 -> 23:18, #2
|
||||
Counter in file 0 23:18 -> 23:19, (#1 - #2)
|
||||
Counter in file 0 24:17 -> 25:14, (#2 + (#1 - #2))
|
||||
Counter in file 0 3:11 -> 18:13, #1
|
||||
Counter in file 0 25:14 -> 33:9, (#1 + 0)
|
||||
Counter in file 0 40:6 -> 60:13, (#1 + 0)
|
||||
Counter in file 0 67:14 -> 75:9, (#1 + 0)
|
||||
Counter in file 0 82:6 -> 93:2, (#1 + 0)
|
||||
Counter in file 0 77:13 -> 77:30, #1
|
||||
Counter in file 0 78:12 -> 78:20, (#1 + 0)
|
||||
Counter in file 0 78:21 -> 80:10, #2
|
||||
Counter in file 0 80:10 -> 80:11, (#1 - #2)
|
||||
Counter in file 0 81:9 -> 82:6, (#2 + (#1 - #2))
|
||||
Counter in file 0 62:21 -> 62:38, #1
|
||||
Counter in file 0 63:20 -> 63:28, (#1 + 0)
|
||||
Counter in file 0 63:29 -> 65:18, #2
|
||||
Counter in file 0 65:18 -> 65:19, (#1 - #2)
|
||||
Counter in file 0 66:17 -> 67:14, (#2 + (#1 - #2))
|
||||
Counter in file 0 35:13 -> 35:30, #1
|
||||
Counter in file 0 36:12 -> 36:20, (#1 + 0)
|
||||
Counter in file 0 36:21 -> 38:10, #2
|
||||
Counter in file 0 38:10 -> 38:11, (#1 - #2)
|
||||
Counter in file 0 39:9 -> 40:6, (#2 + (#1 - #2))
|
||||
Emitting segments for file: ../coverage/closure.rs
|
||||
Combined regions:
|
||||
3:11 -> 18:13 (count=1)
|
||||
20:21 -> 20:38 (count=0)
|
||||
21:20 -> 21:28 (count=0)
|
||||
21:29 -> 23:18 (count=0)
|
||||
23:18 -> 23:19 (count=0)
|
||||
24:17 -> 25:14 (count=0)
|
||||
25:14 -> 33:9 (count=1)
|
||||
35:13 -> 35:30 (count=0)
|
||||
36:12 -> 36:20 (count=0)
|
||||
36:21 -> 38:10 (count=0)
|
||||
38:10 -> 38:11 (count=0)
|
||||
39:9 -> 40:6 (count=0)
|
||||
40:6 -> 60:13 (count=1)
|
||||
62:21 -> 62:38 (count=1)
|
||||
63:20 -> 63:28 (count=1)
|
||||
63:29 -> 65:18 (count=0)
|
||||
65:18 -> 65:19 (count=1)
|
||||
66:17 -> 67:14 (count=1)
|
||||
67:14 -> 75:9 (count=1)
|
||||
77:13 -> 77:30 (count=1)
|
||||
78:12 -> 78:20 (count=1)
|
||||
78:21 -> 80:10 (count=0)
|
||||
80:10 -> 80:11 (count=1)
|
||||
81:9 -> 82:6 (count=1)
|
||||
82:6 -> 93:2 (count=1)
|
||||
Segment at 3:11 (count = 1), RegionEntry
|
||||
Segment at 18:13 (count = 0), Skipped
|
||||
Segment at 20:21 (count = 0), RegionEntry
|
||||
Segment at 20:38 (count = 0), Skipped
|
||||
Segment at 21:20 (count = 0), RegionEntry
|
||||
Segment at 21:28 (count = 0), Skipped
|
||||
Segment at 21:29 (count = 0), RegionEntry
|
||||
Segment at 23:18 (count = 0), RegionEntry
|
||||
Segment at 23:19 (count = 0), Skipped
|
||||
Segment at 24:17 (count = 0), RegionEntry
|
||||
Segment at 25:14 (count = 1), RegionEntry
|
||||
Segment at 33:9 (count = 0), Skipped
|
||||
Segment at 35:13 (count = 0), RegionEntry
|
||||
Segment at 35:30 (count = 0), Skipped
|
||||
Segment at 36:12 (count = 0), RegionEntry
|
||||
Segment at 36:20 (count = 0), Skipped
|
||||
Segment at 36:21 (count = 0), RegionEntry
|
||||
Segment at 38:10 (count = 0), RegionEntry
|
||||
Segment at 38:11 (count = 0), Skipped
|
||||
Segment at 39:9 (count = 0), RegionEntry
|
||||
Segment at 40:6 (count = 1), RegionEntry
|
||||
Segment at 60:13 (count = 0), Skipped
|
||||
Segment at 62:21 (count = 1), RegionEntry
|
||||
Segment at 62:38 (count = 0), Skipped
|
||||
Segment at 63:20 (count = 1), RegionEntry
|
||||
Segment at 63:28 (count = 0), Skipped
|
||||
Segment at 63:29 (count = 0), RegionEntry
|
||||
Segment at 65:18 (count = 1), RegionEntry
|
||||
Segment at 65:19 (count = 0), Skipped
|
||||
Segment at 66:17 (count = 1), RegionEntry
|
||||
Segment at 67:14 (count = 1), RegionEntry
|
||||
Segment at 75:9 (count = 0), Skipped
|
||||
Segment at 77:13 (count = 1), RegionEntry
|
||||
Segment at 77:30 (count = 0), Skipped
|
||||
Segment at 78:12 (count = 1), RegionEntry
|
||||
Segment at 78:20 (count = 0), Skipped
|
||||
Segment at 78:21 (count = 0), RegionEntry
|
||||
Segment at 80:10 (count = 1), RegionEntry
|
||||
Segment at 80:11 (count = 0), Skipped
|
||||
Segment at 81:9 (count = 1), RegionEntry
|
||||
Segment at 82:6 (count = 1), RegionEntry
|
||||
Segment at 93:2 (count = 0), Skipped
|
|
@ -0,0 +1,238 @@
|
|||
Counter in file 0 4:9 -> 4:26, #1
|
||||
Counter in file 0 5:8 -> 5:12, (#1 + 0)
|
||||
Counter in file 0 5:13 -> 7:6, #2
|
||||
Counter in file 0 10:9 -> 10:10, (#4 + #11)
|
||||
Counter in file 0 10:16 -> 10:29, (#2 + 0)
|
||||
Counter in file 0 11:9 -> 12:10, #4
|
||||
Counter in file 0 13:15 -> 13:28, ((#2 + 0) - #3)
|
||||
Counter in file 0 14:12 -> 14:25, #5
|
||||
Counter in file 0 14:29 -> 14:42, (#5 - #13)
|
||||
Counter in file 0 14:42 -> 14:43, (#13 + #14)
|
||||
Counter in file 0 14:42 -> 14:43, ((#5 - #13) - #14)
|
||||
Counter in file 0 14:46 -> 14:60, #21
|
||||
Counter in file 0 14:60 -> 14:61, (#17 + #18)
|
||||
Counter in file 0 14:60 -> 14:61, (#21 - #18)
|
||||
Counter in file 0 14:61 -> 16:10, #22
|
||||
Counter in file 0 16:10 -> 16:11, #23
|
||||
Counter in file 0 17:9 -> 18:18, #11
|
||||
Counter in file 0 20:9 -> 20:15, (((#2 + 0) - #3) - #5)
|
||||
Counter in file 0 23:9 -> 23:26, ((#4 + #11) + 0)
|
||||
Counter in file 0 24:8 -> 24:12, ((#4 + #11) + 0)
|
||||
Counter in file 0 24:13 -> 26:6, #12
|
||||
Counter in file 0 28:8 -> 28:21, (#12 + 0)
|
||||
Counter in file 0 29:9 -> 29:23, #16
|
||||
Counter in file 0 30:15 -> 30:28, ((#12 + 0) - #15)
|
||||
Counter in file 0 31:12 -> 31:25, (((#12 + 0) - #15) - #8)
|
||||
Counter in file 0 31:29 -> 31:42, ((((#12 + 0) - #15) - #8) - #24)
|
||||
Counter in file 0 31:42 -> 31:43, (((((#12 + 0) - #15) - #8) - #24) - #25)
|
||||
Counter in file 0 31:42 -> 31:43, (#24 + #25)
|
||||
Counter in file 0 31:46 -> 31:60, #32
|
||||
Counter in file 0 31:60 -> 31:61, (#28 + #29)
|
||||
Counter in file 0 31:60 -> 31:61, (#32 - #29)
|
||||
Counter in file 0 31:61 -> 33:10, #33
|
||||
Counter in file 0 33:10 -> 33:11, #34
|
||||
Counter in file 0 34:9 -> 34:23, #19
|
||||
Counter in file 0 36:9 -> 36:15, #8
|
||||
Counter in file 0 39:9 -> 39:26, (#16 + #19)
|
||||
Counter in file 0 40:8 -> 40:12, ((#16 + #19) + 0)
|
||||
Counter in file 0 40:13 -> 42:6, #20
|
||||
Counter in file 0 44:9 -> 44:10, (#27 + #30)
|
||||
Counter in file 0 44:16 -> 44:29, (#20 + 0)
|
||||
Counter in file 0 45:9 -> 45:23, #27
|
||||
Counter in file 0 46:15 -> 46:28, ((#20 + 0) - #26)
|
||||
Counter in file 0 47:12 -> 47:25, (((#20 + 0) - #26) - #7)
|
||||
Counter in file 0 47:29 -> 47:42, ((((#20 + 0) - #26) - #7) - #35)
|
||||
Counter in file 0 47:42 -> 47:43, (#35 + #36)
|
||||
Counter in file 0 47:42 -> 47:43, (((((#20 + 0) - #26) - #7) - #35) - #36)
|
||||
Counter in file 0 47:46 -> 47:60, #41
|
||||
Counter in file 0 47:60 -> 47:61, (#37 + #38)
|
||||
Counter in file 0 47:60 -> 47:61, (#41 - #38)
|
||||
Counter in file 0 47:61 -> 49:10, #42
|
||||
Counter in file 0 49:10 -> 49:11, #43
|
||||
Counter in file 0 50:9 -> 50:23, #30
|
||||
Counter in file 0 52:13 -> 54:15, #7
|
||||
Counter in file 0 57:9 -> 57:10, (#9 + #10)
|
||||
Counter in file 0 57:16 -> 57:29, ((#27 + #30) + 0)
|
||||
Counter in file 0 58:9 -> 58:23, #9
|
||||
Counter in file 0 59:15 -> 59:28, ((#27 + #30) - #31)
|
||||
Counter in file 0 60:12 -> 60:25, (((#27 + #30) - #31) - #6)
|
||||
Counter in file 0 60:29 -> 60:42, ((((#27 + #30) - #31) - #6) - #39)
|
||||
Counter in file 0 60:42 -> 60:43, (#39 + #40)
|
||||
Counter in file 0 60:42 -> 60:43, (((((#27 + #30) - #31) - #6) - #39) - #40)
|
||||
Counter in file 0 60:46 -> 60:60, #46
|
||||
Counter in file 0 60:60 -> 60:61, (#46 - #45)
|
||||
Counter in file 0 60:60 -> 60:61, (#44 + #45)
|
||||
Counter in file 0 60:61 -> 62:10, #47
|
||||
Counter in file 0 62:10 -> 62:11, #48
|
||||
Counter in file 0 63:9 -> 63:23, #10
|
||||
Counter in file 0 65:9 -> 65:15, #6
|
||||
Counter in file 0 67:1 -> 67:2, ((#9 + #10) + (((#6 + #7) + #8) + (((#2 + 0) - #3) - #5)))
|
||||
Emitting segments for file: ../coverage/conditions.rs
|
||||
Combined regions:
|
||||
4:9 -> 4:26 (count=1)
|
||||
5:8 -> 5:12 (count=1)
|
||||
5:13 -> 7:6 (count=1)
|
||||
10:9 -> 10:10 (count=1)
|
||||
10:16 -> 10:29 (count=1)
|
||||
11:9 -> 12:10 (count=1)
|
||||
13:15 -> 13:28 (count=0)
|
||||
14:12 -> 14:25 (count=0)
|
||||
14:29 -> 14:42 (count=0)
|
||||
14:42 -> 14:43 (count=0)
|
||||
14:46 -> 14:60 (count=0)
|
||||
14:60 -> 14:61 (count=0)
|
||||
14:61 -> 16:10 (count=0)
|
||||
16:10 -> 16:11 (count=0)
|
||||
17:9 -> 18:18 (count=0)
|
||||
20:9 -> 20:15 (count=0)
|
||||
23:9 -> 23:26 (count=1)
|
||||
24:8 -> 24:12 (count=1)
|
||||
24:13 -> 26:6 (count=1)
|
||||
28:8 -> 28:21 (count=1)
|
||||
29:9 -> 29:23 (count=1)
|
||||
30:15 -> 30:28 (count=0)
|
||||
31:12 -> 31:25 (count=0)
|
||||
31:29 -> 31:42 (count=0)
|
||||
31:42 -> 31:43 (count=0)
|
||||
31:46 -> 31:60 (count=0)
|
||||
31:60 -> 31:61 (count=0)
|
||||
31:61 -> 33:10 (count=0)
|
||||
33:10 -> 33:11 (count=0)
|
||||
34:9 -> 34:23 (count=0)
|
||||
36:9 -> 36:15 (count=0)
|
||||
39:9 -> 39:26 (count=1)
|
||||
40:8 -> 40:12 (count=1)
|
||||
40:13 -> 42:6 (count=1)
|
||||
44:9 -> 44:10 (count=0)
|
||||
44:16 -> 44:29 (count=1)
|
||||
45:9 -> 45:23 (count=0)
|
||||
46:15 -> 46:28 (count=1)
|
||||
47:12 -> 47:25 (count=0)
|
||||
47:29 -> 47:42 (count=0)
|
||||
47:42 -> 47:43 (count=0)
|
||||
47:46 -> 47:60 (count=0)
|
||||
47:60 -> 47:61 (count=0)
|
||||
47:61 -> 49:10 (count=0)
|
||||
49:10 -> 49:11 (count=0)
|
||||
50:9 -> 50:23 (count=0)
|
||||
52:13 -> 54:15 (count=1)
|
||||
57:9 -> 57:10 (count=0)
|
||||
57:16 -> 57:29 (count=0)
|
||||
58:9 -> 58:23 (count=0)
|
||||
59:15 -> 59:28 (count=0)
|
||||
60:12 -> 60:25 (count=0)
|
||||
60:29 -> 60:42 (count=0)
|
||||
60:42 -> 60:43 (count=0)
|
||||
60:46 -> 60:60 (count=0)
|
||||
60:60 -> 60:61 (count=0)
|
||||
60:61 -> 62:10 (count=0)
|
||||
62:10 -> 62:11 (count=0)
|
||||
63:9 -> 63:23 (count=0)
|
||||
65:9 -> 65:15 (count=0)
|
||||
67:1 -> 67:2 (count=1)
|
||||
Segment at 4:9 (count = 1), RegionEntry
|
||||
Segment at 4:26 (count = 0), Skipped
|
||||
Segment at 5:8 (count = 1), RegionEntry
|
||||
Segment at 5:12 (count = 0), Skipped
|
||||
Segment at 5:13 (count = 1), RegionEntry
|
||||
Segment at 7:6 (count = 0), Skipped
|
||||
Segment at 10:9 (count = 1), RegionEntry
|
||||
Segment at 10:10 (count = 0), Skipped
|
||||
Segment at 10:16 (count = 1), RegionEntry
|
||||
Segment at 10:29 (count = 0), Skipped
|
||||
Segment at 11:9 (count = 1), RegionEntry
|
||||
Segment at 12:10 (count = 0), Skipped
|
||||
Segment at 13:15 (count = 0), RegionEntry
|
||||
Segment at 13:28 (count = 0), Skipped
|
||||
Segment at 14:12 (count = 0), RegionEntry
|
||||
Segment at 14:25 (count = 0), Skipped
|
||||
Segment at 14:29 (count = 0), RegionEntry
|
||||
Segment at 14:42 (count = 0), RegionEntry
|
||||
Segment at 14:43 (count = 0), Skipped
|
||||
Segment at 14:46 (count = 0), RegionEntry
|
||||
Segment at 14:60 (count = 0), RegionEntry
|
||||
Segment at 14:61 (count = 0), RegionEntry
|
||||
Segment at 16:10 (count = 0), RegionEntry
|
||||
Segment at 16:11 (count = 0), Skipped
|
||||
Segment at 17:9 (count = 0), RegionEntry
|
||||
Segment at 18:18 (count = 0), Skipped
|
||||
Segment at 20:9 (count = 0), RegionEntry
|
||||
Segment at 20:15 (count = 0), Skipped
|
||||
Segment at 23:9 (count = 1), RegionEntry
|
||||
Segment at 23:26 (count = 0), Skipped
|
||||
Segment at 24:8 (count = 1), RegionEntry
|
||||
Segment at 24:12 (count = 0), Skipped
|
||||
Segment at 24:13 (count = 1), RegionEntry
|
||||
Segment at 26:6 (count = 0), Skipped
|
||||
Segment at 28:8 (count = 1), RegionEntry
|
||||
Segment at 28:21 (count = 0), Skipped
|
||||
Segment at 29:9 (count = 1), RegionEntry
|
||||
Segment at 29:23 (count = 0), Skipped
|
||||
Segment at 30:15 (count = 0), RegionEntry
|
||||
Segment at 30:28 (count = 0), Skipped
|
||||
Segment at 31:12 (count = 0), RegionEntry
|
||||
Segment at 31:25 (count = 0), Skipped
|
||||
Segment at 31:29 (count = 0), RegionEntry
|
||||
Segment at 31:42 (count = 0), RegionEntry
|
||||
Segment at 31:43 (count = 0), Skipped
|
||||
Segment at 31:46 (count = 0), RegionEntry
|
||||
Segment at 31:60 (count = 0), RegionEntry
|
||||
Segment at 31:61 (count = 0), RegionEntry
|
||||
Segment at 33:10 (count = 0), RegionEntry
|
||||
Segment at 33:11 (count = 0), Skipped
|
||||
Segment at 34:9 (count = 0), RegionEntry
|
||||
Segment at 34:23 (count = 0), Skipped
|
||||
Segment at 36:9 (count = 0), RegionEntry
|
||||
Segment at 36:15 (count = 0), Skipped
|
||||
Segment at 39:9 (count = 1), RegionEntry
|
||||
Segment at 39:26 (count = 0), Skipped
|
||||
Segment at 40:8 (count = 1), RegionEntry
|
||||
Segment at 40:12 (count = 0), Skipped
|
||||
Segment at 40:13 (count = 1), RegionEntry
|
||||
Segment at 42:6 (count = 0), Skipped
|
||||
Segment at 44:9 (count = 0), RegionEntry
|
||||
Segment at 44:10 (count = 0), Skipped
|
||||
Segment at 44:16 (count = 1), RegionEntry
|
||||
Segment at 44:29 (count = 0), Skipped
|
||||
Segment at 45:9 (count = 0), RegionEntry
|
||||
Segment at 45:23 (count = 0), Skipped
|
||||
Segment at 46:15 (count = 1), RegionEntry
|
||||
Segment at 46:28 (count = 0), Skipped
|
||||
Segment at 47:12 (count = 0), RegionEntry
|
||||
Segment at 47:25 (count = 0), Skipped
|
||||
Segment at 47:29 (count = 0), RegionEntry
|
||||
Segment at 47:42 (count = 0), RegionEntry
|
||||
Segment at 47:43 (count = 0), Skipped
|
||||
Segment at 47:46 (count = 0), RegionEntry
|
||||
Segment at 47:60 (count = 0), RegionEntry
|
||||
Segment at 47:61 (count = 0), RegionEntry
|
||||
Segment at 49:10 (count = 0), RegionEntry
|
||||
Segment at 49:11 (count = 0), Skipped
|
||||
Segment at 50:9 (count = 0), RegionEntry
|
||||
Segment at 50:23 (count = 0), Skipped
|
||||
Segment at 52:13 (count = 1), RegionEntry
|
||||
Segment at 54:15 (count = 0), Skipped
|
||||
Segment at 57:9 (count = 0), RegionEntry
|
||||
Segment at 57:10 (count = 0), Skipped
|
||||
Segment at 57:16 (count = 0), RegionEntry
|
||||
Segment at 57:29 (count = 0), Skipped
|
||||
Segment at 58:9 (count = 0), RegionEntry
|
||||
Segment at 58:23 (count = 0), Skipped
|
||||
Segment at 59:15 (count = 0), RegionEntry
|
||||
Segment at 59:28 (count = 0), Skipped
|
||||
Segment at 60:12 (count = 0), RegionEntry
|
||||
Segment at 60:25 (count = 0), Skipped
|
||||
Segment at 60:29 (count = 0), RegionEntry
|
||||
Segment at 60:42 (count = 0), RegionEntry
|
||||
Segment at 60:43 (count = 0), Skipped
|
||||
Segment at 60:46 (count = 0), RegionEntry
|
||||
Segment at 60:60 (count = 0), RegionEntry
|
||||
Segment at 60:61 (count = 0), RegionEntry
|
||||
Segment at 62:10 (count = 0), RegionEntry
|
||||
Segment at 62:11 (count = 0), Skipped
|
||||
Segment at 63:9 (count = 0), RegionEntry
|
||||
Segment at 63:23 (count = 0), Skipped
|
||||
Segment at 65:9 (count = 0), RegionEntry
|
||||
Segment at 65:15 (count = 0), Skipped
|
||||
Segment at 67:1 (count = 1), RegionEntry
|
||||
Segment at 67:2 (count = 0), Skipped
|
|
@ -0,0 +1,22 @@
|
|||
Counter in file 0 9:24 -> 11:6, #1
|
||||
Counter in file 0 15:9 -> 17:42, #1
|
||||
Counter in file 0 19:8 -> 19:12, (#1 + 0)
|
||||
Counter in file 0 20:9 -> 21:22, #2
|
||||
Counter in file 0 27:1 -> 27:2, (#2 + 0)
|
||||
Emitting segments for file: ../coverage/drop_trait.rs
|
||||
Combined regions:
|
||||
9:24 -> 11:6 (count=2)
|
||||
15:9 -> 17:42 (count=1)
|
||||
19:8 -> 19:12 (count=1)
|
||||
20:9 -> 21:22 (count=1)
|
||||
27:1 -> 27:2 (count=1)
|
||||
Segment at 9:24 (count = 2), RegionEntry
|
||||
Segment at 11:6 (count = 0), Skipped
|
||||
Segment at 15:9 (count = 1), RegionEntry
|
||||
Segment at 17:42 (count = 0), Skipped
|
||||
Segment at 19:8 (count = 1), RegionEntry
|
||||
Segment at 19:12 (count = 0), Skipped
|
||||
Segment at 20:9 (count = 1), RegionEntry
|
||||
Segment at 21:22 (count = 0), Skipped
|
||||
Segment at 27:1 (count = 1), RegionEntry
|
||||
Segment at 27:2 (count = 0), Skipped
|
|
@ -0,0 +1,48 @@
|
|||
Counter in file 0 17:24 -> 19:6, #1
|
||||
Counter in file 0 17:24 -> 19:6, #1
|
||||
Counter in file 0 23:9 -> 28:28, #1
|
||||
Counter in file 0 30:8 -> 30:12, (#1 + 0)
|
||||
Counter in file 0 31:9 -> 32:22, #2
|
||||
Counter in file 0 38:1 -> 38:2, (#2 + 0)
|
||||
Counter in file 0 10:49 -> 12:6, #1
|
||||
Counter in file 0 10:49 -> 12:6, #1
|
||||
Emitting segments for file: ../coverage/generics.rs
|
||||
Combined regions:
|
||||
10:49 -> 12:6 (count=3)
|
||||
17:24 -> 19:6 (count=2)
|
||||
23:9 -> 28:28 (count=1)
|
||||
30:8 -> 30:12 (count=1)
|
||||
31:9 -> 32:22 (count=1)
|
||||
38:1 -> 38:2 (count=1)
|
||||
Segment at 10:49 (count = 3), RegionEntry
|
||||
Segment at 12:6 (count = 0), Skipped
|
||||
Segment at 17:24 (count = 2), RegionEntry
|
||||
Segment at 19:6 (count = 0), Skipped
|
||||
Segment at 23:9 (count = 1), RegionEntry
|
||||
Segment at 28:28 (count = 0), Skipped
|
||||
Segment at 30:8 (count = 1), RegionEntry
|
||||
Segment at 30:12 (count = 0), Skipped
|
||||
Segment at 31:9 (count = 1), RegionEntry
|
||||
Segment at 32:22 (count = 0), Skipped
|
||||
Segment at 38:1 (count = 1), RegionEntry
|
||||
Segment at 38:2 (count = 0), Skipped
|
||||
Emitting segments for function: _RNvMCs4fqI2P2rA04_8genericsINtB2_8FireworkdE12set_strengthB2_
|
||||
Combined regions:
|
||||
10:49 -> 12:6 (count=2)
|
||||
Segment at 10:49 (count = 2), RegionEntry
|
||||
Segment at 12:6 (count = 0), Skipped
|
||||
Emitting segments for function: _RNvMCs4fqI2P2rA04_8genericsINtB2_8FireworklE12set_strengthB2_
|
||||
Combined regions:
|
||||
10:49 -> 12:6 (count=1)
|
||||
Segment at 10:49 (count = 1), RegionEntry
|
||||
Segment at 12:6 (count = 0), Skipped
|
||||
Emitting segments for function: _RNvXs_Cs4fqI2P2rA04_8genericsINtB4_8FireworklENtNtNtCs7f2nZg1zwMz_4core3ops4drop4Drop4dropB4_
|
||||
Combined regions:
|
||||
17:24 -> 19:6 (count=1)
|
||||
Segment at 17:24 (count = 1), RegionEntry
|
||||
Segment at 19:6 (count = 0), Skipped
|
||||
Emitting segments for function: _RNvXs_Cs4fqI2P2rA04_8genericsINtB4_8FireworkdENtNtNtCs7f2nZg1zwMz_4core3ops4drop4Drop4dropB4_
|
||||
Combined regions:
|
||||
17:24 -> 19:6 (count=1)
|
||||
Segment at 17:24 (count = 1), RegionEntry
|
||||
Segment at 19:6 (count = 0), Skipped
|
|
@ -0,0 +1,21 @@
|
|||
Counter in file 0 8:5 -> 18:10, #1
|
||||
Counter in file 0 21:9 -> 21:16, (#1 + 0)
|
||||
Counter in file 0 22:5 -> 27:6, #2
|
||||
Counter in file 0 27:6 -> 27:7, (#1 - #2)
|
||||
Counter in file 0 28:1 -> 28:2, (#2 + (#1 - #2))
|
||||
Emitting segments for file: ../coverage/if.rs
|
||||
Combined regions:
|
||||
8:5 -> 18:10 (count=1)
|
||||
21:9 -> 21:16 (count=1)
|
||||
22:5 -> 27:6 (count=1)
|
||||
27:6 -> 27:7 (count=0)
|
||||
28:1 -> 28:2 (count=1)
|
||||
Segment at 8:5 (count = 1), RegionEntry
|
||||
Segment at 18:10 (count = 0), Skipped
|
||||
Segment at 21:9 (count = 1), RegionEntry
|
||||
Segment at 21:16 (count = 0), Skipped
|
||||
Segment at 22:5 (count = 1), RegionEntry
|
||||
Segment at 27:6 (count = 0), RegionEntry
|
||||
Segment at 27:7 (count = 0), Skipped
|
||||
Segment at 28:1 (count = 1), RegionEntry
|
||||
Segment at 28:2 (count = 0), Skipped
|
|
@ -0,0 +1,30 @@
|
|||
Counter in file 0 7:9 -> 11:16, #1
|
||||
Counter in file 0 12:5 -> 17:6, #2
|
||||
Counter in file 0 20:9 -> 22:16, (#1 - #2)
|
||||
Counter in file 0 26:9 -> 26:16, (#2 + (#1 - #2))
|
||||
Counter in file 0 27:5 -> 32:6, #3
|
||||
Counter in file 0 34:5 -> 39:6, ((#2 + (#1 - #2)) - #3)
|
||||
Counter in file 0 40:1 -> 40:2, (#3 + ((#2 + (#1 - #2)) - #3))
|
||||
Emitting segments for file: ../coverage/if_else.rs
|
||||
Combined regions:
|
||||
7:9 -> 11:16 (count=1)
|
||||
12:5 -> 17:6 (count=1)
|
||||
20:9 -> 22:16 (count=0)
|
||||
26:9 -> 26:16 (count=1)
|
||||
27:5 -> 32:6 (count=1)
|
||||
34:5 -> 39:6 (count=0)
|
||||
40:1 -> 40:2 (count=1)
|
||||
Segment at 7:9 (count = 1), RegionEntry
|
||||
Segment at 11:16 (count = 0), Skipped
|
||||
Segment at 12:5 (count = 1), RegionEntry
|
||||
Segment at 17:6 (count = 0), Skipped
|
||||
Segment at 20:9 (count = 0), RegionEntry
|
||||
Segment at 22:16 (count = 0), Skipped
|
||||
Segment at 26:9 (count = 1), RegionEntry
|
||||
Segment at 26:16 (count = 0), Skipped
|
||||
Segment at 27:5 (count = 1), RegionEntry
|
||||
Segment at 32:6 (count = 0), Skipped
|
||||
Segment at 34:5 (count = 0), RegionEntry
|
||||
Segment at 39:6 (count = 0), Skipped
|
||||
Segment at 40:1 (count = 1), RegionEntry
|
||||
Segment at 40:2 (count = 0), Skipped
|
|
@ -0,0 +1,60 @@
|
|||
Counter in file 0 19:13 -> 19:18, #1
|
||||
Counter in file 0 20:13 -> 20:14, #2
|
||||
Counter in file 0 20:17 -> 20:22, (#1 + 0)
|
||||
Counter in file 0 21:9 -> 22:6, (#2 + 0)
|
||||
Counter in file 0 7:9 -> 9:26, #1
|
||||
Counter in file 0 10:8 -> 10:15, (#1 + 0)
|
||||
Counter in file 0 10:16 -> 12:6, #2
|
||||
Counter in file 0 12:6 -> 12:7, (#1 - #2)
|
||||
Counter in file 0 48:8 -> 48:15, (#2 + (#1 - #2))
|
||||
Counter in file 0 48:16 -> 50:6, #3
|
||||
Counter in file 0 50:6 -> 50:7, ((#2 + (#1 - #2)) - #3)
|
||||
Counter in file 0 52:9 -> 57:2, (#3 + ((#2 + (#1 - #2)) - #3))
|
||||
Counter in file 0 33:42 -> 36:10, #1
|
||||
Counter in file 0 41:37 -> 41:41, #1
|
||||
Counter in file 0 42:13 -> 43:10, #2
|
||||
Emitting segments for file: ../coverage/inner_items.rs
|
||||
Combined regions:
|
||||
7:9 -> 9:26 (count=1)
|
||||
10:8 -> 10:15 (count=1)
|
||||
10:16 -> 12:6 (count=1)
|
||||
12:6 -> 12:7 (count=0)
|
||||
19:13 -> 19:18 (count=3)
|
||||
20:13 -> 20:14 (count=3)
|
||||
20:17 -> 20:22 (count=3)
|
||||
21:9 -> 22:6 (count=3)
|
||||
33:42 -> 36:10 (count=1)
|
||||
41:37 -> 41:41 (count=1)
|
||||
42:13 -> 43:10 (count=1)
|
||||
48:8 -> 48:15 (count=1)
|
||||
48:16 -> 50:6 (count=1)
|
||||
50:6 -> 50:7 (count=0)
|
||||
52:9 -> 57:2 (count=1)
|
||||
Segment at 7:9 (count = 1), RegionEntry
|
||||
Segment at 9:26 (count = 0), Skipped
|
||||
Segment at 10:8 (count = 1), RegionEntry
|
||||
Segment at 10:15 (count = 0), Skipped
|
||||
Segment at 10:16 (count = 1), RegionEntry
|
||||
Segment at 12:6 (count = 0), RegionEntry
|
||||
Segment at 12:7 (count = 0), Skipped
|
||||
Segment at 19:13 (count = 3), RegionEntry
|
||||
Segment at 19:18 (count = 0), Skipped
|
||||
Segment at 20:13 (count = 3), RegionEntry
|
||||
Segment at 20:14 (count = 0), Skipped
|
||||
Segment at 20:17 (count = 3), RegionEntry
|
||||
Segment at 20:22 (count = 0), Skipped
|
||||
Segment at 21:9 (count = 3), RegionEntry
|
||||
Segment at 22:6 (count = 0), Skipped
|
||||
Segment at 33:42 (count = 1), RegionEntry
|
||||
Segment at 36:10 (count = 0), Skipped
|
||||
Segment at 41:37 (count = 1), RegionEntry
|
||||
Segment at 41:41 (count = 0), Skipped
|
||||
Segment at 42:13 (count = 1), RegionEntry
|
||||
Segment at 43:10 (count = 0), Skipped
|
||||
Segment at 48:8 (count = 1), RegionEntry
|
||||
Segment at 48:15 (count = 0), Skipped
|
||||
Segment at 48:16 (count = 1), RegionEntry
|
||||
Segment at 50:6 (count = 0), RegionEntry
|
||||
Segment at 50:7 (count = 0), Skipped
|
||||
Segment at 52:9 (count = 1), RegionEntry
|
||||
Segment at 57:2 (count = 0), Skipped
|
|
@ -0,0 +1,131 @@
|
|||
Counter in file 0 7:9 -> 9:42, #1
|
||||
Counter in file 0 10:8 -> 10:15, (#1 + 0)
|
||||
Counter in file 0 10:16 -> 14:6, #2
|
||||
Counter in file 0 14:6 -> 14:7, (#1 - #2)
|
||||
Counter in file 0 16:9 -> 16:17, ((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4))
|
||||
Counter in file 0 18:13 -> 18:18, (#2 + (#1 - #2))
|
||||
Counter in file 0 20:13 -> 20:18, ((#2 + (#1 - #2)) - #3)
|
||||
Counter in file 0 20:18 -> 20:19, (#3 + #4)
|
||||
Counter in file 0 20:18 -> 20:19, (((#2 + (#1 - #2)) - #3) - #4)
|
||||
Counter in file 0 23:9 -> 23:17, ((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6))
|
||||
Counter in file 0 25:13 -> 25:18, (((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) + 0)
|
||||
Counter in file 0 27:13 -> 27:18, (((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5)
|
||||
Counter in file 0 27:18 -> 27:19, (#5 + #6)
|
||||
Counter in file 0 27:18 -> 27:19, ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)
|
||||
Counter in file 0 29:9 -> 29:17, ((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8))
|
||||
Counter in file 0 29:20 -> 29:25, (((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) + 0)
|
||||
Counter in file 0 29:29 -> 29:34, #7
|
||||
Counter in file 0 29:34 -> 29:35, ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)
|
||||
Counter in file 0 29:34 -> 29:35, (#7 - #8)
|
||||
Counter in file 0 30:9 -> 30:17, ((#9 - #10) + ((((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) - #9) + #10))
|
||||
Counter in file 0 30:20 -> 30:25, (((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) + 0)
|
||||
Counter in file 0 30:29 -> 30:34, #9
|
||||
Counter in file 0 30:34 -> 30:35, ((((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) - #9) + #10)
|
||||
Counter in file 0 30:34 -> 30:35, (#9 - #10)
|
||||
Counter in file 0 33:9 -> 34:16, (((#9 - #10) + ((((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) - #9) + #10)) + 0)
|
||||
Counter in file 0 35:5 -> 38:6, #11
|
||||
Counter in file 0 38:6 -> 38:7, (((#9 - #10) + ((((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) - #9) + #10)) - #11)
|
||||
Counter in file 0 41:9 -> 41:16, (#11 + (((#9 - #10) + ((((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) - #9) + #10)) - #11))
|
||||
Counter in file 0 42:5 -> 45:6, #12
|
||||
Counter in file 0 47:5 -> 50:6, ((#11 + (((#9 - #10) + ((((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) - #9) + #10)) - #11)) - #12)
|
||||
Counter in file 0 52:8 -> 52:16, (#12 + ((#11 + (((#9 - #10) + ((((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) - #9) + #10)) - #11)) - #12))
|
||||
Counter in file 0 52:17 -> 54:6, #13
|
||||
Counter in file 0 54:6 -> 54:7, ((#12 + ((#11 + (((#9 - #10) + ((((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) - #9) + #10)) - #11)) - #12)) - #13)
|
||||
Counter in file 0 56:8 -> 56:15, (#13 + ((#12 + ((#11 + (((#9 - #10) + ((((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) - #9) + #10)) - #11)) - #12)) - #13))
|
||||
Counter in file 0 56:16 -> 58:6, #14
|
||||
Counter in file 0 58:12 -> 60:6, ((#13 + ((#12 + ((#11 + (((#9 - #10) + ((((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) - #9) + #10)) - #11)) - #12)) - #13)) - #14)
|
||||
Counter in file 0 61:1 -> 61:2, (#14 + ((#13 + ((#12 + ((#11 + (((#9 - #10) + ((((#7 - #8) + ((((#5 + #6) + ((((#3 + #4) + (((#2 + (#1 - #2)) - #3) - #4)) - #5) - #6)) - #7) + #8)) - #9) + #10)) - #11)) - #12)) - #13)) - #14))
|
||||
Emitting segments for file: ../coverage/lazy_boolean.rs
|
||||
Combined regions:
|
||||
7:9 -> 9:42 (count=1)
|
||||
10:8 -> 10:15 (count=1)
|
||||
10:16 -> 14:6 (count=1)
|
||||
14:6 -> 14:7 (count=0)
|
||||
16:9 -> 16:17 (count=1)
|
||||
18:13 -> 18:18 (count=1)
|
||||
20:13 -> 20:18 (count=0)
|
||||
20:18 -> 20:19 (count=1)
|
||||
23:9 -> 23:17 (count=1)
|
||||
25:13 -> 25:18 (count=1)
|
||||
27:13 -> 27:18 (count=1)
|
||||
27:18 -> 27:19 (count=1)
|
||||
29:9 -> 29:17 (count=1)
|
||||
29:20 -> 29:25 (count=1)
|
||||
29:29 -> 29:34 (count=1)
|
||||
29:34 -> 29:35 (count=1)
|
||||
30:9 -> 30:17 (count=1)
|
||||
30:20 -> 30:25 (count=1)
|
||||
30:29 -> 30:34 (count=0)
|
||||
30:34 -> 30:35 (count=1)
|
||||
33:9 -> 34:16 (count=1)
|
||||
35:5 -> 38:6 (count=0)
|
||||
38:6 -> 38:7 (count=1)
|
||||
41:9 -> 41:16 (count=1)
|
||||
42:5 -> 45:6 (count=1)
|
||||
47:5 -> 50:6 (count=0)
|
||||
52:8 -> 52:16 (count=1)
|
||||
52:17 -> 54:6 (count=0)
|
||||
54:6 -> 54:7 (count=1)
|
||||
56:8 -> 56:15 (count=1)
|
||||
56:16 -> 58:6 (count=1)
|
||||
58:12 -> 60:6 (count=0)
|
||||
61:1 -> 61:2 (count=1)
|
||||
Segment at 7:9 (count = 1), RegionEntry
|
||||
Segment at 9:42 (count = 0), Skipped
|
||||
Segment at 10:8 (count = 1), RegionEntry
|
||||
Segment at 10:15 (count = 0), Skipped
|
||||
Segment at 10:16 (count = 1), RegionEntry
|
||||
Segment at 14:6 (count = 0), RegionEntry
|
||||
Segment at 14:7 (count = 0), Skipped
|
||||
Segment at 16:9 (count = 1), RegionEntry
|
||||
Segment at 16:17 (count = 0), Skipped
|
||||
Segment at 18:13 (count = 1), RegionEntry
|
||||
Segment at 18:18 (count = 0), Skipped
|
||||
Segment at 20:13 (count = 0), RegionEntry
|
||||
Segment at 20:18 (count = 1), RegionEntry
|
||||
Segment at 20:19 (count = 0), Skipped
|
||||
Segment at 23:9 (count = 1), RegionEntry
|
||||
Segment at 23:17 (count = 0), Skipped
|
||||
Segment at 25:13 (count = 1), RegionEntry
|
||||
Segment at 25:18 (count = 0), Skipped
|
||||
Segment at 27:13 (count = 1), RegionEntry
|
||||
Segment at 27:18 (count = 1), RegionEntry
|
||||
Segment at 27:19 (count = 0), Skipped
|
||||
Segment at 29:9 (count = 1), RegionEntry
|
||||
Segment at 29:17 (count = 0), Skipped
|
||||
Segment at 29:20 (count = 1), RegionEntry
|
||||
Segment at 29:25 (count = 0), Skipped
|
||||
Segment at 29:29 (count = 1), RegionEntry
|
||||
Segment at 29:34 (count = 1), RegionEntry
|
||||
Segment at 29:35 (count = 0), Skipped
|
||||
Segment at 30:9 (count = 1), RegionEntry
|
||||
Segment at 30:17 (count = 0), Skipped
|
||||
Segment at 30:20 (count = 1), RegionEntry
|
||||
Segment at 30:25 (count = 0), Skipped
|
||||
Segment at 30:29 (count = 0), RegionEntry
|
||||
Segment at 30:34 (count = 1), RegionEntry
|
||||
Segment at 30:35 (count = 0), Skipped
|
||||
Segment at 33:9 (count = 1), RegionEntry
|
||||
Segment at 34:16 (count = 0), Skipped
|
||||
Segment at 35:5 (count = 0), RegionEntry
|
||||
Segment at 38:6 (count = 1), RegionEntry
|
||||
Segment at 38:7 (count = 0), Skipped
|
||||
Segment at 41:9 (count = 1), RegionEntry
|
||||
Segment at 41:16 (count = 0), Skipped
|
||||
Segment at 42:5 (count = 1), RegionEntry
|
||||
Segment at 45:6 (count = 0), Skipped
|
||||
Segment at 47:5 (count = 0), RegionEntry
|
||||
Segment at 50:6 (count = 0), Skipped
|
||||
Segment at 52:8 (count = 1), RegionEntry
|
||||
Segment at 52:16 (count = 0), Skipped
|
||||
Segment at 52:17 (count = 0), RegionEntry
|
||||
Segment at 54:6 (count = 1), RegionEntry
|
||||
Segment at 54:7 (count = 0), Skipped
|
||||
Segment at 56:8 (count = 1), RegionEntry
|
||||
Segment at 56:15 (count = 0), Skipped
|
||||
Segment at 56:16 (count = 1), RegionEntry
|
||||
Segment at 58:6 (count = 0), Skipped
|
||||
Segment at 58:12 (count = 0), RegionEntry
|
||||
Segment at 60:6 (count = 0), Skipped
|
||||
Segment at 61:1 (count = 1), RegionEntry
|
||||
Segment at 61:2 (count = 0), Skipped
|
|
@ -0,0 +1,6 @@
|
|||
Counter in file 0 3:11 -> 13:2, #1
|
||||
Emitting segments for file: ../coverage/loop_break_value.rs
|
||||
Combined regions:
|
||||
3:11 -> 13:2 (count=1)
|
||||
Segment at 3:11 (count = 1), RegionEntry
|
||||
Segment at 13:2 (count = 0), Skipped
|
|
@ -0,0 +1,37 @@
|
|||
Counter in file 0 10:12 -> 10:16, #1
|
||||
Counter in file 0 11:16 -> 11:21, #2
|
||||
Counter in file 0 14:14 -> 14:15, (#2 - #5)
|
||||
Counter in file 0 15:13 -> 15:31, (0 + (#2 - #5))
|
||||
Counter in file 0 15:31 -> 15:32, #4
|
||||
Counter in file 0 17:10 -> 17:11, #3
|
||||
Counter in file 0 18:9 -> 18:15, (#3 + 0)
|
||||
Counter in file 0 19:5 -> 19:6, (#4 + (#3 + 0))
|
||||
Counter in file 0 22:11 -> 25:2, #1
|
||||
Emitting segments for file: ../coverage/loops_branches.rs
|
||||
Combined regions:
|
||||
10:12 -> 10:16 (count=1)
|
||||
11:16 -> 11:21 (count=1)
|
||||
14:14 -> 14:15 (count=1)
|
||||
15:13 -> 15:31 (count=1)
|
||||
15:31 -> 15:32 (count=0)
|
||||
17:10 -> 17:11 (count=1)
|
||||
18:9 -> 18:15 (count=1)
|
||||
19:5 -> 19:6 (count=1)
|
||||
22:11 -> 25:2 (count=1)
|
||||
Segment at 10:12 (count = 1), RegionEntry
|
||||
Segment at 10:16 (count = 0), Skipped
|
||||
Segment at 11:16 (count = 1), RegionEntry
|
||||
Segment at 11:21 (count = 0), Skipped
|
||||
Segment at 14:14 (count = 1), RegionEntry
|
||||
Segment at 14:15 (count = 0), Skipped
|
||||
Segment at 15:13 (count = 1), RegionEntry
|
||||
Segment at 15:31 (count = 0), RegionEntry
|
||||
Segment at 15:32 (count = 0), Skipped
|
||||
Segment at 17:10 (count = 1), RegionEntry
|
||||
Segment at 17:11 (count = 0), Skipped
|
||||
Segment at 18:9 (count = 1), RegionEntry
|
||||
Segment at 18:15 (count = 0), Skipped
|
||||
Segment at 19:5 (count = 1), RegionEntry
|
||||
Segment at 19:6 (count = 0), Skipped
|
||||
Segment at 22:11 (count = 1), RegionEntry
|
||||
Segment at 25:2 (count = 0), Skipped
|
|
@ -0,0 +1,73 @@
|
|||
Counter in file 0 2:9 -> 3:27, #1
|
||||
Counter in file 0 5:19 -> 5:32, (#1 + #2)
|
||||
Counter in file 0 6:13 -> 7:24, ((#1 + #2) - #3)
|
||||
Counter in file 0 8:13 -> 8:14, ((((#1 + #2) - #3) + (#5 + #6)) - #7)
|
||||
Counter in file 0 8:18 -> 8:23, (((#1 + #2) - #3) + (#5 + #6))
|
||||
Counter in file 0 9:16 -> 9:22, (((((#1 + #2) - #3) + (#5 + #6)) - #7) + 0)
|
||||
Counter in file 0 10:17 -> 10:22, #8
|
||||
Counter in file 0 12:13 -> 12:19, #9
|
||||
Counter in file 0 13:13 -> 13:19, #10
|
||||
Counter in file 0 14:16 -> 14:22, (#10 + 0)
|
||||
Counter in file 0 15:17 -> 16:27, #11
|
||||
Counter in file 0 17:21 -> 17:33, #4
|
||||
Counter in file 0 19:21 -> 21:14, #5
|
||||
Counter in file 0 21:14 -> 21:15, #6
|
||||
Counter in file 0 22:10 -> 22:11, (#5 + #6)
|
||||
Counter in file 0 23:9 -> 23:23, #2
|
||||
Counter in file 0 24:6 -> 24:7, #3
|
||||
Counter in file 0 25:1 -> 25:2, (#4 + #3)
|
||||
Emitting segments for file: ../coverage/nested_loops.rs
|
||||
Combined regions:
|
||||
2:9 -> 3:27 (count=1)
|
||||
5:19 -> 5:32 (count=1)
|
||||
6:13 -> 7:24 (count=1)
|
||||
8:13 -> 8:14 (count=3)
|
||||
8:18 -> 8:23 (count=3)
|
||||
9:16 -> 9:22 (count=3)
|
||||
10:17 -> 10:22 (count=0)
|
||||
12:13 -> 12:19 (count=3)
|
||||
13:13 -> 13:19 (count=3)
|
||||
14:16 -> 14:22 (count=3)
|
||||
15:17 -> 16:27 (count=1)
|
||||
17:21 -> 17:33 (count=1)
|
||||
19:21 -> 21:14 (count=0)
|
||||
21:14 -> 21:15 (count=2)
|
||||
22:10 -> 22:11 (count=2)
|
||||
23:9 -> 23:23 (count=0)
|
||||
24:6 -> 24:7 (count=0)
|
||||
25:1 -> 25:2 (count=1)
|
||||
Segment at 2:9 (count = 1), RegionEntry
|
||||
Segment at 3:27 (count = 0), Skipped
|
||||
Segment at 5:19 (count = 1), RegionEntry
|
||||
Segment at 5:32 (count = 0), Skipped
|
||||
Segment at 6:13 (count = 1), RegionEntry
|
||||
Segment at 7:24 (count = 0), Skipped
|
||||
Segment at 8:13 (count = 3), RegionEntry
|
||||
Segment at 8:14 (count = 0), Skipped
|
||||
Segment at 8:18 (count = 3), RegionEntry
|
||||
Segment at 8:23 (count = 0), Skipped
|
||||
Segment at 9:16 (count = 3), RegionEntry
|
||||
Segment at 9:22 (count = 0), Skipped
|
||||
Segment at 10:17 (count = 0), RegionEntry
|
||||
Segment at 10:22 (count = 0), Skipped
|
||||
Segment at 12:13 (count = 3), RegionEntry
|
||||
Segment at 12:19 (count = 0), Skipped
|
||||
Segment at 13:13 (count = 3), RegionEntry
|
||||
Segment at 13:19 (count = 0), Skipped
|
||||
Segment at 14:16 (count = 3), RegionEntry
|
||||
Segment at 14:22 (count = 0), Skipped
|
||||
Segment at 15:17 (count = 1), RegionEntry
|
||||
Segment at 16:27 (count = 0), Skipped
|
||||
Segment at 17:21 (count = 1), RegionEntry
|
||||
Segment at 17:33 (count = 0), Skipped
|
||||
Segment at 19:21 (count = 0), RegionEntry
|
||||
Segment at 21:14 (count = 2), RegionEntry
|
||||
Segment at 21:15 (count = 0), Skipped
|
||||
Segment at 22:10 (count = 2), RegionEntry
|
||||
Segment at 22:11 (count = 0), Skipped
|
||||
Segment at 23:9 (count = 0), RegionEntry
|
||||
Segment at 23:23 (count = 0), Skipped
|
||||
Segment at 24:6 (count = 0), RegionEntry
|
||||
Segment at 24:7 (count = 0), Skipped
|
||||
Segment at 25:1 (count = 1), RegionEntry
|
||||
Segment at 25:2 (count = 0), Skipped
|
|
@ -0,0 +1,27 @@
|
|||
Counter in file 0 7:5 -> 7:6, #1
|
||||
Counter in file 0 21:11 -> 26:2, #1
|
||||
Counter in file 0 4:17 -> 4:22, #1
|
||||
Counter in file 0 13:9 -> 18:6, #1
|
||||
Counter in file 0 4:39 -> 4:40, #1
|
||||
Counter in file 0 4:48 -> 4:49, (#1 + 0)
|
||||
Counter in file 0 8:5 -> 8:17, #1
|
||||
Emitting segments for file: ../coverage/partial_eq.rs
|
||||
Combined regions:
|
||||
4:17 -> 4:22 (count=2)
|
||||
4:39 -> 4:40 (count=1)
|
||||
4:48 -> 4:49 (count=1)
|
||||
7:5 -> 7:6 (count=1)
|
||||
13:9 -> 18:6 (count=2)
|
||||
21:11 -> 26:2 (count=1)
|
||||
Segment at 4:17 (count = 2), RegionEntry
|
||||
Segment at 4:22 (count = 0), Skipped
|
||||
Segment at 4:39 (count = 1), RegionEntry
|
||||
Segment at 4:40 (count = 0), Skipped
|
||||
Segment at 4:48 (count = 1), RegionEntry
|
||||
Segment at 4:49 (count = 0), Skipped
|
||||
Segment at 7:5 (count = 1), RegionEntry
|
||||
Segment at 7:6 (count = 0), Skipped
|
||||
Segment at 13:9 (count = 2), RegionEntry
|
||||
Segment at 18:6 (count = 0), Skipped
|
||||
Segment at 21:11 (count = 1), RegionEntry
|
||||
Segment at 26:2 (count = 0), Skipped
|
|
@ -0,0 +1,37 @@
|
|||
Counter in file 0 7:9 -> 9:26, #1
|
||||
Counter in file 0 12:9 -> 12:16, (#1 + 0)
|
||||
Counter in file 0 13:5 -> 18:6, #2
|
||||
Counter in file 0 18:6 -> 18:7, (#1 - #2)
|
||||
Counter in file 0 23:13 -> 25:14, ((#2 + (#1 - #2)) + #3)
|
||||
Counter in file 0 27:13 -> 27:18, #4
|
||||
Counter in file 0 30:9 -> 32:10, #3
|
||||
Counter in file 0 34:6 -> 34:7, (#2 + (#1 - #2))
|
||||
Counter in file 0 35:1 -> 35:2, (#4 + 0)
|
||||
Emitting segments for file: ../coverage/simple_loop.rs
|
||||
Combined regions:
|
||||
7:9 -> 9:26 (count=1)
|
||||
12:9 -> 12:16 (count=1)
|
||||
13:5 -> 18:6 (count=1)
|
||||
18:6 -> 18:7 (count=0)
|
||||
23:13 -> 25:14 (count=11)
|
||||
27:13 -> 27:18 (count=1)
|
||||
30:9 -> 32:10 (count=10)
|
||||
34:6 -> 34:7 (count=1)
|
||||
35:1 -> 35:2 (count=1)
|
||||
Segment at 7:9 (count = 1), RegionEntry
|
||||
Segment at 9:26 (count = 0), Skipped
|
||||
Segment at 12:9 (count = 1), RegionEntry
|
||||
Segment at 12:16 (count = 0), Skipped
|
||||
Segment at 13:5 (count = 1), RegionEntry
|
||||
Segment at 18:6 (count = 0), RegionEntry
|
||||
Segment at 18:7 (count = 0), Skipped
|
||||
Segment at 23:13 (count = 11), RegionEntry
|
||||
Segment at 25:14 (count = 0), Skipped
|
||||
Segment at 27:13 (count = 1), RegionEntry
|
||||
Segment at 27:18 (count = 0), Skipped
|
||||
Segment at 30:9 (count = 10), RegionEntry
|
||||
Segment at 32:10 (count = 0), Skipped
|
||||
Segment at 34:6 (count = 1), RegionEntry
|
||||
Segment at 34:7 (count = 0), Skipped
|
||||
Segment at 35:1 (count = 1), RegionEntry
|
||||
Segment at 35:2 (count = 0), Skipped
|
|
@ -0,0 +1,57 @@
|
|||
Counter in file 0 7:9 -> 9:26, #1
|
||||
Counter in file 0 10:8 -> 10:15, (#1 + 0)
|
||||
Counter in file 0 10:16 -> 12:6, #2
|
||||
Counter in file 0 12:6 -> 12:7, (#1 - #2)
|
||||
Counter in file 0 15:9 -> 15:10, (((#2 + (#1 - #2)) + (#3 + #4)) - #5)
|
||||
Counter in file 0 17:9 -> 17:13, ((#2 + (#1 - #2)) + (#3 + #4))
|
||||
Counter in file 0 22:13 -> 22:22, ((((#2 + (#1 - #2)) + (#3 + #4)) - #5) + 0)
|
||||
Counter in file 0 24:13 -> 24:14, #3
|
||||
Counter in file 0 26:17 -> 28:18, ((((#2 + (#1 - #2)) + (#3 + #4)) - #5) + 0)
|
||||
Counter in file 0 28:18 -> 28:19, ((((#2 + (#1 - #2)) + (#3 + #4)) - #5) - #3)
|
||||
Counter in file 0 30:13 -> 37:14, (#3 + 0)
|
||||
Counter in file 0 40:13 -> 40:15, #4
|
||||
Counter in file 0 42:6 -> 42:7, (#2 + (#1 - #2))
|
||||
Counter in file 0 42:6 -> 42:7, (#3 + #4)
|
||||
Counter in file 0 43:1 -> 43:2, #5
|
||||
Emitting segments for file: ../coverage/simple_match.rs
|
||||
Combined regions:
|
||||
7:9 -> 9:26 (count=1)
|
||||
10:8 -> 10:15 (count=1)
|
||||
10:16 -> 12:6 (count=1)
|
||||
12:6 -> 12:7 (count=0)
|
||||
15:9 -> 15:10 (count=2)
|
||||
17:9 -> 17:13 (count=3)
|
||||
22:13 -> 22:22 (count=2)
|
||||
24:13 -> 24:14 (count=1)
|
||||
26:17 -> 28:18 (count=2)
|
||||
28:18 -> 28:19 (count=1)
|
||||
30:13 -> 37:14 (count=1)
|
||||
40:13 -> 40:15 (count=1)
|
||||
42:6 -> 42:7 (count=3)
|
||||
43:1 -> 43:2 (count=1)
|
||||
Segment at 7:9 (count = 1), RegionEntry
|
||||
Segment at 9:26 (count = 0), Skipped
|
||||
Segment at 10:8 (count = 1), RegionEntry
|
||||
Segment at 10:15 (count = 0), Skipped
|
||||
Segment at 10:16 (count = 1), RegionEntry
|
||||
Segment at 12:6 (count = 0), RegionEntry
|
||||
Segment at 12:7 (count = 0), Skipped
|
||||
Segment at 15:9 (count = 2), RegionEntry
|
||||
Segment at 15:10 (count = 0), Skipped
|
||||
Segment at 17:9 (count = 3), RegionEntry
|
||||
Segment at 17:13 (count = 0), Skipped
|
||||
Segment at 22:13 (count = 2), RegionEntry
|
||||
Segment at 22:22 (count = 0), Skipped
|
||||
Segment at 24:13 (count = 1), RegionEntry
|
||||
Segment at 24:14 (count = 0), Skipped
|
||||
Segment at 26:17 (count = 2), RegionEntry
|
||||
Segment at 28:18 (count = 1), RegionEntry
|
||||
Segment at 28:19 (count = 0), Skipped
|
||||
Segment at 30:13 (count = 1), RegionEntry
|
||||
Segment at 37:14 (count = 0), Skipped
|
||||
Segment at 40:13 (count = 1), RegionEntry
|
||||
Segment at 40:15 (count = 0), Skipped
|
||||
Segment at 42:6 (count = 3), RegionEntry
|
||||
Segment at 42:7 (count = 0), Skipped
|
||||
Segment at 43:1 (count = 1), RegionEntry
|
||||
Segment at 43:2 (count = 0), Skipped
|
|
@ -0,0 +1,10 @@
|
|||
Counter in file 0 2:8 -> 2:13, #1
|
||||
Counter in file 0 5:1 -> 5:2, (#1 - #2)
|
||||
Emitting segments for file: ../coverage/tight_inf_loop.rs
|
||||
Combined regions:
|
||||
2:8 -> 2:13 (count=1)
|
||||
5:1 -> 5:2 (count=1)
|
||||
Segment at 2:8 (count = 1), RegionEntry
|
||||
Segment at 2:13 (count = 0), Skipped
|
||||
Segment at 5:1 (count = 1), RegionEntry
|
||||
Segment at 5:2 (count = 0), Skipped
|
|
@ -0,0 +1,72 @@
|
|||
Counter in file 0 13:9 -> 14:23, #1
|
||||
Counter in file 0 17:9 -> 17:10, ((#1 + (#2 + #3)) - #4)
|
||||
Counter in file 0 19:9 -> 19:14, (#1 + (#2 + #3))
|
||||
Counter in file 0 21:9 -> 25:26, #8
|
||||
Counter in file 0 27:13 -> 27:41, #9
|
||||
Counter in file 0 27:41 -> 27:42, #5
|
||||
Counter in file 0 28:13 -> 28:42, (#9 - #5)
|
||||
Counter in file 0 28:42 -> 28:43, #6
|
||||
Counter in file 0 32:13 -> 32:42, (#8 - #9)
|
||||
Counter in file 0 32:42 -> 32:43, #7
|
||||
Counter in file 0 33:10 -> 33:11, #2
|
||||
Counter in file 0 33:10 -> 33:11, #3
|
||||
Counter in file 0 34:6 -> 34:7, (#2 + #3)
|
||||
Counter in file 0 35:5 -> 35:11, #4
|
||||
Counter in file 0 36:1 -> 36:2, ((#5 + (#6 + #7)) + #4)
|
||||
Counter in file 0 5:8 -> 5:20, #1
|
||||
Counter in file 0 6:9 -> 6:16, #2
|
||||
Counter in file 0 8:9 -> 8:15, (#1 - #2)
|
||||
Counter in file 0 10:1 -> 10:2, (#2 + (#1 - #2))
|
||||
Emitting segments for file: ../coverage/try_error_result.rs
|
||||
Combined regions:
|
||||
5:8 -> 5:20 (count=6)
|
||||
6:9 -> 6:16 (count=1)
|
||||
8:9 -> 8:15 (count=5)
|
||||
10:1 -> 10:2 (count=6)
|
||||
13:9 -> 14:23 (count=1)
|
||||
17:9 -> 17:10 (count=6)
|
||||
19:9 -> 19:14 (count=6)
|
||||
21:9 -> 25:26 (count=6)
|
||||
27:13 -> 27:41 (count=1)
|
||||
27:41 -> 27:42 (count=1)
|
||||
28:13 -> 28:42 (count=0)
|
||||
28:42 -> 28:43 (count=0)
|
||||
32:13 -> 32:42 (count=5)
|
||||
32:42 -> 32:43 (count=0)
|
||||
33:10 -> 33:11 (count=5)
|
||||
34:6 -> 34:7 (count=5)
|
||||
35:5 -> 35:11 (count=0)
|
||||
36:1 -> 36:2 (count=1)
|
||||
Segment at 5:8 (count = 6), RegionEntry
|
||||
Segment at 5:20 (count = 0), Skipped
|
||||
Segment at 6:9 (count = 1), RegionEntry
|
||||
Segment at 6:16 (count = 0), Skipped
|
||||
Segment at 8:9 (count = 5), RegionEntry
|
||||
Segment at 8:15 (count = 0), Skipped
|
||||
Segment at 10:1 (count = 6), RegionEntry
|
||||
Segment at 10:2 (count = 0), Skipped
|
||||
Segment at 13:9 (count = 1), RegionEntry
|
||||
Segment at 14:23 (count = 0), Skipped
|
||||
Segment at 17:9 (count = 6), RegionEntry
|
||||
Segment at 17:10 (count = 0), Skipped
|
||||
Segment at 19:9 (count = 6), RegionEntry
|
||||
Segment at 19:14 (count = 0), Skipped
|
||||
Segment at 21:9 (count = 6), RegionEntry
|
||||
Segment at 25:26 (count = 0), Skipped
|
||||
Segment at 27:13 (count = 1), RegionEntry
|
||||
Segment at 27:41 (count = 1), RegionEntry
|
||||
Segment at 27:42 (count = 0), Skipped
|
||||
Segment at 28:13 (count = 0), RegionEntry
|
||||
Segment at 28:42 (count = 0), RegionEntry
|
||||
Segment at 28:43 (count = 0), Skipped
|
||||
Segment at 32:13 (count = 5), RegionEntry
|
||||
Segment at 32:42 (count = 0), RegionEntry
|
||||
Segment at 32:43 (count = 0), Skipped
|
||||
Segment at 33:10 (count = 5), RegionEntry
|
||||
Segment at 33:11 (count = 0), Skipped
|
||||
Segment at 34:6 (count = 5), RegionEntry
|
||||
Segment at 34:7 (count = 0), Skipped
|
||||
Segment at 35:5 (count = 0), RegionEntry
|
||||
Segment at 35:11 (count = 0), Skipped
|
||||
Segment at 36:1 (count = 1), RegionEntry
|
||||
Segment at 36:2 (count = 0), Skipped
|
|
@ -0,0 +1,18 @@
|
|||
Counter in file 0 2:9 -> 2:16, #1
|
||||
Counter in file 0 3:11 -> 3:20, (#1 + #2)
|
||||
Counter in file 0 3:21 -> 4:6, #2
|
||||
Counter in file 0 5:1 -> 5:2, ((#1 + #2) - #2)
|
||||
Emitting segments for file: ../coverage/while.rs
|
||||
Combined regions:
|
||||
2:9 -> 2:16 (count=1)
|
||||
3:11 -> 3:20 (count=1)
|
||||
3:21 -> 4:6 (count=0)
|
||||
5:1 -> 5:2 (count=1)
|
||||
Segment at 2:9 (count = 1), RegionEntry
|
||||
Segment at 2:16 (count = 0), Skipped
|
||||
Segment at 3:11 (count = 1), RegionEntry
|
||||
Segment at 3:20 (count = 0), Skipped
|
||||
Segment at 3:21 (count = 0), RegionEntry
|
||||
Segment at 4:6 (count = 0), Skipped
|
||||
Segment at 5:1 (count = 1), RegionEntry
|
||||
Segment at 5:2 (count = 0), Skipped
|
|
@ -0,0 +1,38 @@
|
|||
Counter in file 0 5:9 -> 5:27, #1
|
||||
Counter in file 0 7:9 -> 9:10, (#1 + #2)
|
||||
Counter in file 0 12:13 -> 14:14, ((#1 + #2) - #3)
|
||||
Counter in file 0 18:21 -> 20:22, #6
|
||||
Counter in file 0 22:21 -> 22:27, #4
|
||||
Counter in file 0 26:21 -> 26:27, #5
|
||||
Counter in file 0 30:9 -> 32:10, #2
|
||||
Counter in file 0 35:5 -> 35:11, #3
|
||||
Counter in file 0 36:1 -> 36:2, ((#4 + #5) + #3)
|
||||
Emitting segments for file: ../coverage/while_early_ret.rs
|
||||
Combined regions:
|
||||
5:9 -> 5:27 (count=1)
|
||||
7:9 -> 9:10 (count=7)
|
||||
12:13 -> 14:14 (count=7)
|
||||
18:21 -> 20:22 (count=1)
|
||||
22:21 -> 22:27 (count=0)
|
||||
26:21 -> 26:27 (count=1)
|
||||
30:9 -> 32:10 (count=6)
|
||||
35:5 -> 35:11 (count=0)
|
||||
36:1 -> 36:2 (count=1)
|
||||
Segment at 5:9 (count = 1), RegionEntry
|
||||
Segment at 5:27 (count = 0), Skipped
|
||||
Segment at 7:9 (count = 7), RegionEntry
|
||||
Segment at 9:10 (count = 0), Skipped
|
||||
Segment at 12:13 (count = 7), RegionEntry
|
||||
Segment at 14:14 (count = 0), Skipped
|
||||
Segment at 18:21 (count = 1), RegionEntry
|
||||
Segment at 20:22 (count = 0), Skipped
|
||||
Segment at 22:21 (count = 0), RegionEntry
|
||||
Segment at 22:27 (count = 0), Skipped
|
||||
Segment at 26:21 (count = 1), RegionEntry
|
||||
Segment at 26:27 (count = 0), Skipped
|
||||
Segment at 30:9 (count = 6), RegionEntry
|
||||
Segment at 32:10 (count = 0), Skipped
|
||||
Segment at 35:5 (count = 0), RegionEntry
|
||||
Segment at 35:11 (count = 0), Skipped
|
||||
Segment at 36:1 (count = 1), RegionEntry
|
||||
Segment at 36:2 (count = 0), Skipped
|
|
@ -17,14 +17,14 @@
|
|||
},
|
||||
"lines": {
|
||||
"count": 91,
|
||||
"covered": 75,
|
||||
"percent": 82.41758241758241
|
||||
"covered": 77,
|
||||
"percent": 84.61538461538461
|
||||
},
|
||||
"regions": {
|
||||
"count": 21,
|
||||
"covered": 11,
|
||||
"notcovered": 10,
|
||||
"percent": 52.38095238095239
|
||||
"count": 25,
|
||||
"covered": 13,
|
||||
"notcovered": 12,
|
||||
"percent": 52
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,14 +42,14 @@
|
|||
},
|
||||
"lines": {
|
||||
"count": 91,
|
||||
"covered": 75,
|
||||
"percent": 82.41758241758241
|
||||
"covered": 77,
|
||||
"percent": 84.61538461538461
|
||||
},
|
||||
"regions": {
|
||||
"count": 21,
|
||||
"covered": 11,
|
||||
"notcovered": 10,
|
||||
"percent": 52.38095238095239
|
||||
"count": 25,
|
||||
"covered": 13,
|
||||
"notcovered": 12,
|
||||
"percent": 52
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/various_conditions.rs",
|
||||
"filename": "../coverage/conditions.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
|
@ -21,10 +21,10 @@
|
|||
"percent": 46.93877551020408
|
||||
},
|
||||
"regions": {
|
||||
"count": 51,
|
||||
"covered": 19,
|
||||
"notcovered": 32,
|
||||
"percent": 37.254901960784316
|
||||
"count": 69,
|
||||
"covered": 18,
|
||||
"notcovered": 51,
|
||||
"percent": 26.08695652173913
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,10 +46,10 @@
|
|||
"percent": 46.93877551020408
|
||||
},
|
||||
"regions": {
|
||||
"count": 51,
|
||||
"covered": 19,
|
||||
"notcovered": 32,
|
||||
"percent": 37.254901960784316
|
||||
"count": 69,
|
||||
"covered": 18,
|
||||
"notcovered": 51,
|
||||
"percent": 26.08695652173913
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,10 +21,10 @@
|
|||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 4,
|
||||
"count": 5,
|
||||
"covered": 4,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"notcovered": 1,
|
||||
"percent": 80
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,10 +46,10 @@
|
|||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 4,
|
||||
"count": 5,
|
||||
"covered": 4,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"notcovered": 1,
|
||||
"percent": 80
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@
|
|||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 13,
|
||||
"count": 15,
|
||||
"covered": 13,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"notcovered": 2,
|
||||
"percent": 86.66666666666667
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,10 +46,10 @@
|
|||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 13,
|
||||
"count": 15,
|
||||
"covered": 13,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"notcovered": 2,
|
||||
"percent": 86.66666666666667
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 21,
|
||||
"covered": 19,
|
||||
"percent": 90.47619047619048
|
||||
"count": 40,
|
||||
"covered": 30,
|
||||
"percent": 75
|
||||
},
|
||||
"regions": {
|
||||
"count": 16,
|
||||
"covered": 14,
|
||||
"notcovered": 2,
|
||||
"percent": 87.5
|
||||
"count": 37,
|
||||
"covered": 26,
|
||||
"notcovered": 11,
|
||||
"percent": 70.27027027027027
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,15 +41,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 21,
|
||||
"covered": 19,
|
||||
"percent": 90.47619047619048
|
||||
"count": 40,
|
||||
"covered": 30,
|
||||
"percent": 75
|
||||
},
|
||||
"regions": {
|
||||
"count": 16,
|
||||
"covered": 14,
|
||||
"notcovered": 2,
|
||||
"percent": 87.5
|
||||
"count": 37,
|
||||
"covered": 26,
|
||||
"notcovered": 11,
|
||||
"percent": 70.27027027027027
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/loops_branches.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 11,
|
||||
"covered": 11,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
||||
"covered": 8,
|
||||
"notcovered": 1,
|
||||
"percent": 88.88888888888889
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"functions": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 11,
|
||||
"covered": 11,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
||||
"covered": 8,
|
||||
"notcovered": 1,
|
||||
"percent": 88.88888888888889
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "llvm.coverage.json.export",
|
||||
"version": "2.0.1"
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/nested_loops.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 21,
|
||||
"covered": 16,
|
||||
"percent": 76.19047619047619
|
||||
},
|
||||
"regions": {
|
||||
"count": 18,
|
||||
"covered": 14,
|
||||
"notcovered": 4,
|
||||
"percent": 77.77777777777779
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 21,
|
||||
"covered": 16,
|
||||
"percent": 76.19047619047619
|
||||
},
|
||||
"regions": {
|
||||
"count": 18,
|
||||
"covered": 14,
|
||||
"notcovered": 4,
|
||||
"percent": 77.77777777777779
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "llvm.coverage.json.export",
|
||||
"version": "2.0.1"
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/partial_eq.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 5,
|
||||
"covered": 5,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 8,
|
||||
"covered": 5,
|
||||
"percent": 62.5
|
||||
},
|
||||
"lines": {
|
||||
"count": 15,
|
||||
"covered": 15,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 6,
|
||||
"covered": 6,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"functions": {
|
||||
"count": 5,
|
||||
"covered": 5,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 8,
|
||||
"covered": 5,
|
||||
"percent": 62.5
|
||||
},
|
||||
"lines": {
|
||||
"count": 15,
|
||||
"covered": 15,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 6,
|
||||
"covered": 6,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "llvm.coverage.json.export",
|
||||
"version": "2.0.1"
|
||||
}
|
|
@ -16,15 +16,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 18,
|
||||
"covered": 18,
|
||||
"count": 19,
|
||||
"covered": 19,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 7,
|
||||
"covered": 7,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"count": 9,
|
||||
"covered": 8,
|
||||
"notcovered": 1,
|
||||
"percent": 88.88888888888889
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,15 +41,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 18,
|
||||
"covered": 18,
|
||||
"count": 19,
|
||||
"covered": 19,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 7,
|
||||
"covered": 7,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"count": 9,
|
||||
"covered": 8,
|
||||
"notcovered": 1,
|
||||
"percent": 88.88888888888889
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 26,
|
||||
"covered": 26,
|
||||
"count": 24,
|
||||
"covered": 24,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
||||
"covered": 9,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"count": 15,
|
||||
"covered": 14,
|
||||
"notcovered": 1,
|
||||
"percent": 93.33333333333333
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,15 +41,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 26,
|
||||
"covered": 26,
|
||||
"count": 24,
|
||||
"covered": 24,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
||||
"covered": 9,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
"count": 15,
|
||||
"covered": 14,
|
||||
"notcovered": 1,
|
||||
"percent": 93.33333333333333
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/tight_inf_loop.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"percent": 100
|
||||
},
|
||||
"regions": {
|
||||
"count": 2,
|
||||
"covered": 2,
|
||||
"notcovered": 0,
|
||||
"percent": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "llvm.coverage.json.export",
|
||||
"version": "2.0.1"
|
||||
}
|
|
@ -16,15 +16,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 16,
|
||||
"covered": 15,
|
||||
"percent": 93.75
|
||||
"count": 20,
|
||||
"covered": 18,
|
||||
"percent": 90
|
||||
},
|
||||
"regions": {
|
||||
"count": 13,
|
||||
"covered": 12,
|
||||
"notcovered": 1,
|
||||
"percent": 92.3076923076923
|
||||
"count": 19,
|
||||
"covered": 14,
|
||||
"notcovered": 5,
|
||||
"percent": 73.68421052631578
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,15 +41,15 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 16,
|
||||
"covered": 15,
|
||||
"percent": 93.75
|
||||
"count": 20,
|
||||
"covered": 18,
|
||||
"percent": 90
|
||||
},
|
||||
"regions": {
|
||||
"count": 13,
|
||||
"covered": 12,
|
||||
"notcovered": 1,
|
||||
"percent": 92.3076923076923
|
||||
"count": 19,
|
||||
"covered": 14,
|
||||
"notcovered": 5,
|
||||
"percent": 73.68421052631578
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/while.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 4,
|
||||
"covered": 3,
|
||||
"percent": 75
|
||||
},
|
||||
"regions": {
|
||||
"count": 4,
|
||||
"covered": 3,
|
||||
"notcovered": 1,
|
||||
"percent": 75
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"instantiations": {
|
||||
"count": 1,
|
||||
"covered": 1,
|
||||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 4,
|
||||
"covered": 3,
|
||||
"percent": 75
|
||||
},
|
||||
"regions": {
|
||||
"count": 4,
|
||||
"covered": 3,
|
||||
"notcovered": 1,
|
||||
"percent": 75
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "llvm.coverage.json.export",
|
||||
"version": "2.0.1"
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "../coverage/while_early_return.rs",
|
||||
"filename": "../coverage/while_early_ret.rs",
|
||||
"summary": {
|
||||
"functions": {
|
||||
"count": 1,
|
||||
|
@ -16,9 +16,9 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 18,
|
||||
"covered": 16,
|
||||
"percent": 88.88888888888889
|
||||
"count": 17,
|
||||
"covered": 15,
|
||||
"percent": 88.23529411764706
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
||||
|
@ -41,9 +41,9 @@
|
|||
"percent": 100
|
||||
},
|
||||
"lines": {
|
||||
"count": 18,
|
||||
"covered": 16,
|
||||
"percent": 88.88888888888889
|
||||
"count": 17,
|
||||
"covered": 15,
|
||||
"percent": 88.23529411764706
|
||||
},
|
||||
"regions": {
|
||||
"count": 9,
|
|
@ -62,7 +62,7 @@
|
|||
62| 1| let mut countdown = 0;
|
||||
63| 1| if is_false {
|
||||
64| 0| countdown = 10;
|
||||
65| 0| }
|
||||
65| 1| }
|
||||
66| 1| "alt string 3".to_owned()
|
||||
67| 1| }
|
||||
68| 1| )
|
||||
|
@ -77,7 +77,7 @@
|
|||
77| 1| let mut countdown = 0;
|
||||
78| 1| if is_false {
|
||||
79| 0| countdown = 10;
|
||||
80| 0| }
|
||||
80| 1| }
|
||||
81| 1| "alt string 4".to_owned()
|
||||
82| 1| };
|
||||
83| 1| println!(
|
||||
|
|
|
@ -25,5 +25,6 @@
|
|||
25| 1| 10
|
||||
26| 1| ;
|
||||
27| 1| }
|
||||
^0
|
||||
28| 1|}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
10| 1| if is_true {
|
||||
11| 1| countdown = 10;
|
||||
12| 1| }
|
||||
^0
|
||||
13| |
|
||||
14| | mod in_mod {
|
||||
15| | const IN_MOD_CONST: u32 = 1000;
|
||||
|
@ -48,6 +49,7 @@
|
|||
48| 1| if is_true {
|
||||
49| 1| in_func(countdown);
|
||||
50| 1| }
|
||||
^0
|
||||
51| |
|
||||
52| 1| let mut val = InStruct {
|
||||
53| 1| in_struct_field: 101,
|
||||
|
|
|
@ -12,12 +12,14 @@
|
|||
12| 1| b = 10;
|
||||
13| 1| c = 100;
|
||||
14| 1| }
|
||||
^0
|
||||
15| | let
|
||||
16| 1| somebool
|
||||
17| | =
|
||||
18| 1| a < b
|
||||
19| | ||
|
||||
20| 0| b < c
|
||||
20| 1| b < c
|
||||
^0
|
||||
21| | ;
|
||||
22| | let
|
||||
23| 1| somebool
|
||||
|
@ -26,19 +28,38 @@
|
|||
26| | ||
|
||||
27| 1| b < c
|
||||
28| | ;
|
||||
29| | let
|
||||
30| 1| somebool
|
||||
31| | =
|
||||
32| 1| a < b
|
||||
33| | &&
|
||||
34| 1| b < c
|
||||
35| | ;
|
||||
36| | let
|
||||
37| 1| somebool
|
||||
38| | =
|
||||
39| 1| b < a
|
||||
40| | &&
|
||||
41| 0| b < c
|
||||
42| | ;
|
||||
43| 1|}
|
||||
29| 1| let somebool = a < b && b < c;
|
||||
30| 1| let somebool = b < a && b < c;
|
||||
^0
|
||||
31| |
|
||||
32| | if
|
||||
33| 1| !
|
||||
34| 1| is_true
|
||||
35| 0| {
|
||||
36| 0| a = 2
|
||||
37| 0| ;
|
||||
38| 1| }
|
||||
39| |
|
||||
40| | if
|
||||
41| 1| is_true
|
||||
42| 1| {
|
||||
43| 1| b = 30
|
||||
44| 1| ;
|
||||
45| 1| }
|
||||
46| | else
|
||||
47| 0| {
|
||||
48| 0| c = 400
|
||||
49| 0| ;
|
||||
50| 0| }
|
||||
51| |
|
||||
52| 1| if !is_true {
|
||||
53| 0| a = 2;
|
||||
54| 1| }
|
||||
55| |
|
||||
56| 1| if is_true {
|
||||
57| 1| b = 30;
|
||||
58| 1| } else {
|
||||
59| 0| c = 400;
|
||||
60| 0| }
|
||||
61| 1|}
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
1| |#![allow(unused_assignments)]
|
||||
2| |
|
||||
3| |// This test confirms an earlier problem was resolved, supporting the MIR graph generated by the
|
||||
4| |// structure of this `fmt` function.
|
||||
5| |
|
||||
6| |struct DebugTest;
|
||||
7| |
|
||||
8| |impl std::fmt::Debug for DebugTest {
|
||||
9| | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
10| 1| if true {
|
||||
11| 1| if false {
|
||||
12| | while true {
|
||||
13| | }
|
||||
14| 1| }
|
||||
15| 1| write!(f, "error")?;
|
||||
^0
|
||||
16| | } else {
|
||||
17| 1| }
|
||||
18| 1| Ok(())
|
||||
19| 1| }
|
||||
20| |}
|
||||
21| |
|
||||
22| 1|fn main() {
|
||||
23| 1| let debug_test = DebugTest;
|
||||
24| 1| println!("{:?}", debug_test);
|
||||
25| 1|}
|
||||
26| |
|
||||
27| |/*
|
||||
28| |
|
||||
29| |This is the error message generated, before the issue was fixed:
|
||||
30| |
|
||||
31| |error: internal compiler error: compiler/rustc_mir/src/transform/coverage/mod.rs:374:42:
|
||||
32| |Error processing: DefId(0:6 ~ bug_incomplete_cov_graph_traversal_simplified[317d]::{impl#0}::fmt):
|
||||
33| |Error { message: "`TraverseCoverageGraphWithLoops` missed some `BasicCoverageBlock`s:
|
||||
34| |[bcb6, bcb7, bcb9]" }
|
||||
35| |
|
||||
36| |*/
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
1| |fn main() {
|
||||
2| 1| let is_true = std::env::args().len() == 1;
|
||||
3| 1| let mut countdown = 10;
|
||||
4| |
|
||||
5| 1| 'outer: while countdown > 0 {
|
||||
6| 1| let mut a = 100;
|
||||
7| 1| let mut b = 100;
|
||||
8| 3| for _ in 0..50 {
|
||||
9| 3| if a < 30 {
|
||||
10| 0| break;
|
||||
11| | }
|
||||
12| 3| a -= 5;
|
||||
13| 3| b -= 5;
|
||||
14| 3| if b < 90 {
|
||||
15| 1| a -= 10;
|
||||
16| 1| if is_true {
|
||||
17| 1| break 'outer;
|
||||
18| | } else {
|
||||
19| 0| a -= 2;
|
||||
20| 0| }
|
||||
21| 2| }
|
||||
22| 2| }
|
||||
23| 0| countdown -= 1;
|
||||
24| 0| }
|
||||
25| 1|}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
1| |// This test confirms an earlier problem was resolved, supporting the MIR graph generated by the
|
||||
2| |// structure of this test.
|
||||
3| |
|
||||
4| 2|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
^1 ^1
|
||||
------------------
|
||||
| Unexecuted instantiation: <partial_eq::Version as core::cmp::PartialOrd>::gt
|
||||
------------------
|
||||
| Unexecuted instantiation: <partial_eq::Version as core::cmp::PartialOrd>::le
|
||||
------------------
|
||||
| Unexecuted instantiation: <partial_eq::Version as core::cmp::PartialOrd>::ge
|
||||
------------------
|
||||
| <partial_eq::Version as core::cmp::PartialOrd>::lt:
|
||||
| 4| 1|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
------------------
|
||||
5| |pub struct Version {
|
||||
6| | major: usize,
|
||||
7| 1| minor: usize,
|
||||
8| | patch: usize,
|
||||
9| |}
|
||||
10| |
|
||||
11| |impl Version {
|
||||
12| | pub fn new(major: usize, minor: usize, patch: usize) -> Self {
|
||||
13| 2| Self {
|
||||
14| 2| major,
|
||||
15| 2| minor,
|
||||
16| 2| patch,
|
||||
17| 2| }
|
||||
18| 2| }
|
||||
19| |}
|
||||
20| |
|
||||
21| 1|fn main() {
|
||||
22| 1| let version_3_2_1 = Version::new(3, 2, 1);
|
||||
23| 1| let version_3_3_0 = Version::new(3, 3, 0);
|
||||
24| 1|
|
||||
25| 1| println!("{:?} < {:?} = {}", version_3_2_1, version_3_3_0, version_3_2_1 < version_3_3_0);
|
||||
26| 1|}
|
||||
27| |
|
||||
28| |/*
|
||||
29| |
|
||||
30| |This test verifies a bug was fixed that otherwise generated this error:
|
||||
31| |
|
||||
32| |thread 'rustc' panicked at 'No counters provided the source_hash for function:
|
||||
33| | Instance {
|
||||
34| | def: Item(WithOptConstParam {
|
||||
35| | did: DefId(0:101 ~ autocfg[c44a]::version::{impl#2}::partial_cmp),
|
||||
36| | const_param_did: None
|
||||
37| | }),
|
||||
38| | substs: []
|
||||
39| | }'
|
||||
40| |The `PartialOrd` derived by `Version` happened to generate a MIR that generated coverage
|
||||
41| |without a code region associated with any `Counter`. Code regions were associated with at least
|
||||
42| |one expression, which is allowed, but the `function_source_hash` was only passed to the codegen
|
||||
43| |(coverage mapgen) phase from a `Counter`s code region. A new method was added to pass the
|
||||
44| |`function_source_hash` without a code region, if necessary.
|
||||
45| |
|
||||
46| |*/
|
||||
47| |
|
||||
48| |// FIXME(richkadel): It may be worth investigating why the coverage report for this test produces
|
||||
49| |// the following results:
|
||||
50| |
|
||||
51| |/*
|
||||
52| |
|
||||
53| |1. Why are their two counts below different characters (first and last) of `PartialOrd`, on line 17?
|
||||
54| |
|
||||
55| |2. Line 17 is counted twice, but the `::lt` instance shows a line count of 1? Is there a missing
|
||||
56| | line count with a different instance? Or was it really only called once?
|
||||
57| |
|
||||
58| |3. Line 20 shows another line count (of 1) for a line within a `struct` declaration (on only one of
|
||||
59| | its 3 fields). I doubt the specific field (`minor`) is relevant, but rather I suspect there's a
|
||||
60| | problem computing the file position here, for some reason.
|
||||
61| |
|
||||
62| |<snip>
|
||||
63| | 16| |
|
||||
64| | 17| 2|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
65| | ^1 ^1
|
||||
66| |------------------
|
||||
67| ||Unexecuted instantiation: <partial_eq_counter_without_region::Version as core::cmp::PartialOrd>::gt
|
||||
68| |------------------
|
||||
69| ||Unexecuted instantiation: <partial_eq_counter_without_region::Version as core::cmp::PartialOrd>::le
|
||||
70| |------------------
|
||||
71| ||Unexecuted instantiation: <partial_eq_counter_without_region::Version as core::cmp::PartialOrd>::ge
|
||||
72| |------------------
|
||||
73| ||<partial_eq_counter_without_region::Version as core::cmp::PartialOrd>::lt:
|
||||
74| || 17| 1|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
75| |------------------
|
||||
76| | 18| |pub struct Version {
|
||||
77| | 19| | major: usize,
|
||||
78| | 20| 1| minor: usize,
|
||||
79| | 21| | patch: usize,
|
||||
80| | 22| |}
|
||||
81| | 23| |
|
||||
82| | 24| |impl Version {
|
||||
83| | 25| | pub fn new(major: usize, minor: usize, patch: usize) -> Self {
|
||||
84| | 26| 2| Version {
|
||||
85| | 27| 2| major,
|
||||
86| | 28| 2| minor,
|
||||
87| | 29| 2| patch,
|
||||
88| | 30| 2| }
|
||||
89| | 31| 2| }
|
||||
90| | 32| |}
|
||||
91| | 33| |
|
||||
92| | 34| 1|fn main() {
|
||||
93| | 35| 1| let version_3_2_1 = Version::new(3, 2, 1);
|
||||
94| | 36| 1| let version_3_3_0 = Version::new(3, 3, 0);
|
||||
95| | 37| 1|
|
||||
96| | 38| 1| println!("{:?} < {:?} = {}", version_3_2_1, version_3_3_0, version_3_2_1 < version
|
||||
97| |_3_3_0);
|
||||
98| | 39| 1|}
|
||||
99| |*/
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
16| 1| 10
|
||||
17| 1| ;
|
||||
18| 1| }
|
||||
^0
|
||||
19| |
|
||||
20| | loop
|
||||
21| | {
|
||||
|
@ -31,6 +32,6 @@
|
|||
31| 10| -=
|
||||
32| 10| 1
|
||||
33| | ;
|
||||
34| | }
|
||||
34| 1| }
|
||||
35| 1|}
|
||||
|
||||
|
|
|
@ -10,22 +10,24 @@
|
|||
10| 1| if is_true {
|
||||
11| 1| countdown = 0;
|
||||
12| 1| }
|
||||
^0
|
||||
13| |
|
||||
14| 3| for
|
||||
15| 3| _
|
||||
14| | for
|
||||
15| 2| _
|
||||
16| | in
|
||||
17| 1| 0..2
|
||||
17| 3| 0..2
|
||||
18| | {
|
||||
19| | let z
|
||||
20| | ;
|
||||
21| | match
|
||||
22| 2| countdown
|
||||
23| 2| {
|
||||
24| 2| x
|
||||
25| 2| if
|
||||
23| | {
|
||||
24| 1| x
|
||||
25| | if
|
||||
26| 2| x
|
||||
27| 2| <
|
||||
28| 2| 1
|
||||
^1
|
||||
29| | =>
|
||||
30| 1| {
|
||||
31| 1| z = countdown
|
||||
|
@ -39,6 +41,6 @@
|
|||
39| | =>
|
||||
40| 1| {}
|
||||
41| | }
|
||||
42| | }
|
||||
42| 3| }
|
||||
43| 1|}
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
1| |fn main() {
|
||||
2| 1| if false {
|
||||
3| | loop {}
|
||||
4| | }
|
||||
5| 1|}
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue