Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8a579da
add support or old in while loop inv
thanhnguyen-aws Mar 19, 2025
3bc29ea
Merge branch 'model-checking:main' into loopmodify
thanhnguyen-aws Mar 19, 2025
7520bc3
add expected
thanhnguyen-aws Mar 19, 2025
c393439
add expected
thanhnguyen-aws Mar 19, 2025
8714931
add expected
thanhnguyen-aws Mar 19, 2025
bb7fdff
add more expected tests
thanhnguyen-aws Mar 20, 2025
1c168bd
add clone to assignment, fix expected file name
thanhnguyen-aws Mar 20, 2025
b981fa2
fix expected files and add commnents in tests
thanhnguyen-aws Mar 21, 2025
c4c347d
Merge branch 'model-checking:main' into loopmodify
thanhnguyen-aws Mar 24, 2025
5fc1da1
fix expected files
thanhnguyen-aws Mar 24, 2025
6a30e72
added comments to expain the code
thanhnguyen-aws Mar 24, 2025
6a9e97c
separate continue/break for the case where the loop break on the firs…
thanhnguyen-aws Mar 25, 2025
4338359
fixed expected
thanhnguyen-aws Mar 25, 2025
000c384
fixed expected
thanhnguyen-aws Mar 25, 2025
af11946
Merge branch 'main' into loopmodify
thanhnguyen-aws Mar 27, 2025
7261284
Merge branch 'main' into loopmodify
thanhnguyen-aws Mar 31, 2025
8f7f067
fix some typos
thanhnguyen-aws Apr 1, 2025
35c93fa
Merge branch 'main' into loopmodify
thanhnguyen-aws Apr 1, 2025
95ac221
Merge branch 'main' into loopmodify
thanhnguyen-aws Apr 2, 2025
67d4a96
Merge branch 'main' into loopmodify
thanhnguyen-aws Apr 3, 2025
8bb5b10
update prev semantic
thanhnguyen-aws Apr 3, 2025
8ce1940
Merge branch 'loopmodify' of https://github.com/thanhnguyen-aws/kani …
thanhnguyen-aws Apr 3, 2025
2b80c0a
Merge branch 'main' into loopmodify
thanhnguyen-aws Apr 4, 2025
5737ac0
Merge branch 'main' into loopmodify
thanhnguyen-aws Apr 4, 2025
91fc455
Merge branch 'main' into loopmodify
thanhnguyen-aws Apr 10, 2025
fcd878d
fix comment
thanhnguyen-aws Apr 10, 2025
132723e
separate return statement in loop body
thanhnguyen-aws Apr 10, 2025
609c388
fix format
thanhnguyen-aws Apr 11, 2025
28c9d17
Merge branch 'main' into loopmodify
thanhnguyen-aws Apr 11, 2025
747d2a3
fix typo
thanhnguyen-aws Apr 11, 2025
a7f833a
Merge branch 'loopmodify' of https://github.com/thanhnguyen-aws/kani …
thanhnguyen-aws Apr 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 199 additions & 5 deletions library/kani_macros/src/sysroot/loop_contracts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,135 @@ use proc_macro_error2::abort_call_site;
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::token::AndAnd;
use syn::{BinOp, Expr, ExprBinary, Stmt};
use syn::{BinOp, Block, Expr, ExprBinary, Ident, Stmt, visit_mut::VisitMut};

struct TransformationResult {
transformed_expr: Expr,
declarations_block: Block,
assignments_block: Block,
}

struct CallReplacer {
old_name: String,
replacements: Vec<(Expr, proc_macro2::Ident)>,
counter: usize,
var_prefix: String,
}

impl CallReplacer {
fn new(old_name: &str, var_prefix: String) -> Self {
Self {
old_name: old_name.to_string(),
replacements: Vec::new(),
counter: 0,
var_prefix: var_prefix,
}
}

fn generate_var_name(&mut self) -> proc_macro2::Ident {
let var_name = format_ident!("{}_{}", self.var_prefix, self.counter);
self.counter += 1;
var_name
}

fn should_replace(&self, expr_path: &syn::ExprPath) -> bool {
// Check both simple and qualified paths
if let Some(last_segment) = expr_path.path.segments.last() {
if last_segment.ident == self.old_name {
return true;
}
}

let full_path = expr_path
.path
.segments
.iter()
.map(|seg| seg.ident.to_string())
.collect::<Vec<_>>()
.join("::");

full_path.ends_with(&self.old_name)
}
}

impl VisitMut for CallReplacer {
fn visit_expr_mut(&mut self, expr: &mut Expr) {
// Visit nested expressions first
syn::visit_mut::visit_expr_mut(self, expr);

if let Expr::Call(call) = expr {
if let Expr::Path(expr_path) = &*call.func {
if self.should_replace(expr_path) {
let new_var = self.generate_var_name();
self.replacements.push((expr.clone(), new_var.clone()));
*expr = syn::parse_quote!(#new_var);
}
}
}
}
}

fn transform_function_calls(
expr: Expr,
function_name: &str,
var_prefix: String,
) -> TransformationResult {
let mut replacer = CallReplacer::new(function_name, var_prefix);
let mut transformed_expr = expr;
replacer.visit_expr_mut(&mut transformed_expr);

let mut newreplace: Vec<(Expr, Ident)> = Vec::new();
for (call, var) in replacer.replacements {
match call {
Expr::Call(call_expr) => {
let insideexpr = call_expr.args[0].clone();
newreplace.push((insideexpr, var.clone()));
}
_ => {}
}
}

// Generate declarations block
let declarations: Vec<Stmt> =
newreplace.iter().map(|(call, var)| syn::parse_quote!(let mut #var = #call;)).collect();
let declarations_block: Block = syn::parse_quote!({
#(#declarations)*
});

// Generate assignments block
let assignments: Vec<Stmt> =
newreplace.into_iter().map(|(call, var)| syn::parse_quote!(#var = #call;)).collect();
let assignments_block: Block = syn::parse_quote!({
#(#assignments)*
});

TransformationResult { transformed_expr, declarations_block, assignments_block }
}

struct BreakContinueReplacer;

impl VisitMut for BreakContinueReplacer {
fn visit_expr_mut(&mut self, expr: &mut Expr) {
// Visit nested expressions first
syn::visit_mut::visit_expr_mut(self, expr);

// Replace the expression
*expr = match expr {
Expr::Break(_) => {
syn::parse_quote!(return)
}
Expr::Continue(_) => {
syn::parse_quote!(return)
}
_ => return,
};
}
}

fn transform_break_continue(block: &mut Block) {
let mut replacer = BreakContinueReplacer;
replacer.visit_block_mut(block);
}

/// Expand loop contracts macros.
///
Expand Down Expand Up @@ -41,7 +169,47 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
inv_name.push_str(&loop_id);

// expr of the loop invariant
let inv_expr: Expr = syn::parse(attr).unwrap();
let mut inv_expr: Expr = syn::parse(attr).unwrap();

// adding old variables
let mut old_var_prefix: String = "__kani_old_var".to_owned();
old_var_prefix.push_str(&loop_id);
let replace_old: TransformationResult =
transform_function_calls(inv_expr.clone(), "old", old_var_prefix);
inv_expr = replace_old.transformed_expr.clone();
let old_decl_stms = replace_old.declarations_block.stmts.clone();

// adding prev variables
let mut prev_var_prefix: String = "__kani_prev_var".to_owned();
prev_var_prefix.push_str(&loop_id);
let transform_inv: TransformationResult =
transform_function_calls(inv_expr.clone(), "prev", prev_var_prefix);
let has_prev = !transform_inv.declarations_block.stmts.is_empty();
let decl_stms = transform_inv.declarations_block.stmts.clone();
let mut assign_stms = transform_inv.assignments_block.stmts.clone();
let (mut loop_body, loop_guard) = match loop_stmt {
Stmt::Expr(ref mut e, _) => match e {
Expr::While(ew) => (ew.body.clone(), ew.cond.clone()),
_ => panic!(),
},
_ => panic!(),
};
let loop_body_stms = loop_body.stmts.clone();
assign_stms.extend(loop_body_stms);
transform_break_continue(&mut loop_body);
let mut loop_body_closure_name: String = "__kani_loop_body_closure".to_owned();
loop_body_closure_name.push_str(&loop_id);
let loop_body_closure = format_ident!("{}", loop_body_closure_name);
if has_prev {
inv_expr = transform_inv.transformed_expr.clone();
match loop_stmt {
Stmt::Expr(ref mut e, _) => match e {
Expr::While(ew) => ew.body.stmts = assign_stms.clone(),
_ => panic!(),
},
_ => panic!(),
};
}

// ident of the register function
let mut register_name: String = "kani_register_loop_contract".to_owned();
Expand Down Expand Up @@ -74,8 +242,32 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
note = "for now, loop contracts is only supported for while-loops.";
),
}
quote!(

if has_prev {
quote!(
{
#(#old_decl_stms)*
assert!(#loop_guard);
#(#decl_stms)*
let mut #loop_body_closure = ||
#loop_body;
#loop_body_closure ();
assert!(#inv_expr);
// Dummy function used to force the compiler to capture the environment.
// We cannot call closures inside constant functions.
// This function gets replaced by `kani::internal::call_closure`.
#[inline(never)]
#[kanitool::fn_marker = "kani_register_loop_contract"]
const fn #register_ident<F: Fn() -> bool>(_f: &F, _transformed: usize) -> bool {
true
}
#loop_stmt
})
.into()
} else {
quote!(
{
#(#old_decl_stms)*
// Dummy function used to force the compiler to capture the environment.
// We cannot call closures inside constant functions.
// This function gets replaced by `kani::internal::call_closure`.
Expand All @@ -84,8 +276,10 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
const fn #register_ident<F: Fn() -> bool>(_f: &F, _transformed: usize) -> bool {
true
}
#loop_stmt})
.into()
#loop_stmt
})
.into()
}
}

fn generate_unique_id_from_span(stmt: &Stmt) -> String {
Expand Down
1 change: 1 addition & 0 deletions tests/expected/loop-contract/loop_with_old.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VERIFICATION:- SUCCESSFUL
24 changes: 24 additions & 0 deletions tests/expected/loop-contract/loop_with_old.rs
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: -Z loop-contracts

//! Check if loop contracts is correctly applied.

#![feature(stmt_expr_attributes)]
#![feature(proc_macro_hygiene)]

#[kani::proof]
pub fn loop_with_old() {
let mut x: u8 = kani::any_where(|v| *v < 10);
let mut y: u8 = kani::any();
let mut i = 0;
#[kani::loop_invariant( (i<=5) && (x <= old(x) + i) && (old(x) + i == old(i) + x))]
while i < 5 {
if i == 0 {
y = x
}
x += 1;
i += 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VERIFICATION:- SUCCESSFUL
22 changes: 22 additions & 0 deletions tests/expected/loop-contract/loop_with_old_and_prev.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT

// kani-flags: -Z loop-contracts

//! Check if loop contracts is correctly applied.

#![feature(stmt_expr_attributes)]
#![feature(proc_macro_hygiene)]

#[kani::proof]
pub fn loop_with_old_and_prev() {
let mut i = 100;
#[kani::loop_invariant((i >= 2) && (i <= 100) && (i % 2 == 0) && (old(i) == 100) && (prev(i) == i + 2))]
while i > 2 {
if i == 1 {
break;
}
i = i - 2;
}
assert!(i == 2);
}
24 changes: 24 additions & 0 deletions tests/expected/loop-contract/loop_with_prev.rs
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: -Z loop-contracts

//! Check if loop contracts is correctly applied.

#![feature(stmt_expr_attributes)]
#![feature(proc_macro_hygiene)]

#[kani::proof]
pub fn loop_with_prev() {
let mut i = 100;
let mut j = 100;
#[kani::loop_invariant((i >= 2) && (i <= 100) && (i % 2 == 0) && (j == 2*i-100) && (prev(i) == i + 2) && (prev(j) == j + 4) && (prev(i-j) == i-j-2) )]
while i > 2 {
if i == 1 {
break;
}
i = i - 2;
j = j - 4
}
assert!(i == 2);
}
1 change: 1 addition & 0 deletions tests/expected/loop-contract/loop_with_previous.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VERIFICATION:- SUCCESSFUL
Loading