distributed_verification/
kani_list.rs1use crate::{ProofKind, Result, SerFunction};
2use eyre::ContextCompat;
3use indexmap::{IndexMap, IndexSet};
4use serde::{Deserialize, Serialize};
5use std::{collections::HashMap, process::Command};
6
7#[derive(Debug, Serialize, Deserialize, Clone)]
9#[serde(rename_all = "kebab-case")]
10pub struct KaniList {
11 pub kani_version: String,
12 pub file_version: String,
13 pub standard_harnesses: IndexMap<String, IndexSet<String>>,
14 pub contract_harnesses: IndexMap<String, IndexSet<String>>,
15 pub contracts: IndexSet<ContractedFunction>,
16 pub totals: Total,
17}
18
19#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub struct ContractedFunction {
21 pub function: String,
22 pub file: String,
23 pub harnesses: Vec<String>,
24}
25
26#[derive(Debug, Serialize, Deserialize, Clone)]
27#[serde(rename_all = "kebab-case")]
28pub struct Total {
29 pub standard_harnesses: usize,
30 pub contract_harnesses: usize,
31 pub functions_under_contract: usize,
32}
33
34pub fn check(file: &str, v_ser_fun: &[&SerFunction]) {
36 let list = get_kani_list(file).unwrap();
37 check_proofs(&list, v_ser_fun).unwrap();
38}
39
40pub fn get_kani_list(rs_file_path: &str) -> Result<KaniList> {
42 let args = ["list", "-Zlist", "-Zfunction-contracts", "--format=json", rs_file_path];
44 let output = Command::new("kani").args(args).output()?;
45 assert!(
46 output.status.success(),
47 "Failed to run `kani list -Zlist -Zfunction-contracts --format=json {rs_file_path}`:\n{}",
48 std::str::from_utf8(&output.stderr).unwrap()
49 );
50
51 read_kani_list("kani-list.json")
53}
54
55pub fn read_kani_list(kani_list_path: &str) -> Result<KaniList> {
57 let _span = debug_span!("read_kani_list", kani_list_path).entered();
58 let file_json = std::fs::File::open(kani_list_path)?;
59 Ok(serde_json::from_reader(file_json)?)
60}
61
62pub fn check_proofs(list: &KaniList, v_ser_fun: &[&SerFunction]) -> Result<()> {
64 let totals = &list.totals;
66 {
67 let len = v_ser_fun.len();
68 let total = totals.standard_harnesses + totals.contract_harnesses;
69 ensure!(
70 len == total,
71 "The length of Vec<SerFunction> is {len}, but total {total} proofs in kani list."
72 );
73 }
74 {
75 let len = list.standard_harnesses.values().map(|s| s.len()).sum::<usize>();
76 let total = totals.standard_harnesses;
77 ensure!(
78 len == total,
79 "Found {len} standard proofs, but kani list reports {total} of them."
80 );
81 }
82 {
83 let len = list.contract_harnesses.values().map(|s| s.len()).sum::<usize>();
84 let total = totals.contract_harnesses;
85 ensure!(
86 len == total,
87 "Found {len} contract proofs, but kani list reports {total} of them."
88 );
89 }
90
91 let map: HashMap<_, _> = v_ser_fun
92 .iter()
93 .enumerate()
94 .map(|(idx, f)| ((&*f.file, &*f.name), (idx, f.proof_kind)))
95 .collect();
96
97 for (path, proofs) in &list.standard_harnesses {
99 for proof in proofs {
100 let key = (path.as_str(), proof.as_str());
101 map.get(&key).with_context(|| {
102 format!(
103 "The standard proof {key:?} exists in kani list, \
104 but not in distributed-verification JSON."
105 )
106 })?;
107 }
108 }
109
110 for (path, proofs) in &list.contract_harnesses {
112 for proof in proofs {
113 let key = (path.as_str(), proof.as_str());
114 map.get(&key).with_context(|| {
115 format!(
116 "The contract proof {key:?} exists in kani list, \
117 but not in distributed-verification JSON."
118 )
119 })?;
120 }
121 }
122
123 for (&(path, proof), &(_, kind)) in &map {
125 let harnesses = match kind {
126 Some(ProofKind::Standard) => &list.standard_harnesses[path],
127 Some(ProofKind::Contract) => &list.contract_harnesses[path],
128 None => continue,
129 };
130 harnesses.get(proof).with_context(|| {
131 format!(
132 "The {kind:?} proof {harnesses:?} exists in \
133 distributed-verification JSON, but not in kani list."
134 )
135 })?;
136 }
137
138 Ok(())
139}