Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions library/kani_macros/src/sysroot/contracts/assert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl<'a> ContractConditionsHandler<'a> {
quote!(
#[kanitool::is_contract_generated(assert)]
#[allow(dead_code, unused_variables, unused_mut)]
let mut #assert_ident = || #output #body;
let mut #assert_ident = kani_force_fn_once(|| #output #body);
)
}

Expand All @@ -49,9 +49,9 @@ impl<'a> ContractConditionsHandler<'a> {
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());

parse_quote!(
let mut body_wrapper = || #output {
let mut body_wrapper = kani_force_fn_once(|| #output {
#(#stmts)*
};
});
let #result : #return_type = #body_wrapper_ident();
#result
)
Expand Down
18 changes: 16 additions & 2 deletions library/kani_macros/src/sysroot/contracts/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ impl<'a> ContractConditionsHandler<'a> {
#[kanitool::asserted_with = #assert_name]
#[kanitool::modifies_wrapper = #modifies_name]
#vis #sig {
// Dummy functions used to force the compiler to annotate Kani's
// closures as `FnOnce`. Without this, Rust infers the generated closures as `FnMut`,
// preventing us from writing contracts for functions returning mutable references.
// See https://github.com/model-checking/kani/issues/3764
#[inline(never)]
#[kanitool::fn_marker = "kani_force_fn_once"]
const fn kani_force_fn_once<T, F: FnOnce() -> T>(f: F) -> F {
f
}
#[inline(never)]
#[kanitool::fn_marker = "kani_force_fn_once_with_args"]
const fn kani_force_fn_once_with_args<A, T, F: FnOnce(A) -> T>(f: F) -> F {
f
}
// 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 Down Expand Up @@ -125,7 +139,7 @@ impl<'a> ContractConditionsHandler<'a> {
quote!(
#[kanitool::is_contract_generated(recursion_check)]
#[allow(dead_code, unused_variables, unused_mut)]
let mut #recursion_ident = || #output
let mut #recursion_ident = kani_force_fn_once(|| #output
{
#[kanitool::recursion_tracker]
static mut REENTRY: bool = false;
Expand All @@ -139,7 +153,7 @@ impl<'a> ContractConditionsHandler<'a> {
unsafe { REENTRY = false };
#result
}
};
});
)
}

Expand Down
14 changes: 10 additions & 4 deletions library/kani_macros/src/sysroot/contracts/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl<'a> ContractConditionsHandler<'a> {
quote!(
#[kanitool::is_contract_generated(check)]
#[allow(dead_code, unused_variables, unused_mut)]
let mut #check_ident = || #output #body;
let mut #check_ident = kani_force_fn_once(|| #output #body);
)
}

Expand Down Expand Up @@ -144,10 +144,10 @@ impl<'a> ContractConditionsHandler<'a> {
quote!(
#[kanitool::is_contract_generated(wrapper)]
#[allow(dead_code, unused_variables, unused_mut)]
let mut #modifies_ident = |#wrapper_ident: _| #output {
let mut #modifies_ident = kani_force_fn_once_with_args(|#wrapper_ident: _| #output {
#redefs
#(#stmts)*
};
});
)
}

Expand All @@ -157,7 +157,13 @@ impl<'a> ContractConditionsHandler<'a> {
let Stmt::Local(Local { init: Some(LocalInit { expr, .. }), .. }) = closure_stmt else {
unreachable!()
};
let Expr::Closure(closure) = expr.as_ref() else { unreachable!() };
// The closure is wrapped into `kani_force_fn_once_with_args`
let Expr::Call(call) = expr.as_ref() else { unreachable!() };
if call.args.len() != 1 {
unreachable!()
}
let expr = call.args.first().unwrap();
let Expr::Closure(closure) = expr else { unreachable!() };
let Expr::Block(body) = closure.body.as_ref() else { unreachable!() };
let stream = self.modifies_closure(&closure.output, &body.block, TokenStream2::new());
*closure_stmt = syn::parse2(stream).unwrap();
Expand Down
22 changes: 19 additions & 3 deletions library/kani_macros/src/sysroot/contracts/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,25 @@ pub fn closure_body(closure: &mut Stmt) -> &mut ExprBlock {
let Stmt::Local(Local { init: Some(LocalInit { expr, .. }), .. }) = closure else {
unreachable!()
};
let Expr::Closure(closure) = expr.as_mut() else { unreachable!() };
let Expr::Block(body) = closure.body.as_mut() else { unreachable!() };
body
match expr.as_mut() {
// The case of closures wrapped in `kani_force_fn_once`
Expr::Call(call) if call.args.len() == 1 => {
let arg = call.args.first_mut().unwrap();
match arg {
Expr::Closure(closure) => {
let Expr::Block(body) = closure.body.as_mut() else { unreachable!() };
body
}
_ => unreachable!(),
}
}

Expr::Closure(closure) => {
let Expr::Block(body) = closure.body.as_mut() else { unreachable!() };
body
}
_ => unreachable!(),
}
}

/// Does the provided path have the same chain of identifiers as `mtch` (match)
Expand Down
Loading
Loading