Skip to content

Commit ef8ec1a

Browse files
authored
Merge pull request #97 from os-checker/diff
feat: add `merge` and `diff` subcommand for verify_rust_std CLI
2 parents 36dbd48 + 118e9a2 commit ef8ec1a

File tree

8 files changed

+95324
-16
lines changed

8 files changed

+95324
-16
lines changed

src/bin/verify_rust_std/diff.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use crate::{Result, read_json};
2+
use clap::Parser;
3+
use distributed_verification::diff::{MergeHashKaniList, diff};
4+
5+
pub fn run(args: &[String]) -> Result<()> {
6+
SubCmdDiff::parse_from(args).run()
7+
}
8+
9+
/// `verify_rust_std diff` subcommand that emits a diff merge.json.
10+
#[derive(Parser, Debug)]
11+
struct SubCmdDiff {
12+
/// Path to a previous merge.json that is generated by `verify_rust_std merge`.
13+
#[arg(long)]
14+
old: String,
15+
16+
/// Path to a recent merge.json that is generated by `verify_rust_std merge`.
17+
#[arg(long)]
18+
new: String,
19+
}
20+
21+
impl SubCmdDiff {
22+
fn run(self) -> Result<()> {
23+
let old: Vec<MergeHashKaniList> = read_json(&self.old)?;
24+
let new: Vec<MergeHashKaniList> = read_json(&self.new)?;
25+
26+
let difference = diff(&old, &new);
27+
serde_json::to_writer_pretty(std::io::stdout(), &difference)?;
28+
29+
Ok(())
30+
}
31+
}

src/bin/verify_rust_std/main.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! `VERIFY_RUST_STD_LIBRARY=path/to/verify-rust-std/library` and
22
//! `KANI_DIR=path/to/kani` should be set beforehand.
33
4+
use distributed_verification::logger;
45
use eyre::{Context, Result};
56
use std::process::{Command, Stdio};
67

@@ -12,6 +13,9 @@ extern crate eyre;
1213
mod env;
1314
use env::ENV;
1415

16+
mod diff;
17+
mod merge;
18+
1519
fn main() -> Result<()> {
1620
let mut args = std::env::args().collect::<Vec<_>>();
1721

@@ -38,6 +42,12 @@ fn main() -> Result<()> {
3842
// build non-core crates
3943
run("rustc", rustc_args, &[])
4044
}
45+
} else if args.get(1).map(|arg| arg == "merge").unwrap_or(false) {
46+
logger::init();
47+
merge::run(&args[1..])
48+
} else if args.get(1).map(|arg| arg == "diff").unwrap_or(false) {
49+
logger::init();
50+
diff::run(&args[1..])
4151
} else {
4252
run(
4353
"cargo",
@@ -80,3 +90,9 @@ fn build_core(args: Vec<String>) -> Result<()> {
8090
new_args.extend(args);
8191
run(&ENV.DISTRIBUTED_VERIFICATION, &new_args, &[])
8292
}
93+
94+
fn read_json<T: serde::de::DeserializeOwned>(path: &str) -> Result<T> {
95+
let _span = error_span!("read_json", path).entered();
96+
let file = std::fs::File::open(path)?;
97+
Ok(serde_json::from_reader(file)?)
98+
}

src/bin/verify_rust_std/merge.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use crate::Result;
2+
use clap::Parser;
3+
use distributed_verification::{
4+
SerFunction,
5+
diff::{KaniListJson, MergeHashKaniList},
6+
};
7+
use std::collections::HashMap;
8+
9+
pub fn run(args: &[String]) -> Result<()> {
10+
SubCmdMerge::parse_from(args).run()
11+
}
12+
13+
/// `verify_rust_std merge` subcommand
14+
#[derive(Parser, Debug)]
15+
struct SubCmdMerge {
16+
/// Path to hash json file which is generated by distributed-verification.
17+
#[arg(long)]
18+
hash_json: String,
19+
20+
/// Strip file path prefix in hash json.
21+
#[arg(long, default_value_t)]
22+
strip_hash_json_prefix: String,
23+
24+
/// Path to kani-list.json which is generated by kani.
25+
#[arg(long)]
26+
kani_list: String,
27+
28+
/// Strip file path prefix in kani-list.
29+
#[arg(long, default_value_t)]
30+
strip_kani_list_prefix: String,
31+
}
32+
33+
impl SubCmdMerge {
34+
fn run(self) -> Result<()> {
35+
debug!(?self);
36+
37+
let mut hash_json: Vec<SerFunction> = crate::read_json(&self.hash_json)?;
38+
39+
let mut kani_list: KaniListJson = crate::read_json(&self.kani_list)?;
40+
// normalize paths like core/src/../../portable-simd/crates/core_simd/src/masks.rs
41+
kani_list.normalize_file_path();
42+
43+
if !self.strip_kani_list_prefix.is_empty() {
44+
kani_list.strip_path_prefix_raw(&self.strip_kani_list_prefix);
45+
}
46+
47+
let mut hash_map = HashMap::with_capacity(hash_json.len());
48+
for func in &mut hash_json {
49+
let file = func.file.strip_prefix(&self.strip_hash_json_prefix).unwrap_or(&func.file);
50+
let fn_name = &*func.name;
51+
let value = &*func.hash;
52+
hash_map.insert((file, fn_name), value);
53+
}
54+
55+
let cap = kani_list.totals.standard_harnesses + kani_list.totals.contract_harnesses;
56+
let mut v_merge = Vec::with_capacity(cap);
57+
for file_func in kani_list.file_func_name() {
58+
let (file, func) = file_func;
59+
v_merge.push(MergeHashKaniList {
60+
file: file.into(),
61+
func: func.into(),
62+
hash: hash_map.get(&file_func).map(|s| (*s).into()),
63+
});
64+
}
65+
v_merge.sort_unstable();
66+
67+
serde_json::to_writer_pretty(std::io::stdout(), &v_merge)?;
68+
69+
Ok(())
70+
}
71+
}

src/diff.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::{BoxStr, ProofKind, SerFunction};
22
use indexmap::{IndexMap, IndexSet};
33
use serde::{Deserialize, Serialize};
44
use std::{
5+
collections::HashSet,
56
hash::Hash,
67
path::{Component, MAIN_SEPARATOR, Path, PathBuf},
78
};
@@ -104,6 +105,10 @@ impl KaniListHarnesses {
104105
}
105106
}
106107
}
108+
109+
fn file_func_name(&self) -> impl Iterator<Item = (&str, &str)> {
110+
self.inner.iter().flat_map(|(k, v)| v.iter().map(|func| (&**k, &**func)))
111+
}
107112
}
108113

109114
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord, Hash)]
@@ -119,9 +124,9 @@ pub struct ContractedFunction {
119124
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
120125
#[serde(rename_all = "kebab-case")]
121126
pub struct Totals {
122-
standard_harnesses: usize,
123-
contract_harnesses: usize,
124-
functions_under_contract: usize,
127+
pub standard_harnesses: usize,
128+
pub contract_harnesses: usize,
129+
pub functions_under_contract: usize,
125130
}
126131

127132
/// The datastructure generated from `kani list --json`.
@@ -187,6 +192,10 @@ impl KaniListJson {
187192
assert!(duplicates.is_empty(), "Function name duplicates: {duplicates:#?}");
188193
vec_to_set(&v)
189194
}
195+
196+
pub fn file_func_name(&self) -> impl Iterator<Item = (&str, &str)> {
197+
self.standard_harnesses.file_func_name().chain(self.contract_harnesses.file_func_name())
198+
}
190199
}
191200

192201
fn count_gt1<T: Copy + Hash + Eq>(v: &[T]) -> Vec<(T, u32)> {
@@ -236,3 +245,23 @@ impl MergedHarnesses<'_> {
236245
vec_to_set(&v)
237246
}
238247
}
248+
249+
/// Merge hash json and kani-list.
250+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
251+
pub struct MergeHashKaniList {
252+
pub file: Box<str>,
253+
pub func: Box<str>,
254+
#[serde(skip_serializing_if = "Option::is_none")]
255+
#[serde(default)]
256+
pub hash: Option<Box<str>>,
257+
}
258+
259+
/// Compare two `MergeHashKaniList`, and returns the ones from new that don't match the old,
260+
/// usually the ones don't have hash values or whoes hash values changed.
261+
///
262+
/// If new is sorted, especially directly from the stdout of verify_rust_std merge subcommand,
263+
/// the result is sorted.
264+
pub fn diff(old: &[MergeHashKaniList], new: &[MergeHashKaniList]) -> Vec<MergeHashKaniList> {
265+
let set: HashSet<_> = old.iter().collect();
266+
new.iter().filter(|item| item.hash.is_none() || !set.contains(item)).cloned().collect()
267+
}

0 commit comments

Comments
 (0)