Skip to content

Commit e4bc04d

Browse files
authored
Assess improvements: build tolerance, detect CBMC crashes, allow set default unwind (rust-lang#2267)
I made a few improvements while trying to use assess: - Allow assess to keep building packages even when one or more targets failed to build. Instead, just report how many failed and how many succeeded. - Fix test result reporting for cases where CBMC crashes. We were counting them as success. Now we report the error code. - Allow users to set the default unwind value using --default-unwind. By default, we still use value 1. - Only report as analyzed crates that have any kani-metadata.json file.
1 parent 36d9c38 commit e4bc04d

File tree

11 files changed

+114
-66
lines changed

11 files changed

+114
-66
lines changed

kani-driver/src/assess/mod.rs

+17-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
use self::metadata::{write_metadata, AssessMetadata};
5-
use anyhow::Result;
5+
use anyhow::{bail, Result};
66
use kani_metadata::KaniMetadata;
77

88
use crate::assess::table_builder::TableBuilder;
@@ -44,7 +44,7 @@ fn assess_project(mut session: KaniSession) -> Result<AssessMetadata> {
4444
// This is a temporary hack to make things work, until we get around to refactoring how arguments
4545
// work generally in kani-driver. These arguments, for instance, are all prepended to the subcommand,
4646
// which is not a nice way of taking arguments.
47-
session.args.unwind = Some(1);
47+
session.args.unwind = Some(session.args.default_unwind.unwrap_or(1));
4848
session.args.tests = true;
4949
session.args.output_format = crate::args::OutputFormat::Terse;
5050
session.codegen_tests = true;
@@ -54,7 +54,7 @@ fn assess_project(mut session: KaniSession) -> Result<AssessMetadata> {
5454
session.args.jobs = Some(None); // -j, num_cpu
5555
}
5656

57-
let project = project::cargo_project(&session)?;
57+
let project = project::cargo_project(&session, true)?;
5858
let cargo_metadata = project.cargo_metadata.as_ref().expect("built with cargo");
5959

6060
let packages_metadata = if project.merged_artifacts {
@@ -72,7 +72,15 @@ fn assess_project(mut session: KaniSession) -> Result<AssessMetadata> {
7272
// It would also be interesting to classify them by whether they build without warnings or not.
7373
// Tracking for the latter: https://github.com/model-checking/kani/issues/1758
7474

75-
println!("Found {} packages", packages_metadata.len());
75+
let build_fail = project.failed_targets.as_ref().unwrap();
76+
match (build_fail.len(), packages_metadata.len()) {
77+
(0, 0) => println!("No relevant data was found."),
78+
(0, succeeded) => println!("Analyzed {succeeded} packages"),
79+
(_failed, 0) => bail!("Failed to build all targets"),
80+
(failed, succeeded) => {
81+
println!("Analyzed {succeeded} packages. Failed to build {failed} targets",)
82+
}
83+
}
7684

7785
let metadata = merge_kani_metadata(packages_metadata.clone());
7886
let unsupported_features = table_unsupported_features::build(&packages_metadata);
@@ -161,9 +169,11 @@ fn reconstruct_metadata_structure(
161169
)
162170
}
163171
}
164-
let mut merged = crate::metadata::merge_kani_metadata(package_artifacts);
165-
merged.crate_name = package.name.clone();
166-
package_metas.push(merged);
172+
if !package_artifacts.is_empty() {
173+
let mut merged = crate::metadata::merge_kani_metadata(package_artifacts);
174+
merged.crate_name = package.name.clone();
175+
package_metas.push(merged);
176+
}
167177
}
168178
if !remaining_metas.is_empty() {
169179
let remaining_names: Vec<_> = remaining_metas.into_iter().map(|x| x.crate_name).collect();

kani-driver/src/assess/table_failure_reasons.rs

+12-7
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,19 @@ pub(crate) fn build(results: &[HarnessResult]) -> TableBuilder<FailureReasonsTab
3535
let mut builder = TableBuilder::new();
3636

3737
for r in results {
38-
let failures = r.result.failed_properties();
39-
let classification = if failures.is_empty() {
40-
"none (success)".to_string()
38+
let classification = if let Err(exit_code) = r.result.results {
39+
format!("CBMC failed with status {exit_code}")
4140
} else {
42-
let mut classes: Vec<_> = failures.into_iter().map(|p| p.property_class()).collect();
43-
classes.sort();
44-
classes.dedup();
45-
classes.join(" + ")
41+
let failures = r.result.failed_properties();
42+
if failures.is_empty() {
43+
"none (success)".to_string()
44+
} else {
45+
let mut classes: Vec<_> =
46+
failures.into_iter().map(|p| p.property_class()).collect();
47+
classes.sort();
48+
classes.dedup();
49+
classes.join(" + ")
50+
}
4651
};
4752

4853
let name = r.harness.pretty_name.trim_end_matches("::{closure#0}").to_string();

kani-driver/src/assess/table_promising_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use super::table_builder::{ColumnType, RenderableTableRow, TableBuilder, TableRo
2929
pub(crate) fn build(results: &[HarnessResult]) -> TableBuilder<PromisingTestsTableRow> {
3030
let mut builder = TableBuilder::new();
3131

32-
for r in results {
32+
for r in results.iter().filter(|res| res.result.results.is_ok()) {
3333
// For now we're just reporting "successful" harnesses as candidates.
3434
// In the future this heuristic should be expanded. More data is required to do this, however.
3535
if r.result.failed_properties().is_empty() {

kani-driver/src/call_cargo.rs

+36-3
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use crate::args::KaniArgs;
55
use crate::call_single_file::to_rustc_arg;
66
use crate::project::Artifact;
77
use crate::session::KaniSession;
8+
use crate::util;
89
use anyhow::{bail, Context, Result};
910
use cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel};
1011
use cargo_metadata::{Message, Metadata, MetadataCommand, Package, Target};
1112
use kani_metadata::{ArtifactType, CompilerArtifactStub};
1213
use std::ffi::{OsStr, OsString};
14+
use std::fmt::{self, Display};
1315
use std::fs::{self, File};
1416
use std::io::BufReader;
1517
use std::path::PathBuf;
@@ -35,11 +37,13 @@ pub struct CargoOutputs {
3537
pub metadata: Vec<Artifact>,
3638
/// Recording the cargo metadata from the build
3739
pub cargo_metadata: Metadata,
40+
/// For build `keep_going` mode, we collect the targets that we failed to compile.
41+
pub failed_targets: Option<Vec<String>>,
3842
}
3943

4044
impl KaniSession {
4145
/// Calls `cargo_build` to generate `*.symtab.json` files in `target_dir`
42-
pub fn cargo_build(&self) -> Result<CargoOutputs> {
46+
pub fn cargo_build(&self, keep_going: bool) -> Result<CargoOutputs> {
4347
let build_target = env!("TARGET"); // see build.rs
4448
let metadata = self.cargo_metadata(build_target)?;
4549
let target_dir = self
@@ -102,6 +106,7 @@ impl KaniSession {
102106
let mut found_target = false;
103107
let packages = packages_to_verify(&self.args, &metadata);
104108
let mut artifacts = vec![];
109+
let mut failed_targets = vec![];
105110
for package in packages {
106111
for verification_target in package_targets(&self.args, package) {
107112
let mut cmd = Command::new("cargo");
@@ -115,7 +120,19 @@ impl KaniSession {
115120
.env("CARGO_ENCODED_RUSTFLAGS", rustc_args.join(OsStr::new("\x1f")))
116121
.env("CARGO_TERM_PROGRESS_WHEN", "never");
117122

118-
artifacts.extend(self.run_cargo(cmd, verification_target.target())?.into_iter());
123+
match self.run_cargo(cmd, verification_target.target()) {
124+
Err(err) => {
125+
if keep_going {
126+
let target_str = format!("{verification_target}");
127+
util::error(&format!("Failed to compile {target_str}"));
128+
failed_targets.push(target_str);
129+
} else {
130+
return Err(err);
131+
}
132+
}
133+
Ok(Some(artifact)) => artifacts.push(artifact),
134+
Ok(None) => {}
135+
}
119136
found_target = true;
120137
}
121138
}
@@ -124,7 +141,12 @@ impl KaniSession {
124141
bail!("No supported targets were found.");
125142
}
126143

127-
Ok(CargoOutputs { outdir, metadata: artifacts, cargo_metadata: metadata })
144+
Ok(CargoOutputs {
145+
outdir,
146+
metadata: artifacts,
147+
cargo_metadata: metadata,
148+
failed_targets: keep_going.then_some(failed_targets),
149+
})
128150
}
129151

130152
fn cargo_metadata(&self, build_target: &str) -> Result<Metadata> {
@@ -312,6 +334,7 @@ fn map_kani_artifact(rustc_artifact: cargo_metadata::Artifact) -> Option<Artifac
312334
}
313335

314336
/// Possible verification targets.
337+
#[derive(Debug)]
315338
enum VerificationTarget {
316339
Bin(Target),
317340
Lib(Target),
@@ -337,6 +360,16 @@ impl VerificationTarget {
337360
}
338361
}
339362

363+
impl Display for VerificationTarget {
364+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365+
match self {
366+
VerificationTarget::Test(target) => write!(f, "test `{}`", target.name),
367+
VerificationTarget::Bin(target) => write!(f, "binary `{}`", target.name),
368+
VerificationTarget::Lib(target) => write!(f, "lib `{}`", target.name),
369+
}
370+
}
371+
}
372+
340373
/// Extract the targets inside a package.
341374
///
342375
/// If `--tests` is given, the list of targets will include any integration tests.

kani-driver/src/call_cbmc.rs

+33-39
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,11 @@ pub struct VerificationResult {
3030
/// The parsed output, message by message, of CBMC. However, the `Result` message has been
3131
/// removed and is available in `results` instead.
3232
pub messages: Option<Vec<ParserItem>>,
33-
/// The `Result` properties in detail.
34-
pub results: Option<Vec<Property>>,
35-
/// CBMC process exit status. NOTE: Only potentially useful if `status` is `Failure`.
33+
/// The `Result` properties in detail or the exit_status of CBMC.
34+
/// Note: CBMC process exit status is only potentially useful if `status` is `Failure`.
3635
/// Kani will see CBMC report "failure" that's actually success (interpreting "failed"
3736
/// checks like coverage as expected and desirable.)
38-
pub exit_status: i32,
37+
pub results: Result<Vec<Property>, i32>,
3938
/// The runtime duration of this CBMC invocation.
4039
pub runtime: Duration,
4140
/// Whether concrete playback generated a test
@@ -67,21 +66,17 @@ impl KaniSession {
6766

6867
// Spawn the CBMC process and process its output below
6968
let cbmc_process_opt = self.run_piped(cmd)?;
70-
if let Some(cbmc_process) = cbmc_process_opt {
71-
let output = process_cbmc_output(cbmc_process, |i| {
72-
kani_cbmc_output_filter(
73-
i,
74-
self.args.extra_pointer_checks,
75-
self.args.quiet,
76-
&self.args.output_format,
77-
)
78-
})?;
79-
80-
VerificationResult::from(output, start_time)
81-
} else {
82-
// None is only ever returned when it's a dry run
83-
VerificationResult::mock_success()
84-
}
69+
let cbmc_process = cbmc_process_opt.ok_or(anyhow::Error::msg("Failed to run cbmc"))?;
70+
let output = process_cbmc_output(cbmc_process, |i| {
71+
kani_cbmc_output_filter(
72+
i,
73+
self.args.extra_pointer_checks,
74+
self.args.quiet,
75+
&self.args.output_format,
76+
)
77+
})?;
78+
79+
VerificationResult::from(output, start_time)
8580
};
8681

8782
self.gen_and_add_concrete_playback(harness, &mut verification_results)?;
@@ -247,8 +242,7 @@ impl VerificationResult {
247242
VerificationResult {
248243
status: determine_status_from_properties(&results),
249244
messages: Some(items),
250-
results: Some(results),
251-
exit_status: output.process_status,
245+
results: Ok(results),
252246
runtime,
253247
generated_concrete_test: false,
254248
}
@@ -257,8 +251,7 @@ impl VerificationResult {
257251
VerificationResult {
258252
status: VerificationStatus::Failure,
259253
messages: Some(items),
260-
results: None,
261-
exit_status: output.process_status,
254+
results: Err(output.process_status),
262255
runtime,
263256
generated_concrete_test: false,
264257
}
@@ -269,8 +262,7 @@ impl VerificationResult {
269262
VerificationResult {
270263
status: VerificationStatus::Success,
271264
messages: None,
272-
results: None,
273-
exit_status: 42, // on success, exit code is ignored, so put something weird here
265+
results: Ok(vec![]),
274266
runtime: Duration::from_secs(0),
275267
generated_concrete_test: false,
276268
}
@@ -280,36 +272,38 @@ impl VerificationResult {
280272
VerificationResult {
281273
status: VerificationStatus::Failure,
282274
messages: None,
283-
results: None,
284275
// on failure, exit codes in theory might be used,
285276
// but `mock_failure` should never be used in a context where they will,
286277
// so again use something weird:
287-
exit_status: 42,
278+
results: Err(42),
288279
runtime: Duration::from_secs(0),
289280
generated_concrete_test: false,
290281
}
291282
}
292283

293284
pub fn render(&self, output_format: &OutputFormat) -> String {
294-
if let Some(results) = &self.results {
295-
let show_checks = matches!(output_format, OutputFormat::Regular);
296-
let mut result = format_result(results, show_checks);
297-
writeln!(result, "Verification Time: {}s", self.runtime.as_secs_f32()).unwrap();
298-
result
299-
} else {
300-
let verification_result = console::style("FAILED").red();
301-
format!(
302-
"\nCBMC failed with status {}\nVERIFICATION:- {verification_result}\n",
303-
self.exit_status
304-
)
285+
match &self.results {
286+
Ok(results) => {
287+
let show_checks = matches!(output_format, OutputFormat::Regular);
288+
let mut result = format_result(results, show_checks);
289+
writeln!(result, "Verification Time: {}s", self.runtime.as_secs_f32()).unwrap();
290+
result
291+
}
292+
Err(exit_status) => {
293+
let verification_result = console::style("FAILED").red();
294+
format!(
295+
"\nCBMC failed with status {exit_status}\nVERIFICATION:- {verification_result}\n",
296+
)
297+
}
305298
}
306299
}
307300

308301
/// Find the failed properties from this verification run
309302
pub fn failed_properties(&self) -> Vec<&Property> {
310-
if let Some(properties) = &self.results {
303+
if let Ok(properties) = &self.results {
311304
properties.iter().filter(|prop| prop.status == CheckStatus::Failure).collect()
312305
} else {
306+
debug_assert!(false, "expected error to be handled before invoking this function");
313307
vec![]
314308
}
315309
}

kani-driver/src/concrete_playback.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ impl KaniSession {
3131
None => return Ok(()),
3232
};
3333

34-
if let Some(result_items) = &verification_result.results {
34+
if let Ok(result_items) = &verification_result.results {
3535
match extract_harness_values(result_items) {
3636
None => println!(
3737
"WARNING: Kani could not produce a concrete playback for `{}` because there \

kani-driver/src/main.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ fn cargokani_main(input_args: Vec<OsString>) -> Result<()> {
7070
return assess::run_assess(session, assess::AssessArgs::default());
7171
}
7272

73-
let project = project::cargo_project(&session)?;
74-
debug!(?project, "cargokani_main");
73+
let project = project::cargo_project(&session, false)?;
7574
if session.args.only_codegen { Ok(()) } else { verify_project(project, session) }
7675
}
7776

@@ -82,7 +81,6 @@ fn standalone_main() -> Result<()> {
8281
let session = session::KaniSession::new(args.common_opts)?;
8382

8483
let project = project::standalone_project(&args.input, &session)?;
85-
debug!(?project, "standalone_main");
8684
if session.args.only_codegen { Ok(()) } else { verify_project(project, session) }
8785
}
8886

kani-driver/src/project.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ pub struct Project {
5959
pub merged_artifacts: bool,
6060
/// Records the cargo metadata from the build, if there was any
6161
pub cargo_metadata: Option<cargo_metadata::Metadata>,
62+
/// For build `keep_going` mode, we collect the targets that we failed to compile.
63+
pub failed_targets: Option<Vec<String>>,
6264
}
6365

6466
impl Project {
@@ -145,8 +147,10 @@ fn dump_metadata(metadata: &KaniMetadata, path: &Path) {
145147
}
146148

147149
/// Generate a project using `cargo`.
148-
pub fn cargo_project(session: &KaniSession) -> Result<Project> {
149-
let outputs = session.cargo_build()?;
150+
/// Accept a boolean to build as many targets as possible. The number of failures in that case can
151+
/// be collected from the project.
152+
pub fn cargo_project(session: &KaniSession, keep_going: bool) -> Result<Project> {
153+
let outputs = session.cargo_build(keep_going)?;
150154
let mut artifacts = vec![];
151155
let outdir = outputs.outdir.canonicalize()?;
152156
if session.args.function.is_some() {
@@ -181,6 +185,7 @@ pub fn cargo_project(session: &KaniSession) -> Result<Project> {
181185
metadata: vec![metadata],
182186
merged_artifacts: true,
183187
cargo_metadata: Some(outputs.cargo_metadata),
188+
failed_targets: outputs.failed_targets,
184189
})
185190
} else {
186191
// For the MIR Linker we know there is only one artifact per verification target. Use
@@ -208,6 +213,7 @@ pub fn cargo_project(session: &KaniSession) -> Result<Project> {
208213
metadata,
209214
merged_artifacts: false,
210215
cargo_metadata: Some(outputs.cargo_metadata),
216+
failed_targets: outputs.failed_targets,
211217
})
212218
}
213219
}
@@ -300,6 +306,7 @@ impl<'a> StandaloneProjectBuilder<'a> {
300306
.collect(),
301307
merged_artifacts: false,
302308
cargo_metadata: None,
309+
failed_targets: None,
303310
})
304311
}
305312

0 commit comments

Comments
 (0)