Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kani-cov: A coverage tool for Kani #3121

Merged
merged 78 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
c487f2a
`kanicov`: A tool to visualize coverage in Kani
adpaco-aws Apr 3, 2024
eddb363
Rename and more
adpaco-aws Apr 9, 2024
935dace
Merge branch 'main' into kanicov-tool
adpaco-aws Aug 21, 2024
13c26fd
Scaffold for `kani-cov`
adpaco-aws Aug 22, 2024
bee8f2d
Parse and combine raw results
adpaco-aws Aug 22, 2024
e0b7a10
Merge branch 'main' into kanicov-tool
adpaco-aws Aug 28, 2024
0aa25b9
Add missing copyright notices
adpaco-aws Aug 28, 2024
debeef4
Remove `atty` dependency
adpaco-aws Aug 29, 2024
9e9575a
Add license to Cargo.toml
adpaco-aws Aug 29, 2024
dd53e38
Move unused code to report
adpaco-aws Aug 29, 2024
ad632f3
Save combined results through merge
adpaco-aws Aug 29, 2024
a659c1a
Start summary command
adpaco-aws Aug 29, 2024
76351fd
Some format fixes
adpaco-aws Aug 29, 2024
304cfd2
Fixes for `clippy`
adpaco-aws Aug 29, 2024
c69f465
Add description to Cargo.toml
adpaco-aws Aug 29, 2024
d0bb478
Parse function info with tree-sitter
adpaco-aws Aug 30, 2024
5512d28
Use file-function pairs to combine results
adpaco-aws Aug 31, 2024
0e1dcc0
Change combined results again
adpaco-aws Aug 31, 2024
fabdf44
Produce markdown table with `summary`
adpaco-aws Sep 13, 2024
855d0a6
Sketch Json format for summary
adpaco-aws Sep 13, 2024
0df18b9
Refator some code so it's shared with `report`
adpaco-aws Sep 13, 2024
50bb553
Init. version for terminal reports
adpaco-aws Sep 16, 2024
5b4d717
Simplify processing for markers
adpaco-aws Sep 17, 2024
e1d0160
Remove some formatting code that's not needed anymore
adpaco-aws Sep 17, 2024
6d47c0b
Remove leftover debugging info
adpaco-aws Sep 17, 2024
ae48e21
Complete terminal highlight algorithm
adpaco-aws Sep 19, 2024
8c79baf
Change `coverage` mode to get `kani-cov` reports
adpaco-aws Sep 19, 2024
39f5ac2
Bless expected files
adpaco-aws Sep 19, 2024
778816a
Complete blessing for `coverage` tests
adpaco-aws Sep 19, 2024
b33fd5c
Merge branch 'main' into kanicov-tool
adpaco-aws Sep 20, 2024
06b97f0
Reformat
adpaco-aws Sep 20, 2024
781ba58
Clippy fixes
adpaco-aws Sep 20, 2024
7d7ef6c
Clean up: unused imports and commented out code
adpaco-aws Sep 20, 2024
a997656
Merge branch 'main' into kanicov-tool
adpaco-aws Sep 20, 2024
5cd0687
Report format
adpaco-aws Sep 20, 2024
4694294
Introduce format for reports
adpaco-aws Sep 20, 2024
21f2bd8
Add documentation for several functions
adpaco-aws Sep 20, 2024
923e606
Most clippy fixes
adpaco-aws Sep 20, 2024
812231e
More documentation
adpaco-aws Sep 20, 2024
745a55f
Documentation for main methods
adpaco-aws Sep 20, 2024
a0df867
Fix collapsible match
adpaco-aws Sep 23, 2024
4cc3561
Check if building `kani-cov` works
adpaco-aws Sep 23, 2024
9e28668
Reformat
adpaco-aws Sep 23, 2024
9c02d9f
Bless tests with known issues
adpaco-aws Sep 23, 2024
b2926e3
Complete documentation
adpaco-aws Sep 23, 2024
d20fa28
Reformat
adpaco-aws Sep 23, 2024
94bb87a
Merge branch 'main' into kanicov-tool
adpaco-aws Sep 23, 2024
eba08bb
Merge branch 'main' into kanicov-tool
adpaco-aws Sep 24, 2024
16a23c9
Reformat
adpaco-aws Sep 24, 2024
64ed9ea
Improvements and docs for `compiletest` code
adpaco-aws Oct 1, 2024
da47a24
Add examples for `kani-cov` subcommands
adpaco-aws Oct 1, 2024
a9b7dbf
Document `SummaryFormat`
adpaco-aws Oct 1, 2024
9b8ac3f
Address two minor comments
adpaco-aws Oct 1, 2024
524dc91
Replace `BTreeMap` with `HashMap`
adpaco-aws Oct 1, 2024
c4461be
Include file in coverage region display
adpaco-aws Oct 1, 2024
c7e2661
Auto-format
adpaco-aws Oct 1, 2024
66a5396
Improvements for tree-sitter code
adpaco-aws Oct 1, 2024
4b7e294
Simplify code to retrieve cov results
adpaco-aws Oct 1, 2024
91ed88e
Redo file-function pair collection
adpaco-aws Oct 1, 2024
dabf0c9
Undo change in command
adpaco-aws Oct 1, 2024
28e392e
Merge branch 'main' into kanicov-tool
adpaco-aws Oct 1, 2024
d29c3f3
Update `Cargo.lock` file
adpaco-aws Oct 1, 2024
7741fb1
Use aliases for coverage types
adpaco-aws Oct 4, 2024
c7443f5
Use `usize` instead of `u32`
adpaco-aws Oct 4, 2024
babdc85
Document `CovResult`
adpaco-aws Oct 4, 2024
0137b03
Format
adpaco-aws Oct 4, 2024
d0d1cf4
Fix bug and use partition
adpaco-aws Oct 4, 2024
094ad75
Update tests after fix
adpaco-aws Oct 4, 2024
a6abae5
Merge branch 'main' into kanicov-tool
adpaco-aws Oct 4, 2024
8ea744d
more `clippy` fixes
adpaco-aws Oct 4, 2024
ffa738a
Reformat code
adpaco-aws Oct 4, 2024
3f9b01b
Do not filter out results with out-of-bound regions
adpaco-aws Oct 7, 2024
4f0b913
Auto-fmt
adpaco-aws Oct 7, 2024
421cde4
Update tests
adpaco-aws Oct 7, 2024
c7606f3
More typenames and use across all of `kani-cov`
adpaco-aws Oct 7, 2024
cb123e5
Merge branch 'main' into kanicov-tool
adpaco-aws Oct 7, 2024
7311f49
clippy fixes
adpaco-aws Oct 7, 2024
42314e8
Merge branch 'main' into kanicov-tool
adpaco-aws Oct 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -977,9 +977,9 @@ dependencies = [

[[package]]
name = "serde_json"
version = "1.0.114"
version = "1.0.115"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
dependencies = [
"itoa",
"ryu",
Expand Down Expand Up @@ -1317,6 +1317,17 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"

[[package]]
name = "visualize_coverage"
version = "0.1.0"
dependencies = [
"anyhow",
"console",
"serde",
"serde_derive",
"serde_json",
]

[[package]]
name = "wait-timeout"
version = "0.2.0"
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"tools/bookrunner",
"tools/compiletest",
"tools/build-kani",
"tools/kanicov",
adpaco-aws marked this conversation as resolved.
Show resolved Hide resolved
"kani-driver",
"kani-compiler",
"kani_metadata",
Expand Down
13 changes: 13 additions & 0 deletions tools/kanicov/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "visualize_coverage"
adpaco-aws marked this conversation as resolved.
Show resolved Hide resolved
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.81"
console = "0.15.8"
serde = "1.0.197"
serde_derive = "1.0.197"
serde_json = "1.0.115"
124 changes: 124 additions & 0 deletions tools/kanicov/src/coverage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use anyhow::Result;
use console::style;
use serde_derive::{Deserialize, Serialize};
use std::fmt::{self, Write};
use std::{collections::BTreeMap, fmt::Display};

#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum CheckStatus {
Failure,
Covered, // for `code_coverage` properties only
Satisfied, // for `cover` properties only
Success,
Undetermined,
Unreachable,
Uncovered, // for `code_coverage` properties only
Unsatisfiable, // for `cover` properties only
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CoverageResults {
pub data: BTreeMap<String, Vec<CoverageCheck>>,
}

impl CoverageResults {
pub fn new(data: BTreeMap<String, Vec<CoverageCheck>>) -> Self {
Self { data }
}
}
pub fn fmt_coverage_results(coverage_results: &CoverageResults) -> Result<String> {
let mut fmt_string = String::new();
for (file, checks) in coverage_results.data.iter() {
let mut checks_by_function: BTreeMap<String, Vec<CoverageCheck>> = BTreeMap::new();

// // Group checks by function
for check in checks {
// Insert the check into the vector corresponding to its function
checks_by_function
.entry(check.function.clone())
.or_insert_with(Vec::new)
.push(check.clone());
}

for (function, checks) in checks_by_function {
writeln!(fmt_string, "{file} ({function})")?;
let mut sorted_checks: Vec<CoverageCheck> = checks.to_vec();
sorted_checks.sort_by(|a, b| a.region.start.cmp(&b.region.start));
for check in sorted_checks.iter() {
writeln!(fmt_string, " * {} {}", check.region, check.status)?;
}
writeln!(fmt_string, "")?;
}
}
Ok(fmt_string)
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoverageCheck {
pub function: String,
term: CoverageTerm,
pub region: CoverageRegion,
status: CheckStatus,
}

impl CoverageCheck {
pub fn new(
function: String,
term: CoverageTerm,
region: CoverageRegion,
status: CheckStatus,
) -> Self {
Self { function, term, region, status }
}

pub fn is_covered(&self) -> bool {
self.status == CheckStatus::Covered
}
}

#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct CoverageRegion {
pub file: String,
pub start: (u32, u32),
pub end: (u32, u32),
}

impl Display for CoverageRegion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{} - {}:{}", self.start.0, self.start.1, self.end.0, self.end.1)
}
}

impl CoverageRegion {
pub fn from_str(str: String) -> Self {
let str_splits: Vec<&str> = str.split([':', '-']).map(|s| s.trim()).collect();
assert_eq!(str_splits.len(), 5, "{str:?}");
let file = str_splits[0].to_string();
let start = (str_splits[1].parse().unwrap(), str_splits[2].parse().unwrap());
let end = (str_splits[3].parse().unwrap(), str_splits[4].parse().unwrap());
Self { file, start, end }
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CoverageTerm {
Counter(u32),
Expression(u32),
}

impl std::fmt::Display for CheckStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let check_str = match self {
CheckStatus::Satisfied => style("SATISFIED").green(),
CheckStatus::Success => style("SUCCESS").green(),
CheckStatus::Covered => style("COVERED").green(),
CheckStatus::Uncovered => style("UNCOVERED").red(),
CheckStatus::Failure => style("FAILURE").red(),
CheckStatus::Unreachable => style("UNREACHABLE").yellow(),
CheckStatus::Undetermined => style("UNDETERMINED").yellow(),
CheckStatus::Unsatisfiable => style("UNSATISFIABLE").yellow(),
};
write!(f, "{check_str}")
}
}
113 changes: 113 additions & 0 deletions tools/kanicov/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
mod coverage;
use std::fs::File;
use std::io::Read;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;

use crate::coverage::CoverageResults;
use anyhow::{Context, Result};
use coverage::CoverageCheck;

fn main() -> Result<()> {
let args = std::env::args().collect::<Vec<_>>();

let file_path = args.get(1).context("kanicov file not specified")?;
let mut file = File::open(file_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let path_to_root = cargo_root_dir(file_path.into());
let cov_results: CoverageResults = serde_json::from_str(&contents)?;
let visual_results = visualize_coverage_results(&cov_results, path_to_root.unwrap())
.expect("error: couldn't format coverage results");
println!("{visual_results}");
Ok(())
}

fn visualize_coverage_results(cov_results: &CoverageResults, root_path: PathBuf) -> Result<String> {
let mut formatted_output = String::new();
let cov_data = &cov_results.data;

for file in cov_data.keys() {
let file_path = root_path.join(file);
let file_handle = File::open(file_path)?;
let reader = BufReader::new(file_handle);

let checks = cov_data.get(file).unwrap().to_vec();
let mut must_highlight = false;

for (idx, line) in reader.lines().enumerate() {
let line = format!("{}\n", line.unwrap());

let cur_idx = idx + 1;
let line_checks: Vec<&CoverageCheck> = checks
.iter()
.filter(|c| {
c.is_covered()
&& (cur_idx == c.region.start.0 as usize
|| cur_idx == c.region.end.0 as usize)
})
.collect();
let new_line = if line_checks.is_empty() {
if must_highlight {
insert_escapes(&line, vec![(0, true), (line.len() - 1, false)])
} else {
line
}
} else {
let mut markers = vec![];
if must_highlight {
markers.push((0, true))
};

for check in line_checks {
let start_line = check.region.start.0 as usize;
let start_column = (check.region.start.1 - 1u32) as usize;
let end_line = check.region.end.0 as usize;
let end_column = (check.region.end.1 - 1u32) as usize;
if start_line == cur_idx {
markers.push((start_column, true))
}
if end_line == cur_idx {
markers.push((end_column, false))
}
}

if markers.last().unwrap().1 {
must_highlight = true;
markers.push((line.len() - 1, false))
} else {
must_highlight = false;
}
println!("{:?}", markers);
insert_escapes(&line, markers)
};
formatted_output.push_str(&new_line);
}
}
Ok(formatted_output)
}

fn cargo_root_dir(filepath: PathBuf) -> Option<PathBuf> {
let mut cargo_root_path = filepath.clone();
while !cargo_root_path.join("Cargo.toml").exists() {
let pop_result = cargo_root_path.pop();
if !pop_result {
return None;
}
}
Some(cargo_root_path)
}

fn insert_escapes(str: &String, markers: Vec<(usize, bool)>) -> String {
let mut new_str = str.clone();
let mut offset = 0;

let sym_markers = markers.iter().map(|(i, b)| (i, if *b { "\x1b[42m" } else { "\x1b[0m" }));
// let sym_markers = markers.iter().map(|(i, b)| (i, if *b { "```" } else { "'''" }));
for (i, b) in sym_markers {
println!("{}", i + offset);
new_str.insert_str(i + offset, b);
offset = offset + b.bytes().len();
}
new_str
}
Loading