diff --git a/Cargo.lock b/Cargo.lock index 4ebbb16442eaa..510a0150b5ae9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -703,6 +703,18 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "coverage-dump" +version = "0.1.0" +dependencies = [ + "anyhow", + "leb128", + "md-5", + "miniz_oxide", + "regex", + "rustc-demangle", +] + [[package]] name = "coverage_test_macros" version = "0.0.0" @@ -2009,6 +2021,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.147" diff --git a/Cargo.toml b/Cargo.toml index d2e84d5426f9f..9b11ae8744b4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ members = [ "src/tools/generate-windows-sys", "src/tools/rustdoc-gui-test", "src/tools/opt-dist", + "src/tools/coverage-dump", ] exclude = [ diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs index 7a82d05ce9ea2..763186a58bf9f 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs @@ -1,4 +1,4 @@ -use rustc_middle::mir::coverage::{CounterId, MappedExpressionIndex}; +use rustc_middle::mir::coverage::{CounterId, ExpressionId, Operand}; /// Must match the layout of `LLVMRustCounterKind`. #[derive(Copy, Clone, Debug)] @@ -30,11 +30,8 @@ pub struct Counter { } impl Counter { - /// Constructs a new `Counter` of kind `Zero`. For this `CounterKind`, the - /// `id` is not used. - pub fn zero() -> Self { - Self { kind: CounterKind::Zero, id: 0 } - } + /// A `Counter` of kind `Zero`. For this counter kind, the `id` is not used. + pub(crate) const ZERO: Self = Self { kind: CounterKind::Zero, id: 0 }; /// Constructs a new `Counter` of kind `CounterValueReference`. pub fn counter_value_reference(counter_id: CounterId) -> Self { @@ -42,20 +39,16 @@ impl Counter { } /// Constructs a new `Counter` of kind `Expression`. - pub fn expression(mapped_expression_index: MappedExpressionIndex) -> Self { - Self { kind: CounterKind::Expression, id: mapped_expression_index.into() } - } - - /// Returns true if the `Counter` kind is `Zero`. - pub fn is_zero(&self) -> bool { - matches!(self.kind, CounterKind::Zero) + pub(crate) fn expression(expression_id: ExpressionId) -> Self { + Self { kind: CounterKind::Expression, id: expression_id.as_u32() } } - /// An explicitly-named function to get the ID value, making it more obvious - /// that the stored value is now 0-based. - pub fn zero_based_id(&self) -> u32 { - debug_assert!(!self.is_zero(), "`id` is undefined for CounterKind::Zero"); - self.id + pub(crate) fn from_operand(operand: Operand) -> Self { + match operand { + Operand::Zero => Self::ZERO, + Operand::Counter(id) => Self::counter_value_reference(id), + Operand::Expression(id) => Self::expression(id), + } } } @@ -81,6 +74,11 @@ pub struct CounterExpression { } impl CounterExpression { + /// The dummy expression `(0 - 0)` has a representation of all zeroes, + /// making it marginally more efficient to initialize than `(0 + 0)`. + pub(crate) const DUMMY: Self = + Self { lhs: Counter::ZERO, kind: ExprKind::Subtract, rhs: Counter::ZERO }; + pub fn new(lhs: Counter, kind: ExprKind, rhs: Counter) -> Self { Self { kind, lhs, rhs } } @@ -172,7 +170,7 @@ impl CounterMappingRegion { ) -> Self { Self { counter, - false_counter: Counter::zero(), + false_counter: Counter::ZERO, file_id, expanded_file_id: 0, start_line, @@ -220,8 +218,8 @@ impl CounterMappingRegion { end_col: u32, ) -> Self { Self { - counter: Counter::zero(), - false_counter: Counter::zero(), + counter: Counter::ZERO, + false_counter: Counter::ZERO, file_id, expanded_file_id, start_line, @@ -243,8 +241,8 @@ impl CounterMappingRegion { end_col: u32, ) -> Self { Self { - counter: Counter::zero(), - false_counter: Counter::zero(), + counter: Counter::ZERO, + false_counter: Counter::ZERO, file_id, expanded_file_id: 0, start_line, @@ -268,7 +266,7 @@ impl CounterMappingRegion { ) -> Self { Self { counter, - false_counter: Counter::zero(), + false_counter: Counter::ZERO, file_id, expanded_file_id: 0, start_line, diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs index f1e68af25d406..942feb97dbbec 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs @@ -1,10 +1,8 @@ use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind}; -use rustc_index::{IndexSlice, IndexVec}; -use rustc_middle::bug; -use rustc_middle::mir::coverage::{ - CodeRegion, CounterId, ExpressionId, MappedExpressionIndex, Op, Operand, -}; +use rustc_data_structures::fx::FxHashMap; +use rustc_index::IndexVec; +use rustc_middle::mir::coverage::{CodeRegion, CounterId, ExpressionId, Op, Operand}; use rustc_middle::ty::Instance; use rustc_middle::ty::TyCtxt; @@ -128,6 +126,74 @@ impl<'tcx> FunctionCoverage<'tcx> { self.unreachable_regions.push(region) } + /// Perform some simplifications to make the final coverage mappings + /// slightly smaller. + pub(crate) fn simplify_expressions(&mut self) { + // The set of expressions that were simplified to either `Zero` or a + // `Counter`. Other expressions that refer to these as operands + // can then also be simplified. + let mut simplified_expressions = FxHashMap::default(); + + // For each expression, perform simplifications based on lower-numbered + // expressions, and then update the map of simplified expressions if + // necessary. + // (By construction, expressions can only refer to other expressions + // that have lower IDs, so one simplification pass is sufficient.) + for (id, maybe_expression) in self.expressions.iter_enumerated_mut() { + let Some(expression) = maybe_expression else { + // If an expression is missing, it must have been optimized away, + // so any operand that refers to it can be replaced with zero. + simplified_expressions.insert(id, Operand::Zero); + continue; + }; + + // If an operand refers to an expression that has been simplified, then + // replace that operand with the simplified version. + let maybe_simplify_operand = |operand: &mut Operand| { + if let Operand::Expression(id) = &*operand { + if let Some(simplified) = simplified_expressions.get(id) { + *operand = *simplified; + } + } + }; + + maybe_simplify_operand(&mut expression.lhs); + maybe_simplify_operand(&mut expression.rhs); + + // Coverage counter values cannot be negative, so if an expression + // involves subtraction from zero, assume that its RHS must also be zero. + // (Do this after simplifications that could set the LHS to zero.) + if let Expression { lhs: Operand::Zero, op: Op::Subtract, .. } = expression { + expression.rhs = Operand::Zero; + } + + // After the above simplifications, if the right hand operand is zero, + // we can replace the expression by its left hand side. + if let Expression { lhs, rhs: Operand::Zero, .. } = expression { + simplified_expressions.insert(id, *lhs); + } else + // And the same thing for the left hand side. + if let Expression { lhs: Operand::Zero, rhs, .. } = expression { + simplified_expressions.insert(id, *rhs); + } + } + } + + /// This will further simplify any expression, "inlining" the left hand side operand + /// if the right hand side is `Zero`. This is similar to `simplify_expressions` above, + /// but works for an already referenced expression. + fn simplified_expression(&self, id: ExpressionId) -> Counter { + if let Some(expr) = &self.expressions[id] { + if expr.rhs == Operand::Zero { + return Counter::from_operand(expr.lhs); + } + if expr.lhs == Operand::Zero { + return Counter::from_operand(expr.rhs); + } + } + Counter::expression(id) + } + /// Return the source hash, generated from the HIR node structure, and used to indicate whether /// or not the source code structure changed between different compilations. pub fn source_hash(&self) -> u64 { @@ -146,8 +212,14 @@ impl<'tcx> FunctionCoverage<'tcx> { self.instance ); + let counter_expressions = self.counter_expressions(); + // Expression IDs are indices into `self.expressions`, and on the LLVM + // side they will be treated as indices into `counter_expressions`, so + // the two vectors should correspond 1:1. + assert_eq!(self.expressions.len(), counter_expressions.len()); + let counter_regions = self.counter_regions(); - let (counter_expressions, expression_regions) = self.expressions_with_regions(); + let expression_regions = self.expression_regions(); let unreachable_regions = self.unreachable_regions(); let counter_regions = @@ -163,149 +235,50 @@ impl<'tcx> FunctionCoverage<'tcx> { }) } - fn expressions_with_regions( - &self, - ) -> (Vec, impl Iterator) { - 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(None, self.expressions.len()); - - // 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. - // - // 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. - // - // 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 `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`. - type NewIndexes = IndexSlice>; - let id_to_counter = |new_indexes: &NewIndexes, operand: Operand| match operand { - Operand::Zero => Some(Counter::zero()), - Operand::Counter(id) => Some(Counter::counter_value_reference(id)), - Operand::Expression(id) => { - self.expressions - .get(id) - .expect("expression id is out of range") - .as_ref() - // 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[id].map(Counter::expression)) - } - }; - - 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(|expression| (original_index, expression)) - }) - { - let optional_region = &expression.region; - let Expression { lhs, op, rhs, .. } = *expression; + /// Convert this function's coverage expression data into a form that can be + /// passed through FFI to LLVM. + fn counter_expressions(&self) -> Vec { + // We know that LLVM will optimize out any unused expressions before + // producing the final coverage map, so there's no need to do the same + // thing on the Rust side unless we're confident we can do much better. + // (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.) - if let Some(Some((lhs_counter, mut rhs_counter))) = id_to_counter(&new_indexes, lhs) - .map(|lhs_counter| { - id_to_counter(&new_indexes, rhs).map(|rhs_counter| (lhs_counter, rhs_counter)) - }) - { - if lhs_counter.is_zero() && op.is_subtract() { - // The left side of a subtraction was probably optimized out. As an example, - // a branch condition might be evaluated as a constant expression, and the - // branch could be removed, dropping unused counters in the process. - // - // Since counters are unsigned, we must assume the result of the expression - // can be no more and no less than zero. An expression known to evaluate to zero - // does not need to be added to the coverage map. - // - // Coverage test `loops_branches.rs` includes multiple variations of branches - // based on constant conditional (literal `true` or `false`), and demonstrates - // that the expected counts are still correct. - debug!( - "Expression subtracts from zero (assume unreachable): \ - original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}", - original_index, lhs, op, rhs, optional_region, - ); - rhs_counter = Counter::zero(); + self.expressions + .iter() + .map(|expression| match expression { + None => { + // This expression ID was allocated, but we never saw the + // actual expression, so it must have been optimized out. + // Replace it with a dummy expression, and let LLVM take + // care of omitting it from the expression list. + CounterExpression::DUMMY } - debug_assert!( - lhs_counter.is_zero() - // Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16` - || ((lhs_counter.zero_based_id() as usize) - <= usize::max(self.counters.len(), self.expressions.len())), - "lhs id={} > both counters.len()={} and expressions.len()={} - ({:?} {:?} {:?})", - lhs_counter.zero_based_id(), - self.counters.len(), - self.expressions.len(), - lhs_counter, - op, - rhs_counter, - ); - - debug_assert!( - rhs_counter.is_zero() - // Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16` - || ((rhs_counter.zero_based_id() as usize) - <= usize::max(self.counters.len(), self.expressions.len())), - "rhs id={} > both counters.len()={} and expressions.len()={} - ({:?} {:?} {:?})", - rhs_counter.zero_based_id(), - self.counters.len(), - self.expressions.len(), - lhs_counter, - op, - rhs_counter, - ); - - // Both operands exist. `Expression` operands exist in `self.expressions` and have - // been assigned a `new_index`. - let mapped_expression_index = - MappedExpressionIndex::from(counter_expressions.len()); - let expression = CounterExpression::new( - lhs_counter, + &Some(Expression { lhs, op, rhs, .. }) => CounterExpression::new( + Counter::from_operand(lhs), match op { Op::Add => ExprKind::Add, Op::Subtract => ExprKind::Subtract, }, - rhs_counter, - ); - debug!( - "Adding expression {:?} = {:?}, region: {:?}", - mapped_expression_index, expression, optional_region - ); - counter_expressions.push(expression); - new_indexes[original_index] = Some(mapped_expression_index); - if let Some(region) = optional_region { - expression_regions.push((Counter::expression(mapped_expression_index), region)); - } - } else { - bug!( - "expression has one or more missing operands \ - original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}", - original_index, - lhs, - op, - rhs, - optional_region, - ); - } - } - (counter_expressions, expression_regions.into_iter()) + Counter::from_operand(rhs), + ), + }) + .collect::>() + } + + fn expression_regions(&self) -> Vec<(Counter, &CodeRegion)> { + // Find all of the expression IDs that weren't optimized out AND have + // an attached code region, and return the corresponding mapping as a + // counter/region pair. + self.expressions + .iter_enumerated() + .filter_map(|(id, expression)| { + let code_region = expression.as_ref()?.region.as_ref()?; + Some((self.simplified_expression(id), code_region)) + }) + .collect::>() } fn unreachable_regions(&self) -> impl Iterator { - self.unreachable_regions.iter().map(|region| (Counter::zero(), region)) + self.unreachable_regions.iter().map(|region| (Counter::ZERO, region)) } } diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs index 97a99e5105675..d90ac44621d83 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs @@ -59,8 +59,10 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) { // Encode coverage mappings and generate function records let mut function_data = Vec::new(); - for (instance, function_coverage) in function_coverage_map { + for (instance, mut function_coverage) in function_coverage_map { debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance); + function_coverage.simplify_expressions(); + let mangled_function_name = tcx.symbol_name(instance).name; let source_hash = function_coverage.source_hash(); let is_used = function_coverage.is_used(); diff --git a/compiler/rustc_middle/src/mir/coverage.rs b/compiler/rustc_middle/src/mir/coverage.rs index 1efb54bdb0872..9ef6739229118 100644 --- a/compiler/rustc_middle/src/mir/coverage.rs +++ b/compiler/rustc_middle/src/mir/coverage.rs @@ -45,16 +45,6 @@ impl ExpressionId { } } -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. - #[derive(HashStable)] - #[max = 0xFFFF_FFFF] - #[debug_format = "MappedExpressionIndex({})"] - pub struct MappedExpressionIndex {} -} - /// Operand of a coverage-counter expression. /// /// Operands can be a constant zero value, an actual coverage counter, or another diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs index f979ddd00fa01..7e93a236308b8 100644 --- a/compiler/rustc_middle/src/ty/structural_impls.rs +++ b/compiler/rustc_middle/src/ty/structural_impls.rs @@ -469,7 +469,6 @@ TrivialTypeTraversalAndLiftImpls! { ::rustc_target::spec::abi::Abi, crate::mir::coverage::CounterId, crate::mir::coverage::ExpressionId, - crate::mir::coverage::MappedExpressionIndex, crate::mir::Local, crate::mir::Promoted, crate::traits::Reveal, diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index b366619285338..a24a6a4636d48 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -703,7 +703,8 @@ impl<'a> Builder<'a> { llvm::Lld, llvm::CrtBeginEnd, tool::RustdocGUITest, - tool::OptimizedDist + tool::OptimizedDist, + tool::CoverageDump, ), Kind::Check | Kind::Clippy | Kind::Fix => describe!( check::Std, @@ -725,6 +726,7 @@ impl<'a> Builder<'a> { test::Tidy, test::Ui, test::RunPassValgrind, + test::CoverageMap, test::RunCoverage, test::MirOpt, test::Codegen, diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index db3b7ffbea4e5..4e944334485fb 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -1335,6 +1335,12 @@ host_test!(RunMakeFullDeps { default_test!(Assembly { path: "tests/assembly", mode: "assembly", suite: "assembly" }); +default_test!(CoverageMap { + path: "tests/coverage-map", + mode: "coverage-map", + suite: "coverage-map" +}); + host_test!(RunCoverage { path: "tests/run-coverage", mode: "run-coverage", suite: "run-coverage" }); host_test!(RunCoverageRustdoc { path: "tests/run-coverage-rustdoc", @@ -1540,6 +1546,14 @@ note: if you're sure you want to do this, please open an issue as to why. In the .arg(builder.ensure(tool::JsonDocLint { compiler: json_compiler, target })); } + if mode == "coverage-map" { + let coverage_dump = builder.ensure(tool::CoverageDump { + compiler: compiler.with_stage(0), + target: compiler.host, + }); + cmd.arg("--coverage-dump-path").arg(coverage_dump); + } + if mode == "run-make" || mode == "run-coverage" { let rust_demangler = builder .ensure(tool::RustDemangler { diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 07ff3da6b4aa5..f094dd9d7c90b 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -306,6 +306,7 @@ bootstrap_tool!( GenerateWindowsSys, "src/tools/generate-windows-sys", "generate-windows-sys"; RustdocGUITest, "src/tools/rustdoc-gui-test", "rustdoc-gui-test", is_unstable_tool = true, allow_features = "test"; OptimizedDist, "src/tools/opt-dist", "opt-dist"; + CoverageDump, "src/tools/coverage-dump", "coverage-dump"; ); #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 7c17e92d0dfe2..b91d5a958bb62 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -66,6 +66,7 @@ string_enum! { JsDocTest => "js-doc-test", MirOpt => "mir-opt", Assembly => "assembly", + CoverageMap => "coverage-map", RunCoverage => "run-coverage", } } @@ -161,6 +162,9 @@ pub struct Config { /// The rust-demangler executable. pub rust_demangler_path: Option, + /// The coverage-dump executable. + pub coverage_dump_path: Option, + /// The Python executable to use for LLDB and htmldocck. pub python: String, @@ -639,6 +643,7 @@ pub const UI_EXTENSIONS: &[&str] = &[ UI_STDERR_32, UI_STDERR_16, UI_COVERAGE, + UI_COVERAGE_MAP, ]; pub const UI_STDERR: &str = "stderr"; pub const UI_STDOUT: &str = "stdout"; @@ -649,6 +654,7 @@ pub const UI_STDERR_64: &str = "64bit.stderr"; pub const UI_STDERR_32: &str = "32bit.stderr"; pub const UI_STDERR_16: &str = "16bit.stderr"; pub const UI_COVERAGE: &str = "coverage"; +pub const UI_COVERAGE_MAP: &str = "cov-map"; /// Absolute path to the directory where all output for all tests in the given /// `relative_dir` group should reside. Example: diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index 1a765477fe501..619ff9b322114 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -48,6 +48,7 @@ pub fn parse_config(args: Vec) -> Config { .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH") .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH") .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH") + .optopt("", "coverage-dump-path", "path to coverage-dump to use in tests", "PATH") .reqopt("", "python", "path to python to use for doc tests", "PATH") .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH") .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH") @@ -218,6 +219,7 @@ pub fn parse_config(args: Vec) -> Config { rustc_path: opt_path(matches, "rustc-path"), rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from), rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from), + coverage_dump_path: matches.opt_str("coverage-dump-path").map(PathBuf::from), python: matches.opt_str("python").unwrap(), jsondocck_path: matches.opt_str("jsondocck-path"), jsondoclint_path: matches.opt_str("jsondoclint-path"), diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 4ef79af3124a8..dcdba1fa8bb4b 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -6,8 +6,8 @@ use crate::common::{Assembly, Incremental, JsDocTest, MirOpt, RunMake, RustdocJs use crate::common::{Codegen, CodegenUnits, DebugInfo, Debugger, Rustdoc}; use crate::common::{CompareMode, FailMode, PassMode}; use crate::common::{Config, TestPaths}; -use crate::common::{Pretty, RunCoverage, RunPassValgrind}; -use crate::common::{UI_COVERAGE, UI_RUN_STDERR, UI_RUN_STDOUT}; +use crate::common::{CoverageMap, Pretty, RunCoverage, RunPassValgrind}; +use crate::common::{UI_COVERAGE, UI_COVERAGE_MAP, UI_RUN_STDERR, UI_RUN_STDOUT}; use crate::compute_diff::{write_diff, write_filtered_diff}; use crate::errors::{self, Error, ErrorKind}; use crate::header::TestProps; @@ -254,6 +254,7 @@ impl<'test> TestCx<'test> { MirOpt => self.run_mir_opt_test(), Assembly => self.run_assembly_test(), JsDocTest => self.run_js_doc_test(), + CoverageMap => self.run_coverage_map_test(), RunCoverage => self.run_coverage_test(), } } @@ -467,6 +468,46 @@ impl<'test> TestCx<'test> { } } + fn run_coverage_map_test(&self) { + let Some(coverage_dump_path) = &self.config.coverage_dump_path else { + self.fatal("missing --coverage-dump"); + }; + + let proc_res = self.compile_test_and_save_ir(); + if !proc_res.status.success() { + self.fatal_proc_rec("compilation failed!", &proc_res); + } + drop(proc_res); + + let llvm_ir_path = self.output_base_name().with_extension("ll"); + + let mut dump_command = Command::new(coverage_dump_path); + dump_command.arg(llvm_ir_path); + let proc_res = self.run_command_to_procres(&mut dump_command); + if !proc_res.status.success() { + self.fatal_proc_rec("coverage-dump failed!", &proc_res); + } + + let kind = UI_COVERAGE_MAP; + + let expected_coverage_dump = self.load_expected_output(kind); + let actual_coverage_dump = self.normalize_output(&proc_res.stdout, &[]); + + let coverage_dump_errors = self.compare_output( + kind, + &actual_coverage_dump, + &expected_coverage_dump, + self.props.compare_output_lines_by_subset, + ); + + if coverage_dump_errors > 0 { + self.fatal_proc_rec( + &format!("{coverage_dump_errors} errors occurred comparing coverage output."), + &proc_res, + ); + } + } + fn run_coverage_test(&self) { let should_run = self.run_if_enabled(); let proc_res = self.compile_test(should_run, Emit::None); @@ -650,6 +691,10 @@ impl<'test> TestCx<'test> { let mut cmd = Command::new(tool_path); configure_cmd_fn(&mut cmd); + self.run_command_to_procres(&mut cmd) + } + + fn run_command_to_procres(&self, cmd: &mut Command) -> ProcRes { let output = cmd.output().unwrap_or_else(|_| panic!("failed to exec `{cmd:?}`")); let proc_res = ProcRes { @@ -2399,6 +2444,12 @@ impl<'test> TestCx<'test> { rustc.arg(dir_opt); } + CoverageMap => { + rustc.arg("-Cinstrument-coverage"); + // These tests only compile to MIR, so they don't need the + // profiler runtime to be present. + rustc.arg("-Zno-profiler-runtime"); + } RunCoverage => { rustc.arg("-Cinstrument-coverage"); } diff --git a/src/tools/coverage-dump/Cargo.toml b/src/tools/coverage-dump/Cargo.toml new file mode 100644 index 0000000000000..7f14286b5d0c4 --- /dev/null +++ b/src/tools/coverage-dump/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "coverage-dump" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.71" +leb128 = "0.2.5" +md5 = { package = "md-5" , version = "0.10.5" } +miniz_oxide = "0.7.1" +regex = "1.8.4" +rustc-demangle = "0.1.23" diff --git a/src/tools/coverage-dump/README.md b/src/tools/coverage-dump/README.md new file mode 100644 index 0000000000000..e2625d5adf27e --- /dev/null +++ b/src/tools/coverage-dump/README.md @@ -0,0 +1,8 @@ +This tool extracts coverage mapping information from an LLVM IR assembly file +(`.ll`), and prints it in a more human-readable form that can be used for +snapshot tests. + +The output format is mostly arbitrary, so it's OK to change the output as long +as any affected tests are also re-blessed. However, the output should be +consistent across different executions on different platforms, so avoid +printing any information that is platform-specific or non-deterministic. diff --git a/src/tools/coverage-dump/src/covfun.rs b/src/tools/coverage-dump/src/covfun.rs new file mode 100644 index 0000000000000..d07ba3e82f051 --- /dev/null +++ b/src/tools/coverage-dump/src/covfun.rs @@ -0,0 +1,293 @@ +use crate::parser::{unescape_llvm_string_contents, Parser}; +use anyhow::{anyhow, Context}; +use regex::Regex; +use std::collections::HashMap; +use std::fmt::{self, Debug, Write as _}; +use std::sync::OnceLock; + +pub(crate) fn dump_covfun_mappings( + llvm_ir: &str, + function_names: &HashMap, +) -> anyhow::Result<()> { + // Extract function coverage entries from the LLVM IR assembly, and associate + // each entry with its (demangled) name. + let mut covfun_entries = llvm_ir + .lines() + .filter_map(covfun_line_data) + .map(|line_data| (function_names.get(&line_data.name_hash).map(String::as_str), line_data)) + .collect::>(); + covfun_entries.sort_by(|a, b| { + // Sort entries primarily by name, to help make the order consistent + // across platforms and relatively insensitive to changes. + // (Sadly we can't use `sort_by_key` because we would need to return references.) + Ord::cmp(&a.0, &b.0) + .then_with(|| Ord::cmp(&a.1.is_used, &b.1.is_used)) + .then_with(|| Ord::cmp(a.1.payload.as_slice(), b.1.payload.as_slice())) + }); + + for (name, line_data) in &covfun_entries { + let name = name.unwrap_or("(unknown)"); + let unused = if line_data.is_used { "" } else { " (unused)" }; + println!("Function name: {name}{unused}"); + + let payload: &[u8] = &line_data.payload; + println!("Raw bytes ({len}): 0x{payload:02x?}", len = payload.len()); + + let mut parser = Parser::new(payload); + + let num_files = parser.read_uleb128_u32()?; + println!("Number of files: {num_files}"); + + for i in 0..num_files { + let global_file_id = parser.read_uleb128_u32()?; + println!("- file {i} => global file {global_file_id}"); + } + + let num_expressions = parser.read_uleb128_u32()?; + println!("Number of expressions: {num_expressions}"); + + let mut expression_resolver = ExpressionResolver::new(); + for i in 0..num_expressions { + let lhs = parser.read_simple_operand()?; + let rhs = parser.read_simple_operand()?; + println!("- expression {i} operands: lhs = {lhs:?}, rhs = {rhs:?}"); + expression_resolver.push_operands(lhs, rhs); + } + + for i in 0..num_files { + let num_mappings = parser.read_uleb128_u32()?; + println!("Number of file {i} mappings: {num_mappings}"); + + for _ in 0..num_mappings { + let (kind, region) = parser.read_mapping_kind_and_region()?; + println!("- {kind:?} at {region:?}"); + + match kind { + // Also print expression mappings in resolved form. + MappingKind::Code(operand @ Operand::Expression { .. }) + | MappingKind::Gap(operand @ Operand::Expression { .. }) => { + println!(" = {}", expression_resolver.format_operand(operand)); + } + // If the mapping is a branch region, print both of its arms + // in resolved form (even if they aren't expressions). + MappingKind::Branch { r#true, r#false } => { + println!(" true = {}", expression_resolver.format_operand(r#true)); + println!(" false = {}", expression_resolver.format_operand(r#false)); + } + _ => (), + } + } + } + + parser.ensure_empty()?; + println!(); + } + Ok(()) +} + +struct CovfunLineData { + name_hash: u64, + is_used: bool, + payload: Vec, +} + +/// Checks a line of LLVM IR assembly to see if it contains an `__llvm_covfun` +/// entry, and if so extracts relevant data in a `CovfunLineData`. +fn covfun_line_data(line: &str) -> Option { + let re = { + // We cheat a little bit and match variable names `@__covrec_[HASH]u` + // rather than the section name, because the section name is harder to + // extract and differs across Linux/Windows/macOS. We also extract the + // symbol name hash from the variable name rather than the data, since + // it's easier and both should match. + static RE: OnceLock = OnceLock::new(); + RE.get_or_init(|| { + Regex::new( + r#"^@__covrec_(?[0-9A-Z]+)(?u)? = .*\[[0-9]+ x i8\] c"(?[^"]*)".*$"#, + ) + .unwrap() + }) + }; + + let captures = re.captures(line)?; + let name_hash = u64::from_str_radix(&captures["name_hash"], 16).unwrap(); + let is_used = captures.name("is_used").is_some(); + let payload = unescape_llvm_string_contents(&captures["payload"]); + + Some(CovfunLineData { name_hash, is_used, payload }) +} + +// Extra parser methods only needed when parsing `covfun` payloads. +impl<'a> Parser<'a> { + fn read_simple_operand(&mut self) -> anyhow::Result { + let raw_operand = self.read_uleb128_u32()?; + Operand::decode(raw_operand).context("decoding operand") + } + + fn read_mapping_kind_and_region(&mut self) -> anyhow::Result<(MappingKind, MappingRegion)> { + let mut kind = self.read_raw_mapping_kind()?; + let mut region = self.read_raw_mapping_region()?; + + const HIGH_BIT: u32 = 1u32 << 31; + if region.end_column & HIGH_BIT != 0 { + region.end_column &= !HIGH_BIT; + kind = match kind { + MappingKind::Code(operand) => MappingKind::Gap(operand), + // LLVM's coverage mapping reader will actually handle this + // case without complaint, but the result is almost certainly + // a meaningless implementation artifact. + _ => return Err(anyhow!("unexpected base kind for gap region: {kind:?}")), + } + } + + Ok((kind, region)) + } + + fn read_raw_mapping_kind(&mut self) -> anyhow::Result { + let raw_mapping_kind = self.read_uleb128_u32()?; + if let Some(operand) = Operand::decode(raw_mapping_kind) { + return Ok(MappingKind::Code(operand)); + } + + assert_eq!(raw_mapping_kind & 0b11, 0); + assert_ne!(raw_mapping_kind, 0); + + let (high, is_expansion) = (raw_mapping_kind >> 3, raw_mapping_kind & 0b100 != 0); + if is_expansion { + Ok(MappingKind::Expansion(high)) + } else { + match high { + 0 => unreachable!("zero kind should have already been handled as a code mapping"), + 2 => Ok(MappingKind::Skip), + 4 => { + let r#true = self.read_simple_operand()?; + let r#false = self.read_simple_operand()?; + Ok(MappingKind::Branch { r#true, r#false }) + } + _ => Err(anyhow!("unknown mapping kind: {raw_mapping_kind:#x}")), + } + } + } + + fn read_raw_mapping_region(&mut self) -> anyhow::Result { + let start_line_offset = self.read_uleb128_u32()?; + let start_column = self.read_uleb128_u32()?; + let end_line_offset = self.read_uleb128_u32()?; + let end_column = self.read_uleb128_u32()?; + Ok(MappingRegion { start_line_offset, start_column, end_line_offset, end_column }) + } +} + +// Represents an expression operand (lhs/rhs), branch region operand (true/false), +// or the value used by a code region or gap region. +#[derive(Clone, Copy, Debug)] +pub(crate) enum Operand { + Zero, + Counter(u32), + Expression(u32, Op), +} + +/// Operator (addition or subtraction) used by an expression. +#[derive(Clone, Copy, Debug)] +pub(crate) enum Op { + Sub, + Add, +} + +impl Operand { + pub(crate) fn decode(input: u32) -> Option { + let (high, tag) = (input >> 2, input & 0b11); + match tag { + 0b00 if high == 0 => Some(Self::Zero), + 0b01 => Some(Self::Counter(high)), + 0b10 => Some(Self::Expression(high, Op::Sub)), + 0b11 => Some(Self::Expression(high, Op::Add)), + // When reading expression or branch operands, the LLVM coverage + // mapping reader will always interpret a `0b00` tag as a zero + // operand, even when the high bits are non-zero. + // We treat that case as failure instead, so that this code can be + // shared by the full mapping-kind reader as well. + _ => None, + } + } +} + +#[derive(Debug)] +enum MappingKind { + Code(Operand), + Gap(Operand), + Expansion(u32), + Skip, + // Using raw identifiers here makes the dump output a little bit nicer + // (via the derived Debug), at the expense of making this tool's source + // code a little bit uglier. + Branch { r#true: Operand, r#false: Operand }, +} + +struct MappingRegion { + /// Offset of this region's start line, relative to the *start line* of + /// the *previous mapping* (or 0). Line numbers are 1-based. + start_line_offset: u32, + /// This region's start column, absolute and 1-based. + start_column: u32, + /// Offset of this region's end line, relative to the *this mapping's* + /// start line. Line numbers are 1-based. + end_line_offset: u32, + /// This region's end column, absolute, 1-based, and exclusive. + /// + /// If the highest bit is set, that bit is cleared and the associated + /// mapping becomes a gap region mapping. + end_column: u32, +} + +impl Debug for MappingRegion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "(prev + {}, {}) to (start + {}, {})", + self.start_line_offset, self.start_column, self.end_line_offset, self.end_column + ) + } +} + +/// Helper type that prints expressions in a "resolved" form, so that +/// developers reading the dump don't need to resolve expressions by hand. +struct ExpressionResolver { + operands: Vec<(Operand, Operand)>, +} + +impl ExpressionResolver { + fn new() -> Self { + Self { operands: Vec::new() } + } + + fn push_operands(&mut self, lhs: Operand, rhs: Operand) { + self.operands.push((lhs, rhs)); + } + + fn format_operand(&self, operand: Operand) -> String { + let mut output = String::new(); + self.write_operand(&mut output, operand); + output + } + + fn write_operand(&self, output: &mut String, operand: Operand) { + match operand { + Operand::Zero => output.push_str("Zero"), + Operand::Counter(id) => write!(output, "c{id}").unwrap(), + Operand::Expression(id, op) => { + let (lhs, rhs) = self.operands[id as usize]; + let op = match op { + Op::Sub => "-", + Op::Add => "+", + }; + + output.push('('); + self.write_operand(output, lhs); + write!(output, " {op} ").unwrap(); + self.write_operand(output, rhs); + output.push(')'); + } + } + } +} diff --git a/src/tools/coverage-dump/src/main.rs b/src/tools/coverage-dump/src/main.rs new file mode 100644 index 0000000000000..93fed1799e041 --- /dev/null +++ b/src/tools/coverage-dump/src/main.rs @@ -0,0 +1,17 @@ +mod covfun; +mod parser; +mod prf_names; + +fn main() -> anyhow::Result<()> { + use anyhow::Context as _; + + let args = std::env::args().collect::>(); + + let llvm_ir_path = args.get(1).context("LLVM IR file not specified")?; + let llvm_ir = std::fs::read_to_string(llvm_ir_path).context("couldn't read LLVM IR file")?; + + let function_names = crate::prf_names::make_function_names_table(&llvm_ir)?; + crate::covfun::dump_covfun_mappings(&llvm_ir, &function_names)?; + + Ok(()) +} diff --git a/src/tools/coverage-dump/src/parser.rs b/src/tools/coverage-dump/src/parser.rs new file mode 100644 index 0000000000000..eefac1a4f94c1 --- /dev/null +++ b/src/tools/coverage-dump/src/parser.rs @@ -0,0 +1,80 @@ +#[cfg(test)] +mod tests; + +use anyhow::ensure; +use regex::bytes; +use std::sync::OnceLock; + +/// Given the raw contents of a string literal in LLVM IR assembly, decodes any +/// backslash escapes and returns a vector containing the resulting byte string. +pub(crate) fn unescape_llvm_string_contents(contents: &str) -> Vec { + let escape_re = { + static RE: OnceLock = OnceLock::new(); + // LLVM IR supports two string escapes: `\\` and `\xx`. + RE.get_or_init(|| bytes::Regex::new(r"\\\\|\\([0-9A-Za-z]{2})").unwrap()) + }; + + fn u8_from_hex_digits(digits: &[u8]) -> u8 { + // We know that the input contains exactly 2 hex digits, so these calls + // should never fail. + assert_eq!(digits.len(), 2); + let digits = std::str::from_utf8(digits).unwrap(); + u8::from_str_radix(digits, 16).unwrap() + } + + escape_re + .replace_all(contents.as_bytes(), |captures: &bytes::Captures<'_>| { + let byte = match captures.get(1) { + None => b'\\', + Some(hex_digits) => u8_from_hex_digits(hex_digits.as_bytes()), + }; + [byte] + }) + .into_owned() +} + +pub(crate) struct Parser<'a> { + rest: &'a [u8], +} + +impl<'a> Parser<'a> { + pub(crate) fn new(input: &'a [u8]) -> Self { + Self { rest: input } + } + + pub(crate) fn ensure_empty(self) -> anyhow::Result<()> { + ensure!(self.rest.is_empty(), "unparsed bytes: 0x{:02x?}", self.rest); + Ok(()) + } + + pub(crate) fn read_n_bytes(&mut self, n: usize) -> anyhow::Result<&'a [u8]> { + ensure!(n <= self.rest.len()); + + let (bytes, rest) = self.rest.split_at(n); + self.rest = rest; + Ok(bytes) + } + + pub(crate) fn read_uleb128_u32(&mut self) -> anyhow::Result { + self.read_uleb128_u64_and_convert() + } + + pub(crate) fn read_uleb128_usize(&mut self) -> anyhow::Result { + self.read_uleb128_u64_and_convert() + } + + fn read_uleb128_u64_and_convert(&mut self) -> anyhow::Result + where + T: TryFrom + 'static, + T::Error: std::error::Error + Send + Sync, + { + let mut temp_rest = self.rest; + let raw_value: u64 = leb128::read::unsigned(&mut temp_rest)?; + let converted_value = T::try_from(raw_value)?; + + // Only update `self.rest` if the above steps succeeded, so that the + // parser position can be used for error reporting if desired. + self.rest = temp_rest; + Ok(converted_value) + } +} diff --git a/src/tools/coverage-dump/src/parser/tests.rs b/src/tools/coverage-dump/src/parser/tests.rs new file mode 100644 index 0000000000000..a673606b9c4c8 --- /dev/null +++ b/src/tools/coverage-dump/src/parser/tests.rs @@ -0,0 +1,38 @@ +use super::unescape_llvm_string_contents; + +// WARNING: These tests don't necessarily run in CI, and were mainly used to +// help track down problems when originally developing this tool. +// (The tool is still tested indirectly by snapshot tests that rely on it.) + +// Tests for `unescape_llvm_string_contents`: + +#[test] +fn unescape_empty() { + assert_eq!(unescape_llvm_string_contents(""), &[]); +} + +#[test] +fn unescape_noop() { + let input = "The quick brown fox jumps over the lazy dog."; + assert_eq!(unescape_llvm_string_contents(input), input.as_bytes()); +} + +#[test] +fn unescape_backslash() { + let input = r"\\Hello\\world\\"; + assert_eq!(unescape_llvm_string_contents(input), r"\Hello\world\".as_bytes()); +} + +#[test] +fn unescape_hex() { + let input = r"\01\02\03\04\0a\0b\0C\0D\fd\fE\FF"; + let expected: &[u8] = &[0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d, 0xfd, 0xfe, 0xff]; + assert_eq!(unescape_llvm_string_contents(input), expected); +} + +#[test] +fn unescape_mixed() { + let input = r"\\01.\5c\5c"; + let expected: &[u8] = br"\01.\\"; + assert_eq!(unescape_llvm_string_contents(input), expected); +} diff --git a/src/tools/coverage-dump/src/prf_names.rs b/src/tools/coverage-dump/src/prf_names.rs new file mode 100644 index 0000000000000..2c0c942e630ce --- /dev/null +++ b/src/tools/coverage-dump/src/prf_names.rs @@ -0,0 +1,85 @@ +use crate::parser::{unescape_llvm_string_contents, Parser}; +use anyhow::{anyhow, ensure}; +use regex::Regex; +use std::collections::HashMap; +use std::sync::OnceLock; + +/// Scans through the contents of an LLVM IR assembly file to find `__llvm_prf_names` +/// entries, decodes them, and creates a table that maps name hash values to +/// (demangled) function names. +pub(crate) fn make_function_names_table(llvm_ir: &str) -> anyhow::Result> { + fn prf_names_payload(line: &str) -> Option<&str> { + let re = { + // We cheat a little bit and match the variable name `@__llvm_prf_nm` + // rather than the section name, because the section name is harder + // to extract and differs across Linux/Windows/macOS. + static RE: OnceLock = OnceLock::new(); + RE.get_or_init(|| { + Regex::new(r#"^@__llvm_prf_nm =.*\[[0-9]+ x i8\] c"([^"]*)".*$"#).unwrap() + }) + }; + + let payload = re.captures(line)?.get(1).unwrap().as_str(); + Some(payload) + } + + /// LLVM's profiler/coverage metadata often uses an MD5 hash truncated to + /// 64 bits as a way to associate data stored in different tables/sections. + fn truncated_md5(bytes: &[u8]) -> u64 { + use md5::{Digest, Md5}; + let mut hasher = Md5::new(); + hasher.update(bytes); + let hash: [u8; 8] = hasher.finalize().as_slice()[..8].try_into().unwrap(); + u64::from_le_bytes(hash) + } + + fn demangle_if_able(symbol_name_bytes: &[u8]) -> anyhow::Result { + // In practice, raw symbol names should always be ASCII. + let symbol_name_str = std::str::from_utf8(symbol_name_bytes)?; + match rustc_demangle::try_demangle(symbol_name_str) { + Ok(d) => Ok(format!("{d:#}")), + // If demangling failed, don't treat it as an error. This lets us + // run the dump tool against non-Rust coverage maps produced by + // `clang`, for testing purposes. + Err(_) => Ok(format!("(couldn't demangle) {symbol_name_str}")), + } + } + + let mut map = HashMap::new(); + + for payload in llvm_ir.lines().filter_map(prf_names_payload).map(unescape_llvm_string_contents) + { + let mut parser = Parser::new(&payload); + let uncompressed_len = parser.read_uleb128_usize()?; + let compressed_len = parser.read_uleb128_usize()?; + + let uncompressed_bytes_vec; + let uncompressed_bytes: &[u8] = if compressed_len == 0 { + // The symbol name bytes are uncompressed, so read them directly. + parser.read_n_bytes(uncompressed_len)? + } else { + // The symbol name bytes are compressed, so read and decompress them. + let compressed_bytes = parser.read_n_bytes(compressed_len)?; + + uncompressed_bytes_vec = miniz_oxide::inflate::decompress_to_vec_zlib_with_limit( + compressed_bytes, + uncompressed_len, + ) + .map_err(|e| anyhow!("{e:?}"))?; + ensure!(uncompressed_bytes_vec.len() == uncompressed_len); + + &uncompressed_bytes_vec + }; + + // Symbol names in the payload are separated by `0x01` bytes. + for raw_name in uncompressed_bytes.split(|&b| b == 0x01) { + let hash = truncated_md5(raw_name); + let demangled = demangle_if_able(raw_name)?; + map.insert(hash, demangled); + } + + parser.ensure_empty()?; + } + + Ok(map) +} diff --git a/tests/coverage-map/if.cov-map b/tests/coverage-map/if.cov-map new file mode 100644 index 0000000000000..3cedb5ffbecb1 --- /dev/null +++ b/tests/coverage-map/if.cov-map @@ -0,0 +1,15 @@ +Function name: if::main +Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 03, 01, 02, 0c, 05, 02, 0d, 02, 06, 02, 02, 06, 00, 07, 07, 01, 05, 01, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 2 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub) +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 3, 1) to (start + 2, 12) +- Code(Counter(1)) at (prev + 2, 13) to (start + 2, 6) +- Code(Expression(0, Sub)) at (prev + 2, 6) to (start + 0, 7) + = (c0 - c1) +- Code(Expression(1, Add)) at (prev + 1, 5) to (start + 1, 2) + = (c1 + (c0 - c1)) + diff --git a/tests/coverage-map/if.rs b/tests/coverage-map/if.rs new file mode 100644 index 0000000000000..ed3f69bdc98d2 --- /dev/null +++ b/tests/coverage-map/if.rs @@ -0,0 +1,9 @@ +// compile-flags: --edition=2021 + +fn main() { + let cond = std::env::args().len() == 1; + if cond { + println!("true"); + } + println!("done"); +} diff --git a/tests/coverage-map/long_and_wide.cov-map b/tests/coverage-map/long_and_wide.cov-map new file mode 100644 index 0000000000000..97aebf9b18ab0 --- /dev/null +++ b/tests/coverage-map/long_and_wide.cov-map @@ -0,0 +1,32 @@ +Function name: long_and_wide::far_function +Raw bytes (10): 0x[01, 01, 00, 01, 01, 96, 01, 01, 00, 15] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 150, 1) to (start + 0, 21) + +Function name: long_and_wide::long_function +Raw bytes (10): 0x[01, 01, 00, 01, 01, 10, 01, 84, 01, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 16, 1) to (start + 132, 2) + +Function name: long_and_wide::main +Raw bytes (9): 0x[01, 01, 00, 01, 01, 07, 01, 04, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 7, 1) to (start + 4, 2) + +Function name: long_and_wide::wide_function +Raw bytes (10): 0x[01, 01, 00, 01, 01, 0e, 01, 00, 8b, 01] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 14, 1) to (start + 0, 139) + diff --git a/tests/coverage-map/long_and_wide.rs b/tests/coverage-map/long_and_wide.rs new file mode 100644 index 0000000000000..a7cbcd4802791 --- /dev/null +++ b/tests/coverage-map/long_and_wide.rs @@ -0,0 +1,150 @@ +// compile-flags: --edition=2021 +// ignore-tidy-linelength + +// This file deliberately contains line and column numbers larger than 127, +// to verify that `coverage-dump`'s ULEB128 parser can handle them. + +fn main() { + wide_function(); + long_function(); + far_function(); +} + +#[rustfmt::skip] +fn wide_function() { /* */ (); } + +fn long_function() { + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // +} + +fn far_function() {} diff --git a/tests/coverage-map/status-quo/README.md b/tests/coverage-map/status-quo/README.md new file mode 100644 index 0000000000000..c3b9f16ac54ff --- /dev/null +++ b/tests/coverage-map/status-quo/README.md @@ -0,0 +1,9 @@ +The tests in this directory were copied from `tests/run-coverage` in order to +capture the current behavior of the instrumentor on non-trivial programs. +The actual mappings have not been closely inspected. + +## Maintenance note + +If a MIR optimization or LLVM upgrade causes these tests to fail, +it should usually be OK to just `--bless` them, +as long as the `run-coverage` test suite still works. diff --git a/tests/coverage-map/status-quo/async2.cov-map b/tests/coverage-map/status-quo/async2.cov-map new file mode 100644 index 0000000000000..773daa4feaaa1 --- /dev/null +++ b/tests/coverage-map/status-quo/async2.cov-map @@ -0,0 +1,130 @@ +Function name: async2::async_func +Raw bytes (9): 0x[01, 01, 00, 01, 01, 14, 01, 00, 17] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 20, 1) to (start + 0, 23) + +Function name: async2::async_func::{closure#0} +Raw bytes (28): 0x[01, 01, 02, 01, 05, 05, 02, 04, 01, 14, 17, 03, 09, 05, 03, 0a, 02, 06, 02, 02, 06, 00, 07, 07, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 2 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Counter(1), rhs = Expression(0, Sub) +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 20, 23) to (start + 3, 9) +- Code(Counter(1)) at (prev + 3, 10) to (start + 2, 6) +- Code(Expression(0, Sub)) at (prev + 2, 6) to (start + 0, 7) + = (c0 - c1) +- Code(Expression(1, Add)) at (prev + 1, 1) to (start + 0, 2) + = (c1 + (c0 - c1)) + +Function name: async2::async_func_just_println +Raw bytes (9): 0x[01, 01, 00, 01, 01, 1f, 01, 00, 24] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 31, 1) to (start + 0, 36) + +Function name: async2::async_func_just_println::{closure#0} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 1f, 24, 02, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 31, 36) to (start + 2, 2) + +Function name: async2::executor::block_on:: +Raw bytes (40): 0x[01, 01, 03, 0b, 05, 01, 05, 01, 05, 06, 01, 33, 05, 0a, 36, 02, 0d, 20, 00, 23, 0b, 00, 27, 00, 49, 02, 01, 17, 00, 1a, 05, 01, 0e, 00, 0f, 02, 02, 05, 00, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 3 +- expression 0 operands: lhs = Expression(2, Add), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Counter(1) +- expression 2 operands: lhs = Counter(0), rhs = Counter(1) +Number of file 0 mappings: 6 +- Code(Counter(0)) at (prev + 51, 5) to (start + 10, 54) +- Code(Expression(0, Sub)) at (prev + 13, 32) to (start + 0, 35) + = ((c0 + c1) - c1) +- Code(Expression(2, Add)) at (prev + 0, 39) to (start + 0, 73) + = (c0 + c1) +- Code(Expression(0, Sub)) at (prev + 1, 23) to (start + 0, 26) + = ((c0 + c1) - c1) +- Code(Counter(1)) at (prev + 1, 14) to (start + 0, 15) +- Code(Expression(0, Sub)) at (prev + 2, 5) to (start + 0, 6) + = ((c0 + c1) - c1) + +Function name: async2::executor::block_on:: +Raw bytes (40): 0x[01, 01, 03, 0b, 05, 01, 05, 01, 05, 06, 01, 33, 05, 0a, 36, 02, 0d, 20, 00, 23, 0b, 00, 27, 00, 49, 02, 01, 17, 00, 1a, 05, 01, 0e, 00, 0f, 02, 02, 05, 00, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 3 +- expression 0 operands: lhs = Expression(2, Add), rhs = Counter(1) +- expression 1 operands: lhs = Counter(0), rhs = Counter(1) +- expression 2 operands: lhs = Counter(0), rhs = Counter(1) +Number of file 0 mappings: 6 +- Code(Counter(0)) at (prev + 51, 5) to (start + 10, 54) +- Code(Expression(0, Sub)) at (prev + 13, 32) to (start + 0, 35) + = ((c0 + c1) - c1) +- Code(Expression(2, Add)) at (prev + 0, 39) to (start + 0, 73) + = (c0 + c1) +- Code(Expression(0, Sub)) at (prev + 1, 23) to (start + 0, 26) + = ((c0 + c1) - c1) +- Code(Counter(1)) at (prev + 1, 14) to (start + 0, 15) +- Code(Expression(0, Sub)) at (prev + 2, 5) to (start + 0, 6) + = ((c0 + c1) - c1) + +Function name: async2::executor::block_on::VTABLE::{closure#0} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 37, 11, 00, 33] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 55, 17) to (start + 0, 51) + +Function name: async2::executor::block_on::VTABLE::{closure#1} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 38, 11, 00, 33] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 56, 17) to (start + 0, 51) + +Function name: async2::executor::block_on::VTABLE::{closure#2} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 39, 11, 00, 33] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 57, 17) to (start + 0, 51) + +Function name: async2::executor::block_on::VTABLE::{closure#3} +Raw bytes (9): 0x[01, 01, 00, 01, 01, 3a, 11, 00, 13] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 58, 17) to (start + 0, 19) + +Function name: async2::main +Raw bytes (9): 0x[01, 01, 00, 01, 01, 23, 01, 07, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 35, 1) to (start + 7, 2) + +Function name: async2::non_async_func +Raw bytes (24): 0x[01, 01, 00, 04, 01, 09, 01, 03, 09, 05, 03, 0a, 02, 06, 00, 02, 06, 00, 07, 05, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 9, 1) to (start + 3, 9) +- Code(Counter(1)) at (prev + 3, 10) to (start + 2, 6) +- Code(Zero) at (prev + 2, 6) to (start + 0, 7) +- Code(Counter(1)) at (prev + 1, 1) to (start + 0, 2) + diff --git a/tests/coverage-map/status-quo/async2.rs b/tests/coverage-map/status-quo/async2.rs new file mode 100644 index 0000000000000..959d48ce9db16 --- /dev/null +++ b/tests/coverage-map/status-quo/async2.rs @@ -0,0 +1,69 @@ +// compile-flags: --edition=2018 + +use core::{ + future::Future, + marker::Send, + pin::Pin, +}; + +fn non_async_func() { + println!("non_async_func was covered"); + let b = true; + if b { + println!("non_async_func println in block"); + } +} + + + + +async fn async_func() { + println!("async_func was covered"); + let b = true; + if b { + println!("async_func println in block"); + } +} + + + + +async fn async_func_just_println() { + println!("async_func_just_println was covered"); +} + +fn main() { + println!("codecovsample::main"); + + non_async_func(); + + executor::block_on(async_func()); + executor::block_on(async_func_just_println()); +} + +mod executor { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + }; + + pub fn block_on(mut future: F) -> F::Output { + let mut future = unsafe { Pin::new_unchecked(&mut future) }; + use std::hint::unreachable_unchecked; + static VTABLE: RawWakerVTable = RawWakerVTable::new( + |_| unsafe { unreachable_unchecked() }, // clone + |_| unsafe { unreachable_unchecked() }, // wake + |_| unsafe { unreachable_unchecked() }, // wake_by_ref + |_| (), + ); + let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + let mut context = Context::from_waker(&waker); + + loop { + if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + break val; + } + } + } +} diff --git a/tests/coverage-map/status-quo/conditions.cov-map b/tests/coverage-map/status-quo/conditions.cov-map new file mode 100644 index 0000000000000..844a625cf4723 --- /dev/null +++ b/tests/coverage-map/status-quo/conditions.cov-map @@ -0,0 +1,163 @@ +Function name: conditions::main +Raw bytes (503): 0x[01, 01, 3c, 09, 13, 2d, 31, 05, 09, 0d, 39, 2d, 31, ee, 01, 0d, 05, 09, 35, 3d, 32, 29, 35, 3d, 2e, 51, 32, 29, 35, 3d, 45, 49, 3d, 3f, 45, 49, 55, 59, 56, 25, 55, 59, 52, 79, 56, 25, 55, 59, 5d, 61, 59, 63, 5d, 61, 6d, c7, 01, 71, 75, 65, 6d, 82, 01, 21, 65, 6d, 7e, 7d, 82, 01, 21, 65, 6d, 71, 75, 11, d7, 01, 15, 19, 6d, c7, 01, 71, 75, c3, 01, 11, 6d, c7, 01, 71, 75, be, 01, 1d, c3, 01, 11, 6d, c7, 01, 71, 75, ba, 01, 89, 01, be, 01, 1d, c3, 01, 11, 6d, c7, 01, 71, 75, 15, 19, d3, 01, db, 01, 11, d7, 01, 15, 19, df, 01, ea, 01, e3, 01, e7, 01, 1d, 21, 25, 29, ee, 01, 0d, 05, 09, 44, 01, 03, 01, 02, 0c, 05, 02, 0d, 02, 06, 00, 02, 06, 00, 07, 03, 03, 09, 00, 0a, 05, 00, 10, 00, 1d, 09, 01, 09, 01, 0a, ee, 01, 02, 0f, 00, 1c, 0d, 01, 0c, 00, 19, 0e, 00, 1d, 00, 2a, 41, 00, 2e, 00, 3c, 2d, 00, 3d, 02, 0a, 31, 02, 0a, 00, 0b, 13, 01, 09, 01, 12, ea, 01, 03, 09, 00, 0f, 03, 03, 09, 01, 0c, 35, 01, 0d, 02, 06, 00, 02, 06, 00, 07, 35, 02, 08, 00, 15, 3d, 00, 16, 02, 06, 32, 02, 0f, 00, 1c, 2e, 01, 0c, 00, 19, 2a, 00, 1d, 00, 2a, 69, 00, 2e, 00, 3c, 45, 00, 3d, 02, 0a, 49, 02, 0a, 00, 0b, 3f, 01, 09, 00, 17, 29, 02, 09, 00, 0f, 3b, 03, 08, 00, 0c, 4d, 01, 0d, 01, 10, 55, 01, 11, 02, 0a, 00, 02, 0a, 00, 0b, 55, 02, 0c, 00, 19, 59, 00, 1a, 02, 0a, 56, 03, 11, 00, 1e, 52, 01, 10, 00, 1d, 4e, 00, 21, 00, 2e, 81, 01, 00, 32, 00, 40, 5d, 00, 41, 02, 0e, 61, 02, 0e, 00, 0f, 63, 01, 0d, 00, 1b, 25, 02, 0d, 00, 13, 00, 02, 06, 00, 07, 5f, 03, 09, 01, 0c, 65, 01, 0d, 02, 06, 00, 02, 06, 00, 07, c3, 01, 02, 09, 00, 0a, 65, 00, 10, 00, 1d, 6d, 00, 1e, 02, 06, 82, 01, 02, 0f, 00, 1c, 7e, 01, 0c, 00, 19, 7a, 00, 1d, 00, 2a, 85, 01, 00, 2e, 00, 3c, 71, 00, 3d, 02, 0a, 75, 02, 0a, 00, 0b, c7, 01, 01, 09, 00, 17, 21, 02, 0d, 02, 0f, d3, 01, 05, 09, 00, 0a, c3, 01, 00, 10, 00, 1d, 11, 00, 1e, 02, 06, be, 01, 02, 0f, 00, 1c, ba, 01, 01, 0c, 00, 19, b6, 01, 00, 1d, 00, 2a, 8d, 01, 00, 2e, 00, 3c, 15, 00, 3d, 02, 0a, 19, 02, 0a, 00, 0b, d7, 01, 01, 09, 00, 17, 1d, 02, 09, 00, 0f, cf, 01, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 60 +- expression 0 operands: lhs = Counter(2), rhs = Expression(4, Add) +- expression 1 operands: lhs = Counter(11), rhs = Counter(12) +- expression 2 operands: lhs = Counter(1), rhs = Counter(2) +- expression 3 operands: lhs = Counter(3), rhs = Counter(14) +- expression 4 operands: lhs = Counter(11), rhs = Counter(12) +- expression 5 operands: lhs = Expression(59, Sub), rhs = Counter(3) +- expression 6 operands: lhs = Counter(1), rhs = Counter(2) +- expression 7 operands: lhs = Counter(13), rhs = Counter(15) +- expression 8 operands: lhs = Expression(12, Sub), rhs = Counter(10) +- expression 9 operands: lhs = Counter(13), rhs = Counter(15) +- expression 10 operands: lhs = Expression(11, Sub), rhs = Counter(20) +- expression 11 operands: lhs = Expression(12, Sub), rhs = Counter(10) +- expression 12 operands: lhs = Counter(13), rhs = Counter(15) +- expression 13 operands: lhs = Counter(17), rhs = Counter(18) +- expression 14 operands: lhs = Counter(15), rhs = Expression(15, Add) +- expression 15 operands: lhs = Counter(17), rhs = Counter(18) +- expression 16 operands: lhs = Counter(21), rhs = Counter(22) +- expression 17 operands: lhs = Expression(21, Sub), rhs = Counter(9) +- expression 18 operands: lhs = Counter(21), rhs = Counter(22) +- expression 19 operands: lhs = Expression(20, Sub), rhs = Counter(30) +- expression 20 operands: lhs = Expression(21, Sub), rhs = Counter(9) +- expression 21 operands: lhs = Counter(21), rhs = Counter(22) +- expression 22 operands: lhs = Counter(23), rhs = Counter(24) +- expression 23 operands: lhs = Counter(22), rhs = Expression(24, Add) +- expression 24 operands: lhs = Counter(23), rhs = Counter(24) +- expression 25 operands: lhs = Counter(27), rhs = Expression(49, Add) +- expression 26 operands: lhs = Counter(28), rhs = Counter(29) +- expression 27 operands: lhs = Counter(25), rhs = Counter(27) +- expression 28 operands: lhs = Expression(32, Sub), rhs = Counter(8) +- expression 29 operands: lhs = Counter(25), rhs = Counter(27) +- expression 30 operands: lhs = Expression(31, Sub), rhs = Counter(31) +- expression 31 operands: lhs = Expression(32, Sub), rhs = Counter(8) +- expression 32 operands: lhs = Counter(25), rhs = Counter(27) +- expression 33 operands: lhs = Counter(28), rhs = Counter(29) +- expression 34 operands: lhs = Counter(4), rhs = Expression(53, Add) +- expression 35 operands: lhs = Counter(5), rhs = Counter(6) +- expression 36 operands: lhs = Counter(27), rhs = Expression(49, Add) +- expression 37 operands: lhs = Counter(28), rhs = Counter(29) +- expression 38 operands: lhs = Expression(48, Add), rhs = Counter(4) +- expression 39 operands: lhs = Counter(27), rhs = Expression(49, Add) +- expression 40 operands: lhs = Counter(28), rhs = Counter(29) +- expression 41 operands: lhs = Expression(47, Sub), rhs = Counter(7) +- expression 42 operands: lhs = Expression(48, Add), rhs = Counter(4) +- expression 43 operands: lhs = Counter(27), rhs = Expression(49, Add) +- expression 44 operands: lhs = Counter(28), rhs = Counter(29) +- expression 45 operands: lhs = Expression(46, Sub), rhs = Counter(34) +- expression 46 operands: lhs = Expression(47, Sub), rhs = Counter(7) +- expression 47 operands: lhs = Expression(48, Add), rhs = Counter(4) +- expression 48 operands: lhs = Counter(27), rhs = Expression(49, Add) +- expression 49 operands: lhs = Counter(28), rhs = Counter(29) +- expression 50 operands: lhs = Counter(5), rhs = Counter(6) +- expression 51 operands: lhs = Expression(52, Add), rhs = Expression(54, Add) +- expression 52 operands: lhs = Counter(4), rhs = Expression(53, Add) +- expression 53 operands: lhs = Counter(5), rhs = Counter(6) +- expression 54 operands: lhs = Expression(55, Add), rhs = Expression(58, Sub) +- expression 55 operands: lhs = Expression(56, Add), rhs = Expression(57, Add) +- expression 56 operands: lhs = Counter(7), rhs = Counter(8) +- expression 57 operands: lhs = Counter(9), rhs = Counter(10) +- expression 58 operands: lhs = Expression(59, Sub), rhs = Counter(3) +- expression 59 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 68 +- Code(Counter(0)) at (prev + 3, 1) to (start + 2, 12) +- Code(Counter(1)) at (prev + 2, 13) to (start + 2, 6) +- Code(Zero) at (prev + 2, 6) to (start + 0, 7) +- Code(Expression(0, Add)) at (prev + 3, 9) to (start + 0, 10) + = (c2 + (c11 + c12)) +- Code(Counter(1)) at (prev + 0, 16) to (start + 0, 29) +- Code(Counter(2)) at (prev + 1, 9) to (start + 1, 10) +- Code(Expression(59, Sub)) at (prev + 2, 15) to (start + 0, 28) + = (c1 - c2) +- Code(Counter(3)) at (prev + 1, 12) to (start + 0, 25) +- Code(Expression(3, Sub)) at (prev + 0, 29) to (start + 0, 42) + = (c3 - c14) +- Code(Counter(16)) at (prev + 0, 46) to (start + 0, 60) +- Code(Counter(11)) at (prev + 0, 61) to (start + 2, 10) +- Code(Counter(12)) at (prev + 2, 10) to (start + 0, 11) +- Code(Expression(4, Add)) at (prev + 1, 9) to (start + 1, 18) + = (c11 + c12) +- Code(Expression(58, Sub)) at (prev + 3, 9) to (start + 0, 15) + = ((c1 - c2) - c3) +- Code(Expression(0, Add)) at (prev + 3, 9) to (start + 1, 12) + = (c2 + (c11 + c12)) +- Code(Counter(13)) at (prev + 1, 13) to (start + 2, 6) +- Code(Zero) at (prev + 2, 6) to (start + 0, 7) +- Code(Counter(13)) at (prev + 2, 8) to (start + 0, 21) +- Code(Counter(15)) at (prev + 0, 22) to (start + 2, 6) +- Code(Expression(12, Sub)) at (prev + 2, 15) to (start + 0, 28) + = (c13 - c15) +- Code(Expression(11, Sub)) at (prev + 1, 12) to (start + 0, 25) + = ((c13 - c15) - c10) +- Code(Expression(10, Sub)) at (prev + 0, 29) to (start + 0, 42) + = (((c13 - c15) - c10) - c20) +- Code(Counter(26)) at (prev + 0, 46) to (start + 0, 60) +- Code(Counter(17)) at (prev + 0, 61) to (start + 2, 10) +- Code(Counter(18)) at (prev + 2, 10) to (start + 0, 11) +- Code(Expression(15, Add)) at (prev + 1, 9) to (start + 0, 23) + = (c17 + c18) +- Code(Counter(10)) at (prev + 2, 9) to (start + 0, 15) +- Code(Expression(14, Add)) at (prev + 3, 8) to (start + 0, 12) + = (c15 + (c17 + c18)) +- Code(Counter(19)) at (prev + 1, 13) to (start + 1, 16) +- Code(Counter(21)) at (prev + 1, 17) to (start + 2, 10) +- Code(Zero) at (prev + 2, 10) to (start + 0, 11) +- Code(Counter(21)) at (prev + 2, 12) to (start + 0, 25) +- Code(Counter(22)) at (prev + 0, 26) to (start + 2, 10) +- Code(Expression(21, Sub)) at (prev + 3, 17) to (start + 0, 30) + = (c21 - c22) +- Code(Expression(20, Sub)) at (prev + 1, 16) to (start + 0, 29) + = ((c21 - c22) - c9) +- Code(Expression(19, Sub)) at (prev + 0, 33) to (start + 0, 46) + = (((c21 - c22) - c9) - c30) +- Code(Counter(32)) at (prev + 0, 50) to (start + 0, 64) +- Code(Counter(23)) at (prev + 0, 65) to (start + 2, 14) +- Code(Counter(24)) at (prev + 2, 14) to (start + 0, 15) +- Code(Expression(24, Add)) at (prev + 1, 13) to (start + 0, 27) + = (c23 + c24) +- Code(Counter(9)) at (prev + 2, 13) to (start + 0, 19) +- Code(Zero) at (prev + 2, 6) to (start + 0, 7) +- Code(Expression(23, Add)) at (prev + 3, 9) to (start + 1, 12) + = (c22 + (c23 + c24)) +- Code(Counter(25)) at (prev + 1, 13) to (start + 2, 6) +- Code(Zero) at (prev + 2, 6) to (start + 0, 7) +- Code(Expression(48, Add)) at (prev + 2, 9) to (start + 0, 10) + = (c27 + (c28 + c29)) +- Code(Counter(25)) at (prev + 0, 16) to (start + 0, 29) +- Code(Counter(27)) at (prev + 0, 30) to (start + 2, 6) +- Code(Expression(32, Sub)) at (prev + 2, 15) to (start + 0, 28) + = (c25 - c27) +- Code(Expression(31, Sub)) at (prev + 1, 12) to (start + 0, 25) + = ((c25 - c27) - c8) +- Code(Expression(30, Sub)) at (prev + 0, 29) to (start + 0, 42) + = (((c25 - c27) - c8) - c31) +- Code(Counter(33)) at (prev + 0, 46) to (start + 0, 60) +- Code(Counter(28)) at (prev + 0, 61) to (start + 2, 10) +- Code(Counter(29)) at (prev + 2, 10) to (start + 0, 11) +- Code(Expression(49, Add)) at (prev + 1, 9) to (start + 0, 23) + = (c28 + c29) +- Code(Counter(8)) at (prev + 2, 13) to (start + 2, 15) +- Code(Expression(52, Add)) at (prev + 5, 9) to (start + 0, 10) + = (c4 + (c5 + c6)) +- Code(Expression(48, Add)) at (prev + 0, 16) to (start + 0, 29) + = (c27 + (c28 + c29)) +- Code(Counter(4)) at (prev + 0, 30) to (start + 2, 6) +- Code(Expression(47, Sub)) at (prev + 2, 15) to (start + 0, 28) + = ((c27 + (c28 + c29)) - c4) +- Code(Expression(46, Sub)) at (prev + 1, 12) to (start + 0, 25) + = (((c27 + (c28 + c29)) - c4) - c7) +- Code(Expression(45, Sub)) at (prev + 0, 29) to (start + 0, 42) + = ((((c27 + (c28 + c29)) - c4) - c7) - c34) +- Code(Counter(35)) at (prev + 0, 46) to (start + 0, 60) +- Code(Counter(5)) at (prev + 0, 61) to (start + 2, 10) +- Code(Counter(6)) at (prev + 2, 10) to (start + 0, 11) +- Code(Expression(53, Add)) at (prev + 1, 9) to (start + 0, 23) + = (c5 + c6) +- Code(Counter(7)) at (prev + 2, 9) to (start + 0, 15) +- Code(Expression(51, Add)) at (prev + 2, 1) to (start + 0, 2) + = ((c4 + (c5 + c6)) + (((c7 + c8) + (c9 + c10)) + ((c1 - c2) - c3))) + diff --git a/tests/coverage-map/status-quo/conditions.rs b/tests/coverage-map/status-quo/conditions.rs new file mode 100644 index 0000000000000..057599d1b471a --- /dev/null +++ b/tests/coverage-map/status-quo/conditions.rs @@ -0,0 +1,87 @@ +#![allow(unused_assignments, unused_variables)] + +fn main() { + let mut countdown = 0; + if true { + countdown = 10; + } + + const B: u32 = 100; + let x = if countdown > 7 { + countdown -= 4; + B + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + countdown + } else { + return; + }; + + let mut countdown = 0; + if true { + countdown = 10; + } + + if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + } + + if true { + let mut countdown = 0; + if true { + countdown = 10; + } + + if countdown > 7 { + countdown -= 4; + } + else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + } + } + + + let mut countdown = 0; + if true { + countdown = 1; + } + + let z = if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + let should_be_reachable = countdown; + println!("reached"); + return; + }; + + let w = if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + }; +} diff --git a/tests/coverage-map/status-quo/drop_trait.cov-map b/tests/coverage-map/status-quo/drop_trait.cov-map new file mode 100644 index 0000000000000..cf3df9fce1d23 --- /dev/null +++ b/tests/coverage-map/status-quo/drop_trait.cov-map @@ -0,0 +1,19 @@ +Function name: ::drop +Raw bytes (9): 0x[01, 01, 00, 01, 01, 09, 05, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 9, 5) to (start + 2, 6) + +Function name: drop_trait::main +Raw bytes (24): 0x[01, 01, 00, 04, 01, 0e, 01, 05, 0c, 05, 06, 09, 01, 16, 00, 02, 06, 04, 0b, 05, 05, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 14, 1) to (start + 5, 12) +- Code(Counter(1)) at (prev + 6, 9) to (start + 1, 22) +- Code(Zero) at (prev + 2, 6) to (start + 4, 11) +- Code(Counter(1)) at (prev + 5, 1) to (start + 0, 2) + diff --git a/tests/coverage-map/status-quo/drop_trait.rs b/tests/coverage-map/status-quo/drop_trait.rs new file mode 100644 index 0000000000000..a9b5d1d1e7fe9 --- /dev/null +++ b/tests/coverage-map/status-quo/drop_trait.rs @@ -0,0 +1,33 @@ +#![allow(unused_assignments)] +// failure-status: 1 + +struct Firework { + strength: i32, +} + +impl Drop for Firework { + fn drop(&mut self) { + println!("BOOM times {}!!!", self.strength); + } +} + +fn main() -> Result<(),u8> { + let _firecracker = Firework { strength: 1 }; + + let _tnt = Firework { strength: 100 }; + + if true { + println!("Exiting with error..."); + return Err(1); + } + + let _ = Firework { strength: 1000 }; + + Ok(()) +} + +// Expected program output: +// Exiting with error... +// BOOM times 100!!! +// BOOM times 1!!! +// Error: 1 diff --git a/tests/coverage-map/status-quo/generics.cov-map b/tests/coverage-map/status-quo/generics.cov-map new file mode 100644 index 0000000000000..b7e3c772dd237 --- /dev/null +++ b/tests/coverage-map/status-quo/generics.cov-map @@ -0,0 +1,43 @@ +Function name: as core::ops::drop::Drop>::drop +Raw bytes (9): 0x[01, 01, 00, 01, 01, 11, 05, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 17, 5) to (start + 2, 6) + +Function name: >::set_strength +Raw bytes (9): 0x[01, 01, 00, 01, 01, 0a, 05, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 10, 5) to (start + 2, 6) + +Function name: as core::ops::drop::Drop>::drop +Raw bytes (9): 0x[01, 01, 00, 01, 01, 11, 05, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 17, 5) to (start + 2, 6) + +Function name: >::set_strength +Raw bytes (9): 0x[01, 01, 00, 01, 01, 0a, 05, 02, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 10, 5) to (start + 2, 6) + +Function name: generics::main +Raw bytes (24): 0x[01, 01, 00, 04, 01, 16, 01, 08, 0c, 05, 09, 09, 01, 16, 00, 02, 06, 08, 0b, 05, 09, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 22, 1) to (start + 8, 12) +- Code(Counter(1)) at (prev + 9, 9) to (start + 1, 22) +- Code(Zero) at (prev + 2, 6) to (start + 8, 11) +- Code(Counter(1)) at (prev + 9, 1) to (start + 0, 2) + diff --git a/tests/coverage-map/status-quo/generics.rs b/tests/coverage-map/status-quo/generics.rs new file mode 100644 index 0000000000000..150ffb9db395a --- /dev/null +++ b/tests/coverage-map/status-quo/generics.rs @@ -0,0 +1,48 @@ +#![allow(unused_assignments)] +// failure-status: 1 + +struct Firework where T: Copy + std::fmt::Display { + strength: T, +} + +impl Firework where T: Copy + std::fmt::Display { + #[inline(always)] + fn set_strength(&mut self, new_strength: T) { + self.strength = new_strength; + } +} + +impl Drop for Firework where T: Copy + std::fmt::Display { + #[inline(always)] + fn drop(&mut self) { + println!("BOOM times {}!!!", self.strength); + } +} + +fn main() -> Result<(),u8> { + let mut firecracker = Firework { strength: 1 }; + firecracker.set_strength(2); + + let mut tnt = Firework { strength: 100.1 }; + tnt.set_strength(200.1); + tnt.set_strength(300.3); + + if true { + println!("Exiting with error..."); + return Err(1); + } + + + + + + let _ = Firework { strength: 1000 }; + + Ok(()) +} + +// Expected program output: +// Exiting with error... +// BOOM times 100!!! +// BOOM times 1!!! +// Error: 1 diff --git a/tests/coverage-map/status-quo/loops_branches.cov-map b/tests/coverage-map/status-quo/loops_branches.cov-map new file mode 100644 index 0000000000000..199f9aa9830f8 --- /dev/null +++ b/tests/coverage-map/status-quo/loops_branches.cov-map @@ -0,0 +1,167 @@ +Function name: ::fmt +Raw bytes (210): 0x[01, 01, 25, 05, 09, 8f, 01, 19, 0d, 93, 01, 11, 15, 0d, 93, 01, 11, 15, 8f, 01, 19, 0d, 93, 01, 11, 15, 8a, 01, 15, 8f, 01, 19, 0d, 93, 01, 11, 15, 86, 01, 1d, 8a, 01, 15, 8f, 01, 19, 0d, 93, 01, 11, 15, 86, 01, 1d, 8a, 01, 15, 8f, 01, 19, 0d, 93, 01, 11, 15, 82, 01, 11, 86, 01, 1d, 8a, 01, 15, 8f, 01, 19, 0d, 93, 01, 11, 15, 25, 7b, 7e, 19, 82, 01, 11, 86, 01, 1d, 8a, 01, 15, 8f, 01, 19, 0d, 93, 01, 11, 15, 14, 01, 09, 05, 01, 10, 05, 02, 10, 00, 15, 00, 01, 17, 00, 1b, 00, 00, 1c, 01, 12, 02, 02, 0e, 00, 0f, 02, 01, 0d, 00, 1e, 25, 00, 1e, 00, 1f, 00, 01, 10, 01, 0a, 8a, 01, 03, 0d, 00, 0e, 8f, 01, 00, 12, 00, 17, 8a, 01, 01, 10, 00, 14, 86, 01, 01, 14, 00, 19, 00, 01, 1b, 00, 1f, 00, 00, 20, 00, 22, 82, 01, 01, 12, 00, 13, 82, 01, 01, 11, 00, 22, 7e, 00, 22, 00, 23, 00, 01, 14, 01, 0e, 19, 03, 09, 00, 0f, 77, 01, 05, 00, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 37 +- expression 0 operands: lhs = Counter(1), rhs = Counter(2) +- expression 1 operands: lhs = Expression(35, Add), rhs = Counter(6) +- expression 2 operands: lhs = Counter(3), rhs = Expression(36, Add) +- expression 3 operands: lhs = Counter(4), rhs = Counter(5) +- expression 4 operands: lhs = Counter(3), rhs = Expression(36, Add) +- expression 5 operands: lhs = Counter(4), rhs = Counter(5) +- expression 6 operands: lhs = Expression(35, Add), rhs = Counter(6) +- expression 7 operands: lhs = Counter(3), rhs = Expression(36, Add) +- expression 8 operands: lhs = Counter(4), rhs = Counter(5) +- expression 9 operands: lhs = Expression(34, Sub), rhs = Counter(5) +- expression 10 operands: lhs = Expression(35, Add), rhs = Counter(6) +- expression 11 operands: lhs = Counter(3), rhs = Expression(36, Add) +- expression 12 operands: lhs = Counter(4), rhs = Counter(5) +- expression 13 operands: lhs = Expression(33, Sub), rhs = Counter(7) +- expression 14 operands: lhs = Expression(34, Sub), rhs = Counter(5) +- expression 15 operands: lhs = Expression(35, Add), rhs = Counter(6) +- expression 16 operands: lhs = Counter(3), rhs = Expression(36, Add) +- expression 17 operands: lhs = Counter(4), rhs = Counter(5) +- expression 18 operands: lhs = Expression(33, Sub), rhs = Counter(7) +- expression 19 operands: lhs = Expression(34, Sub), rhs = Counter(5) +- expression 20 operands: lhs = Expression(35, Add), rhs = Counter(6) +- expression 21 operands: lhs = Counter(3), rhs = Expression(36, Add) +- expression 22 operands: lhs = Counter(4), rhs = Counter(5) +- expression 23 operands: lhs = Expression(32, Sub), rhs = Counter(4) +- expression 24 operands: lhs = Expression(33, Sub), rhs = Counter(7) +- expression 25 operands: lhs = Expression(34, Sub), rhs = Counter(5) +- expression 26 operands: lhs = Expression(35, Add), rhs = Counter(6) +- expression 27 operands: lhs = Counter(3), rhs = Expression(36, Add) +- expression 28 operands: lhs = Counter(4), rhs = Counter(5) +- expression 29 operands: lhs = Counter(9), rhs = Expression(30, Add) +- expression 30 operands: lhs = Expression(31, Sub), rhs = Counter(6) +- expression 31 operands: lhs = Expression(32, Sub), rhs = Counter(4) +- expression 32 operands: lhs = Expression(33, Sub), rhs = Counter(7) +- expression 33 operands: lhs = Expression(34, Sub), rhs = Counter(5) +- expression 34 operands: lhs = Expression(35, Add), rhs = Counter(6) +- expression 35 operands: lhs = Counter(3), rhs = Expression(36, Add) +- expression 36 operands: lhs = Counter(4), rhs = Counter(5) +Number of file 0 mappings: 20 +- Code(Counter(0)) at (prev + 9, 5) to (start + 1, 16) +- Code(Counter(1)) at (prev + 2, 16) to (start + 0, 21) +- Code(Zero) at (prev + 1, 23) to (start + 0, 27) +- Code(Zero) at (prev + 0, 28) to (start + 1, 18) +- Code(Expression(0, Sub)) at (prev + 2, 14) to (start + 0, 15) + = (c1 - c2) +- Code(Expression(0, Sub)) at (prev + 1, 13) to (start + 0, 30) + = (c1 - c2) +- Code(Counter(9)) at (prev + 0, 30) to (start + 0, 31) +- Code(Zero) at (prev + 1, 16) to (start + 1, 10) +- Code(Expression(34, Sub)) at (prev + 3, 13) to (start + 0, 14) + = ((c3 + (c4 + c5)) - c6) +- Code(Expression(35, Add)) at (prev + 0, 18) to (start + 0, 23) + = (c3 + (c4 + c5)) +- Code(Expression(34, Sub)) at (prev + 1, 16) to (start + 0, 20) + = ((c3 + (c4 + c5)) - c6) +- Code(Expression(33, Sub)) at (prev + 1, 20) to (start + 0, 25) + = (((c3 + (c4 + c5)) - c6) - c5) +- Code(Zero) at (prev + 1, 27) to (start + 0, 31) +- Code(Zero) at (prev + 0, 32) to (start + 0, 34) +- Code(Expression(32, Sub)) at (prev + 1, 18) to (start + 0, 19) + = ((((c3 + (c4 + c5)) - c6) - c5) - c7) +- Code(Expression(32, Sub)) at (prev + 1, 17) to (start + 0, 34) + = ((((c3 + (c4 + c5)) - c6) - c5) - c7) +- Code(Expression(31, Sub)) at (prev + 0, 34) to (start + 0, 35) + = (((((c3 + (c4 + c5)) - c6) - c5) - c7) - c4) +- Code(Zero) at (prev + 1, 20) to (start + 1, 14) +- Code(Counter(6)) at (prev + 3, 9) to (start + 0, 15) +- Code(Expression(29, Add)) at (prev + 1, 5) to (start + 0, 6) + = (c9 + ((((((c3 + (c4 + c5)) - c6) - c5) - c7) - c4) + c6)) + +Function name: ::fmt +Raw bytes (242): 0x[01, 01, 2f, 01, 05, 02, 09, 02, 09, af, 01, 19, b3, 01, b7, 01, 05, 0d, 11, 15, b3, 01, b7, 01, 05, 0d, 11, 15, af, 01, 19, b3, 01, b7, 01, 05, 0d, 11, 15, aa, 01, 11, af, 01, 19, b3, 01, b7, 01, 05, 0d, 11, 15, a6, 01, 1d, aa, 01, 11, af, 01, 19, b3, 01, b7, 01, 05, 0d, 11, 15, a6, 01, 1d, aa, 01, 11, af, 01, 19, b3, 01, b7, 01, 05, 0d, 11, 15, a2, 01, 15, a6, 01, 1d, aa, 01, 11, af, 01, 19, b3, 01, b7, 01, 05, 0d, 11, 15, 9e, 01, bb, 01, a2, 01, 15, a6, 01, 1d, aa, 01, 11, af, 01, 19, b3, 01, b7, 01, 05, 0d, 11, 15, 19, 25, 14, 01, 23, 05, 01, 11, 00, 01, 12, 01, 0a, 02, 02, 10, 00, 15, 00, 01, 17, 00, 1b, 00, 00, 1c, 00, 1e, 0a, 01, 0e, 00, 0f, 0a, 01, 0d, 00, 1e, 25, 00, 1e, 00, 1f, aa, 01, 02, 0d, 00, 0e, af, 01, 00, 12, 00, 17, aa, 01, 01, 10, 00, 15, 00, 00, 16, 01, 0e, a6, 01, 02, 14, 00, 19, 00, 01, 1b, 00, 1f, 00, 00, 20, 00, 22, a2, 01, 01, 12, 00, 13, a2, 01, 01, 11, 00, 22, 9e, 01, 00, 22, 00, 23, 19, 03, 09, 00, 0f, 9b, 01, 01, 05, 00, 06] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 47 +- expression 0 operands: lhs = Counter(0), rhs = Counter(1) +- expression 1 operands: lhs = Expression(0, Sub), rhs = Counter(2) +- expression 2 operands: lhs = Expression(0, Sub), rhs = Counter(2) +- expression 3 operands: lhs = Expression(43, Add), rhs = Counter(6) +- expression 4 operands: lhs = Expression(44, Add), rhs = Expression(45, Add) +- expression 5 operands: lhs = Counter(1), rhs = Counter(3) +- expression 6 operands: lhs = Counter(4), rhs = Counter(5) +- expression 7 operands: lhs = Expression(44, Add), rhs = Expression(45, Add) +- expression 8 operands: lhs = Counter(1), rhs = Counter(3) +- expression 9 operands: lhs = Counter(4), rhs = Counter(5) +- expression 10 operands: lhs = Expression(43, Add), rhs = Counter(6) +- expression 11 operands: lhs = Expression(44, Add), rhs = Expression(45, Add) +- expression 12 operands: lhs = Counter(1), rhs = Counter(3) +- expression 13 operands: lhs = Counter(4), rhs = Counter(5) +- expression 14 operands: lhs = Expression(42, Sub), rhs = Counter(4) +- expression 15 operands: lhs = Expression(43, Add), rhs = Counter(6) +- expression 16 operands: lhs = Expression(44, Add), rhs = Expression(45, Add) +- expression 17 operands: lhs = Counter(1), rhs = Counter(3) +- expression 18 operands: lhs = Counter(4), rhs = Counter(5) +- expression 19 operands: lhs = Expression(41, Sub), rhs = Counter(7) +- expression 20 operands: lhs = Expression(42, Sub), rhs = Counter(4) +- expression 21 operands: lhs = Expression(43, Add), rhs = Counter(6) +- expression 22 operands: lhs = Expression(44, Add), rhs = Expression(45, Add) +- expression 23 operands: lhs = Counter(1), rhs = Counter(3) +- expression 24 operands: lhs = Counter(4), rhs = Counter(5) +- expression 25 operands: lhs = Expression(41, Sub), rhs = Counter(7) +- expression 26 operands: lhs = Expression(42, Sub), rhs = Counter(4) +- expression 27 operands: lhs = Expression(43, Add), rhs = Counter(6) +- expression 28 operands: lhs = Expression(44, Add), rhs = Expression(45, Add) +- expression 29 operands: lhs = Counter(1), rhs = Counter(3) +- expression 30 operands: lhs = Counter(4), rhs = Counter(5) +- expression 31 operands: lhs = Expression(40, Sub), rhs = Counter(5) +- expression 32 operands: lhs = Expression(41, Sub), rhs = Counter(7) +- expression 33 operands: lhs = Expression(42, Sub), rhs = Counter(4) +- expression 34 operands: lhs = Expression(43, Add), rhs = Counter(6) +- expression 35 operands: lhs = Expression(44, Add), rhs = Expression(45, Add) +- expression 36 operands: lhs = Counter(1), rhs = Counter(3) +- expression 37 operands: lhs = Counter(4), rhs = Counter(5) +- expression 38 operands: lhs = Expression(39, Sub), rhs = Expression(46, Add) +- expression 39 operands: lhs = Expression(40, Sub), rhs = Counter(5) +- expression 40 operands: lhs = Expression(41, Sub), rhs = Counter(7) +- expression 41 operands: lhs = Expression(42, Sub), rhs = Counter(4) +- expression 42 operands: lhs = Expression(43, Add), rhs = Counter(6) +- expression 43 operands: lhs = Expression(44, Add), rhs = Expression(45, Add) +- expression 44 operands: lhs = Counter(1), rhs = Counter(3) +- expression 45 operands: lhs = Counter(4), rhs = Counter(5) +- expression 46 operands: lhs = Counter(6), rhs = Counter(9) +Number of file 0 mappings: 20 +- Code(Counter(0)) at (prev + 35, 5) to (start + 1, 17) +- Code(Zero) at (prev + 1, 18) to (start + 1, 10) +- Code(Expression(0, Sub)) at (prev + 2, 16) to (start + 0, 21) + = (c0 - c1) +- Code(Zero) at (prev + 1, 23) to (start + 0, 27) +- Code(Zero) at (prev + 0, 28) to (start + 0, 30) +- Code(Expression(2, Sub)) at (prev + 1, 14) to (start + 0, 15) + = ((c0 - c1) - c2) +- Code(Expression(2, Sub)) at (prev + 1, 13) to (start + 0, 30) + = ((c0 - c1) - c2) +- Code(Counter(9)) at (prev + 0, 30) to (start + 0, 31) +- Code(Expression(42, Sub)) at (prev + 2, 13) to (start + 0, 14) + = (((c1 + c3) + (c4 + c5)) - c6) +- Code(Expression(43, Add)) at (prev + 0, 18) to (start + 0, 23) + = ((c1 + c3) + (c4 + c5)) +- Code(Expression(42, Sub)) at (prev + 1, 16) to (start + 0, 21) + = (((c1 + c3) + (c4 + c5)) - c6) +- Code(Zero) at (prev + 0, 22) to (start + 1, 14) +- Code(Expression(41, Sub)) at (prev + 2, 20) to (start + 0, 25) + = ((((c1 + c3) + (c4 + c5)) - c6) - c4) +- Code(Zero) at (prev + 1, 27) to (start + 0, 31) +- Code(Zero) at (prev + 0, 32) to (start + 0, 34) +- Code(Expression(40, Sub)) at (prev + 1, 18) to (start + 0, 19) + = (((((c1 + c3) + (c4 + c5)) - c6) - c4) - c7) +- Code(Expression(40, Sub)) at (prev + 1, 17) to (start + 0, 34) + = (((((c1 + c3) + (c4 + c5)) - c6) - c4) - c7) +- Code(Expression(39, Sub)) at (prev + 0, 34) to (start + 0, 35) + = ((((((c1 + c3) + (c4 + c5)) - c6) - c4) - c7) - c5) +- Code(Counter(6)) at (prev + 3, 9) to (start + 0, 15) +- Code(Expression(38, Add)) at (prev + 1, 5) to (start + 0, 6) + = (((((((c1 + c3) + (c4 + c5)) - c6) - c4) - c7) - c5) + (c6 + c9)) + +Function name: loops_branches::main +Raw bytes (9): 0x[01, 01, 00, 01, 01, 38, 01, 05, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 56, 1) to (start + 5, 2) + diff --git a/tests/coverage-map/status-quo/loops_branches.rs b/tests/coverage-map/status-quo/loops_branches.rs new file mode 100644 index 0000000000000..7116ce47f4b9d --- /dev/null +++ b/tests/coverage-map/status-quo/loops_branches.rs @@ -0,0 +1,61 @@ +#![allow(unused_assignments, unused_variables, while_true)] + +// This test confirms that (1) unexecuted infinite loops are handled correctly by the +// InstrumentCoverage MIR pass; and (2) Counter Expressions that subtract from zero can be dropped. + +struct DebugTest; + +impl std::fmt::Debug for DebugTest { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if true { + if false { + while true { + } + } + write!(f, "cool")?; + } else { + } + + for i in 0..10 { + if true { + if false { + while true {} + } + write!(f, "cool")?; + } else { + } + } + Ok(()) + } +} + +struct DisplayTest; + +impl std::fmt::Display for DisplayTest { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if false { + } else { + if false { + while true {} + } + write!(f, "cool")?; + } + for i in 0..10 { + if false { + } else { + if false { + while true {} + } + write!(f, "cool")?; + } + } + Ok(()) + } +} + +fn main() { + let debug_test = DebugTest; + println!("{:?}", debug_test); + let display_test = DisplayTest; + println!("{}", display_test); +} diff --git a/tests/coverage-map/status-quo/unused.cov-map b/tests/coverage-map/status-quo/unused.cov-map new file mode 100644 index 0000000000000..84bbb2c262b06 --- /dev/null +++ b/tests/coverage-map/status-quo/unused.cov-map @@ -0,0 +1,94 @@ +Function name: unused::foo:: +Raw bytes (42): 0x[01, 01, 04, 01, 0f, 05, 09, 03, 0d, 05, 09, 06, 01, 01, 01, 01, 12, 03, 02, 0b, 00, 11, 0a, 01, 09, 00, 0f, 09, 00, 13, 00, 19, 0f, 01, 09, 00, 0f, 0d, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 4 +- expression 0 operands: lhs = Counter(0), rhs = Expression(3, Add) +- expression 1 operands: lhs = Counter(1), rhs = Counter(2) +- expression 2 operands: lhs = Expression(0, Add), rhs = Counter(3) +- expression 3 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 6 +- Code(Counter(0)) at (prev + 1, 1) to (start + 1, 18) +- Code(Expression(0, Add)) at (prev + 2, 11) to (start + 0, 17) + = (c0 + (c1 + c2)) +- Code(Expression(2, Sub)) at (prev + 1, 9) to (start + 0, 15) + = ((c0 + (c1 + c2)) - c3) +- Code(Counter(2)) at (prev + 0, 19) to (start + 0, 25) +- Code(Expression(3, Add)) at (prev + 1, 9) to (start + 0, 15) + = (c1 + c2) +- Code(Counter(3)) at (prev + 2, 1) to (start + 0, 2) + +Function name: unused::foo:: +Raw bytes (42): 0x[01, 01, 04, 01, 0f, 05, 09, 03, 0d, 05, 09, 06, 01, 01, 01, 01, 12, 03, 02, 0b, 00, 11, 0a, 01, 09, 00, 0f, 09, 00, 13, 00, 19, 0f, 01, 09, 00, 0f, 0d, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 4 +- expression 0 operands: lhs = Counter(0), rhs = Expression(3, Add) +- expression 1 operands: lhs = Counter(1), rhs = Counter(2) +- expression 2 operands: lhs = Expression(0, Add), rhs = Counter(3) +- expression 3 operands: lhs = Counter(1), rhs = Counter(2) +Number of file 0 mappings: 6 +- Code(Counter(0)) at (prev + 1, 1) to (start + 1, 18) +- Code(Expression(0, Add)) at (prev + 2, 11) to (start + 0, 17) + = (c0 + (c1 + c2)) +- Code(Expression(2, Sub)) at (prev + 1, 9) to (start + 0, 15) + = ((c0 + (c1 + c2)) - c3) +- Code(Counter(2)) at (prev + 0, 19) to (start + 0, 25) +- Code(Expression(3, Add)) at (prev + 1, 9) to (start + 0, 15) + = (c1 + c2) +- Code(Counter(3)) at (prev + 2, 1) to (start + 0, 2) + +Function name: unused::main +Raw bytes (9): 0x[01, 01, 00, 01, 01, 23, 01, 04, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 35, 1) to (start + 4, 2) + +Function name: unused::unused_func (unused) +Raw bytes (24): 0x[01, 01, 00, 04, 01, 11, 01, 01, 0e, 00, 01, 0f, 02, 06, 00, 02, 06, 00, 07, 00, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 17, 1) to (start + 1, 14) +- Code(Zero) at (prev + 1, 15) to (start + 2, 6) +- Code(Zero) at (prev + 2, 6) to (start + 0, 7) +- Code(Zero) at (prev + 1, 1) to (start + 0, 2) + +Function name: unused::unused_func2 (unused) +Raw bytes (24): 0x[01, 01, 00, 04, 01, 17, 01, 01, 0e, 00, 01, 0f, 02, 06, 00, 02, 06, 00, 07, 00, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 23, 1) to (start + 1, 14) +- Code(Zero) at (prev + 1, 15) to (start + 2, 6) +- Code(Zero) at (prev + 2, 6) to (start + 0, 7) +- Code(Zero) at (prev + 1, 1) to (start + 0, 2) + +Function name: unused::unused_func3 (unused) +Raw bytes (24): 0x[01, 01, 00, 04, 01, 1d, 01, 01, 0e, 00, 01, 0f, 02, 06, 00, 02, 06, 00, 07, 00, 01, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 4 +- Code(Counter(0)) at (prev + 29, 1) to (start + 1, 14) +- Code(Zero) at (prev + 1, 15) to (start + 2, 6) +- Code(Zero) at (prev + 2, 6) to (start + 0, 7) +- Code(Zero) at (prev + 1, 1) to (start + 0, 2) + +Function name: unused::unused_template_func::<_> (unused) +Raw bytes (34): 0x[01, 01, 00, 06, 01, 09, 01, 01, 12, 00, 02, 0b, 00, 11, 00, 01, 09, 00, 0f, 00, 00, 13, 00, 19, 00, 01, 09, 00, 0f, 00, 02, 01, 00, 02] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 6 +- Code(Counter(0)) at (prev + 9, 1) to (start + 1, 18) +- Code(Zero) at (prev + 2, 11) to (start + 0, 17) +- Code(Zero) at (prev + 1, 9) to (start + 0, 15) +- Code(Zero) at (prev + 0, 19) to (start + 0, 25) +- Code(Zero) at (prev + 1, 9) to (start + 0, 15) +- Code(Zero) at (prev + 2, 1) to (start + 0, 2) + diff --git a/tests/coverage-map/status-quo/unused.rs b/tests/coverage-map/status-quo/unused.rs new file mode 100644 index 0000000000000..fb6113eb01c2d --- /dev/null +++ b/tests/coverage-map/status-quo/unused.rs @@ -0,0 +1,39 @@ +fn foo(x: T) { + let mut i = 0; + while i < 10 { + i != 0 || i != 0; + i += 1; + } +} + +fn unused_template_func(x: T) { + let mut i = 0; + while i < 10 { + i != 0 || i != 0; + i += 1; + } +} + +fn unused_func(mut a: u32) { + if a != 0 { + a += 1; + } +} + +fn unused_func2(mut a: u32) { + if a != 0 { + a += 1; + } +} + +fn unused_func3(mut a: u32) { + if a != 0 { + a += 1; + } +} + +fn main() -> Result<(), u8> { + foo::(0); + foo::(0.0); + Ok(()) +} diff --git a/tests/coverage-map/trivial.cov-map b/tests/coverage-map/trivial.cov-map new file mode 100644 index 0000000000000..874e294a1c498 --- /dev/null +++ b/tests/coverage-map/trivial.cov-map @@ -0,0 +1,8 @@ +Function name: trivial::main +Raw bytes (9): 0x[01, 01, 00, 01, 01, 03, 01, 00, 0d] +Number of files: 1 +- file 0 => global file 1 +Number of expressions: 0 +Number of file 0 mappings: 1 +- Code(Counter(0)) at (prev + 3, 1) to (start + 0, 13) + diff --git a/tests/coverage-map/trivial.rs b/tests/coverage-map/trivial.rs new file mode 100644 index 0000000000000..d0a9b44fb3605 --- /dev/null +++ b/tests/coverage-map/trivial.rs @@ -0,0 +1,3 @@ +// compile-flags: --edition=2021 + +fn main() {}