-
Notifications
You must be signed in to change notification settings - Fork 107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for loop-contract historic values #3951
Open
thanhnguyen-aws
wants to merge
8
commits into
model-checking:main
Choose a base branch
from
thanhnguyen-aws:loopmodify
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
8a579da
add support or old in while loop inv
thanhnguyen-aws 3bc29ea
Merge branch 'model-checking:main' into loopmodify
thanhnguyen-aws 7520bc3
add expected
thanhnguyen-aws c393439
add expected
thanhnguyen-aws 8714931
add expected
thanhnguyen-aws bb7fdff
add more expected tests
thanhnguyen-aws 1c168bd
add clone to assignment, fix expected file name
thanhnguyen-aws b981fa2
fix expected files and add commnents in tests
thanhnguyen-aws File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add more comments to the newly added functions? |
||
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. | ||
/// | ||
|
@@ -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(); | ||
|
@@ -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`. | ||
|
@@ -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 { | ||
|
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 @@ | ||
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,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; | ||
} | ||
} |
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 @@ | ||
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,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); | ||
} |
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: -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); | ||
} |
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 @@ | ||
VERIFICATION:- SUCCESSFUL | ||
tautschnig marked this conversation as resolved.
Show resolved
Hide resolved
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add some comments to explain when
should_replace
return true?