Skip to content

Commit bb7662d

Browse files
authored
Allow users to select a subset of harness to run (rust-lang#2202)
Users can now select a subset of harnesses to run using `--harness` multiple times. Previously users could either run a single harness or they had to run them all. For the example provided in rust-lang#1778: ```rust // example.rs #[kani::proof] fn a() {} #[kani::proof] fn b() {} #[kani::proof] fn c() {std::unimplemented!();} ``` Users can select harnesses `a` and `b` by running: ```bash $ kani example.rs --harness a --harness b ``` In case of multiple matches, Kani will run all harnesses that matches at least one of the `--harness` argument.
1 parent 84068cf commit bb7662d

File tree

29 files changed

+344
-97
lines changed

29 files changed

+344
-97
lines changed

Cargo.lock

+10
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,15 @@ dependencies = [
469469
"windows-sys 0.45.0",
470470
]
471471

472+
[[package]]
473+
name = "itertools"
474+
version = "0.10.5"
475+
source = "registry+https://github.com/rust-lang/crates.io-index"
476+
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
477+
dependencies = [
478+
"either",
479+
]
480+
472481
[[package]]
473482
name = "itoa"
474483
version = "1.0.5"
@@ -492,6 +501,7 @@ dependencies = [
492501
"clap",
493502
"cprover_bindings",
494503
"home",
504+
"itertools",
495505
"kani_metadata",
496506
"kani_queries",
497507
"lazy_static",

kani-compiler/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ bitflags = { version = "1.0", optional = true }
1515
cbmc = { path = "../cprover_bindings", package = "cprover_bindings", optional = true }
1616
clap = { version = "4.1.3", features = ["cargo"] }
1717
home = "0.5"
18+
itertools = "0.10"
1819
kani_queries = {path = "kani_queries"}
1920
kani_metadata = { path = "../kani_metadata", optional = true }
2021
lazy_static = "1.4.0"

kani-compiler/src/kani_compiler.rs

+23-17
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ use crate::kani_middle::stubbing;
2020
use crate::parser::{self, KaniCompilerParser};
2121
use crate::session::init_session;
2222
use clap::ArgMatches;
23+
use itertools::Itertools;
2324
use kani_queries::{QueryDb, ReachabilityType, UserInput};
2425
use rustc_codegen_ssa::traits::CodegenBackend;
25-
use rustc_data_structures::fx::FxHashMap;
26+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
2627
use rustc_driver::{Callbacks, Compilation, RunCompiler};
2728
use rustc_hir::definitions::DefPathHash;
2829
use rustc_interface::Config;
@@ -101,9 +102,18 @@ impl KaniCompiler {
101102
let all_stubs = stubbing::collect_stub_mappings(tcx);
102103
if all_stubs.is_empty() {
103104
FxHashMap::default()
104-
} else if let Some(harness) = self.args.as_ref().unwrap().get_one::<String>(parser::HARNESS)
105+
} else if let Some(harnesses) =
106+
self.args.as_ref().unwrap().get_many::<String>(parser::HARNESS)
105107
{
106-
find_harness_stub_mapping(harness, all_stubs).unwrap_or_default()
108+
let mappings = filter_stub_mapping(harnesses.collect(), all_stubs);
109+
if mappings.len() > 1 {
110+
tcx.sess.err(format!(
111+
"Failed to apply stubs. Harnesses with stubs must be verified separately. Found: `{}`",
112+
mappings.into_keys().join("`, `")));
113+
FxHashMap::default()
114+
} else {
115+
mappings.into_values().next().unwrap_or_default()
116+
}
107117
} else {
108118
// No harness was provided. Nothing to do.
109119
FxHashMap::default()
@@ -167,20 +177,16 @@ impl Callbacks for KaniCompiler {
167177
}
168178
}
169179

170-
/// Find the stub mapping for the given harness.
180+
/// Find the stub mapping for the given harnesses.
171181
///
172182
/// This function is necessary because Kani currently allows a harness to be
173-
/// specified by a partially qualified name, whereas stub mappings use fully
174-
/// qualified names.
175-
fn find_harness_stub_mapping(
176-
harness: &str,
177-
stub_mappings: FxHashMap<String, FxHashMap<DefPathHash, DefPathHash>>,
178-
) -> Option<FxHashMap<DefPathHash, DefPathHash>> {
179-
let suffix = String::from("::") + harness;
180-
for (name, mapping) in stub_mappings {
181-
if name == harness || name.ends_with(&suffix) {
182-
return Some(mapping);
183-
}
184-
}
185-
None
183+
/// specified as a filter, whereas stub mappings use fully qualified names.
184+
fn filter_stub_mapping(
185+
harnesses: FxHashSet<&String>,
186+
mut stub_mappings: FxHashMap<String, FxHashMap<DefPathHash, DefPathHash>>,
187+
) -> FxHashMap<String, FxHashMap<DefPathHash, DefPathHash>> {
188+
stub_mappings.retain(|name, _| {
189+
harnesses.contains(name) || harnesses.iter().any(|harness| name.contains(*harness))
190+
});
191+
stub_mappings
186192
}

kani-compiler/src/parser.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ pub fn parser() -> Command {
129129
.long(HARNESS)
130130
.help("Selects the harness to target.")
131131
.value_name("HARNESS")
132-
.action(ArgAction::Set),
132+
.action(ArgAction::Append),
133133
)
134134
.arg(
135135
Arg::new(ENABLE_STUBBING)

kani-driver/src/args.rs

+30-5
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,15 @@ pub struct KaniArgs {
128128
/// This is an unstable feature. Consider using --harness instead
129129
#[arg(long, hide = true, requires("enable_unstable"))]
130130
pub function: Option<String>,
131-
/// Entry point for verification (proof harness)
132-
#[arg(long, conflicts_with = "function")]
133-
pub harness: Option<String>,
131+
/// If specified, only run harnesses that match this filter. This option can be provided
132+
/// multiple times, which will run all tests matching any of the filters.
133+
#[arg(
134+
long = "harness",
135+
conflicts_with = "function",
136+
num_args(1),
137+
value_name = "HARNESS_FILTER"
138+
)]
139+
pub harnesses: Vec<String>,
134140

135141
/// Link external C files referenced by Rust code.
136142
/// This is an experimental feature and requires `--enable-unstable` to be used
@@ -155,7 +161,7 @@ pub struct KaniArgs {
155161
#[arg(long)]
156162
pub default_unwind: Option<u32>,
157163
/// Specify the value used for loop unwinding for the specified harness in CBMC
158-
#[arg(long, requires("harness"))]
164+
#[arg(long, requires("harnesses"))]
159165
pub unwind: Option<u32>,
160166
/// Specify the CBMC solver to use. Overrides the harness `solver` attribute.
161167
#[arg(long, value_parser = CbmcSolverValueParser::new(CbmcSolver::VARIANTS))]
@@ -232,7 +238,7 @@ pub struct KaniArgs {
232238
long,
233239
hide_short_help = true,
234240
requires("enable_unstable"),
235-
requires("harness"),
241+
requires("harnesses"),
236242
conflicts_with("concrete_playback")
237243
)]
238244
pub enable_stubbing: bool,
@@ -650,6 +656,25 @@ mod tests {
650656
.unwrap();
651657
// no assertion: the above might fail if it fails to allow 0 args to cbmc-args
652658
}
659+
660+
/// Ensure users can pass multiple harnesses options and that the value is accumulated.
661+
#[test]
662+
fn check_multiple_harnesses() {
663+
let args =
664+
StandaloneArgs::try_parse_from("kani input.rs --harness a --harness b".split(" "))
665+
.unwrap();
666+
assert_eq!(args.common_opts.harnesses, vec!["a".to_owned(), "b".to_owned()]);
667+
}
668+
669+
#[test]
670+
fn check_multiple_harnesses_without_flag_fail() {
671+
let result = StandaloneArgs::try_parse_from(
672+
"kani input.rs --harness harness_1 harness_2".split(" "),
673+
);
674+
assert!(result.is_err());
675+
assert_eq!(result.unwrap_err().kind(), ErrorKind::UnknownArgument);
676+
}
677+
653678
#[test]
654679
fn check_multiple_packages() {
655680
// accepts repeated:

kani-driver/src/args_toml.rs

+30-2
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ fn toml_to_args(tomldata: &str) -> Result<(Vec<OsString>, Vec<OsString>)> {
106106
for (flag, value) in map {
107107
if flag == "cbmc-args" {
108108
// --cbmc-args has to come last because it eats all remaining arguments
109-
insert_arg_from_toml(&flag, &value, &mut cbmc_args)?;
109+
cbmc_args.push("--cbmc-args".into());
110+
cbmc_args.append(&mut cbmc_arg_from_toml(&value)?);
110111
} else {
111112
insert_arg_from_toml(&flag, &value, &mut args)?;
112113
}
@@ -129,9 +130,9 @@ fn insert_arg_from_toml(flag: &str, value: &Value, args: &mut Vec<OsString>) ->
129130
}
130131
}
131132
Value::Array(a) => {
132-
args.push(format!("--{flag}").into());
133133
for arg in a {
134134
if let Some(arg) = arg.as_str() {
135+
args.push(format!("--{flag}").into());
135136
args.push(arg.into());
136137
} else {
137138
bail!("flag {} contains non-string values", flag);
@@ -149,6 +150,33 @@ fn insert_arg_from_toml(flag: &str, value: &Value, args: &mut Vec<OsString>) ->
149150
Ok(())
150151
}
151152

153+
/// Translates one toml entry (flag, value) into arguments and inserts it into `args`
154+
fn cbmc_arg_from_toml(value: &Value) -> Result<Vec<OsString>> {
155+
let mut args = vec![];
156+
const CBMC_FLAG: &str = "--cbmc-args";
157+
match value {
158+
Value::Boolean(_) => {
159+
bail!("cannot pass boolean value to `{CBMC_FLAG}`")
160+
}
161+
Value::Array(a) => {
162+
for arg in a {
163+
if let Some(arg) = arg.as_str() {
164+
args.push(arg.into());
165+
} else {
166+
bail!("flag {CBMC_FLAG} contains non-string values");
167+
}
168+
}
169+
}
170+
Value::String(s) => {
171+
args.push(s.into());
172+
}
173+
_ => {
174+
bail!("Unknown key type {CBMC_FLAG}");
175+
}
176+
}
177+
Ok(args)
178+
}
179+
152180
/// Take 'a.b.c' and turn it into 'start['a']['b']['c']' reliably, and interpret the result as a table
153181
fn get_table<'a>(start: &'a Value, table: &str) -> Option<&'a Table> {
154182
let mut current = start;

kani-driver/src/assess/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ fn assess_project(mut session: KaniSession) -> Result<AssessMetadata> {
9191
}
9292

9393
// Done with the 'cargo-kani' part, now we're going to run *test* harnesses instead of proof:
94-
let harnesses = metadata.test_harnesses;
95-
let runner = crate::harness_runner::HarnessRunner { sess: &session, project };
94+
let harnesses = Vec::from_iter(metadata.test_harnesses.iter());
95+
let runner = crate::harness_runner::HarnessRunner { sess: &session, project: &project };
9696

9797
let results = runner.check_all_harnesses(&harnesses)?;
9898

kani-driver/src/call_single_file.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ impl KaniSession {
9090
if self.args.enable_stubbing {
9191
flags.push("--enable-stubbing".into());
9292
}
93-
if let Some(harness) = &self.args.harness {
93+
for harness in &self.args.harnesses {
9494
flags.push(format!("--harness={harness}"));
9595
}
9696

kani-driver/src/harness_runner.rs

+33-17
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright Kani Contributors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

4-
use anyhow::Result;
4+
use anyhow::{bail, Result};
55
use kani_metadata::{ArtifactType, HarnessMetadata};
66
use rayon::prelude::*;
77
use std::path::Path;
@@ -10,33 +10,33 @@ use crate::args::OutputFormat;
1010
use crate::call_cbmc::{VerificationResult, VerificationStatus};
1111
use crate::project::Project;
1212
use crate::session::KaniSession;
13-
use crate::util::specialized_harness_name;
13+
use crate::util::{error, specialized_harness_name};
1414

1515
/// A HarnessRunner is responsible for checking all proof harnesses. The data in this structure represents
1616
/// "background information" that the controlling driver (e.g. cargo-kani or kani) computed.
1717
///
1818
/// This struct is basically just a nicer way of passing many arguments to [`Self::check_all_harnesses`]
19-
pub(crate) struct HarnessRunner<'sess> {
19+
pub(crate) struct HarnessRunner<'sess, 'pr> {
2020
/// The underlying kani session
2121
pub sess: &'sess KaniSession,
2222
/// The project under verification.
23-
pub project: Project,
23+
pub project: &'pr Project,
2424
}
2525

2626
/// The result of checking a single harness. This both hangs on to the harness metadata
2727
/// (as a means to identify which harness), and provides that harness's verification result.
28-
pub(crate) struct HarnessResult<'sess> {
29-
pub harness: &'sess HarnessMetadata,
28+
pub(crate) struct HarnessResult<'pr> {
29+
pub harness: &'pr HarnessMetadata,
3030
pub result: VerificationResult,
3131
}
3232

33-
impl<'sess> HarnessRunner<'sess> {
33+
impl<'sess, 'pr> HarnessRunner<'sess, 'pr> {
3434
/// Given a [`HarnessRunner`] (to abstract over how these harnesses were generated), this runs
3535
/// the proof-checking process for each harness in `harnesses`.
36-
pub(crate) fn check_all_harnesses<'a>(
36+
pub(crate) fn check_all_harnesses(
3737
&self,
38-
harnesses: &'a [HarnessMetadata],
39-
) -> Result<Vec<HarnessResult<'a>>> {
38+
harnesses: &'pr [&HarnessMetadata],
39+
) -> Result<Vec<HarnessResult<'pr>>> {
4040
let sorted_harnesses = crate::metadata::sort_harnesses_by_loc(harnesses);
4141

4242
let pool = {
@@ -47,10 +47,10 @@ impl<'sess> HarnessRunner<'sess> {
4747
builder.build()?
4848
};
4949

50-
let results = pool.install(|| -> Result<Vec<HarnessResult<'a>>> {
50+
let results = pool.install(|| -> Result<Vec<HarnessResult<'pr>>> {
5151
sorted_harnesses
5252
.par_iter()
53-
.map(|harness| -> Result<HarnessResult<'a>> {
53+
.map(|harness| -> Result<HarnessResult<'pr>> {
5454
let harness_filename = harness.pretty_name.replace("::", "-");
5555
let report_dir = self.project.outdir.join(format!("report-{harness_filename}"));
5656
let goto_file =
@@ -139,11 +139,27 @@ impl KaniSession {
139139
"Complete - {succeeding} successfully verified harnesses, {failing} failures, {total} total."
140140
);
141141
} else {
142-
// TODO: This could use a better error message, possibly with links to Kani documentation.
143-
// New users may encounter this and could use a pointer to how to write proof harnesses.
144-
println!(
145-
"No proof harnesses (functions with #[kani::proof]) were found to verify."
146-
);
142+
match (self.args.harnesses.as_slice(), &self.args.function) {
143+
([], None) =>
144+
// TODO: This could use a better message, possibly with links to Kani documentation.
145+
// New users may encounter this and could use a pointer to how to write proof harnesses.
146+
{
147+
println!(
148+
"No proof harnesses (functions with #[kani::proof]) were found to verify."
149+
)
150+
}
151+
([harness], None) => {
152+
bail!("no harnesses matched the harness filter: `{harness}`")
153+
}
154+
(harnesses, None) => bail!(
155+
"no harnesses matched the harness filters: `{}`",
156+
harnesses.join("`, `")
157+
),
158+
([], Some(func)) => error(&format!("No function named {func} was found")),
159+
_ => unreachable!(
160+
"invalid configuration. Cannot specify harness and function at the same time"
161+
),
162+
};
147163
}
148164
}
149165

kani-driver/src/main.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33
#![feature(let_chains)]
44
#![feature(array_methods)]
5-
65
use std::ffi::OsString;
76
use std::process::ExitCode;
87

@@ -91,7 +90,7 @@ fn verify_project(project: Project, session: KaniSession) -> Result<()> {
9190
debug!(n = harnesses.len(), ?harnesses, "verify_project");
9291

9392
// Verification
94-
let runner = harness_runner::HarnessRunner { sess: &session, project };
93+
let runner = harness_runner::HarnessRunner { sess: &session, project: &project };
9594
let results = runner.check_all_harnesses(&harnesses)?;
9695

9796
session.print_final_summary(&results)

0 commit comments

Comments
 (0)