Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
ded56d8
add for loop transformation
thanhnguyen-aws Jun 18, 2025
2f782bf
add support for array, slice and Iter + expected tests
thanhnguyen-aws Jun 18, 2025
d0a902e
add support for Range, StepBy + expected tests
thanhnguyen-aws Jun 18, 2025
32e978c
add support for Vec + expected tests
thanhnguyen-aws Jun 18, 2025
8529488
add expected tests for quantifiers and tuples
thanhnguyen-aws Jun 18, 2025
d028bb0
add support for Zip, Chain, Map + expected tests
thanhnguyen-aws Jun 18, 2025
3fc2abc
add support for Enumerate + expected tests
thanhnguyen-aws Jun 18, 2025
8e69667
edit loop-contract docs
thanhnguyen-aws Jun 18, 2025
746311a
edit doc and some typos
thanhnguyen-aws Jun 19, 2025
475718e
Merge branch 'main' into forloopnew1
Jun 24, 2025
09f5dcc
merge origin
thanhnguyen-aws Jul 1, 2025
ded103c
restore mod.rs after merge
thanhnguyen-aws Jul 1, 2025
418f4f3
Merge branch 'main' into forloopnew1
thanhnguyen-aws Jul 14, 2025
2712069
handling StorageLive/StorageDead for forloop variables
thanhnguyen-aws Jul 17, 2025
da1666d
add expected tests for nested forloop
thanhnguyen-aws Jul 17, 2025
f9a3c2f
fix clippy
thanhnguyen-aws Jul 17, 2025
f8e2232
Merge branch 'main' into forloopnew1
thanhnguyen-aws Jul 18, 2025
8eb51e6
add for-loop rewrite method in loop-contract rfc
thanhnguyen-aws Jul 18, 2025
b4f470d
Merge branch 'forloopnew1' of https://github.com/thanhnguyen-aws/kani…
thanhnguyen-aws Jul 18, 2025
6c3e159
Update docs/src/reference/experimental/loop-contracts.md
thanhnguyen-aws Jul 22, 2025
fa5436e
Merge branch 'main' into forloopnew1
thanhnguyen-aws Jul 22, 2025
ceaed0b
add more docs
thanhnguyen-aws Jul 23, 2025
cbcc7c3
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Jul 25, 2025
8618f55
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Jul 25, 2025
42e6fc6
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Jul 25, 2025
1e4ffdd
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Jul 28, 2025
bb3f439
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Jul 28, 2025
b716b97
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Jul 28, 2025
e67148d
Update docs/src/reference/experimental/loop-contracts.md
thanhnguyen-aws Jul 28, 2025
860430f
Update docs/src/reference/experimental/loop-contracts.md
thanhnguyen-aws Jul 28, 2025
64833dd
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Jul 28, 2025
202880e
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Jul 28, 2025
57950a5
add suuport for Take
thanhnguyen-aws Jul 31, 2025
4fc4402
fix the bug of using reference in for-loop pat
thanhnguyen-aws Jul 31, 2025
6cf3afc
gerge branch 'forloopnew1' of https://github.com/thanhnguyen-aws/kani…
thanhnguyen-aws Jul 31, 2025
8bc3f59
update upstream
thanhnguyen-aws Jul 31, 2025
dfbf9eb
fix import
thanhnguyen-aws Jul 31, 2025
cf41994
change kaniindex to kani_index + kaniiterlen to kani_iter_len
thanhnguyen-aws Aug 1, 2025
9b7f100
change loop-invariant to loop invariant
thanhnguyen-aws Aug 1, 2025
2d71127
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Aug 1, 2025
12b0fda
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Aug 1, 2025
2ec15b4
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Aug 1, 2025
a837f80
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Aug 1, 2025
558460b
change kani_index to kani::index and kani_iter_len to kani::iter_len
thanhnguyen-aws Aug 8, 2025
6926302
unchange loop contracts to loop invariants
thanhnguyen-aws Aug 8, 2025
969b414
add replacer for macro
thanhnguyen-aws Aug 8, 2025
0948b90
Merge remote-tracking branch 'upstream/main' into forloopnew1
thanhnguyen-aws Aug 8, 2025
94736ac
merge while-let loop
thanhnguyen-aws Aug 8, 2025
b28f1d5
fix clippy
thanhnguyen-aws Aug 8, 2025
0d1d2d2
add KaniIntoIter impl for Rev
thanhnguyen-aws Aug 11, 2025
8f7410b
fix variable name & indentation
thanhnguyen-aws Aug 11, 2025
333eeab
Update docs/src/reference/experimental/loop-contracts.md
thanhnguyen-aws Aug 11, 2025
fd2945d
Update docs/src/reference/experimental/loop-contracts.md
thanhnguyen-aws Aug 11, 2025
edf339d
Update docs/src/reference/experimental/loop-contracts.md
thanhnguyen-aws Aug 11, 2025
d1eee1f
Update rfc/src/rfcs/0012-loop-contracts.md
thanhnguyen-aws Aug 11, 2025
da30387
Merge branch 'forloopnew1' of https://github.com/thanhnguyen-aws/kani…
thanhnguyen-aws Aug 11, 2025
a121b1b
add underscores to function name
thanhnguyen-aws Aug 11, 2025
222f89c
fix typo comments
thanhnguyen-aws Aug 11, 2025
149674d
add impl KaniIter for u128 and i128 for Range
thanhnguyen-aws Aug 11, 2025
cff691a
change function name indexing to nth
thanhnguyen-aws Aug 11, 2025
b22d152
edit for-loop docs
thanhnguyen-aws Aug 12, 2025
1299e5a
add Rod Chapman example as fixme expected test
thanhnguyen-aws Aug 12, 2025
259fcf8
Update docs/src/reference/experimental/loop-contracts.md
thanhnguyen-aws Aug 13, 2025
d7b3f0a
Update docs/src/reference/experimental/loop-contracts.md
thanhnguyen-aws Aug 13, 2025
fcb696d
fix typo mistakes in doc
thanhnguyen-aws Aug 13, 2025
7d9257a
Update library/kani_macros/src/sysroot/loop_contracts/mod.rs
thanhnguyen-aws Aug 13, 2025
97aa688
add KaniIter impl for Range of i128 and u128 + remove comments
thanhnguyen-aws Aug 13, 2025
3dd9587
Merge branch 'forloopnew1' of https://github.com/thanhnguyen-aws/kani…
thanhnguyen-aws Aug 13, 2025
f90cb1a
remove comments + fix typos
thanhnguyen-aws Aug 13, 2025
0f9f59d
remove unneccessary mut
thanhnguyen-aws Aug 13, 2025
2edf3c7
fix typo
thanhnguyen-aws Aug 13, 2025
7e03b83
fix indentation
thanhnguyen-aws Aug 13, 2025
b4bffda
fix comments
thanhnguyen-aws Aug 13, 2025
80e8822
change traitbound for KaniRefIter & KaniPtrIter
thanhnguyen-aws Aug 13, 2025
a77f0cd
fix arr_inc test
thanhnguyen-aws Aug 13, 2025
1b613e9
Merge branch 'main' into forloopnew1
thanhnguyen-aws Aug 14, 2025
7a7e158
remove array clone in arr_inc test
thanhnguyen-aws Aug 14, 2025
b2d3758
Merge branch 'forloopnew1' of https://github.com/thanhnguyen-aws/kani…
thanhnguyen-aws Aug 14, 2025
1f99e4c
Update docs/src/reference/experimental/loop-contracts.md
thanhnguyen-aws Aug 14, 2025
67b38c1
fix typo mistakes
thanhnguyen-aws Aug 14, 2025
afc7322
revert loop contract -> loop invariant
thanhnguyen-aws Aug 15, 2025
c4915cc
fix typo
thanhnguyen-aws Aug 15, 2025
10db6f2
Merge branch 'main' into forloopnew1
thanhnguyen-aws Aug 19, 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
211 changes: 204 additions & 7 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,9 @@ use proc_macro_error2::abort_call_site;
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::token::AndAnd;
use syn::{BinOp, Block, Expr, ExprBinary, Ident, Stmt, parse_quote, visit_mut::VisitMut};
use syn::{
BinOp, Block, Expr, ExprBinary, ExprForLoop, Ident, Stmt, parse_quote, visit_mut::VisitMut,
};

/*
Transform the loop to support on_entry(expr) : the value of expr before entering the loop
Expand Down Expand Up @@ -230,18 +232,147 @@ fn transform_break_continue(block: &mut Block) {
block.stmts.push(return_stmt);
}

/*
`for` loop implementation:
A for loop of the form
``` rust
for pat in expr {
body
}
```
is transformed into a `while` loop:

``` rust
let kaniiter = kani::KaniIntoIter::kani_into_iter(expr); \\ init_iter_stmt
let kaniiterlen = kani::KaniIter::len(&kaniiter); \\ init_len_stmt
if kaniiterlen > 0 {
let mut kaniindex = 0; \\ init_index_stmt
kani::assume (kani::KaniIter::assumption(&kaniiter)); \\ iter_assume_stmt
let pat = kani::KaniIter::first(&kaniiter); \\ init_pat_stmt
while kaniindex < kaniiterlen {
kani::assume (kani::KaniIter::assumption(&kaniiter)); \\ iter_assume_stmt
let pat = kani::KaniIter::indexing(&kaniiter, kaniindex); \\ pat_assign_stmt
kaniindex = kaniindex + 1; \\ increase_iter_stmt
body
}
}
```
Note that

1. expr's type is required to impl KaniIntoIter trait, which is the override impl of Rust's IntoIter (see library/kani_core/src/iter.rs).
Reason:
a) Reduce stack-call
b) Avoid types that cannot be havoc effectively (user cannot state the type invariant in the loop invariant due to private fields)
c) There is no generic way to handle Rust's into_iter().

2. The init_index_stmt and init_pat_stmt statements supports writing the loop-invariant properties that involve pat and kaniindex

3. The iter_assume_stmt assumes some truths about allocation, so that the user does not neet to specify them in the loop invariant
*/

pub struct ForLoopExtraStmts {
init_iter_stmt: Stmt,
init_len_stmt: Stmt,
init_index_stmt: Stmt,
init_pat_stmt: Stmt,
iter_assume_stmt: Stmt,
}

pub fn transform_for_to_loop(
for_loop: ExprForLoop,
loop_id: &str,
) -> (Stmt, Option<ForLoopExtraStmts>) {
// Extract components from the for loop
let pat = *for_loop.pat;
let expr = for_loop.expr;
let body = for_loop.body;

// Create an iterator variable name
let indexname = "kaniindex".to_owned();
let index_ident = format_ident!("{}", indexname);

let mut itername = "kaniiter".to_owned();
itername.push_str(loop_id);
let iter_ident = format_ident!("{}", itername);

let lenname = "kaniiterlen".to_owned();
let len_ident = format_ident!("{}", lenname);

// Create initialization statement for the iterator
let init_iter_stmt: Stmt = parse_quote! {
let #iter_ident = kani::KaniIntoIter::kani_into_iter(#expr);
};

let init_index_stmt: Stmt = parse_quote! {
let mut #index_ident = 0;
};

let init_len_stmt: Stmt = parse_quote! {
let #len_ident = kani::KaniIter::len(&#iter_ident);
};

let init_pat_stmt: Stmt = parse_quote! {
let #pat = kani::KaniIter::first(&#iter_ident);
};

// Create the new loop body with iterator advancement
let mut new_body_stmts = Vec::new();

let iter_assume_stmt: Stmt = parse_quote! {
kani::assume (kani::KaniIter::assumption(&#iter_ident));
};

// Increase the iter
let increase_iter_stmt: Stmt = parse_quote! {
#index_ident = #index_ident + 1;
};

let pat_assign_stmt: Stmt = parse_quote! {
let #pat = kani::KaniIter::indexing(&#iter_ident, #index_ident);
};

new_body_stmts.push(iter_assume_stmt.clone());
new_body_stmts.push(pat_assign_stmt);
new_body_stmts.push(increase_iter_stmt);

// Add the original loop body statements
new_body_stmts.extend(body.stmts.iter().cloned());

// Create the final expression with the iterator initialization
let loop_loop: Stmt = parse_quote! {
while (#index_ident < #len_ident) {
#(#new_body_stmts)*
}
};
let for_loop_extras = ForLoopExtraStmts {
init_iter_stmt,
init_len_stmt,
init_index_stmt,
init_pat_stmt,
iter_assume_stmt,
};

(loop_loop, Some(for_loop_extras))
}

pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
// parse the stmt of the loop
let mut loop_stmt: Stmt = syn::parse(item.clone()).unwrap();

let loop_id = generate_unique_id_from_span(&loop_stmt);
let mut forloopextras: Option<ForLoopExtraStmts> = None;
if let Stmt::Expr(Expr::ForLoop(for_loop), _) = &loop_stmt {
(loop_stmt, forloopextras) = transform_for_to_loop(for_loop.clone(), &loop_id);
}
// name of the loop invariant as closure of the form
// __kani_loop_invariant_#startline_#startcol_#endline_#endcol
let mut inv_name: String = "__kani_loop_invariant".to_owned();
let loop_id = generate_unique_id_from_span(&loop_stmt);
inv_name.push_str(&loop_id);

// expr of the loop invariant
let mut inv_expr: Expr = syn::parse(attr).unwrap();
if forloopextras.is_some() {
inv_expr = parse_quote! { kaniindex <= kaniiterlen && #inv_expr}
}

// adding on_entry variables
let mut onentry_var_prefix: String = "__kani_onentry_var".to_owned();
Expand Down Expand Up @@ -313,7 +444,6 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
});
}
Expr::Loop(ref mut el) => {
//let retexpr = get_return_statement(&el.body);
let invstmt: Stmt = syn::parse(quote!(if !(#register_ident(&||->bool{#inv_expr}, 0)) {assert!(false); unreachable!()};).into()).unwrap();
let mut new_stmts: Vec<Stmt> = Vec::new();
new_stmts.push(invstmt);
Expand All @@ -330,8 +460,74 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
note = "for now, loop contracts is only supported for while-loops.";
),
}

if has_prev {
let ret: TokenStream = if let Some(ForLoopExtraStmts {
init_iter_stmt,
init_len_stmt,
init_index_stmt,
init_pat_stmt,
iter_assume_stmt,
}) = forloopextras
{
if has_prev {
quote!(
{
#init_iter_stmt
#init_len_stmt
assert!(kaniiterlen > 0, "undefined prev when iter's length is zero");
{
#init_index_stmt
#iter_assume_stmt
#init_pat_stmt
#(#onentry_decl_stms)*
#(#prev_decl_stms)*
let mut #loop_body_closure = ||
#loop_body;
let (#loop_body_closure_ret_1, #loop_body_closure_ret_2) = #loop_body_closure ();
if #loop_body_closure_ret_2.is_some() {
return #loop_body_closure_ret_2.unwrap();
}
if #loop_body_closure_ret_1 {
// 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
}
else {
assert!(#inv_expr);
};
}
})
.into()
} else {
quote!(
{
#init_iter_stmt
#init_len_stmt
if kaniiterlen > 0
{
#init_index_stmt
#iter_assume_stmt
#init_pat_stmt
#(#onentry_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`.
#[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 if has_prev {
quote!(
{
if (#loop_guard) {
Expand Down Expand Up @@ -375,7 +571,8 @@ pub fn loop_invariant(attr: TokenStream, item: TokenStream) -> TokenStream {
#loop_stmt
})
.into()
}
};
ret
}

fn generate_unique_id_from_span(stmt: &Stmt) -> String {
Expand Down