Skip to content

Commit 28bd56e

Browse files
celinvaltedinski
authored andcommitted
Fix config argument precedence and handling of CBMC options. (rust-lang#977)
Cargo kani wouldn't work if the project Cargo.toml had --cbmc-args and if the command line also included --cbmc-args. The other issue is that the options in the project configuration had precedence over the ones from the command line, which is misleading. For example, if the Cargo.toml had a harness function defined to run by default, the user would still run the default harness even if they ran: ``` cargo kani --function <different_harness> ```
1 parent 2934527 commit 28bd56e

File tree

6 files changed

+103
-25
lines changed

6 files changed

+103
-25
lines changed

src/cargo-kani/src/args_toml.rs

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,54 @@ use std::process::Command;
99
use toml::value::Table;
1010
use toml::Value;
1111

12-
/// Produces a set of arguments to pass to ourself (cargo-kani) from a Cargo.toml project file
13-
pub fn config_toml_to_args() -> Result<Vec<OsString>> {
12+
/// Produce the list of arguments to pass to ourself (cargo-kani).
13+
///
14+
/// The arguments passed via command line have precedence over the ones from the Cargo.toml.
15+
pub fn join_args(input_args: Vec<OsString>) -> Result<Vec<OsString>> {
1416
let file = std::fs::read_to_string(cargo_locate_project()?)?;
15-
toml_to_args(&file)
17+
let (kani_args, cbmc_args) = toml_to_args(&file)?;
18+
merge_args(input_args, kani_args, cbmc_args)
19+
}
20+
21+
/// Join the arguments passed via command line with the ones found in the Cargo.toml.
22+
///
23+
/// The arguments passed via command line have precedence over the ones from the Cargo.toml. Thus,
24+
/// we need to inject the command line arguments after the ones read from Cargo.toml. This can be
25+
/// a bit annoying given that cbmc args have to be at the end of the arguments and the "--cbmc-args"
26+
/// flag must only be included once.
27+
///
28+
/// This function will return the arguments in the following order:
29+
/// ```
30+
/// <bin_name> [<cfg_kani_args>]* [<cmd_kani_args>]* [--cbmc-args [<cfg_cbmc_args>]* [<cmd_cbmc_args>]*]
31+
/// ```
32+
fn merge_args(
33+
cmd_args: Vec<OsString>,
34+
cfg_kani_args: Vec<OsString>,
35+
cfg_cbmc_args: Vec<OsString>,
36+
) -> Result<Vec<OsString>> {
37+
let mut merged_args =
38+
vec![cmd_args.first().expect("Expected binary path as one argument").clone()];
39+
merged_args.extend(cfg_kani_args);
40+
if cfg_cbmc_args.is_empty() {
41+
// No need to handle cbmc_args. Just merge the Cargo.toml args with the original input:
42+
// [<config_kani_args>]* [input_args]*
43+
merged_args.extend_from_slice(&cmd_args[1..]);
44+
} else {
45+
let cbmc_flag = cmd_args.iter().enumerate().find(|&f| f.1.eq("--cbmc-args".into()));
46+
if let Some((idx, _)) = cbmc_flag {
47+
// Both command line and config file have --cbmc-args. Merge them to be in order.
48+
merged_args.extend_from_slice(&cmd_args[1..idx]);
49+
merged_args.extend(cfg_cbmc_args);
50+
// Remove --cbmc-args from the input.
51+
merged_args.extend_from_slice(&cmd_args[idx + 1..]);
52+
} else {
53+
// Command line doesn't have --cbmc-args. Put command line arguments in the middle.
54+
// [<cfg_kani_args>]* [<cmd_args>]* --cbmc-args [<cfg_cbmc_args>]+
55+
merged_args.extend_from_slice(&cmd_args[1..]);
56+
merged_args.extend(cfg_cbmc_args);
57+
}
58+
}
59+
Ok(merged_args)
1660
}
1761

1862
/// `locate-project` produces a response like: `/full/path/to/src/cargo-kani/Cargo.toml`
@@ -28,8 +72,9 @@ fn cargo_locate_project() -> Result<PathBuf> {
2872
Ok(path.trim().into())
2973
}
3074

31-
/// Parse a config toml string and extract the cargo-kani arguments we should try injecting
32-
fn toml_to_args(tomldata: &str) -> Result<Vec<OsString>> {
75+
/// Parse a config toml string and extract the cargo-kani arguments we should try injecting.
76+
/// This returns two different vectors since all cbmc-args have to be at the end.
77+
fn toml_to_args(tomldata: &str) -> Result<(Vec<OsString>, Vec<OsString>)> {
3378
let config = tomldata.parse::<Value>()?;
3479
// To make testing easier, our function contract is to produce a stable ordering of flags for a given input.
3580
// Consequently, we use BTreeMap instead of HashMap here.
@@ -43,20 +88,18 @@ fn toml_to_args(tomldata: &str) -> Result<Vec<OsString>> {
4388
}
4489

4590
let mut args = Vec::new();
46-
let mut suffixed_args = Vec::new();
91+
let mut cbmc_args = Vec::new();
4792

4893
for (flag, value) in map {
4994
if flag == "cbmc-args" {
5095
// --cbmc-args has to come last because it eats all remaining arguments
51-
insert_arg_from_toml(&flag, &value, &mut suffixed_args)?;
96+
insert_arg_from_toml(&flag, &value, &mut cbmc_args)?;
5297
} else {
5398
insert_arg_from_toml(&flag, &value, &mut args)?;
5499
}
55100
}
56101

57-
args.extend(suffixed_args);
58-
59-
Ok(args)
102+
Ok((args, cbmc_args))
60103
}
61104

62105
/// Translates one toml entry (flag, value) into arguments and inserts it into `args`
@@ -113,6 +156,44 @@ mod tests {
113156
let b = toml_to_args(a).unwrap();
114157
// default first, then unwind thanks to btree ordering.
115158
// cbmc-args always last.
116-
assert_eq!(b, vec!["--no-default-checks", "--unwind", "2", "--cbmc-args", "--fake"]);
159+
assert_eq!(b.0, vec!["--no-default-checks", "--unwind", "2"]);
160+
assert_eq!(b.1, vec!["--cbmc-args", "--fake"]);
161+
}
162+
163+
#[test]
164+
fn check_merge_args_with_only_command_line_args() {
165+
let cmd_args: Vec<OsString> =
166+
["cargo_kani", "--no-default-checks", "--unwind", "2", "--cbmc-args", "--fake"]
167+
.iter()
168+
.map(|&s| s.into())
169+
.collect();
170+
let merged = merge_args(cmd_args.clone(), Vec::new(), Vec::new()).unwrap();
171+
assert_eq!(merged, cmd_args);
172+
}
173+
174+
#[test]
175+
fn check_merge_args_with_only_config_kani_args() {
176+
let cfg_args: Vec<OsString> =
177+
["--no-default-checks", "--unwind", "2"].iter().map(|&s| s.into()).collect();
178+
let merged = merge_args(vec!["kani".into()], cfg_args.clone(), Vec::new()).unwrap();
179+
assert_eq!(merged[0], OsString::from("kani"));
180+
assert_eq!(merged[1..], cfg_args);
181+
}
182+
183+
#[test]
184+
fn check_merge_args_order() {
185+
let cmd_args: Vec<OsString> =
186+
vec!["kani".into(), "--debug".into(), "--cbmc-args".into(), "--fake".into()];
187+
let cfg_kani_args: Vec<OsString> = vec!["--no-default-checks".into()];
188+
let cfg_cbmc_args: Vec<OsString> = vec!["--cbmc-args".into(), "--trace".into()];
189+
let merged =
190+
merge_args(cmd_args.clone(), cfg_kani_args.clone(), cfg_cbmc_args.clone()).unwrap();
191+
assert_eq!(merged.len(), cmd_args.len() + cfg_kani_args.len() + cfg_cbmc_args.len() - 1);
192+
assert_eq!(merged[0], OsString::from("kani"));
193+
assert_eq!(merged[1], OsString::from("--no-default-checks"));
194+
assert_eq!(merged[2], OsString::from("--debug"));
195+
assert_eq!(merged[3], OsString::from("--cbmc-args"));
196+
assert_eq!(merged[4], OsString::from("--trace"));
197+
assert_eq!(merged[5], OsString::from("--fake"));
117198
}
118199
}

src/cargo-kani/src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
use anyhow::Result;
5-
use args_toml::config_toml_to_args;
5+
use args_toml::join_args;
66
use call_cbmc::VerificationStatus;
77
use std::ffi::OsString;
88
use std::path::PathBuf;
@@ -28,8 +28,8 @@ fn main() -> Result<()> {
2828
}
2929
}
3030

31-
fn cargokani_main(mut input_args: Vec<OsString>) -> Result<()> {
32-
input_args.extend(config_toml_to_args()?);
31+
fn cargokani_main(input_args: Vec<OsString>) -> Result<()> {
32+
let input_args = join_args(input_args)?;
3333
let args = args::CargoKaniArgs::from_iter(input_args);
3434
args.validate();
3535
let ctx = session::KaniSession::new(args.common_opts)?;

tests/cargo-kani/simple-unwind-annotation/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ edition = "2021"
88

99
[dependencies]
1010

11-
[kani.flags]
12-
function = "harness"
11+
# Remove this once unwind has been hooked up
12+
[package.metadata.kani]
13+
flags = {function = "harness"}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
assertion failed: counter < 10
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
FAILURE\
2-
assertion failed: counter < 10
1+
Failed Checks: assertion failed: 1 == 2

tests/cargo-kani/simple-unwind-annotation/src/main.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
3-
4-
// kani-flags: --function harness
5-
6-
// The expected file presently looks for "1 == 2" above.
7-
// But eventually this test may start to fail as we might stop regarding 'main'
8-
// as a valid proof harness, since it isn't annotated as such.
9-
// This test should be updated if we go that route.
3+
//
4+
// This harness should fail because unwind limit is too high.
105

116
fn main() {
127
assert!(1 == 2);

0 commit comments

Comments
 (0)