Skip to content

Commit c87c7d0

Browse files
Add support for loop-contract historic values (#3951)
Add support for `old` and `prev` to refers to historic values in loop contracts where - `old(expr)` refers to the value of the expr before the loop. - `prev(expr)` refers to the value of the expr at the previous iteration. Resolves #3697 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 e2d9c3b commit c87c7d0

File tree

9 files changed

+406
-5
lines changed

9 files changed

+406
-5
lines changed

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

Lines changed: 295 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,72 @@ use proc_macro_error2::abort_call_site;
99
use quote::{format_ident, quote};
1010
use syn::spanned::Spanned;
1111
use syn::token::AndAnd;
12-
use syn::{BinOp, Expr, ExprBinary, Stmt};
12+
use syn::{BinOp, Block, Expr, ExprBinary, Ident, Stmt, visit_mut::VisitMut};
1313

14+
/*
15+
Transform the loop to support on_entry(expr) : the value of expr before entering the loop
16+
1. For each on_entry(expr) in the loop variant, replace it with a newly generated "memory" variable old_k
17+
2. Add the declaration of i before the loop: let old_k = expr
18+
For example:
19+
#[kani::loop_invariant(on_entry(x+y) = x + y -1)]
20+
while(....)
21+
22+
is transformed into
23+
let old_1 = x + y
24+
#[kani::loop_invariant(old_1 = x + y -1)]
25+
while(....)
26+
27+
Then the loop_invartiant is transformed
28+
29+
*/
30+
31+
/*
32+
Transform the loop to support prev(expr) : the value of expr at the end of the previous iteration
33+
Semantic: If the loop has at least 1 iteration: prev(expr) is the value of expr at the end of the previous iteration. Otherwise, just remove the loop (without check for the invariant too).
34+
35+
Transformation: basically, if the loop has at least 1 iteration (loop_quard is satisfied at the beginning), we unfold the loop once, declare the variables for prev values and update them at the beginning of the loop body.
36+
Otherwise, we remove the loop.
37+
If there is a prev(expr) in the loop_invariant:
38+
1. Firstly, add an if block whose condition is the loop_quard, inside its body add/do the followings:
39+
2. For each prev(expr) in the loop variant, replace it with a newly generated "memory" variable prev_k
40+
3. Add the declaration of prev_k before the loop: let mut prev_k = expr
41+
4. Define a mut closure whose body is exactly the loop body, but replace all continue/break statements with return true/false statements,
42+
then add a final return true statement at the end of it
43+
5. Add an if statement with condition to be the that closure's call (the same as run the loop once):
44+
True block: add the loop with expanded macros (see next section) and inside the loop body:
45+
add the assignment statements (exactly the same as the declarations without the "let mut") on the top to update the "memory" variables
46+
Else block: Add the assertion for the loop_invariant (not includes the loop_quard): check if the loop_invariant holds after the first iteration.
47+
48+
For example:
49+
#[kani::loop_invariant(prev(x+y) = x + y -1 && ...)]
50+
while(loop_guard)
51+
{
52+
loop_body
53+
}
54+
55+
is transformed into
56+
57+
assert!(loop_guard);
58+
let mut prev_1 = x + y;
59+
let mut loop_body_closure = || {
60+
loop_body_replaced //replace breaks/continues in loop_body with returns
61+
};
62+
if loop_body_closure(){
63+
#[kani::loop_invariant(prev_1 = x + y -1)]
64+
while(loop_guard)
65+
{
66+
prev_1 = x + y;
67+
loop_body
68+
}
69+
}
70+
else{
71+
assert!(prev_1 = x + y -1 && ...);
72+
}
73+
74+
75+
*/
76+
77+
/// After that:
1478
/// Expand loop contracts macros.
1579
///
1680
/// A while loop of the form
@@ -30,6 +94,152 @@ use syn::{BinOp, Expr, ExprBinary, Stmt};
3094
/// body
3195
/// }
3296
/// ```
97+
98+
struct TransformationResult {
99+
transformed_expr: Expr,
100+
declarations_block: Block,
101+
assignments_block: Block,
102+
}
103+
104+
struct CallReplacer {
105+
old_name: String,
106+
replacements: Vec<(Expr, proc_macro2::Ident)>,
107+
counter: usize,
108+
var_prefix: String,
109+
}
110+
111+
// This impl replaces any function call of a function name : old_name with a newly generated variable.
112+
impl CallReplacer {
113+
fn new(old_name: &str, var_prefix: String) -> Self {
114+
Self {
115+
old_name: old_name.to_string(),
116+
replacements: Vec::new(),
117+
counter: 0,
118+
var_prefix: var_prefix,
119+
}
120+
}
121+
122+
fn generate_var_name(&mut self) -> proc_macro2::Ident {
123+
let var_name = format_ident!("{}_{}", self.var_prefix, self.counter);
124+
self.counter += 1;
125+
var_name
126+
}
127+
128+
//Check if the function name is old_name
129+
fn should_replace(&self, expr_path: &syn::ExprPath) -> bool {
130+
let full_path = expr_path
131+
.path
132+
.segments
133+
.iter()
134+
.map(|seg| seg.ident.to_string())
135+
.collect::<Vec<_>>()
136+
.join("::");
137+
138+
full_path == self.old_name
139+
}
140+
}
141+
142+
impl VisitMut for CallReplacer {
143+
fn visit_expr_mut(&mut self, expr: &mut Expr) {
144+
// Visit nested expressions first
145+
syn::visit_mut::visit_expr_mut(self, expr);
146+
147+
if let Expr::Call(call) = expr {
148+
if let Expr::Path(expr_path) = &*call.func {
149+
if self.should_replace(expr_path) {
150+
let new_var = self.generate_var_name();
151+
self.replacements.push((expr.clone(), new_var.clone()));
152+
*expr = syn::parse_quote!(#new_var);
153+
}
154+
}
155+
}
156+
}
157+
}
158+
159+
// The main function to replace the function call with the variables
160+
fn transform_function_calls(
161+
expr: Expr,
162+
function_name: &str,
163+
var_prefix: String,
164+
) -> TransformationResult {
165+
let mut replacer = CallReplacer::new(function_name, var_prefix);
166+
let mut transformed_expr = expr;
167+
replacer.visit_expr_mut(&mut transformed_expr);
168+
169+
let mut newreplace: Vec<(Expr, Ident)> = Vec::new();
170+
for (call, var) in replacer.replacements {
171+
match call {
172+
Expr::Call(call_expr) => {
173+
let insideexpr = call_expr.args[0].clone();
174+
newreplace.push((insideexpr, var.clone()));
175+
}
176+
_ => {}
177+
}
178+
}
179+
180+
// Generate declarations block of the newly generated variables (will added before the loop)
181+
let declarations: Vec<Stmt> = newreplace
182+
.iter()
183+
.map(|(call, var)| syn::parse_quote!(let mut #var = #call.clone();))
184+
.collect();
185+
let declarations_block: Block = syn::parse_quote!({
186+
#(#declarations)*
187+
});
188+
189+
// Generate declarations block of the newly generated variables (will be added on the loop of the loop body)
190+
let assignments: Vec<Stmt> = newreplace
191+
.into_iter()
192+
.map(|(call, var)| syn::parse_quote!(#var = #call.clone();))
193+
.collect();
194+
let assignments_block: Block = syn::parse_quote!({
195+
#(#assignments)*
196+
});
197+
198+
TransformationResult { transformed_expr, declarations_block, assignments_block }
199+
}
200+
201+
struct BreakContinueReplacer;
202+
203+
impl VisitMut for BreakContinueReplacer {
204+
fn visit_expr_mut(&mut self, expr: &mut Expr) {
205+
// Visit nested expressions first
206+
syn::visit_mut::visit_expr_mut(self, expr);
207+
208+
// Replace the expression
209+
*expr = match expr {
210+
Expr::Break(_) => {
211+
syn::parse_quote!(return (false, None))
212+
}
213+
Expr::Continue(_) => {
214+
syn::parse_quote!(return (true, None))
215+
}
216+
Expr::Return(rexpr) => match rexpr.expr.clone() {
217+
Some(ret) => syn::parse_quote!(return (false, Some(#ret))),
218+
_ => syn::parse_quote!(return (false, Some(()))),
219+
},
220+
_ => return,
221+
};
222+
}
223+
}
224+
225+
// This function replace the break/continue statements inside a loop body with return statements
226+
fn transform_break_continue(block: &mut Block) {
227+
let mut replacer = BreakContinueReplacer;
228+
replacer.visit_block_mut(block);
229+
let return_stmt: Stmt = syn::parse_quote! {
230+
return (true, None);
231+
};
232+
// Add semicolon to the last statement if it's an expression without semicolon
233+
if let Some(last_stmt) = block.stmts.last_mut() {
234+
if let Stmt::Expr(expr, ref mut semi) = last_stmt {
235+
if semi.is_none() {
236+
*semi = Some(Default::default());
237+
}
238+
}
239+
}
240+
block.stmts.push(return_stmt);
241+
}
242+
33243
pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
34244
// parse the stmt of the loop
35245
let mut loop_stmt: Stmt = syn::parse(item.clone()).unwrap();
@@ -41,7 +251,53 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
41251
inv_name.push_str(&loop_id);
42252

43253
// expr of the loop invariant
44-
let inv_expr: Expr = syn::parse(attr).unwrap();
254+
let mut inv_expr: Expr = syn::parse(attr).unwrap();
255+
256+
// adding on_entry variables
257+
let mut onentry_var_prefix: String = "__kani_onentry_var".to_owned();
258+
onentry_var_prefix.push_str(&loop_id);
259+
let replace_onentry: TransformationResult =
260+
transform_function_calls(inv_expr.clone(), "on_entry", onentry_var_prefix);
261+
inv_expr = replace_onentry.transformed_expr.clone();
262+
let onentry_decl_stms = replace_onentry.declarations_block.stmts.clone();
263+
264+
// adding prev variables
265+
let mut prev_var_prefix: String = "__kani_prev_var".to_owned();
266+
prev_var_prefix.push_str(&loop_id);
267+
let transform_inv: TransformationResult =
268+
transform_function_calls(inv_expr.clone(), "prev", prev_var_prefix);
269+
let has_prev = !transform_inv.declarations_block.stmts.is_empty();
270+
let prev_decl_stms = transform_inv.declarations_block.stmts.clone();
271+
let mut assign_stms = transform_inv.assignments_block.stmts.clone();
272+
let (mut loop_body, loop_guard) = match loop_stmt {
273+
Stmt::Expr(ref mut e, _) => match e {
274+
Expr::While(ew) => (ew.body.clone(), ew.cond.clone()),
275+
_ => panic!(),
276+
},
277+
_ => panic!(),
278+
};
279+
let loop_body_stms = loop_body.stmts.clone();
280+
assign_stms.extend(loop_body_stms);
281+
transform_break_continue(&mut loop_body);
282+
let mut loop_body_closure_name: String = "__kani_loop_body_closure".to_owned();
283+
loop_body_closure_name.push_str(&loop_id);
284+
let loop_body_closure = format_ident!("{}", loop_body_closure_name);
285+
let mut loop_body_closure_ret_1_name: String = "__kani_loop_body_closure_ret_1".to_owned();
286+
loop_body_closure_ret_1_name.push_str(&loop_id);
287+
let loop_body_closure_ret_1 = format_ident!("{}", loop_body_closure_ret_1_name);
288+
let mut loop_body_closure_ret_2_name: String = "__kani_loop_body_closure_ret_2".to_owned();
289+
loop_body_closure_ret_2_name.push_str(&loop_id);
290+
let loop_body_closure_ret_2 = format_ident!("{}", loop_body_closure_ret_2_name);
291+
if has_prev {
292+
inv_expr = transform_inv.transformed_expr.clone();
293+
match loop_stmt {
294+
Stmt::Expr(ref mut e, _) => match e {
295+
Expr::While(ew) => ew.body.stmts = assign_stms.clone(),
296+
_ => panic!(),
297+
},
298+
_ => panic!(),
299+
};
300+
}
45301

46302
// ident of the register function
47303
let mut register_name: String = "kani_register_loop_contract".to_owned();
@@ -74,8 +330,40 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
74330
note = "for now, loop contracts is only supported for while-loops.";
75331
),
76332
}
77-
quote!(
333+
334+
if has_prev {
335+
quote!(
78336
{
337+
if (#loop_guard) {
338+
#(#onentry_decl_stms)*
339+
#(#prev_decl_stms)*
340+
let mut #loop_body_closure = ||
341+
#loop_body;
342+
let (#loop_body_closure_ret_1, #loop_body_closure_ret_2) = #loop_body_closure ();
343+
if #loop_body_closure_ret_2.is_some() {
344+
return #loop_body_closure_ret_2.unwrap();
345+
}
346+
if #loop_body_closure_ret_1 {
347+
// Dummy function used to force the compiler to capture the environment.
348+
// We cannot call closures inside constant functions.
349+
// This function gets replaced by `kani::internal::call_closure`.
350+
#[inline(never)]
351+
#[kanitool::fn_marker = "kani_register_loop_contract"]
352+
const fn #register_ident<F: Fn() -> bool>(_f: &F, _transformed: usize) -> bool {
353+
true
354+
}
355+
#loop_stmt
356+
}
357+
else {
358+
assert!(#inv_expr);
359+
};
360+
}
361+
})
362+
.into()
363+
} else {
364+
quote!(
365+
{
366+
#(#onentry_decl_stms)*
79367
// Dummy function used to force the compiler to capture the environment.
80368
// We cannot call closures inside constant functions.
81369
// This function gets replaced by `kani::internal::call_closure`.
@@ -84,8 +372,10 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
84372
const fn #register_ident<F: Fn() -> bool>(_f: &F, _transformed: usize) -> bool {
85373
true
86374
}
87-
#loop_stmt})
88-
.into()
375+
#loop_stmt
376+
})
377+
.into()
378+
}
89379
}
90380

91381
fn generate_unique_id_from_span(stmt: &Stmt) -> String {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
loop_with_old.loop_invariant_base.1\
2+
- Status: SUCCESS\
3+
- Description: "Check invariant before entry for loop loop_with_old.0"
4+
5+
VERIFICATION:- SUCCESSFUL
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// kani-flags: -Z loop-contracts
5+
6+
//! Check the use of "old" in loop invariant
7+
8+
#![feature(stmt_expr_attributes)]
9+
#![feature(proc_macro_hygiene)]
10+
11+
#[kani::proof]
12+
pub fn loop_with_old() {
13+
let mut x: u8 = kani::any_where(|v| *v < 10);
14+
let mut y: u8 = kani::any();
15+
let mut i = 0;
16+
#[kani::loop_invariant( (i<=5) && (x <= on_entry(x) + i) && (on_entry(x) + i == on_entry(i) + x))]
17+
while i < 5 {
18+
if i == 0 {
19+
y = x
20+
}
21+
x += 1;
22+
i += 1;
23+
}
24+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
loop_with_old_and_prev.loop_invariant_base.1\
2+
- Status: SUCCESS\
3+
- Description: "Check invariant before entry for loop loop_with_old_and_prev.0"\
4+
5+
6+
VERIFICATION:- SUCCESSFUL
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// kani-flags: -Z loop-contracts
5+
6+
//! Check the use of both "old" and "prev" in loop invariant
7+
8+
#![feature(stmt_expr_attributes)]
9+
#![feature(proc_macro_hygiene)]
10+
11+
#[kani::proof]
12+
pub fn loop_with_old_and_prev() {
13+
let mut i = 100;
14+
#[kani::loop_invariant((i >= 2) && (i <= 100) && (i % 2 == 0) && (on_entry(i) == 100) && (prev(i) == i + 2))]
15+
while i > 2 {
16+
if i == 1 {
17+
break;
18+
}
19+
i = i - 2;
20+
}
21+
assert!(i == 2);
22+
}

0 commit comments

Comments
 (0)