Skip to content

Commit 9414fee

Browse files
Move autoharness_analyzer to scripts/ (#350)
Move `autoharness_analyzer` to this repository so it can be community-owned and maintained. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Michael Tautschnig <mt@debian.org>
1 parent 1eb9a53 commit 9414fee

File tree

8 files changed

+549
-10
lines changed

8 files changed

+549
-10
lines changed

.github/workflows/kani.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,12 +291,14 @@ jobs:
291291
# Step 3: Add output to job summary
292292
- name: Add Autoharness Analyzer output to job summary
293293
run: |
294+
pushd scripts/autoharness_analyzer
294295
echo "# Autoharness Failure Summary" >> "$GITHUB_STEP_SUMMARY"
295296
echo "## Crate core, all functions" >> "$GITHUB_STEP_SUMMARY"
296-
cat autoharness_analyzer/core_autoharness_data.md >> "$GITHUB_STEP_SUMMARY"
297+
cat core_autoharness_data.md >> "$GITHUB_STEP_SUMMARY"
297298
echo "## Crate core, unsafe functions" >> "$GITHUB_STEP_SUMMARY"
298-
cat autoharness_analyzer/core_autoharness_data.md >> "$GITHUB_STEP_SUMMARY"
299+
cat core_autoharness_data.md >> "$GITHUB_STEP_SUMMARY"
299300
echo "## Crate std, all functions" >> "$GITHUB_STEP_SUMMARY"
300-
cat autoharness_analyzer/std_autoharness_data.md >> "$GITHUB_STEP_SUMMARY"
301+
cat std_autoharness_data.md >> "$GITHUB_STEP_SUMMARY"
301302
echo "## Crate std, unsafe functions" >> "$GITHUB_STEP_SUMMARY"
302-
cat autoharness_analyzer/std_unsafe_autoharness_data.md >> "$GITHUB_STEP_SUMMARY"
303+
cat std_unsafe_autoharness_data.md >> "$GITHUB_STEP_SUMMARY"
304+
popd

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Session.vim
2222
/kani_build/
2323
/target
2424
library/target
25+
scripts/autoharness_analyzer/target/
2526
*.rlib
2627
*.rmeta
2728
*.mir
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "autoharness_analyzer"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
anyhow = "1.0.97"
8+
clap = {version = "4.5.37", features = ["derive"] }
9+
serde = { version = "1.0.219", features = ["derive"] }
10+
serde_json = "1.0.140"
11+
strum = "0.27.1"
12+
strum_macros = "0.27.1"
13+
to_markdown_table = "0.1.5"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Invoke with:
2+
```
3+
cargo run metadata/ scanner_results/
4+
```
5+
6+
The metadata/ folder contains the kani-metadata.json files produced by running:
7+
8+
```
9+
kani autoharness --std ./library -Z function-contracts -Z mem-predicates -Z float-lib -Z c-ffi -Z loop-contracts --only-codegen --no-assert-contracts -j --output-format=terse -Z unstable-options -Z autoharness --cbmc-args --object-bits 12
10+
```
11+
12+
on the standard library. The scanner_results/ directory contains the CSV files that the [scanner tool in Kani](https://github.com/model-checking/kani/tree/main/tools/scanner) produces.
13+
14+
The output is `autoharness_data.md`, which contains Markdown tables summarizing the autoharness application across all the crates in the standard library.
15+
16+
One of the tables has a column for "Skipped Type Categories." Generally speaking, "precise types" are what we think of as actual Rust types, and "type categories" are my subjective sense of how to group those types further. For example, `&mut i32` and `&mut u32` are two precise types, but they're in the same type category `&mut`. See the code for exact details on how we create type categories; the TL;DR is that we have a few hardcoded ones for raw pointers and references, and the rest we create using a fully-qualified path splitting heuristic.
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
use anyhow::Result;
2+
use clap::Parser;
3+
use make_tables::compute_metrics;
4+
use parse_scanner_output::process_scan_fns;
5+
use serde::{Deserialize, Serialize};
6+
use serde_json::Value;
7+
use std::{
8+
collections::{BTreeMap, HashMap},
9+
fs,
10+
path::Path,
11+
};
12+
use strum_macros::{Display, EnumString};
13+
14+
mod make_tables;
15+
mod parse_scanner_output;
16+
17+
#[derive(Parser, Debug)]
18+
struct Args {
19+
/// Path to directory with kani_metadata.json files
20+
#[arg(required = true)]
21+
metadata_dir_path: String,
22+
23+
/// Scanner results directory path
24+
#[arg(required = true)]
25+
scanner_results_path: String,
26+
27+
/// Generate per-crate tables instead of combined data
28+
#[arg(long)]
29+
per_crate: bool,
30+
31+
/// Process only the specified crate
32+
#[arg(long, value_name = "CRATE")]
33+
for_crate: Option<String>,
34+
35+
/// Show precise types in the output tables
36+
#[arg(long)]
37+
show_precise_types: bool,
38+
39+
/// Only output data for unsafe functions
40+
#[arg(long)]
41+
unsafe_fns_only: bool,
42+
}
43+
#[derive(Debug, Clone, Serialize, Deserialize)]
44+
pub struct AutoHarnessMetadata {
45+
/// Functions we generated automatic harnesses for.
46+
pub chosen: Vec<String>,
47+
/// Map function names to the reason why we did not generate an automatic harness for that function.
48+
/// We use an ordered map so that when we print the data, it is ordered alphabetically by function name.
49+
pub skipped: BTreeMap<String, AutoHarnessSkipReason>,
50+
}
51+
52+
/// Reasons that Kani does not generate an automatic harness for a function.
53+
#[derive(Debug, Clone, Serialize, Deserialize, Display, EnumString)]
54+
pub enum AutoHarnessSkipReason {
55+
/// The function is generic.
56+
#[strum(serialize = "Generic Function")]
57+
GenericFn,
58+
/// A Kani-internal function: already a harness, implementation of a Kani associated item or Kani contract instrumentation functions).
59+
#[strum(serialize = "Kani implementation")]
60+
KaniImpl,
61+
/// At least one of the function's arguments does not implement kani::Arbitrary
62+
/// (The Vec<(String, String)> contains the list of (name, type) tuples for each argument that does not implement it
63+
#[strum(serialize = "Missing Arbitrary implementation for argument(s)")]
64+
MissingArbitraryImpl(Vec<(String, String)>),
65+
/// The function does not have a body.
66+
#[strum(serialize = "The function does not have a body")]
67+
NoBody,
68+
/// The function doesn't match the user's provided filters.
69+
#[strum(serialize = "Did not match provided filters")]
70+
UserFilter,
71+
}
72+
73+
impl Default for AutoHarnessMetadata {
74+
fn default() -> Self {
75+
Self::new()
76+
}
77+
}
78+
79+
impl AutoHarnessMetadata {
80+
pub fn new() -> Self {
81+
Self {
82+
chosen: Vec::new(),
83+
skipped: BTreeMap::new(),
84+
}
85+
}
86+
87+
pub fn extend(&mut self, other: AutoHarnessMetadata) {
88+
self.chosen.extend(other.chosen);
89+
self.skipped.extend(other.skipped);
90+
}
91+
}
92+
93+
fn main() -> Result<()> {
94+
let args = Args::parse();
95+
96+
// Collection of data from all crates
97+
let mut cross_crate_fn_to_row_data = HashMap::new();
98+
let mut cross_crate_autoharness_md = AutoHarnessMetadata::new();
99+
100+
// Iterate over all kani-metadata.json files; one per crate
101+
for entry in fs::read_dir(&args.metadata_dir_path)? {
102+
let entry = entry?;
103+
let path = entry.path();
104+
if !path.to_string_lossy().contains("kani-metadata.json") {
105+
continue;
106+
}
107+
108+
let kani_md_file_data =
109+
std::fs::read_to_string(&path).unwrap_or_else(|_| panic!("Unable to read {:?}", path));
110+
let v: Value = serde_json::from_str(&kani_md_file_data)?;
111+
let crate_name = v["crate_name"].as_str().unwrap();
112+
113+
// Skip if a specific crate was requested and this isn't it
114+
if let Some(ref target_crate) = args.for_crate {
115+
if target_crate != crate_name {
116+
continue;
117+
}
118+
}
119+
120+
println!("Processing crate {crate_name}");
121+
122+
let scanner_fn_csv = format!(
123+
"{}/{}_scan_functions.csv",
124+
args.scanner_results_path, crate_name
125+
);
126+
let scanner_fn_csv_path = Path::new(&scanner_fn_csv);
127+
let fn_to_row_data = process_scan_fns(scanner_fn_csv_path)?;
128+
129+
let autoharness_md: AutoHarnessMetadata =
130+
serde_json::from_value(v["autoharness_md"].clone())?;
131+
132+
if args.per_crate {
133+
// Process each crate separately
134+
compute_metrics(
135+
crate_name,
136+
&autoharness_md,
137+
&fn_to_row_data,
138+
args.show_precise_types,
139+
args.unsafe_fns_only,
140+
)?;
141+
} else if args.for_crate.is_some() {
142+
return compute_metrics(
143+
crate_name,
144+
&autoharness_md,
145+
&fn_to_row_data,
146+
args.show_precise_types,
147+
args.unsafe_fns_only,
148+
);
149+
} else {
150+
cross_crate_fn_to_row_data.extend(fn_to_row_data);
151+
cross_crate_autoharness_md.extend(autoharness_md);
152+
}
153+
}
154+
155+
// Process combined data if not doing per-crate or single-crate analysis
156+
if !args.per_crate {
157+
compute_metrics(
158+
"all_crates",
159+
&cross_crate_autoharness_md,
160+
&cross_crate_fn_to_row_data,
161+
args.show_precise_types,
162+
args.unsafe_fns_only,
163+
)?;
164+
}
165+
166+
Ok(())
167+
}

0 commit comments

Comments
 (0)