Skip to content

Commit a4139e6

Browse files
committed
Run all proof harnesses by default (rust-lang#962)
* Introduce --harness and parse kani-metadata. * run all proof harnesses * enable function-less 'expected' tests for cargo-kani in compiletest * Only mention --harness not --function in docs * use --harness instead of --function in tests, mostly. * Run all harnesses, even if one fails. Also generate all reports in separate directories * Output reports (and harness) with - not :: in file names * Print all failed harnesses in a final summary * Make harness search robust against possible future name collisions
1 parent fc87d91 commit a4139e6

File tree

59 files changed

+453
-238
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+453
-238
lines changed

docs/src/kani-single-file.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ kani filenames.rs --visualize --cbmc-args --object-bits 11 --unwind 15
2222
**`--visualize`** will generate a report in the local directory accessible through `report/html/index.html`.
2323
This report will shows coverage information, as well as give traces for each failure Kani finds.
2424

25-
**`--function <name>`** Kani defaults to assuming the starting function is called `main`.
26-
You can change it to a different function with this argument.
27-
Note that to "find" the function given, it needs to be given the `#[kani::proof]` annotation.
25+
**`--harness <name>`** Kani defaults to checking all proof harnesses.
26+
You can switch to checking just one harness using this flag.
27+
Proof harnesses are functions that have been given the `#[kani::proof]` annotation.
2828

2929
**`--gen-c`** will generate a C file that roughly corresponds to the input Rust file.
3030
This can sometimes be helpful when trying to debug a problem with Kani.

docs/src/tutorial-first-steps.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ Fortunately, Kani is able to report a coverage metric for each proof harness.
185185
Try running:
186186

187187
```
188-
kani --visualize src/final_form.rs --function verify_success
188+
kani --visualize src/final_form.rs --harness verify_success
189189
open report/html/index.html
190190
```
191191

docs/src/tutorial-kinds-of-failure.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ But we're able to check this unsafe code with Kani:
4343
```
4444

4545
```
46-
# kani src/bounds_check.rs --function bound_check
46+
# kani src/bounds_check.rs --harness bound_check
4747
[...]
4848
** 15 of 448 failed
4949
[...]
@@ -56,7 +56,7 @@ Kani is inserting a lot more checks than appear as asserts in our code, so the o
5656
Let's narrow that output down a bit:
5757

5858
```
59-
# kani src/bounds_check.rs --function bound_check | grep Failed
59+
# kani src/bounds_check.rs --harness bound_check | grep Failed
6060
Failed Checks: attempt to compute offset which would overflow
6161
Failed Checks: attempt to calculate the remainder with a divisor of zero
6262
Failed Checks: attempt to add with overflow
@@ -92,7 +92,7 @@ Consider trying a few more small exercises with this example:
9292
Having switched back to the safe indexing operation, Kani reports two failures:
9393

9494
```
95-
# kani src/bounds_check.rs --function bound_check | grep Failed
95+
# kani src/bounds_check.rs --harness bound_check | grep Failed
9696
Failed Checks: index out of bounds: the length is less than or equal to the given index
9797
Failed Checks: dereference failure: pointer outside object bounds
9898
```
@@ -176,7 +176,7 @@ Kani will find these failures as well.
176176
Here's the output from Kani:
177177

178178
```
179-
# kani src/overflow.rs --function add_overflow
179+
# kani src/overflow.rs --harness add_overflow
180180
[...]
181181
RESULTS:
182182
Check 1: simple_addition.assertion.1

docs/src/tutorial-nondeterministic-variables.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ You can try it out by running the example under
3232
[arbitrary-variables directory](https://github.com/model-checking/kani/tree/main/docs/src/tutorial/arbitrary-variables/):
3333

3434
```
35-
cargo kani --function safe_update
35+
cargo kani --harness safe_update
3636
```
3737

3838
## Unsafe nondeterministic variables
@@ -57,7 +57,7 @@ The compiler is able to represent `Option<NonZeroU32>` using `32` bits by using
5757
You can try it out by running the example under [arbitrary-variables directory](https://github.com/model-checking/kani/tree/main/docs/src/tutorial/arbitrary-variables/):
5858

5959
```
60-
cargo kani --function unsafe_update
60+
cargo kani --harness unsafe_update
6161
```
6262

6363
## Safe nondeterministic variables for custom types
@@ -88,5 +88,5 @@ You can try it out by running the example under
8888
[`docs/src/tutorial/arbitrary-variables`](https://github.com/model-checking/kani/tree/main/docs/src/tutorial/arbitrary-variables/):
8989

9090
```
91-
cargo kani --function check_rating
91+
cargo kani --harness check_rating
9292
```

src/cargo-kani/src/args.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,14 @@ pub struct KaniArgs {
7272
#[structopt(flatten)]
7373
pub checks: CheckArgs,
7474

75-
/// Entry point for verification
76-
#[structopt(long, default_value = "main")]
77-
pub function: String,
75+
/// Entry point for verification (symbol name)
76+
#[structopt(long, hidden = true)]
77+
pub function: Option<String>,
78+
/// Entry point for verification (proof harness)
79+
// In a dry-run, we don't have kani-metadata.json to read, so we can't use this flag
80+
#[structopt(long, conflicts_with = "function", conflicts_with = "dry-run")]
81+
pub harness: Option<String>,
82+
7883
/// Link external C files referenced by Rust code.
7984
/// This is an experimental feature.
8085
#[structopt(long, parse(from_os_str), hidden = true)]
@@ -286,6 +291,14 @@ impl KaniArgs {
286291
)
287292
.exit();
288293
}
294+
295+
if self.cbmc_args.contains(&OsString::from("--function")) {
296+
Error::with_description(
297+
"Invalid flag: --function should be provided to Kani directly, not via --cbmc-args.",
298+
ErrorKind::ArgumentConflict,
299+
)
300+
.exit();
301+
}
289302
}
290303
}
291304

@@ -317,4 +330,13 @@ mod tests {
317330
assert_eq!(t, format!("{}", AbstractionType::from_str(t).unwrap()));
318331
}
319332
}
333+
334+
#[test]
335+
fn check_dry_run_harness_conflicts() {
336+
// harness needs metadata which we don't have with dry-run
337+
let args = vec!["kani", "file.rs", "--dry-run", "--harness", "foo"];
338+
let app = StandaloneArgs::clap();
339+
let err = app.get_matches_from_safe(args).unwrap_err();
340+
assert!(err.kind == ErrorKind::ArgumentConflict);
341+
}
320342
}

src/cargo-kani/src/call_cargo.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use anyhow::{Context, Result};
55
use std::ffi::OsString;
6-
use std::path::PathBuf;
6+
use std::path::{Path, PathBuf};
77
use std::process::Command;
88

99
use crate::session::KaniSession;
@@ -17,6 +17,8 @@ pub struct CargoOutputs {
1717
pub symtabs: Vec<PathBuf>,
1818
/// The location of vtable restrictions files (a directory of *.restrictions.json)
1919
pub restrictions: Option<PathBuf>,
20+
/// The kani-metadata.json files written by kani-compiler.
21+
pub metadata: Vec<PathBuf>,
2022
}
2123

2224
impl KaniSession {
@@ -44,7 +46,7 @@ impl KaniSession {
4446
args.push(build_target.into());
4547

4648
args.push("--target-dir".into());
47-
args.push(target_dir.clone().into());
49+
args.push(target_dir.into());
4850

4951
if self.args.verbose {
5052
args.push("-v".into());
@@ -59,26 +61,26 @@ impl KaniSession {
5961
self.run_terminal(cmd)?;
6062

6163
if self.args.dry_run {
62-
// mock an answer
64+
// mock an answer: mostly the same except we don't/can't expand the globs
6365
return Ok(CargoOutputs {
6466
outdir: outdir.clone(),
65-
symtabs: vec![outdir.join("dry-run.symtab.json")],
67+
symtabs: vec![outdir.join("*.symtab.json")],
68+
metadata: vec![outdir.join("*.kani-metadata.json")],
6669
restrictions: self.args.restrict_vtable().then(|| outdir),
6770
});
6871
}
6972

70-
let symtabs = glob(&outdir.join("*.symtab.json"));
71-
7273
Ok(CargoOutputs {
7374
outdir: outdir.clone(),
74-
symtabs: symtabs?,
75+
symtabs: glob(&outdir.join("*.symtab.json"))?,
76+
metadata: glob(&outdir.join("*.kani-metadata.json"))?,
7577
restrictions: self.args.restrict_vtable().then(|| outdir),
7678
})
7779
}
7880
}
7981

8082
/// Given a `path` with glob characters in it (e.g. `*.json`), return a vector of matching files
81-
fn glob(path: &PathBuf) -> Result<Vec<PathBuf>> {
83+
fn glob(path: &Path) -> Result<Vec<PathBuf>> {
8284
let results = glob::glob(path.to_str().context("Non-UTF-8 path enountered")?)?;
8385
// the logic to turn "Iter<Result<T, E>>" into "Result<Vec<T>, E>" doesn't play well
8486
// with anyhow, so a type annotation is required

src/cargo-kani/src/call_cbmc_viewer.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::util::alter_extension;
1212
impl KaniSession {
1313
/// Run CBMC appropriately to produce 3 output XML files, then run cbmc-viewer on them to produce a report.
1414
/// Viewer doesn't give different error codes depending on verification failure, so as long as it works, we report success.
15-
pub fn run_visualize(&self, file: &Path, default_reportdir: &str) -> Result<()> {
15+
pub fn run_visualize(&self, file: &Path, report_dir: &Path) -> Result<()> {
1616
let results_filename = alter_extension(file, "results.xml");
1717
let coverage_filename = alter_extension(file, "coverage.xml");
1818
let property_filename = alter_extension(file, "property.xml");
@@ -28,12 +28,6 @@ impl KaniSession {
2828
self.cbmc_variant(file, &["--xml-ui", "--cover", "location"], &coverage_filename)?;
2929
self.cbmc_variant(file, &["--xml-ui", "--show-properties"], &property_filename)?;
3030

31-
let reportdir = if let Some(pb) = &self.args.target_dir {
32-
pb.join("report").into_os_string()
33-
} else {
34-
default_reportdir.into()
35-
};
36-
3731
let args: Vec<OsString> = vec![
3832
"--result".into(),
3933
results_filename.into(),
@@ -48,7 +42,7 @@ impl KaniSession {
4842
"--goto".into(),
4943
file.into(),
5044
"--reportdir".into(),
51-
reportdir.clone(),
45+
report_dir.into(),
5246
];
5347

5448
// TODO get cbmc-viewer path from self
@@ -59,7 +53,7 @@ impl KaniSession {
5953

6054
// Let the user know
6155
if !self.args.quiet {
62-
println!("Report written to: {}/html/index.html", reportdir.to_string_lossy());
56+
println!("Report written to: {}/html/index.html", report_dir.to_string_lossy());
6357
}
6458

6559
Ok(())

src/cargo-kani/src/call_goto_instrument.rs

Lines changed: 4 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,10 @@ use std::ffi::OsString;
66
use std::path::Path;
77
use std::process::Command;
88

9+
use crate::metadata::collect_and_link_function_pointer_restrictions;
910
use crate::session::KaniSession;
1011
use crate::util::alter_extension;
1112

12-
use kani_metadata::{InternedString, TraitDefinedMethod, VtableCtxResults};
13-
use std::collections::HashMap;
14-
use std::fs::File;
15-
use std::io::{BufReader, BufWriter};
16-
1713
impl KaniSession {
1814
/// Postprocess a goto binary (before cbmc, after linking) in-place by calling goto-instrument
1915
pub fn run_goto_instrument(&self, input: &Path, output: &Path, function: &str) -> Result<()> {
@@ -28,6 +24,9 @@ impl KaniSession {
2824
}
2925

3026
if self.args.gen_c {
27+
if !self.args.quiet {
28+
println!("Generated C code written to {}", output.to_string_lossy());
29+
}
3130
self.gen_c(output)?;
3231
}
3332

@@ -124,77 +123,3 @@ impl KaniSession {
124123
self.run_suppress(cmd)
125124
}
126125
}
127-
128-
/// Collect all vtable restriction metadata together, and write one combined output in CBMC's format
129-
fn link_function_pointer_restrictions(
130-
data_per_crate: Vec<VtableCtxResults>,
131-
output_filename: &Path,
132-
) -> Result<()> {
133-
// Combine all method possibilities into one global mapping
134-
let mut combined_possible_methods: HashMap<TraitDefinedMethod, Vec<InternedString>> =
135-
HashMap::new();
136-
for crate_data in &data_per_crate {
137-
for entry in &crate_data.possible_methods {
138-
combined_possible_methods
139-
.insert(entry.trait_method.clone(), entry.possibilities.clone());
140-
}
141-
}
142-
143-
// Emit a restriction for every call site
144-
let mut output = HashMap::new();
145-
for crate_data in data_per_crate {
146-
for call_site in crate_data.call_sites {
147-
// CBMC Now supports referencing callsites by label:
148-
// https://github.com/diffblue/cbmc/pull/6508
149-
let cbmc_call_site_name = format!("{}.{}", call_site.function_name, call_site.label);
150-
let trait_def = call_site.trait_method;
151-
152-
// Look up all possibilities, defaulting to the empty set
153-
let possibilities =
154-
combined_possible_methods.get(&trait_def).unwrap_or(&vec![]).clone();
155-
output.insert(cbmc_call_site_name, possibilities);
156-
}
157-
}
158-
159-
let f = File::create(output_filename)?;
160-
let f = BufWriter::new(f);
161-
serde_json::to_writer(f, &output)?;
162-
Ok(())
163-
}
164-
165-
/// From either a file or a path with multiple files, output the CBMC restrictions file we should use.
166-
fn collect_and_link_function_pointer_restrictions(
167-
path: &Path,
168-
output_filename: &Path,
169-
) -> Result<()> {
170-
let md = std::fs::metadata(path)?;
171-
172-
// Fill with data from all files in that path with the expected suffix
173-
let mut per_crate_restrictions = Vec::new();
174-
175-
if md.is_dir() {
176-
for element in path.read_dir()? {
177-
let path = element?.path();
178-
if path.as_os_str().to_str().unwrap().ends_with(".restrictions.json") {
179-
let restrictions = read_restrictions(&path)?;
180-
per_crate_restrictions.push(restrictions);
181-
}
182-
}
183-
} else if md.is_file() {
184-
assert!(path.as_os_str().to_str().unwrap().ends_with(".restrictions.json"));
185-
let restrictions = read_restrictions(path)?;
186-
per_crate_restrictions.push(restrictions);
187-
} else {
188-
unreachable!("Path must be restrcitions file or directory containing restrictions files")
189-
}
190-
191-
link_function_pointer_restrictions(per_crate_restrictions, output_filename)
192-
}
193-
194-
/// Deserialize a *.restrictions.json file
195-
fn read_restrictions(path: &Path) -> Result<VtableCtxResults> {
196-
let file = File::open(path)?;
197-
let reader = BufReader::new(file);
198-
let restrictions = serde_json::from_reader(reader)?;
199-
Ok(restrictions)
200-
}

src/cargo-kani/src/call_single_file.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub struct SingleOutputs {
1818
pub symtab: PathBuf,
1919
/// The vtable restrictions files, if any.
2020
pub restrictions: Option<PathBuf>,
21+
/// The kani-metadata.json file written by kani-compiler.
22+
pub metadata: PathBuf,
2123
}
2224

2325
impl KaniSession {
@@ -34,7 +36,7 @@ impl KaniSession {
3436
let mut temps = self.temporaries.borrow_mut();
3537
temps.push(output_filename.clone());
3638
temps.push(typemap_filename);
37-
temps.push(metadata_filename);
39+
temps.push(metadata_filename.clone());
3840
if self.args.restrict_vtable() {
3941
temps.push(restrictions_filename.clone());
4042
}
@@ -55,11 +57,11 @@ impl KaniSession {
5557
args.push(t);
5658
}
5759
} else {
58-
if self.args.function != "main" {
59-
// Unless entry function for proof harness is main, compile code as lib.
60-
// This ensures that rustc won't prune functions that are not reachable
61-
// from main as well as enable compilation of crates that don't have a main
62-
// function.
60+
// If we specifically request "--function main" then don't override crate type
61+
if Some("main".to_string()) != self.args.function {
62+
// We only run against proof harnesses normally, and this change
63+
// 1. Means we do not require a `fn main` to exist
64+
// 2. Don't forget it also changes visibility rules.
6365
args.push("--crate-type".into());
6466
args.push("lib".into());
6567
}
@@ -80,6 +82,7 @@ impl KaniSession {
8082
Ok(SingleOutputs {
8183
outdir,
8284
symtab: output_filename,
85+
metadata: metadata_filename,
8386
restrictions: if self.args.restrict_vtable() {
8487
Some(restrictions_filename)
8588
} else {

0 commit comments

Comments
 (0)