Skip to content

Commit 33e3c36

Browse files
authored
Fix contract expansion for old (#3491)
Fixes the macro expansion for contracts to properly place history expressions. ## Problem Before this PR, instantiations of "remembers variables" (i.e., the variables that save the value before the function executes) were always put *above* any statements from previous macro expansions. For example, for this code (from #3359): ```rust #[kani::requires(val < i32::MAX)] #[kani::ensures(|result| *result == old(val + 1))] pub fn next(mut val: i32) -> i32 { val + 1 } ``` Kani would first expand the `requires` attribute and insert `kani::assume(val < i32::MAX)`. The expansion of `ensures` would then put the remembers variables first, generating this: ``` let remember_kani_internal_1e725538cd5566b8 = val + 1; kani::assume(val < i32::MAX); ``` which causes an integer overflow because we don't restrict the value of `val` before adding 1. Instead, we want: ``` kani::assume(val < i32::MAX); let remember_kani_internal_1e725538cd5566b8 = val + 1; ``` ## Solution The solution is to insert the remembers variables immediately after preconditions--that way, they respect the preconditions but are still declared before the function under contract executes. When we're expanding an `ensures` clause, we iterate through each of the already-generated statements, find the position where the preconditions end, then insert the remembers variables there. For instance: ``` kani::assume(x < 100); kani::assume(y < 10); kani::assume(x + y < 105); <-- remembers variables go here --> let _wrapper_arg = ... ``` --- Resolves #3359 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent 4a9a70c commit 33e3c36

File tree

10 files changed

+152
-6
lines changed

10 files changed

+152
-6
lines changed

library/kani_macros/src/sysroot/contracts/check.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use std::mem;
99
use syn::{parse_quote, Block, Expr, FnArg, Local, LocalInit, Pat, PatIdent, ReturnType, Stmt};
1010

1111
use super::{
12-
helpers::*, shared::build_ensures, ContractConditionsData, ContractConditionsHandler,
13-
INTERNAL_RESULT_IDENT,
12+
helpers::*, shared::build_ensures, ClosureType, ContractConditionsData,
13+
ContractConditionsHandler, INTERNAL_RESULT_IDENT,
1414
};
1515

1616
const WRAPPER_ARG: &str = "_wrapper_arg";
@@ -38,9 +38,14 @@ impl<'a> ContractConditionsHandler<'a> {
3838
);
3939

4040
let return_expr = body_stmts.pop();
41+
42+
let (assumes, rest_of_body) =
43+
split_for_remembers(&body_stmts[..], ClosureType::Check);
44+
4145
quote!({
46+
#(#assumes)*
4247
#remembers
43-
#(#body_stmts)*
48+
#(#rest_of_body)*
4449
#exec_postconditions
4550
#return_expr
4651
})

library/kani_macros/src/sysroot/contracts/helpers.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
//! Functions that operate third party data structures with no logic that is
55
//! specific to Kani and contracts.
66
7+
use crate::attr_impl::contracts::ClosureType;
78
use proc_macro2::{Ident, Span};
89
use std::borrow::Cow;
910
use syn::spanned::Spanned;
10-
use syn::{parse_quote, Attribute, Expr, ExprBlock, Local, LocalInit, PatIdent, Stmt};
11+
use syn::{
12+
parse_quote, Attribute, Expr, ExprBlock, ExprCall, ExprPath, Local, LocalInit, PatIdent, Path,
13+
Stmt,
14+
};
1115

1216
/// If an explicit return type was provided it is returned, otherwise `()`.
1317
pub fn return_type_to_type(return_type: &syn::ReturnType) -> Cow<syn::Type> {
@@ -169,6 +173,53 @@ pub fn chunks_by<'a, T, C: Default + Extend<T>>(
169173
})
170174
}
171175

176+
/// Splits `stmts` into (preconditions, rest).
177+
/// For example, ClosureType::Check assumes preconditions, so given this sequence of statements:
178+
/// ```ignore
179+
/// kani::assume(.. precondition_1);
180+
/// kani::assume(.. precondition_2);
181+
/// let _wrapper_arg = (ptr as * const _,);
182+
/// ...
183+
/// ```
184+
/// This function would return the two kani::assume statements in the former slice
185+
/// and the remaining statements in the latter.
186+
/// The flow for ClosureType::Replace is the same, except preconditions are asserted rather than assumed.
187+
///
188+
/// The caller can use the returned tuple to insert remembers statements after `preconditions` and before `rest`.
189+
/// Inserting the remembers statements after `preconditions` ensures that they are bound by the preconditions.
190+
/// To understand why this is important, take the following example:
191+
/// ```ignore
192+
/// #[kani::requires(x < u32::MAX)]
193+
/// #[kani::ensures(|result| old(x + 1) == *result)]
194+
/// fn add_one(x: u32) -> u32 {...}
195+
/// ```
196+
/// If the `old(x + 1)` statement didn't respect the precondition's upper bound on `x`, Kani would encounter an integer overflow.
197+
///
198+
/// Inserting the remembers statements before `rest` ensures that they are declared before the original function executes,
199+
/// so that they will store historical, pre-computation values as intended.
200+
pub fn split_for_remembers(stmts: &[Stmt], closure_type: ClosureType) -> (&[Stmt], &[Stmt]) {
201+
let mut pos = 0;
202+
203+
let check_str = match closure_type {
204+
ClosureType::Check => "assume",
205+
ClosureType::Replace => "assert",
206+
};
207+
208+
for stmt in stmts {
209+
if let Stmt::Expr(Expr::Call(ExprCall { func, .. }), _) = stmt {
210+
if let Expr::Path(ExprPath { path: Path { segments, .. }, .. }) = func.as_ref() {
211+
let first_two_idents =
212+
segments.iter().take(2).map(|sgmt| sgmt.ident.to_string()).collect::<Vec<_>>();
213+
214+
if first_two_idents == vec!["kani", check_str] {
215+
pos += 1;
216+
}
217+
}
218+
}
219+
}
220+
stmts.split_at(pos)
221+
}
222+
172223
macro_rules! assert_spanned_err {
173224
($condition:expr, $span_source:expr, $msg:expr, $($args:expr),+) => {
174225
if !$condition {

library/kani_macros/src/sysroot/contracts/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,13 @@ enum ContractConditionsData {
514514
},
515515
}
516516

517+
/// Which function are we currently generating?
518+
#[derive(Copy, Clone, Eq, PartialEq)]
519+
enum ClosureType {
520+
Check,
521+
Replace,
522+
}
523+
517524
impl<'a> ContractConditionsHandler<'a> {
518525
/// Handle the contract state and return the generated code
519526
fn dispatch_on(mut self, state: ContractFunctionState) -> TokenStream2 {

library/kani_macros/src/sysroot/contracts/replace.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use syn::Stmt;
1111
use super::{
1212
helpers::*,
1313
shared::{build_ensures, try_as_result_assign},
14-
ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT,
14+
ClosureType, ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT,
1515
};
1616

1717
impl<'a> ContractConditionsHandler<'a> {
@@ -84,9 +84,13 @@ impl<'a> ContractConditionsHandler<'a> {
8484
ContractConditionsData::Ensures { attr } => {
8585
let (remembers, ensures_clause) = build_ensures(attr);
8686
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
87+
88+
let (asserts, rest_of_before) = split_for_remembers(before, ClosureType::Replace);
89+
8790
quote!({
91+
#(#asserts)*
8892
#remembers
89-
#(#before)*
93+
#(#rest_of_before)*
9094
#(#after)*
9195
kani::assume(#ensures_clause);
9296
#result
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
next\
2+
- Status: SUCCESS\
3+
- Description: "attempt to add with overflow"
4+
5+
VERIFICATION:- SUCCESSFUL
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Zfunction-contracts
4+
5+
// Demonstrate that when the ensures contract is before the requires contract,
6+
// the history expression respects the upper bound on x, so x + 1 doesn't overflow
7+
// This example is taken from https://github.com/model-checking/kani/issues/3359
8+
9+
#[kani::ensures(|result| *result == old(val + 1))]
10+
#[kani::requires(val < i32::MAX)]
11+
pub fn next(val: i32) -> i32 {
12+
val + 1
13+
}
14+
15+
#[kani::proof_for_contract(next)]
16+
pub fn check_next() {
17+
let _ = next(kani::any());
18+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
modify\
2+
- Status: SUCCESS\
3+
- Description: "attempt to add with overflow"
4+
5+
VERIFICATION:- SUCCESSFUL
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Zfunction-contracts
4+
5+
// Demonstrate the the history expression respects preconditions
6+
// with multiple interleaved preconditions, modifies contracts, and history expressions
7+
8+
#[derive(kani::Arbitrary)]
9+
struct Point<X, Y> {
10+
x: X,
11+
y: Y,
12+
}
13+
14+
#[kani::requires(ptr.x < 100)]
15+
#[kani::ensures(|result| old(ptr.x + 1) == ptr.x)]
16+
#[kani::modifies(&mut ptr.x)]
17+
#[kani::ensures(|result| old(ptr.y - 1) == ptr.y)]
18+
#[kani::modifies(&mut ptr.y)]
19+
#[kani::requires(ptr.y > 0)]
20+
fn modify(ptr: &mut Point<u32, u32>) {
21+
ptr.x += 1;
22+
ptr.y -= 1;
23+
}
24+
25+
#[kani::proof_for_contract(modify)]
26+
fn main() {
27+
let mut p: Point<u32, u32> = kani::any();
28+
modify(&mut p);
29+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
next\
2+
- Status: SUCCESS\
3+
- Description: "attempt to add with overflow"
4+
5+
VERIFICATION:- SUCCESSFUL
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Zfunction-contracts
4+
5+
// Demonstrate that when the requires contract is before the ensures contract, the history expression respects the upper bound on x, so x + 1 doesn't overflow
6+
// This example is taken from https://github.com/model-checking/kani/issues/3359
7+
8+
#[kani::requires(val < i32::MAX)]
9+
#[kani::ensures(|result| *result == old(val + 1))]
10+
pub fn next(val: i32) -> i32 {
11+
val + 1
12+
}
13+
14+
#[kani::proof_for_contract(next)]
15+
pub fn check_next() {
16+
let _ = next(kani::any());
17+
}

0 commit comments

Comments
 (0)