-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for inductively verifying contracts of recursive functions (#…
…2809) This adds support for verifying the contracts of recursive functions inductively. The idea is quite simple. We use a global variable to track whether we're reentering a function and depending on that value we either continue with checking the contract or assume the hypothesis by using its replacement. The precise mechanism that I implemented is explained in the documentation [here](https://github.com/JustusAdam/kani/blob/a31176c79098205ca0b2c2b199cdf06653754551/library/kani_macros/src/sysroot/contracts.rs#L126) Resolves #2724 **Open Question:** to facilitate induction the return type of the function must implement `kani::Arbitrary`. Since we can't discriminate between recursive and non-recursive functions in the proc-macro this means that this applies to all functions with contracts, not just recursive ones. This may be an unacceptable restriction. We could consider making inductive verification explicit with an annotation like `#[kani::inductive]` or conversely make inductive the default and let users opt-out with `#[kani::no_inductive]`. This is now tracked in #2823 I had to remove the names of the functions in which the messages occur in the test cases, since they are now generated, hard to read names By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com>
- Loading branch information
1 parent
1b57856
commit 2d32361
Showing
23 changed files
with
611 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
tests/expected/function-contract/arbitrary_ensures_fail.expected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
max.assertion\ | ||
assertion\ | ||
- Status: FAILURE\ | ||
- Description: "result == x"\ | ||
in function max | ||
|
2 changes: 1 addition & 1 deletion
2
tests/expected/function-contract/arbitrary_ensures_pass.expected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
max.assertion\ | ||
assertion\ | ||
- Status: SUCCESS\ | ||
- Description: "result == x || result == y"\ | ||
in function max | ||
|
2 changes: 1 addition & 1 deletion
2
tests/expected/function-contract/arbitrary_requires_pass.expected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
assertion\ | ||
- Status: FAILURE\ | ||
- Description: "internal error: entered unreachable code: fail on two" | ||
|
||
Failed Checks: internal error: entered unreachable code: fail on two | ||
|
||
|
||
assertion\ | ||
- Status: FAILURE\ | ||
- Description: "result < 3" | ||
|
||
Failed Checks: result < 3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Copyright Kani Contributors | ||
// SPDX-License-Identifier: Apache-2.0 OR MIT | ||
// kani-flags: -Zfunction-contracts | ||
|
||
//! The test cases try to ensure applying the hypothesis is only done in | ||
//! inductive verification of a function call. E.g. on every second encounter of | ||
//! the called function in its own call stack rather than globally in the | ||
//! program. | ||
//! | ||
//! In each case we have a recursive function that is called and we expect that | ||
//! the recursive call within the first call has the hypothesis applied. (That's | ||
//! not actually tested here but separately.) | ||
//! | ||
//! Then we call the function again and we've set up the cases such that *if* | ||
//! the actual body is used then that call with fail (once because of panic, | ||
//! once because the postcondition is violated). If instead the hypothesis (e.g. | ||
//! contract replacement) is used we'd expect the verification to succeed. | ||
|
||
#[kani::ensures(result < 3)] | ||
fn fail_on_two(i: i32) -> i32 { | ||
match i { | ||
0 => fail_on_two(i + 1), | ||
1 => 2, | ||
_ => unreachable!("fail on two"), | ||
} | ||
} | ||
|
||
#[kani::proof_for_contract(fail_on_two)] | ||
fn harness() { | ||
let first = fail_on_two(0); | ||
let _ = fail_on_two(first); | ||
} | ||
|
||
#[kani::ensures(result < 3)] | ||
fn fail_on_two_in_postcondition(i: i32) -> i32 { | ||
let j = i + 1; | ||
if i < 2 { fail_on_two_in_postcondition(j) } else { j } | ||
} | ||
|
||
#[kani::proof_for_contract(fail_on_two_in_postcondition)] | ||
fn harness2() { | ||
let first = fail_on_two_in_postcondition(1); | ||
let _ = fail_on_two_in_postcondition(first); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Failed Checks: result != 0 && x % result == 0 && y % result == 0 | ||
|
||
VERIFICATION:- FAILED |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright Kani Contributors | ||
// SPDX-License-Identifier: Apache-2.0 OR MIT | ||
// kani-flags: -Zfunction-contracts | ||
type T = u8; | ||
|
||
/// Euclid's algorithm for calculating the GCD of two numbers | ||
#[kani::requires(x != 0 && y != 0)] | ||
#[kani::ensures(result != 0 && x % result == 0 && y % result == 0)] | ||
fn gcd(x: T, y: T) -> T { | ||
let mut max = x; | ||
let mut min = y; | ||
if min > max { | ||
let val = max; | ||
max = min; | ||
min = val; | ||
} | ||
let res = max % min; | ||
if res == 0 { min } else { gcd(min, res + 1) } | ||
} | ||
|
||
#[kani::proof_for_contract(gcd)] | ||
fn simple_harness() { | ||
let _ = gcd(kani::any(), kani::any()); | ||
} |
9 changes: 9 additions & 0 deletions
9
tests/expected/function-contract/gcd_rec_comparison_pass.expected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
assertion\ | ||
- Status: SUCCESS\ | ||
- Description: "x != 0 && y != 0" | ||
|
||
assertion\ | ||
- Status: SUCCESS\ | ||
- Description: "result != 0 && x % result == 0 && y % result == 0" | ||
|
||
VERIFICATION:- SUCCESSFUL |
68 changes: 68 additions & 0 deletions
68
tests/expected/function-contract/gcd_rec_comparison_pass.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// Copyright Kani Contributors | ||
// SPDX-License-Identifier: Apache-2.0 OR MIT | ||
// kani-flags: -Zfunction-contracts | ||
type T = u8; | ||
|
||
/// Euclid's algorithm for calculating the GCD of two numbers | ||
#[kani::requires(x != 0 && y != 0)] | ||
#[kani::ensures(result != 0 && x % result == 0 && y % result == 0)] | ||
fn gcd(x: T, y: T) -> T { | ||
let mut max = x; | ||
let mut min = y; | ||
if min > max { | ||
let val = max; | ||
max = min; | ||
min = val; | ||
} | ||
|
||
let res = max % min; | ||
if res == 0 { min } else { gcd(min, res) } | ||
} | ||
|
||
struct Frac { | ||
pub num: T, | ||
pub den: T, | ||
} | ||
|
||
impl Frac { | ||
// constructor | ||
pub fn new(num: T, den: T) -> Self { | ||
Frac { num, den } | ||
} | ||
|
||
/// Method to simplify fraction | ||
/// For example, `Frac { num: 10, den: 15 }` gets simplified to | ||
/// `Frac { num: 2, num: 3 }` | ||
pub fn simplify(&self) -> Frac { | ||
let gcd = gcd(self.num, self.den); | ||
Frac::new(self.num / gcd, self.den / gcd) | ||
} | ||
|
||
pub fn check_equals(&self, f2: Frac) { | ||
assert_eq!(self.num % f2.num, 0); | ||
assert_eq!(self.den % f2.den, 0); | ||
let gcd1 = self.num / f2.num; | ||
let gcd2 = self.den / f2.den; | ||
assert_eq!(gcd1, gcd2); | ||
} | ||
} | ||
|
||
#[kani::proof_for_contract(gcd)] | ||
#[kani::unwind(12)] | ||
fn harness_as_check() { | ||
gcd_harness() | ||
} | ||
|
||
fn gcd_harness() { | ||
// Needed to avoid having `free` be removed as unused function. This is | ||
// because DFCC contract enforcement assumes that a definition for `free` | ||
// exists. | ||
let _ = Box::new(9_usize); | ||
let num: T = kani::any(); | ||
let den: T = kani::any(); | ||
kani::assume(num != 0); | ||
kani::assume(den != 0); | ||
let frac = Frac::new(num, den); | ||
let simplified_frac = frac.simplify(); | ||
frac.check_equals(simplified_frac); | ||
} |
3 changes: 3 additions & 0 deletions
3
tests/expected/function-contract/gcd_rec_contract_fail.expected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Failed Checks: result != 0 && x % result == 1 && y % result == 0 | ||
|
||
VERIFICATION:- FAILED |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright Kani Contributors | ||
// SPDX-License-Identifier: Apache-2.0 OR MIT | ||
// kani-flags: -Zfunction-contracts | ||
type T = u8; | ||
|
||
/// Euclid's algorithm for calculating the GCD of two numbers | ||
#[kani::requires(x != 0 && y != 0)] | ||
// Changed `0` to `1` in `x % result == 0` to mess with this contract | ||
#[kani::ensures(result != 0 && x % result == 1 && y % result == 0)] | ||
fn gcd(x: T, y: T) -> T { | ||
let mut max = x; | ||
let mut min = y; | ||
if min > max { | ||
let val = max; | ||
max = min; | ||
min = val; | ||
} | ||
|
||
let res = max % min; | ||
if res == 0 { min } else { gcd(min, res) } | ||
} | ||
#[kani::proof_for_contract(gcd)] | ||
fn simple_harness() { | ||
let _ = gcd(kani::any(), kani::any()); | ||
} |
17 changes: 17 additions & 0 deletions
17
tests/expected/function-contract/gcd_rec_replacement_pass.expected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
Frac::check_equals.assertion\ | ||
- Status: SUCCESS\ | ||
- Description: "assertion failed: self.num % f2.num == 0" | ||
|
||
Frac::check_equals.assertion\ | ||
- Status: SUCCESS\ | ||
- Description: "assertion failed: self.den % f2.den == 0" | ||
|
||
Frac::check_equals.assertion\ | ||
- Status: SUCCESS\ | ||
- Description: "assertion failed: gcd1 == gcd2" | ||
|
||
gcd.assertion\ | ||
- Status: SUCCESS\ | ||
- Description: "x != 0 && y != 0" | ||
|
||
VERIFICATION:- SUCCESSFUL |
68 changes: 68 additions & 0 deletions
68
tests/expected/function-contract/gcd_rec_replacement_pass.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// Copyright Kani Contributors | ||
// SPDX-License-Identifier: Apache-2.0 OR MIT | ||
// kani-flags: -Zfunction-contracts | ||
type T = u8; | ||
|
||
/// Euclid's algorithm for calculating the GCD of two numbers | ||
#[kani::requires(x != 0 && y != 0)] | ||
#[kani::ensures(result != 0 && x % result == 0 && y % result == 0)] | ||
fn gcd(x: T, y: T) -> T { | ||
let mut max = x; | ||
let mut min = y; | ||
if min > max { | ||
let val = max; | ||
max = min; | ||
min = val; | ||
} | ||
|
||
let res = max % min; | ||
if res == 0 { min } else { gcd(min, res) } | ||
} | ||
|
||
struct Frac { | ||
pub num: T, | ||
pub den: T, | ||
} | ||
|
||
impl Frac { | ||
// constructor | ||
pub fn new(num: T, den: T) -> Self { | ||
Frac { num, den } | ||
} | ||
|
||
/// Method to simplify fraction | ||
/// For example, `Frac { num: 10, den: 15 }` gets simplified to | ||
/// `Frac { num: 2, num: 3 }` | ||
pub fn simplify(&self) -> Frac { | ||
let gcd = gcd(self.num, self.den); | ||
Frac::new(self.num / gcd, self.den / gcd) | ||
} | ||
|
||
pub fn check_equals(&self, f2: Frac) { | ||
assert_eq!(self.num % f2.num, 0); | ||
assert_eq!(self.den % f2.den, 0); | ||
let gcd1 = self.num / f2.num; | ||
let gcd2 = self.den / f2.den; | ||
assert_eq!(gcd1, gcd2); | ||
} | ||
} | ||
|
||
#[kani::proof] | ||
#[kani::stub_verified(gcd)] | ||
fn gcd_as_replace() { | ||
gcd_harness() | ||
} | ||
|
||
fn gcd_harness() { | ||
// Needed to avoid having `free` be removed as unused function. This is | ||
// because DFCC contract enforcement assumes that a definition for `free` | ||
// exists. | ||
let _ = Box::new(9_usize); | ||
let num: T = kani::any(); | ||
let den: T = kani::any(); | ||
kani::assume(num != 0); | ||
kani::assume(den != 0); | ||
let frac = Frac::new(num, den); | ||
let simplified_frac = frac.simplify(); | ||
frac.check_equals(simplified_frac); | ||
} |
9 changes: 9 additions & 0 deletions
9
tests/expected/function-contract/gcd_rec_simple_pass.expected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
assertion\ | ||
- Status: SUCCESS\ | ||
- Description: "x != 0 && y != 0" | ||
|
||
assertion\ | ||
- Status: SUCCESS\ | ||
- Description: "result != 0 && x % result == 0 && y % result == 0" | ||
|
||
VERIFICATION:- SUCCESSFUL |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright Kani Contributors | ||
// SPDX-License-Identifier: Apache-2.0 OR MIT | ||
// kani-flags: -Zfunction-contracts | ||
type T = u8; | ||
|
||
/// Euclid's algorithm for calculating the GCD of two numbers | ||
#[kani::requires(x != 0 && y != 0)] | ||
#[kani::ensures(result != 0 && x % result == 0 && y % result == 0)] | ||
fn gcd(x: T, y: T) -> T { | ||
let mut max = x; | ||
let mut min = y; | ||
if min > max { | ||
let val = max; | ||
max = min; | ||
min = val; | ||
} | ||
|
||
let res = max % min; | ||
if res == 0 { min } else { gcd(min, res) } | ||
} | ||
|
||
#[kani::proof_for_contract(gcd)] | ||
fn simple_harness() { | ||
let _ = gcd(kani::any(), kani::any()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.