Skip to content

Commit 23472fc

Browse files
jaisnantedinski
authored andcommitted
Feature : Unwind attribute (rust-lang#1031)
* Hook up unwind attribute to Kani * Add tests and make Harness, a required argument * Refactor tests and replace --unwind with --default-unwind
1 parent d61f172 commit 23472fc

File tree

62 files changed

+210
-103
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+210
-103
lines changed

docs/src/kani-single-file.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ The most common `kani` arguments are the following:
2424
* `--harness <name>`: By default, Kani checks all proof harnesses it finds. You
2525
can switch to checking a single harness using this flag.
2626

27-
* `--unwind <n>`: Set a global upper [loop
27+
* `--default-unwind <n>`: Set a global upper [loop
2828
unwinding](./tutorial-loop-unwinding.md) bound on all loops. This can force
2929
termination when CBMC tries to unwind loops indefinitely.
3030

docs/src/regression-testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ be specified in single Rust files by adding a comment at the top of the file:
107107
For example, to use an unwinding value of 4 in a test, we can write:
108108

109109
```rust,noplaypen
110-
// kani-flags: --unwind 4
110+
// kani-flags: --default-unwind 4
111111
```
112112

113113
For `cargo-kani` tests, the preferred way to pass command-line options is adding

docs/src/tutorial-loop-unwinding.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ Bounding proofs like this means we may no longer be proving as much as we origin
3030
Who's to say, if we prove everything works up to size 10, that there isn't a novel bug lurking, expressible only with problems of size 11+?
3131
But, let's get back to the issue at hand.
3232

33-
We can "make progress" in our work by giving Kani a global bound on the problem size using the `--unwind <bound>` flag.
33+
We can "make progress" in our work by giving Kani a global bound on the problem size using the `--default-unwind <bound>` flag.
3434
This flag puts a fixed upper bound on loop unwinding.
3535
Kani will automatically generate verification conditions that help us understand if that bound isn't enough.
3636
Let's start with a small unwinding value:
3737

3838
```
39-
# kani src/lib.rs --unwind 1
39+
# kani src/lib.rs --default-unwind 1
4040
Check 69: .unwind.0
4141
- Status: FAILURE
4242
- Description: "unwinding assertion loop 0"
@@ -50,19 +50,19 @@ This output is showing us two things:
5050
2. We aren't seeing other failures if we only unwind the loop once.
5151
The execution can't progress far enough to reveal the bug we're interested in (which actually only happens in the last iteration of the loop).
5252

53-
Doing an initial `--unwind 1` is generally enough to force termination, but often too little for verification.
53+
Doing an initial `--default-unwind 1` is generally enough to force termination, but often too little for verification.
5454

5555
We were clearly aiming at a size limit of 10 in our proof harness, so let's try a few things:
5656

5757
```
58-
# kani src/lib.rs --unwind 10 | grep Failed
58+
# kani src/lib.rs --default-unwind 10 | grep Failed
5959
Failed Checks: unwinding assertion loop 0
6060
```
6161

6262
A bound of 10 still isn't enough because we generally need to unwind one greater than the number of executed loop iterations:
6363

6464
```
65-
# kani src/lib.rs --unwind 11 | grep Failed
65+
# kani src/lib.rs --default-unwind 11 | grep Failed
6666
Failed Checks: index out of bounds: the length is less than or equal to the given index
6767
Failed Checks: dereference failure: pointer outside object bounds
6868
Failed Checks: unwinding assertion loop 0
@@ -72,12 +72,12 @@ We're still not seeing the unwinding assertion failure go away!
7272
This is because our error is really an off-by-one problem, we loop one too many times, so let's add one more:
7373

7474
```
75-
# kani src/lib.rs --unwind 12 | grep Failed
75+
# kani src/lib.rs --default-unwind 12 | grep Failed
7676
Failed Checks: index out of bounds: the length is less than or equal to the given index
7777
Failed Checks: dereference failure: pointer outside object bounds
7878
```
7979

8080
Kani is now sure we've unwound the loop enough to verify our proof harness, and now we're seeing just the bound checking failures from the off-by-one error.
8181

8282
1. Exercise: Fix the off-by-one bounds error and get Kani to verify successfully.
83-
2. Exercise: After fixing the error, `--unwind 11` works. Why?
83+
2. Exercise: After fixing the error, `--default-unwind 11` works. Why?

docs/src/tutorial-real-code.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Running Kani on this simple starting point will help figure out:
7777
2. Whether you're over-constrained. Check the coverage report using `--visualize`. Ideally you'd see 100% coverage, and if not, it's usually because now you've over-constrained the inputs.
7878
3. Whether Kani will support all the Rust features involved.
7979
4. Whether you've started with a tractable problem.
80-
(If the problem is initially intractable, try `--unwind 1` and see if you can follow the techniques in the previous section to put a bound on the problem.)
80+
(If the problem is initially intractable, try `--default-unwind 1` and see if you can follow the techniques in the previous section to put a bound on the problem.)
8181

8282
Once you've got something working, the next step is to prove more interesting properties than what Kani covers by default.
8383
You accomplish this by adding new assertions (not just in your harness, but also to the code being run).

docs/src/tutorial/loops-unwinding/Cargo.toml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,4 @@ name = "loops-unwinding"
55
version = "0.1.0"
66
edition = "2018"
77

8-
[dependencies]
9-
10-
[workspace]
11-
12-
[package.metadata.kani]
13-
flags = { unwind = "11" }
8+
[workspace]

docs/src/tutorial/loops-unwinding/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ fn initialize_prefix(length: usize, buffer: &mut [u8]) {
1717
// ANCHOR: kani
1818
#[cfg(kani)]
1919
#[kani::proof]
20+
#[kani::unwind(11)]
2021
fn main() {
2122
const LIMIT: usize = 10;
2223
let mut buffer: [u8; LIMIT] = [1; LIMIT];

docs/src/workarounds.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ issues in the Kani project.
66
## CBMC arguments
77

88
Kani is able to handle common CBMC arguments as if they were its own (e.g.,
9-
`--unwind <n>`), but sometimes it may be necessary to use CBMC arguments which
9+
`--default-unwind <n>`), but sometimes it may be necessary to use CBMC arguments which
1010
are not handled by Kani.
1111

1212
To pass additional arguments for CBMC, you pass `--cbmc-args` to Kani. Note that
@@ -26,7 +26,7 @@ cargo kani [<kani-args>]* --cbmc-args [<cbmc-args>]*
2626
2727
### Individual loop bounds
2828

29-
Setting `--unwind <n>` affects every loop in a harness.
29+
Setting `--default-unwind <n>` affects every loop in a harness.
3030
Once you know a particular loop is causing trouble, sometimes it can be helpful to provide a specific bound for it.
3131

3232
In the general case, specifying just the highest bound globally for all loops

kani-driver/src/args.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ pub struct KaniArgs {
104104
pub object_bits: u32,
105105
/// Specify the value used for loop unwinding in CBMC
106106
#[structopt(long)]
107+
pub default_unwind: Option<u32>,
108+
/// Specify the value used for loop unwinding for the specified harness in CBMC
109+
#[structopt(long, requires("harness"))]
107110
pub unwind: Option<u32>,
108111
/// Turn on automatic loop unwinding
109112
#[structopt(long)]
@@ -283,15 +286,15 @@ impl KaniArgs {
283286
let extra_unwind = self.cbmc_args.contains(&OsString::from("--unwind"));
284287
let extra_auto_unwind = self.cbmc_args.contains(&OsString::from("--auto-unwind"));
285288
let extras = extra_auto_unwind || extra_unwind;
286-
let natives = self.auto_unwind || self.unwind.is_some();
289+
let natives = self.auto_unwind || self.default_unwind.is_some();
287290
let any_auto_unwind = extra_auto_unwind || self.auto_unwind;
288-
let any_unwind = self.unwind.is_some() || extra_unwind;
291+
let any_unwind = self.default_unwind.is_some() || extra_unwind;
289292

290293
// TODO: these conflicting flags reflect what's necessary to pass current tests unmodified.
291294
// We should consider improving the error messages slightly in a later pull request.
292295
if any_auto_unwind && any_unwind {
293296
Error::with_description(
294-
"Conflicting flags: `--auto-unwind` is not compatible with other `--unwind` flags.",
297+
"Conflicting flags: `--auto-unwind` is not compatible with other `--default-unwind` flags.",
295298
ErrorKind::ArgumentConflict,
296299
)
297300
.exit();
@@ -365,6 +368,15 @@ mod tests {
365368
assert_eq!(err.kind, ErrorKind::ArgumentConflict);
366369
}
367370

371+
#[test]
372+
fn check_unwind_conflicts() {
373+
// --unwind cannot be called without --harness
374+
let args = vec!["kani", "file.rs", "--unwind", "3"];
375+
let app = StandaloneArgs::clap();
376+
let err = app.get_matches_from_safe(args).unwrap_err();
377+
assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
378+
}
379+
368380
fn parse_unstable_disabled(args: &str) -> Result<ArgMatches<'_>, Error> {
369381
let args = format!("kani file.rs {}", args);
370382
let app = StandaloneArgs::clap();

kani-driver/src/args_toml.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,18 @@ mod tests {
152152
#[test]
153153
fn check_toml_parsing() {
154154
let a = "[workspace.metadata.kani]
155-
flags = { default-checks = false, unwind = \"2\", cbmc-args = [\"--fake\"] }";
155+
flags = { default-checks = false, default-unwind = \"2\", cbmc-args = [\"--fake\"] }";
156156
let b = toml_to_args(a).unwrap();
157157
// default first, then unwind thanks to btree ordering.
158158
// cbmc-args always last.
159-
assert_eq!(b.0, vec!["--no-default-checks", "--unwind", "2"]);
159+
assert_eq!(b.0, vec!["--no-default-checks", "--default-unwind", "2"]);
160160
assert_eq!(b.1, vec!["--cbmc-args", "--fake"]);
161161
}
162162

163163
#[test]
164164
fn check_merge_args_with_only_command_line_args() {
165165
let cmd_args: Vec<OsString> =
166-
["cargo_kani", "--no-default-checks", "--unwind", "2", "--cbmc-args", "--fake"]
166+
["cargo_kani", "--no-default-checks", "--default-unwind", "2", "--cbmc-args", "--fake"]
167167
.iter()
168168
.map(|&s| s.into())
169169
.collect();
@@ -174,7 +174,7 @@ mod tests {
174174
#[test]
175175
fn check_merge_args_with_only_config_kani_args() {
176176
let cfg_args: Vec<OsString> =
177-
["--no-default-checks", "--unwind", "2"].iter().map(|&s| s.into()).collect();
177+
["--no-default-checks", "--default-unwind", "2"].iter().map(|&s| s.into()).collect();
178178
let merged = merge_args(vec!["kani".into()], cfg_args.clone(), Vec::new()).unwrap();
179179
assert_eq!(merged[0], OsString::from("kani"));
180180
assert_eq!(merged[1..], cfg_args);

kani-driver/src/call_cbmc.rs

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

44
use anyhow::{bail, Result};
5+
use kani_metadata::HarnessMetadata;
56
use std::ffi::OsString;
67
use std::path::Path;
78
use std::process::Command;
89

10+
use crate::args::KaniArgs;
911
use crate::session::KaniSession;
1012

1113
#[derive(PartialEq)]
@@ -16,15 +18,15 @@ pub enum VerificationStatus {
1618

1719
impl KaniSession {
1820
/// Verify a goto binary that's been prepared with goto-instrument
19-
pub fn run_cbmc(&self, file: &Path) -> Result<VerificationStatus> {
21+
pub fn run_cbmc(&self, file: &Path, harness: &HarnessMetadata) -> Result<VerificationStatus> {
2022
let output_filename = crate::util::append_path(file, "cbmc_output");
2123

2224
{
2325
let mut temps = self.temporaries.borrow_mut();
2426
temps.push(output_filename.clone());
2527
}
2628

27-
let args: Vec<OsString> = self.cbmc_flags(file)?;
29+
let args: Vec<OsString> = self.cbmc_flags(file, harness)?;
2830

2931
// TODO get cbmc path from self
3032
let mut cmd = Command::new("cbmc");
@@ -73,15 +75,19 @@ impl KaniSession {
7375
}
7476

7577
/// "Internal," but also used by call_cbmc_viewer
76-
pub fn cbmc_flags(&self, file: &Path) -> Result<Vec<OsString>> {
78+
pub fn cbmc_flags(
79+
&self,
80+
file: &Path,
81+
harness_metadata: &HarnessMetadata,
82+
) -> Result<Vec<OsString>> {
7783
let mut args = self.cbmc_check_flags();
7884

7985
args.push("--object-bits".into());
8086
args.push(self.args.object_bits.to_string().into());
8187

82-
if let Some(unwind) = self.args.unwind {
88+
if let Some(unwind_value) = resolve_unwind_value(&self.args, harness_metadata) {
8389
args.push("--unwind".into());
84-
args.push(unwind.to_string().into());
90+
args.push(unwind_value.to_string().into());
8591
} else if self.args.auto_unwind {
8692
args.push("--auto-unwind".into());
8793
}
@@ -127,3 +133,76 @@ impl KaniSession {
127133
args
128134
}
129135
}
136+
137+
/// Solve Unwind Value from conflicting inputs of unwind values. (--default-unwind, annotation-unwind, --unwind)
138+
pub fn resolve_unwind_value(args: &KaniArgs, harness_metadata: &HarnessMetadata) -> Option<u32> {
139+
// Check for which flag is being passed and prioritize extracting unwind from the
140+
// respective flag/annotation.
141+
if let Some(harness_unwind) = args.unwind {
142+
Some(harness_unwind)
143+
} else if let Some(annotation_unwind) = harness_metadata.unwind_value {
144+
Some(annotation_unwind)
145+
} else if let Some(default_unwind) = args.default_unwind {
146+
Some(default_unwind)
147+
} else {
148+
None
149+
}
150+
}
151+
152+
#[cfg(test)]
153+
mod tests {
154+
use crate::args;
155+
use crate::metadata::mock_proof_harness;
156+
use structopt::StructOpt;
157+
158+
use super::*;
159+
160+
#[test]
161+
fn check_resolve_unwind_value() {
162+
// Command line unwind value for specific harnesses take precedence over default annotation value
163+
let args_empty = ["kani"];
164+
let args_only_default = ["kani", "--default-unwind", "2"];
165+
let args_only_harness = ["kani", "--unwind", "1", "--harness", "check_one"];
166+
let args_both =
167+
["kani", "--default-unwind", "2", "--unwind", "1", "--harness", "check_one"];
168+
169+
let harness_none = mock_proof_harness("check_one", None);
170+
let harness_some = mock_proof_harness("check_one", Some(3));
171+
172+
// test against no unwind annotation
173+
assert_eq!(
174+
resolve_unwind_value(&args::KaniArgs::from_iter(args_empty), &harness_none),
175+
None
176+
);
177+
assert_eq!(
178+
resolve_unwind_value(&args::KaniArgs::from_iter(args_only_default), &harness_none),
179+
Some(2)
180+
);
181+
assert_eq!(
182+
resolve_unwind_value(&args::KaniArgs::from_iter(args_only_harness), &harness_none),
183+
Some(1)
184+
);
185+
assert_eq!(
186+
resolve_unwind_value(&args::KaniArgs::from_iter(args_both), &harness_none),
187+
Some(1)
188+
);
189+
190+
// test against unwind annotation
191+
assert_eq!(
192+
resolve_unwind_value(&args::KaniArgs::from_iter(args_empty), &harness_some),
193+
Some(3)
194+
);
195+
assert_eq!(
196+
resolve_unwind_value(&args::KaniArgs::from_iter(args_only_default), &harness_some),
197+
Some(3)
198+
);
199+
assert_eq!(
200+
resolve_unwind_value(&args::KaniArgs::from_iter(args_only_harness), &harness_some),
201+
Some(1)
202+
);
203+
assert_eq!(
204+
resolve_unwind_value(&args::KaniArgs::from_iter(args_both), &harness_some),
205+
Some(1)
206+
);
207+
}
208+
}

kani-driver/src/call_cbmc_viewer.rs

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

44
use anyhow::Result;
5+
use kani_metadata::HarnessMetadata;
56
use std::ffi::OsString;
67
use std::path::Path;
78
use std::process::Command;
@@ -12,7 +13,12 @@ use crate::util::alter_extension;
1213
impl KaniSession {
1314
/// Run CBMC appropriately to produce 3 output XML files, then run cbmc-viewer on them to produce a report.
1415
/// Viewer doesn't give different error codes depending on verification failure, so as long as it works, we report success.
15-
pub fn run_visualize(&self, file: &Path, report_dir: &Path) -> Result<()> {
16+
pub fn run_visualize(
17+
&self,
18+
file: &Path,
19+
report_dir: &Path,
20+
harness_metadata: &HarnessMetadata,
21+
) -> Result<()> {
1622
let results_filename = alter_extension(file, "results.xml");
1723
let coverage_filename = alter_extension(file, "coverage.xml");
1824
let property_filename = alter_extension(file, "property.xml");
@@ -24,9 +30,19 @@ impl KaniSession {
2430
temps.push(property_filename.clone());
2531
}
2632

27-
self.cbmc_variant(file, &["--xml-ui", "--trace"], &results_filename)?;
28-
self.cbmc_variant(file, &["--xml-ui", "--cover", "location"], &coverage_filename)?;
29-
self.cbmc_variant(file, &["--xml-ui", "--show-properties"], &property_filename)?;
33+
self.cbmc_variant(file, &["--xml-ui", "--trace"], &results_filename, harness_metadata)?;
34+
self.cbmc_variant(
35+
file,
36+
&["--xml-ui", "--cover", "location"],
37+
&coverage_filename,
38+
harness_metadata,
39+
)?;
40+
self.cbmc_variant(
41+
file,
42+
&["--xml-ui", "--show-properties"],
43+
&property_filename,
44+
harness_metadata,
45+
)?;
3046

3147
let args: Vec<OsString> = vec![
3248
"--result".into(),
@@ -59,8 +75,14 @@ impl KaniSession {
5975
Ok(())
6076
}
6177

62-
fn cbmc_variant(&self, file: &Path, extra_args: &[&str], output: &Path) -> Result<()> {
63-
let mut args = self.cbmc_flags(file)?;
78+
fn cbmc_variant(
79+
&self,
80+
file: &Path,
81+
extra_args: &[&str],
82+
output: &Path,
83+
harness: &HarnessMetadata,
84+
) -> Result<()> {
85+
let mut args = self.cbmc_flags(file, harness)?;
6486
args.extend(extra_args.iter().map(|x| x.into()));
6587

6688
// TODO fix this hack, abstractions are wrong

0 commit comments

Comments
 (0)