Skip to content

Commit 1aee91f

Browse files
pi314mmMatias Scharager
and
Matias Scharager
authored
Function Contracts: remove instances of _renamed (rust-lang#3274)
The current method for creating the modifies wrapper requires changing the `ensures` clause to have `_renamed` variables which are unsafe copies of the original function arguments. This causes issues with regards to some possible tests as in rust-lang#3239. This change removes the `_renamed` variables and instead simply changes the modifies clauses within the replace to unsafely dereference the pointer to modify the contents of it unsafely, condensing all instances of unsafe Rust into a single location. Resolves rust-lang#3239 Resolves rust-lang#3026 May affect rust-lang#3027. In my attempt to run this example with slight modification to fit the current implementation, I got the error `CBMC appears to have run out of memory. You may want to rerun your proof in an environment with additional memory or use stubbing to reduce the size of the code the verifier reasons about.` This suggests that the compilation is working appropriately but the test case is just too large for CBMC. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. Co-authored-by: Matias Scharager <[email protected]>
1 parent 2971595 commit 1aee91f

File tree

5 files changed

+61
-145
lines changed

5 files changed

+61
-145
lines changed

library/kani_macros/src/sysroot/contracts/check.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,12 @@ impl<'a> ContractConditionsHandler<'a> {
3434
)
3535
}
3636
ContractConditionsData::Ensures { attr } => {
37-
let (arg_copies, copy_clean, ensures_clause) =
38-
build_ensures(&self.annotated_fn.sig, attr);
37+
let (remembers, ensures_clause) = build_ensures(attr);
3938

4039
// The code that enforces the postconditions and cleans up the shallow
4140
// argument copies (with `mem::forget`).
4241
let exec_postconditions = quote!(
4342
kani::assert(#ensures_clause, stringify!(#attr_copy));
44-
#copy_clean
4543
);
4644

4745
assert!(matches!(
@@ -52,7 +50,7 @@ impl<'a> ContractConditionsHandler<'a> {
5250

5351
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
5452
quote!(
55-
#arg_copies
53+
#remembers
5654
#(#inner)*
5755
#exec_postconditions
5856
#result

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

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,7 @@
242242
//! state. Each occurrence of `old` is lifted, so is is necessary that
243243
//! each lifted occurrence is closed with respect to the function arguments.
244244
//! The results of these old computations are placed into
245-
//! `remember_kani_internal_XXX` variables of incrementing index to avoid
246-
//! collisions of variable names. Consider the following example:
245+
//! `remember_kani_internal_XXX` variables which are hashed. Consider the following example:
247246
//!
248247
//! ```
249248
//! #[kani::ensures(|result| old(*ptr + 1) == *ptr)]
@@ -265,50 +264,73 @@
265264
//! This expands to
266265
//!
267266
//! ```
267+
//! #[kanitool::checked_with = "modify_recursion_wrapper_633496"]
268+
//! #[kanitool::replaced_with = "modify_replace_633496"]
269+
//! #[kanitool::inner_check = "modify_wrapper_633496"]
270+
//! fn modify(ptr: &mut u32) { { *ptr += 1; } }
271+
//! #[allow(dead_code, unused_variables, unused_mut)]
272+
//! #[kanitool::is_contract_generated(recursion_wrapper)]
273+
//! fn modify_recursion_wrapper_633496(arg0: &mut u32) {
274+
//! static mut REENTRY: bool = false;
275+
//! if unsafe { REENTRY } {
276+
//! modify_replace_633496(arg0)
277+
//! } else {
278+
//! unsafe { REENTRY = true };
279+
//! let result_kani_internal = modify_check_633496(arg0);
280+
//! unsafe { REENTRY = false };
281+
//! result_kani_internal
282+
//! }
283+
//! }
268284
//! #[allow(dead_code, unused_variables, unused_mut)]
269285
//! #[kanitool::is_contract_generated(check)]
270286
//! fn modify_check_633496(ptr: &mut u32) {
271287
//! let _wrapper_arg_1 =
272288
//! unsafe { kani::internal::Pointer::decouple_lifetime(&ptr) };
273289
//! kani::assume(*ptr < 100);
274-
//! let remember_kani_internal_1 = *ptr + 1;
275-
//! let ptr_renamed = kani::internal::untracked_deref(&ptr);
276-
//! let remember_kani_internal_0 = *ptr + 1;
277-
//! let ptr_renamed = kani::internal::untracked_deref(&ptr);
290+
//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1;
291+
//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1;
278292
//! let result_kani_internal: () = modify_wrapper_633496(ptr, _wrapper_arg_1);
279293
//! kani::assert((|result|
280-
//! (remember_kani_internal_0) ==
281-
//! *ptr_renamed)(&result_kani_internal),
294+
//! (remember_kani_internal_92cc419d8aca576c) ==
295+
//! *ptr)(&result_kani_internal),
282296
//! "|result| old(*ptr + 1) == *ptr");
283-
//! std::mem::forget(ptr_renamed);
284297
//! kani::assert((|result|
285-
//! (remember_kani_internal_1) ==
286-
//! *ptr_renamed)(&result_kani_internal),
298+
//! (remember_kani_internal_92cc419d8aca576c) ==
299+
//! *ptr)(&result_kani_internal),
287300
//! "|result| old(*ptr + 1) == *ptr");
288-
//! std::mem::forget(ptr_renamed);
289301
//! result_kani_internal
290302
//! }
291303
//! #[allow(dead_code, unused_variables, unused_mut)]
292304
//! #[kanitool::is_contract_generated(replace)]
293305
//! fn modify_replace_633496(ptr: &mut u32) {
294306
//! kani::assert(*ptr < 100, "*ptr < 100");
295-
//! let remember_kani_internal_1 = *ptr + 1;
296-
//! let ptr_renamed = kani::internal::untracked_deref(&ptr);
297-
//! let remember_kani_internal_0 = *ptr + 1;
298-
//! let ptr_renamed = kani::internal::untracked_deref(&ptr);
307+
//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1;
308+
//! let remember_kani_internal_92cc419d8aca576c = *ptr + 1;
299309
//! let result_kani_internal: () = kani::any_modifies();
300-
//! *unsafe { kani::internal::Pointer::assignable(ptr) } =
301-
//! kani::any_modifies();
310+
//! *unsafe {
311+
//! kani::internal::Pointer::assignable(kani::internal::untracked_deref(&(ptr)))
312+
//! } = kani::any_modifies();
302313
//! kani::assume((|result|
303-
//! (remember_kani_internal_0) ==
304-
//! *ptr_renamed)(&result_kani_internal));
305-
//! std::mem::forget(ptr_renamed);
314+
//! (remember_kani_internal_92cc419d8aca576c) ==
315+
//! *ptr)(&result_kani_internal));
306316
//! kani::assume((|result|
307-
//! (remember_kani_internal_1) ==
308-
//! *ptr_renamed)(&result_kani_internal));
309-
//! std::mem::forget(ptr_renamed);
317+
//! (remember_kani_internal_92cc419d8aca576c) ==
318+
//! *ptr)(&result_kani_internal));
310319
//! result_kani_internal
311320
//! }
321+
//! #[kanitool::modifies(_wrapper_arg_1)]
322+
//! #[allow(dead_code, unused_variables, unused_mut)]
323+
//! #[kanitool::is_contract_generated(wrapper)]
324+
//! fn modify_wrapper_633496<'_wrapper_arg_1,
325+
//! WrapperArgType1>(ptr: &mut u32, _wrapper_arg_1: &WrapperArgType1) {
326+
//! *ptr += 1;
327+
//! }
328+
//! #[allow(dead_code)]
329+
//! #[kanitool::proof_for_contract = "modify"]
330+
//! fn main() {
331+
//! kani::internal::init_contracts();
332+
//! { let mut i = kani::any(); modify(&mut i); }
333+
//! }
312334
//! ```
313335
314336
use proc_macro::TokenStream;

library/kani_macros/src/sysroot/contracts/replace.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,21 @@ impl<'a> ContractConditionsHandler<'a> {
8080
)
8181
}
8282
ContractConditionsData::Ensures { attr } => {
83-
let (arg_copies, copy_clean, ensures_clause) =
84-
build_ensures(&self.annotated_fn.sig, attr);
83+
let (remembers, ensures_clause) = build_ensures(attr);
8584
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
8685
quote!(
87-
#arg_copies
86+
#remembers
8887
#(#before)*
8988
#(#after)*
9089
kani::assume(#ensures_clause);
91-
#copy_clean
9290
#result
9391
)
9492
}
9593
ContractConditionsData::Modifies { attr } => {
9694
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
9795
quote!(
9896
#(#before)*
99-
#(*unsafe { kani::internal::Pointer::assignable(#attr) } = kani::any_modifies();)*
97+
#(*unsafe { kani::internal::Pointer::assignable(kani::internal::untracked_deref(&(#attr))) } = kani::any_modifies();)*
10098
#(#after)*
10199
#result
102100
)

library/kani_macros/src/sysroot/contracts/shared.rs

Lines changed: 7 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@
77
//! This is so we can keep [`super`] distraction-free as the definitions of data
88
//! structures and the entry point for contract handling.
99
10-
use std::collections::{HashMap, HashSet};
10+
use std::collections::HashMap;
1111

1212
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
1313
use quote::{quote, ToTokens};
1414
use std::hash::{DefaultHasher, Hash, Hasher};
1515
use syn::{
16-
spanned::Spanned, visit::Visit, visit_mut::VisitMut, Attribute, Expr, ExprCall, ExprClosure,
17-
ExprPath, Path,
16+
spanned::Spanned, visit_mut::VisitMut, Attribute, Expr, ExprCall, ExprClosure, ExprPath, Path,
1817
};
1918

2019
use super::{ContractConditionsHandler, ContractFunctionState, INTERNAL_RESULT_IDENT};
@@ -102,26 +101,6 @@ pub fn identifier_for_generated_function(
102101
Ident::new(&identifier, proc_macro2::Span::mixed_site())
103102
}
104103

105-
/// We make shallow copies of the argument for the postconditions in both
106-
/// `requires` and `ensures` clauses and later clean them up.
107-
///
108-
/// This function creates the code necessary to both make the copies (first
109-
/// tuple elem) and to clean them (second tuple elem).
110-
pub fn make_unsafe_argument_copies(
111-
renaming_map: &HashMap<Ident, Ident>,
112-
) -> (TokenStream2, TokenStream2) {
113-
let arg_names = renaming_map.values();
114-
let also_arg_names = renaming_map.values();
115-
let arg_values = renaming_map.keys();
116-
(
117-
quote!(#(let #arg_names = kani::internal::untracked_deref(&#arg_values);)*),
118-
#[cfg(not(feature = "no_core"))]
119-
quote!(#(std::mem::forget(#also_arg_names);)*),
120-
#[cfg(feature = "no_core")]
121-
quote!(#(core::mem::forget(#also_arg_names);)*),
122-
)
123-
}
124-
125104
/// Used as the "single source of truth" for [`try_as_result_assign`] and [`try_as_result_assign_mut`]
126105
/// since we can't abstract over mutability. Input is the object to match on and the name of the
127106
/// function used to convert an `Option<LocalInit>` into the result type (e.g. `as_ref` and `as_mut`
@@ -184,28 +163,20 @@ pub fn try_as_result_assign_mut(stmt: &mut syn::Stmt) -> Option<&mut syn::LocalI
184163
/// When a `#[kani::ensures(|result|expr)]` is expanded, this function is called on with `build_ensures(|result|expr)`.
185164
/// This function goes through the expr and extracts out all the `old` expressions and creates a sequence
186165
/// of statements that instantiate these expressions as `let remember_kani_internal_x = old_expr;`
187-
/// where x is a unique hash. This is returned as the first return parameter along with changing all the
188-
/// variables to `_renamed`. The second parameter is the closing of all the unsafe argument copies. The third
189-
/// return parameter is the expression formed by passing in the result variable into the input closure and
190-
/// changing all the variables to `_renamed`.
191-
pub fn build_ensures(
192-
fn_sig: &syn::Signature,
193-
data: &ExprClosure,
194-
) -> (TokenStream2, TokenStream2, Expr) {
166+
/// where x is a unique hash. This is returned as the first return parameter. The second
167+
/// return parameter is the expression formed by passing in the result variable into the input closure.
168+
pub fn build_ensures(data: &ExprClosure) -> (TokenStream2, Expr) {
195169
let mut remembers_exprs = HashMap::new();
196170
let mut vis = OldVisitor { t: OldLifter::new(), remembers_exprs: &mut remembers_exprs };
197171
let mut expr = &mut data.clone();
198172
vis.visit_expr_closure_mut(&mut expr);
199173

200-
let arg_names = rename_argument_occurrences(fn_sig, &mut expr);
201-
let (start, end) = make_unsafe_argument_copies(&arg_names);
202-
203174
let remembers_stmts: TokenStream2 = remembers_exprs
204175
.iter()
205-
.fold(start, |collect, (ident, expr)| quote!(let #ident = #expr; #collect));
176+
.fold(quote!(), |collect, (ident, expr)| quote!(let #ident = #expr; #collect));
206177

207178
let result: Ident = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
208-
(remembers_stmts, end, Expr::Verbatim(quote!((#expr)(&#result))))
179+
(remembers_stmts, Expr::Verbatim(quote!((#expr)(&#result))))
209180
}
210181

211182
trait OldTrigger {
@@ -304,76 +275,3 @@ impl OldTrigger for OldLifter {
304275
true
305276
}
306277
}
307-
308-
/// A supporting function for creating shallow, unsafe copies of the arguments
309-
/// for the postconditions.
310-
///
311-
/// This function:
312-
/// - Collects all [`Ident`]s found in the argument patterns;
313-
/// - Creates new names for them;
314-
/// - Replaces all occurrences of those idents in `attrs` with the new names and;
315-
/// - Returns the mapping of old names to new names.
316-
fn rename_argument_occurrences(
317-
sig: &syn::Signature,
318-
attr: &mut ExprClosure,
319-
) -> HashMap<Ident, Ident> {
320-
let mut arg_ident_collector = ArgumentIdentCollector::new();
321-
arg_ident_collector.visit_signature(&sig);
322-
323-
let mk_new_ident_for = |id: &Ident| Ident::new(&format!("{}_renamed", id), Span::mixed_site());
324-
let arg_idents = arg_ident_collector
325-
.0
326-
.into_iter()
327-
.map(|i| {
328-
let new = mk_new_ident_for(&i);
329-
(i, new)
330-
})
331-
.collect::<HashMap<_, _>>();
332-
333-
let mut ident_rewriter = Renamer(&arg_idents);
334-
ident_rewriter.visit_expr_closure_mut(attr);
335-
arg_idents
336-
}
337-
338-
/// Collect all named identifiers used in the argument patterns of a function.
339-
struct ArgumentIdentCollector(HashSet<Ident>);
340-
341-
impl ArgumentIdentCollector {
342-
fn new() -> Self {
343-
Self(HashSet::new())
344-
}
345-
}
346-
347-
impl<'ast> Visit<'ast> for ArgumentIdentCollector {
348-
fn visit_pat_ident(&mut self, i: &'ast syn::PatIdent) {
349-
self.0.insert(i.ident.clone());
350-
syn::visit::visit_pat_ident(self, i)
351-
}
352-
fn visit_receiver(&mut self, _: &'ast syn::Receiver) {
353-
self.0.insert(Ident::new("self", proc_macro2::Span::call_site()));
354-
}
355-
}
356-
357-
/// Applies the contained renaming (key renamed to value) to every ident pattern
358-
/// and ident expr visited.
359-
struct Renamer<'a>(&'a HashMap<Ident, Ident>);
360-
361-
impl<'a> VisitMut for Renamer<'a> {
362-
fn visit_expr_path_mut(&mut self, i: &mut syn::ExprPath) {
363-
if i.path.segments.len() == 1 {
364-
i.path
365-
.segments
366-
.first_mut()
367-
.and_then(|p| self.0.get(&p.ident).map(|new| p.ident = new.clone()));
368-
}
369-
}
370-
371-
/// This restores shadowing. Without this we would rename all ident
372-
/// occurrences, but not rebinding location. This is because our
373-
/// [`Self::visit_expr_path_mut`] is scope-unaware.
374-
fn visit_pat_ident_mut(&mut self, i: &mut syn::PatIdent) {
375-
if let Some(new) = self.0.get(&i.ident) {
376-
i.ident = new.clone();
377-
}
378-
}
379-
}

tests/expected/function-contract/modifies/field_replace_pass.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ struct S<'a> {
99
#[kani::requires(*s.target < 100)]
1010
#[kani::modifies(s.target)]
1111
#[kani::ensures(|result| *s.target == prior + 1)]
12-
fn modify(s: S, prior: u32) {
12+
fn modify(s: &mut S, prior: u32) {
1313
*s.target += 1;
1414
}
1515

@@ -20,8 +20,8 @@ fn main() {
2020
let i_copy = i;
2121
let mut distraction = kani::any();
2222
let distraction_copy = distraction;
23-
let s = S { distraction: &mut distraction, target: &mut i };
24-
modify(s, i_copy);
23+
let mut s = S { distraction: &mut distraction, target: &mut i };
24+
modify(&mut s, i_copy);
2525
kani::assert(i == i_copy + 1, "Increment");
2626
kani::assert(distraction == distraction_copy, "Unchanged");
2727
}

0 commit comments

Comments
 (0)