Skip to content

Commit bfdedb8

Browse files
Introduce project and stop merging all files generated by the compiler (rust-lang#1956)
This PR is a pre-req for getting a per-harness reachability algorithm. First we need to stop treating all goto generated by the compiler as the same model which is what this PR does. For that, I introduce the concept of a project, which is basically a container of artifacts produced by the build system. The goal is to abstracts the verification code from the build system we pick. It also provide methods to get artifacts relevant to a harness. Today this method will return the artifact related to the crate where it is contained, but in the future it can be easily modified to be the harness specific goto model. The only behavioral change in this PR is that we disabled `--gen-c` for `--function` and `--legacy-linker` to simplify the code since all these options are for internal usage or on track to be deleted. This change also fixes inconsistent / incorrect behaviors. They are the following: 1. Kani is now sound when two or more targets contains harnesses or no mangled functions that have the same name (rust-lang#1974). Note that this is not the case for `--legacy-linker` and `--function` options. 2. `kani` now respects the `--target-dir` argument provided by the customer. 3. `kani` no longer crash when the crate has no harness (rust-lang#1910). Co-authored-by: Zyad Hassan <[email protected]>
1 parent f4796ea commit bfdedb8

File tree

20 files changed

+572
-262
lines changed

20 files changed

+572
-262
lines changed

kani-driver/src/args.rs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#[cfg(feature = "unsound_experiments")]
55
use crate::unsound_experiments::UnsoundExperimentArgs;
66

7-
use clap::{error::Error, error::ErrorKind, Parser, ValueEnum};
7+
use clap::{error::Error, error::ErrorKind, CommandFactory, Parser, ValueEnum};
88
use std::ffi::OsString;
99
use std::path::PathBuf;
1010

@@ -72,7 +72,8 @@ pub struct KaniArgs {
7272
value_enum
7373
)]
7474
pub concrete_playback: Option<ConcretePlaybackMode>,
75-
/// Keep temporary files generated throughout Kani process
75+
/// Keep temporary files generated throughout Kani process. This is already the default
76+
/// behavior for `cargo-kani`.
7677
#[arg(long, hide_short_help = true)]
7778
pub keep_temps: bool,
7879

@@ -96,11 +97,12 @@ pub struct KaniArgs {
9697

9798
/// Generate C file equivalent to inputted program.
9899
/// This feature is unstable and it requires `--enable-unstable` to be used
99-
#[arg(long, hide_short_help = true, requires("enable_unstable"))]
100+
#[arg(long, hide_short_help = true, requires("enable_unstable"),
101+
conflicts_with_all(&["function", "legacy_linker"])
102+
)]
100103
pub gen_c: bool,
101104

102-
// TODO: currently only cargo-kani pays attention to this.
103-
/// Directory for all generated artifacts. Only effective when running Kani with cargo
105+
/// Directory for all generated artifacts.
104106
#[arg(long)]
105107
pub target_dir: Option<PathBuf>,
106108

@@ -371,11 +373,39 @@ impl CheckArgs {
371373
impl StandaloneArgs {
372374
pub fn validate(&self) {
373375
self.common_opts.validate::<Self>();
376+
self.valid_input()
377+
.or_else(|e| -> Result<(), ()> { e.format(&mut Self::command()).exit() })
378+
.unwrap()
379+
}
380+
381+
fn valid_input(&self) -> Result<(), Error> {
382+
if !self.input.is_file() {
383+
Err(Error::raw(
384+
ErrorKind::InvalidValue,
385+
&format!(
386+
"Invalid argument: Input invalid. `{}` is not a regular file.",
387+
self.input.display()
388+
),
389+
))
390+
} else {
391+
Ok(())
392+
}
374393
}
375394
}
376395
impl CargoKaniArgs {
377396
pub fn validate(&self) {
378397
self.common_opts.validate::<Self>();
398+
// --assess requires --enable-unstable, but the subcommand needs manual checking
399+
if (matches!(self.command, Some(CargoKaniSubcommand::Assess)) || self.common_opts.assess)
400+
&& !self.common_opts.enable_unstable
401+
{
402+
Self::command()
403+
.error(
404+
ErrorKind::MissingRequiredArgument,
405+
"Assess is unstable and requires 'cargo kani --enable-unstable assess'",
406+
)
407+
.exit()
408+
}
379409
}
380410
}
381411
impl KaniArgs {
@@ -454,6 +484,17 @@ impl KaniArgs {
454484
"The `--dry-run` option is obsolete. Use --verbose instead.",
455485
));
456486
}
487+
if let Some(out_dir) = &self.target_dir {
488+
if out_dir.exists() && !out_dir.is_dir() {
489+
return Err(Error::raw(
490+
ErrorKind::InvalidValue,
491+
&format!(
492+
"Invalid argument: `--target-dir` argument `{}` is not a directory",
493+
out_dir.display()
494+
),
495+
));
496+
}
497+
}
457498

458499
Ok(())
459500
}
@@ -524,6 +565,14 @@ mod tests {
524565
assert_eq!(err.kind(), ErrorKind::ValueValidation);
525566
}
526567

568+
/// Kani should fail if the argument given is not a file.
569+
#[test]
570+
fn check_invalid_input_fails() {
571+
let args = vec!["kani", "."];
572+
let err = StandaloneArgs::parse_from(&args).valid_input().unwrap_err();
573+
assert_eq!(err.kind(), ErrorKind::InvalidValue);
574+
}
575+
527576
#[test]
528577
fn check_unwind_conflicts() {
529578
// --unwind cannot be called without --harness

kani-driver/src/assess.rs

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// Copyright Kani Contributors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

4-
use std::{collections::HashMap, path::PathBuf};
4+
use std::collections::HashMap;
55

66
use anyhow::Result;
77
use comfy_table::Table;
8-
use kani_metadata::{ArtifactType, KaniMetadata};
8+
use kani_metadata::KaniMetadata;
99

1010
use crate::harness_runner::HarnessResult;
11+
use crate::metadata::merge_kani_metadata;
12+
use crate::project;
1113
use crate::session::KaniSession;
1214

1315
/// Set some defaults for how we format tables
@@ -211,65 +213,42 @@ fn build_promising_tests_table(results: &[HarnessResult]) -> Table {
211213
}
212214
}
213215

214-
pub(crate) fn cargokani_assess_main(mut ctx: KaniSession) -> Result<()> {
216+
pub(crate) fn cargokani_assess_main(mut session: KaniSession) -> Result<()> {
215217
// fix some settings
216-
ctx.args.unwind = Some(1);
217-
ctx.args.tests = true;
218-
ctx.args.output_format = crate::args::OutputFormat::Terse;
219-
ctx.codegen_tests = true;
220-
if ctx.args.jobs.is_none() {
218+
session.args.unwind = Some(1);
219+
session.args.tests = true;
220+
session.args.output_format = crate::args::OutputFormat::Terse;
221+
session.codegen_tests = true;
222+
if session.args.jobs.is_none() {
221223
// assess will default to fully parallel instead of single-threaded.
222224
// can be overridden with e.g. `cargo kani --enable-unstable -j 8 assess`
223-
ctx.args.jobs = Some(None); // -j, num_cpu
225+
session.args.jobs = Some(None); // -j, num_cpu
224226
}
225227

226-
let outputs = ctx.cargo_build()?;
227-
let metadata = ctx.collect_kani_metadata(&outputs.metadata)?;
228+
let project = project::cargo_project(&session)?;
228229

229-
let crate_count = outputs.metadata.len();
230+
let crate_count = project.metadata.len();
230231

231232
// An interesting thing to print here would be "number of crates without any warnings"
232233
// however this will have to wait until a refactoring of how we aggregate metadata
233234
// from multiple crates together here.
234235
// tracking for that: https://github.com/model-checking/kani/issues/1758
235236
println!("Analyzed {crate_count} crates");
236237

238+
let metadata = merge_kani_metadata(project.metadata.clone());
237239
if !metadata.unsupported_features.is_empty() {
238240
println!("{}", build_unsupported_features_table(&metadata));
239241
} else {
240242
println!("No crates contained Rust features unsupported by Kani");
241243
}
242244

243-
// The section is a "copy and paste" from cargo kani.
244-
// We could start thinking about abtracting this stuff out into a shared function...
245-
let mut goto_objs: Vec<PathBuf> = Vec::new();
246-
for symtab in &outputs.symtabs {
247-
let goto_obj_filename = symtab.with_extension(ArtifactType::Goto);
248-
goto_objs.push(goto_obj_filename);
249-
}
250-
251-
if ctx.args.only_codegen {
245+
if session.args.only_codegen {
252246
return Ok(());
253247
}
254248

255-
let linked_obj = outputs.outdir.join("cbmc-linked.out");
256-
ctx.link_goto_binary(&goto_objs, &linked_obj)?;
257-
if let Some(restrictions) = outputs.restrictions {
258-
ctx.apply_vtable_restrictions(&linked_obj, &restrictions)?;
259-
}
260-
261249
// Done with the 'cargo-kani' part, now we're going to run *test* harnesses instead of proof:
262-
263250
let harnesses = metadata.test_harnesses;
264-
let report_base = ctx.args.target_dir.clone().unwrap_or(PathBuf::from("target"));
265-
266-
let runner = crate::harness_runner::HarnessRunner {
267-
sess: &ctx,
268-
linked_obj: &linked_obj,
269-
report_base: &report_base,
270-
symtabs: &outputs.symtabs,
271-
retain_specialized_harnesses: false,
272-
};
251+
let runner = crate::harness_runner::HarnessRunner { sess: &session, project };
273252

274253
let results = runner.check_all_harnesses(&harnesses)?;
275254

kani-driver/src/call_goto_instrument.rs

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,27 @@ use std::path::Path;
99
use std::process::Command;
1010

1111
use crate::metadata::collect_and_link_function_pointer_restrictions;
12+
use crate::project::Project;
1213
use crate::session::KaniSession;
1314
use crate::util::alter_extension;
15+
use kani_metadata::{ArtifactType, HarnessMetadata};
1416

1517
impl KaniSession {
16-
/// Postprocess a goto binary (before cbmc, after linking) in-place by calling goto-instrument
17-
pub fn run_goto_instrument(
18+
/// Instrument and optimize a goto binary in-place.
19+
pub fn instrument_model(
1820
&self,
1921
input: &Path,
2022
output: &Path,
21-
symtabs: &[impl AsRef<Path>],
22-
function: &str,
23+
project: &Project,
24+
harness: &HarnessMetadata,
2325
) -> Result<()> {
2426
// We actually start by calling goto-cc to start the specialization:
25-
self.specialize_to_proof_harness(input, output, function)?;
27+
self.specialize_to_proof_harness(input, output, &harness.mangled_name)?;
28+
29+
let restrictions = project.get_harness_artifact(&harness, ArtifactType::VTableRestriction);
30+
if let Some(restrictions_path) = restrictions {
31+
self.apply_vtable_restrictions(&output, restrictions_path)?;
32+
}
2633

2734
// Run sanity checks in the model generated by kani-compiler before any goto-instrument
2835
// transformation.
@@ -50,7 +57,8 @@ impl KaniSession {
5057
}
5158

5259
let c_demangled = alter_extension(output, "demangled.c");
53-
self.demangle_c(symtabs, &c_outfile, &c_demangled)?;
60+
let symtab = project.get_harness_artifact(&harness, ArtifactType::SymTab).unwrap();
61+
self.demangle_c(symtab, &c_outfile, &c_demangled)?;
5462
if !self.args.quiet {
5563
println!("Demangled GotoC code written to {}", c_demangled.to_string_lossy())
5664
}
@@ -60,19 +68,16 @@ impl KaniSession {
6068
}
6169

6270
/// Apply --restrict-vtable to a goto binary.
63-
/// `source` is either a `*.restrictions.json` file or a directory containing mutiple such files.
64-
pub fn apply_vtable_restrictions(&self, file: &Path, source: &Path) -> Result<()> {
65-
let linked_restrictions = alter_extension(file, "linked-restrictions.json");
66-
71+
pub fn apply_vtable_restrictions(&self, goto_file: &Path, restrictions: &Path) -> Result<()> {
72+
let linked_restrictions = alter_extension(goto_file, "linked-restrictions.json");
6773
self.record_temporary_files(&[&linked_restrictions]);
68-
69-
collect_and_link_function_pointer_restrictions(source, &linked_restrictions)?;
74+
collect_and_link_function_pointer_restrictions(restrictions, &linked_restrictions)?;
7075

7176
let args: Vec<OsString> = vec![
7277
"--function-pointer-restrictions-file".into(),
7378
linked_restrictions.into(),
74-
file.to_owned().into_os_string(), // input
75-
file.to_owned().into_os_string(), // output
79+
goto_file.to_owned().into_os_string(), // input
80+
goto_file.to_owned().into_os_string(), // output
7681
];
7782

7883
self.call_goto_instrument(args)
@@ -154,29 +159,27 @@ impl KaniSession {
154159
self.call_goto_instrument(args)
155160
}
156161

157-
/// Generate a .demangled.c file from the .c file using the `prettyName`s from the symbol tables
162+
/// Generate a .demangled.c file from the .c file using the `prettyName`s from the symbol table
158163
///
159164
/// Currently, only top-level function names and (most) type names are demangled.
160165
/// For local variables, it would be more complicated than a simple search and replace to obtain the demangled name.
161166
pub fn demangle_c(
162167
&self,
163-
symtab_files: &[impl AsRef<Path>],
168+
symtab_file: &impl AsRef<Path>,
164169
c_file: &Path,
165170
demangled_file: &Path,
166171
) -> Result<()> {
167172
let mut c_code = std::fs::read_to_string(c_file)?;
168-
for symtab_file in symtab_files {
169-
let reader = BufReader::new(File::open(symtab_file.as_ref())?);
170-
let symtab: serde_json::Value = serde_json::from_reader(reader)?;
171-
for (_, symbol) in symtab["symbolTable"].as_object().unwrap() {
172-
if let Some(serde_json::Value::String(name)) = symbol.get("name") {
173-
if let Some(serde_json::Value::String(pretty)) = symbol.get("prettyName") {
174-
// Struct names start with "tag-", but this prefix is not used in the GotoC files, so we strip it.
175-
// If there is no such prefix, we leave the name unchanged.
176-
let name = name.strip_prefix("tag-").unwrap_or(name);
177-
if !pretty.is_empty() && pretty != name {
178-
c_code = c_code.replace(name, pretty);
179-
}
173+
let reader = BufReader::new(File::open(symtab_file)?);
174+
let symtab: serde_json::Value = serde_json::from_reader(reader)?;
175+
for (_, symbol) in symtab["symbolTable"].as_object().unwrap() {
176+
if let Some(serde_json::Value::String(name)) = symbol.get("name") {
177+
if let Some(serde_json::Value::String(pretty)) = symbol.get("prettyName") {
178+
// Struct names start with "tag-", but this prefix is not used in the GotoC files, so we strip it.
179+
// If there is no such prefix, we leave the name unchanged.
180+
let name = name.strip_prefix("tag-").unwrap_or(name);
181+
if !pretty.is_empty() && pretty != name {
182+
c_code = c_code.replace(name, pretty);
180183
}
181184
}
182185
}

0 commit comments

Comments
 (0)