Skip to content

Commit 319040b

Browse files
authored
Autoharness: --list option (#3952)
Add `--list` option to the autoharness subcommand, which invokes the driver logic from the list subcommand to list automatic and manual harness metadata together. Towards #3832 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent 5d0ccca commit 319040b

File tree

12 files changed

+242
-32
lines changed

12 files changed

+242
-32
lines changed

kani-compiler/src/kani_middle/codegen_units.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use std::sync::OnceLock;
3636
use tracing::debug;
3737

3838
/// An identifier for the harness function.
39-
type Harness = Instance;
39+
pub type Harness = Instance;
4040

4141
/// A set of stubs.
4242
pub type Stubs = HashMap<FnDef, FnDef>;
@@ -92,8 +92,7 @@ impl CodegenUnits {
9292
args,
9393
*kani_fns.get(&KaniModel::Any.into()).unwrap(),
9494
);
95-
let chosen_fn_names =
96-
chosen.iter().map(|func| func.name().clone()).collect::<Vec<_>>();
95+
let chosen_fn_names = chosen.iter().map(|func| func.name()).collect::<Vec<_>>();
9796
AUTOHARNESS_MD
9897
.set(AutoHarnessMetadata { chosen: chosen_fn_names, skipped })
9998
.expect("Initializing the autoharness metdata failed");
@@ -167,7 +166,7 @@ impl CodegenUnits {
167166
proof_harnesses,
168167
unsupported_features: vec![],
169168
test_harnesses,
170-
contracted_functions: gen_contracts_metadata(tcx),
169+
contracted_functions: gen_contracts_metadata(tcx, &self.harness_info),
171170
autoharness_md: AUTOHARNESS_MD.get().cloned(),
172171
}
173172
}

kani-compiler/src/kani_middle/metadata.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::collections::HashMap;
77
use std::path::Path;
88

99
use crate::kani_middle::attributes::{KaniAttributes, test_harness_name};
10+
use crate::kani_middle::codegen_units::Harness;
1011
use crate::kani_middle::{SourceLocation, stable_fn_def};
1112
use kani_metadata::ContractedFunction;
1213
use kani_metadata::{ArtifactType, HarnessAttributes, HarnessKind, HarnessMetadata};
@@ -47,7 +48,10 @@ pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) ->
4748
///
4849
/// For each function with contracts (or that is a target of a contract harness),
4950
/// construct a `ContractedFunction` object for it.
50-
pub fn gen_contracts_metadata(tcx: TyCtxt) -> Vec<ContractedFunction> {
51+
pub fn gen_contracts_metadata(
52+
tcx: TyCtxt,
53+
harness_info: &HashMap<Harness, HarnessMetadata>,
54+
) -> Vec<ContractedFunction> {
5155
// We work with `stable_mir::CrateItem` instead of `stable_mir::Instance` to include generic items
5256
let crate_items: CrateItems = stable_mir::all_local_items();
5357

@@ -61,8 +65,8 @@ pub fn gen_contracts_metadata(tcx: TyCtxt) -> Vec<ContractedFunction> {
6165
if attributes.has_contract() {
6266
fn_to_data
6367
.insert(item.def_id(), ContractedFunction { function, file, harnesses: vec![] });
64-
} else if let Some((target_name, internal_def_id, _)) =
65-
attributes.interpret_for_contract_attribute()
68+
// This logic finds manual contract harnesses only (automatic harnesses are a Kani intrinsic, not crate items annotated with the proof_for_contract attribute).
69+
} else if let Some((_, internal_def_id, _)) = attributes.interpret_for_contract_attribute()
6670
{
6771
let target_def_id = stable_fn_def(tcx, internal_def_id)
6872
.expect("The target of a proof for contract should be a function definition")
@@ -73,7 +77,9 @@ pub fn gen_contracts_metadata(tcx: TyCtxt) -> Vec<ContractedFunction> {
7377
fn_to_data.insert(
7478
target_def_id,
7579
ContractedFunction {
76-
function: target_name.to_string(),
80+
// Note that we use the item's fully qualified-name, rather than the target name specified in the attribute.
81+
// This is necessary for the automatic contract harness lookup, see below.
82+
function: item.name(),
7783
file,
7884
harnesses: vec![function],
7985
},
@@ -82,6 +88,24 @@ pub fn gen_contracts_metadata(tcx: TyCtxt) -> Vec<ContractedFunction> {
8288
}
8389
}
8490

91+
// Find automatically generated contract harnesses (if the `autoharness` subcommand is running)
92+
for (harness, metadata) in harness_info {
93+
if !metadata.is_automatically_generated {
94+
continue;
95+
}
96+
if let HarnessKind::ProofForContract { target_fn } = &metadata.attributes.kind {
97+
// FIXME: This is a bit hacky. We can't resolve the target_fn to a DefId because we need somewhere to start the name resolution from.
98+
// For a manual harness, we could just start from the harness, but since automatic harnesses are Kani intrinsics, we can't resolve the target starting from them.
99+
// Instead, we rely on the fact that the ContractedFunction objects store the function's fully qualified name,
100+
// and that `gen_automatic_proof_metadata` uses the fully qualified name as well.
101+
// Once we implement multiple automatic harnesses for a single function, we will have to revise the HarnessMetadata anyway,
102+
// and then we can revisit the idea of storing the target_fn's DefId somewhere.
103+
let (_, target_cf) =
104+
fn_to_data.iter_mut().find(|(_, cf)| &cf.function == target_fn).unwrap();
105+
target_cf.harnesses.push(harness.name());
106+
}
107+
}
108+
85109
fn_to_data.into_values().collect()
86110
}
87111

kani-driver/src/args/autoharness_args.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
use std::path::PathBuf;
66

7+
use crate::args::list_args::Format;
78
use crate::args::{ValidateArgs, VerificationArgs, validate_std_path};
89
use clap::{Error, Parser, error::ErrorKind};
910
use kani_metadata::UnstableFeature;
@@ -29,6 +30,13 @@ pub struct CommonAutoharnessArgs {
2930
pub exclude_function: Vec<String>,
3031
// TODO: It would be nice if we could borrow --exact here from VerificationArgs to differentiate between partial/exact matches,
3132
// like --harnesses does. Sharing arguments with VerificationArgs doesn't work with our current structure, though.
33+
/// Run the `list` subcommand after generating the automatic harnesses. Requires -Z list. Note that this option implies --only-codegen.
34+
#[arg(long)]
35+
pub list: bool,
36+
37+
/// The format of the `list` output. Requires --list.
38+
#[arg(long, default_value = "pretty", requires = "list")]
39+
pub format: Format,
3240
}
3341

3442
/// Automatically verify functions in a crate.
@@ -76,6 +84,24 @@ impl ValidateArgs for CargoAutoharnessArgs {
7684
));
7785
}
7886

87+
if self.common_autoharness_args.list
88+
&& !self.verify_opts.common_args.unstable_features.contains(UnstableFeature::List)
89+
{
90+
return Err(Error::raw(
91+
ErrorKind::MissingRequiredArgument,
92+
format!("The `list` feature is unstable and requires -Z {}", UnstableFeature::List),
93+
));
94+
}
95+
96+
if self.common_autoharness_args.format == Format::Pretty
97+
&& self.verify_opts.common_args.quiet
98+
{
99+
return Err(Error::raw(
100+
ErrorKind::ArgumentConflict,
101+
"The `--quiet` flag is not compatible with the `pretty` format, since `pretty` prints to the terminal. Either specify a different format or don't pass `--quiet`.",
102+
));
103+
}
104+
79105
if self
80106
.verify_opts
81107
.common_args
@@ -105,6 +131,24 @@ impl ValidateArgs for StandaloneAutoharnessArgs {
105131
));
106132
}
107133

134+
if self.common_autoharness_args.list
135+
&& !self.verify_opts.common_args.unstable_features.contains(UnstableFeature::List)
136+
{
137+
return Err(Error::raw(
138+
ErrorKind::MissingRequiredArgument,
139+
format!("The `list` feature is unstable and requires -Z {}", UnstableFeature::List),
140+
));
141+
}
142+
143+
if self.common_autoharness_args.format == Format::Pretty
144+
&& self.verify_opts.common_args.quiet
145+
{
146+
return Err(Error::raw(
147+
ErrorKind::ArgumentConflict,
148+
"The `--quiet` flag is not compatible with the `pretty` format, since `pretty` prints to the terminal. Either specify a different format or don't pass `--quiet`.",
149+
));
150+
}
151+
108152
if self.std {
109153
validate_std_path(&self.input)?;
110154
} else if !self.input.is_file() {

kani-driver/src/args/list_args.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ impl ValidateArgs for CargoListArgs {
6464
));
6565
}
6666

67+
if self.format == Format::Pretty && self.common_args.quiet {
68+
return Err(Error::raw(
69+
ErrorKind::ArgumentConflict,
70+
"The `--quiet` flag is not compatible with the `pretty` format, since `pretty` prints to the terminal. Either specify a different format or don't pass `--quiet`.",
71+
));
72+
}
73+
6774
Ok(())
6875
}
6976
}
@@ -78,6 +85,13 @@ impl ValidateArgs for StandaloneListArgs {
7885
));
7986
}
8087

88+
if self.format == Format::Pretty && self.common_args.quiet {
89+
return Err(Error::raw(
90+
ErrorKind::ArgumentConflict,
91+
"The `--quiet` flag is not compatible with the `pretty` format, since `pretty` prints to the terminal. Either specify a different format or don't pass `--quiet`.",
92+
));
93+
}
94+
8195
if self.std {
8296
validate_std_path(&self.input)
8397
} else if self.input.is_file() {

kani-driver/src/autoharness/mod.rs

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
use std::str::FromStr;
55

66
use crate::args::Timeout;
7-
use crate::args::autoharness_args::{CargoAutoharnessArgs, StandaloneAutoharnessArgs};
7+
use crate::args::autoharness_args::{
8+
CargoAutoharnessArgs, CommonAutoharnessArgs, StandaloneAutoharnessArgs,
9+
};
810
use crate::call_cbmc::VerificationStatus;
911
use crate::call_single_file::to_rustc_arg;
1012
use crate::harness_runner::HarnessResult;
11-
use crate::project::{standalone_project, std_project};
13+
use crate::list::collect_metadata::process_metadata;
14+
use crate::list::output::output_list_results;
15+
use crate::project::{Project, standalone_project, std_project};
1216
use crate::session::KaniSession;
1317
use crate::{InvocationType, print_kani_version, project, verify_project};
1418
use anyhow::Result;
@@ -20,30 +24,18 @@ const LOOP_UNWIND_DEFAULT: u32 = 20;
2024

2125
pub fn autoharness_cargo(args: CargoAutoharnessArgs) -> Result<()> {
2226
let mut session = KaniSession::new(args.verify_opts)?;
23-
session.enable_autoharness();
24-
session.add_default_bounds();
25-
session.add_auto_harness_args(
26-
args.common_autoharness_args.include_function,
27-
args.common_autoharness_args.exclude_function,
28-
);
27+
setup_session(&mut session, &args.common_autoharness_args);
28+
2929
if !session.args.common_args.quiet {
3030
print_kani_version(InvocationType::CargoKani(vec![]));
3131
}
3232
let project = project::cargo_project(&mut session, false)?;
33-
if !session.args.common_args.quiet {
34-
print_metadata(project.metadata.clone());
35-
}
36-
if session.args.only_codegen { Ok(()) } else { verify_project(project, session) }
33+
postprocess_project(project, session, args.common_autoharness_args)
3734
}
3835

3936
pub fn autoharness_standalone(args: StandaloneAutoharnessArgs) -> Result<()> {
4037
let mut session = KaniSession::new(args.verify_opts)?;
41-
session.enable_autoharness();
42-
session.add_default_bounds();
43-
session.add_auto_harness_args(
44-
args.common_autoharness_args.include_function,
45-
args.common_autoharness_args.exclude_function,
46-
);
38+
setup_session(&mut session, &args.common_autoharness_args);
4739

4840
if !session.args.common_args.quiet {
4941
print_kani_version(InvocationType::Standalone);
@@ -55,14 +47,41 @@ pub fn autoharness_standalone(args: StandaloneAutoharnessArgs) -> Result<()> {
5547
standalone_project(&args.input, args.crate_name, &session)?
5648
};
5749

50+
postprocess_project(project, session, args.common_autoharness_args)
51+
}
52+
53+
/// Execute autoharness-specific KaniSession configuration.
54+
fn setup_session(session: &mut KaniSession, common_autoharness_args: &CommonAutoharnessArgs) {
55+
session.enable_autoharness();
56+
session.add_default_bounds();
57+
session.add_auto_harness_args(
58+
&common_autoharness_args.include_function,
59+
&common_autoharness_args.exclude_function,
60+
);
61+
}
62+
63+
/// After generating the automatic harnesses, postprocess metadata and run verification.
64+
fn postprocess_project(
65+
project: Project,
66+
session: KaniSession,
67+
common_autoharness_args: CommonAutoharnessArgs,
68+
) -> Result<()> {
5869
if !session.args.common_args.quiet {
59-
print_metadata(project.metadata.clone());
70+
print_autoharness_metadata(project.metadata.clone());
71+
}
72+
if common_autoharness_args.list {
73+
let list_metadata = process_metadata(project.metadata.clone());
74+
return output_list_results(
75+
list_metadata,
76+
common_autoharness_args.format,
77+
session.args.common_args.quiet,
78+
);
6079
}
6180
if session.args.only_codegen { Ok(()) } else { verify_project(project, session) }
6281
}
6382

6483
/// Print automatic harness metadata to the terminal.
65-
fn print_metadata(metadata: Vec<KaniMetadata>) {
84+
fn print_autoharness_metadata(metadata: Vec<KaniMetadata>) {
6685
let mut chosen_table = PrettyTable::new();
6786
chosen_table.set_header(vec!["Chosen Function"]);
6887

@@ -135,7 +154,7 @@ impl KaniSession {
135154
}
136155

137156
/// Add the compiler arguments specific to the `autoharness` subcommand.
138-
pub fn add_auto_harness_args(&mut self, included: Vec<String>, excluded: Vec<String>) {
157+
pub fn add_auto_harness_args(&mut self, included: &[String], excluded: &[String]) {
139158
for func in included {
140159
self.pkg_args
141160
.push(to_rustc_arg(vec![format!("--autoharness-include-function {}", func)]));

kani-driver/src/list/collect_metadata.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use anyhow::Result;
2020
use kani_metadata::{ContractedFunction, HarnessKind, KaniMetadata};
2121

2222
/// Process the KaniMetadata output from kani-compiler and output the list subcommand results
23-
fn process_metadata(metadata: Vec<KaniMetadata>) -> ListMetadata {
23+
pub fn process_metadata(metadata: Vec<KaniMetadata>) -> ListMetadata {
2424
// We use ordered maps and sets so that the output is in lexicographic order (and consistent across invocations).
2525

2626
// Map each file to a vector of its harnesses.

kani-driver/src/list/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use kani_metadata::ContractedFunction;
66
use std::collections::{BTreeMap, BTreeSet};
77

88
pub mod collect_metadata;
9-
mod output;
9+
pub mod output;
1010

11-
struct ListMetadata {
11+
pub struct ListMetadata {
1212
// Files mapped to their #[kani::proof] harnesses
1313
standard_harnesses: BTreeMap<String, BTreeSet<String>>,
1414
// Total number of #[kani::proof] harnesses
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
[package]
5+
name = "cargo_autoharness_list"
6+
version = "0.1.0"
7+
edition = "2024"
8+
9+
[lints.rust]
10+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)'] }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
script: list.sh
4+
expected: list.expected
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Kani generated automatic harnesses for 3 function(s):
2+
+---------------------------+
3+
| Chosen Function |
4+
+===========================+
5+
| f_u8 |
6+
|---------------------------|
7+
| has_recursion_gcd |
8+
|---------------------------|
9+
| verify::has_recursion_gcd |
10+
+---------------------------+
11+
12+
Skipped Functions: None. Kani generated automatic harnesses for all functions in the available crate(s).
13+
14+
Contracts:
15+
+-------+---------------------------+-----------------------------------------------------------------------------+
16+
| | Function | Contract Harnesses (#[kani::proof_for_contract]) |
17+
+=================================================================================================================+
18+
| | has_recursion_gcd | my_harness, my_harness_2, kani::internal::automatic_harness |
19+
|-------+---------------------------+-----------------------------------------------------------------------------|
20+
| | verify::has_recursion_gcd | verify::my_harness, verify::my_harness_2, kani::internal::automatic_harness |
21+
|-------+---------------------------+-----------------------------------------------------------------------------|
22+
| Total | 2 | 6 |
23+
+-------+---------------------------+-----------------------------------------------------------------------------+
24+
25+
Standard Harnesses (#[kani::proof]):
26+
1. f_u8

0 commit comments

Comments
 (0)