Skip to content

Commit 2971595

Browse files
pi314mmMatias ScharagerJustusAdamfeliperodri
authored
Contracts: History Expressions via "old" monad (rust-lang#3232)
Add support for History Expressions. This allows an ensures clause to evaluate expressions before the function runs, bing them to anonymous variables and use them after calculating the result to be able to compare to the state before the function evaluation. Resolves rust-lang#2803 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 <mscharag@amazon.com> Co-authored-by: Justus Adam <dev@justus.science> Co-authored-by: Felipe R. Monteiro <rms.felipe@gmail.com>
1 parent bfb497a commit 2971595

39 files changed

+746
-101
lines changed

library/kani/src/contracts.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,27 @@
225225
//! Rust pointer type (`&T`, `&mut T`, `*const T` or `*mut T`). In addition `T`
226226
//! must implement [`Arbitrary`](super::Arbitrary). This is used to assign
227227
//! `kani::any()` to the location when the function is used in a `stub_verified`.
228+
//!
229+
//! ## History Expressions
230+
//!
231+
//! Additionally, an ensures clause is allowed to refer to the state of the function arguments before function execution and perform simple computations on them
232+
//! via an `old` monad. Any instance of `old(computation)` will evaluate the
233+
//! computation before the function is called. It is required that this computation
234+
//! is effect free and closed with respect to the function arguments.
235+
//!
236+
//! For example, the following code passes kani tests:
237+
//!
238+
//! ```
239+
//! #[kani::modifies(a)]
240+
//! #[kani::ensures(|result| old(*a).wrapping_add(1) == *a)]
241+
//! #[kani::ensures(|result : &u32| old(*a).wrapping_add(1) == *result)]
242+
//! fn add1(a : &mut u32) -> u32 {
243+
//! *a=a.wrapping_add(1);
244+
//! *a
245+
//! }
246+
//! ```
247+
//!
248+
//! Here, the value stored in `a` is precomputed and remembered after the function
249+
//! is called, even though the contents of `a` changed during the function execution.
250+
//!
228251
pub use super::{ensures, modifies, proof_for_contract, requires, stub_verified};

library/kani_macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ proc-macro = true
1515
proc-macro2 = "1.0"
1616
proc-macro-error = "1.0.4"
1717
quote = "1.0.20"
18-
syn = { version = "2.0.18", features = ["full", "visit-mut", "visit"] }
18+
syn = { version = "2.0.18", features = ["full", "visit-mut", "visit", "extra-traits"] }
1919

2020
[package.metadata.rust-analyzer]
2121
# This package uses rustc crates.

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use syn::{Expr, FnArg, ItemFn, Token};
99

1010
use super::{
1111
helpers::*,
12-
shared::{make_unsafe_argument_copies, try_as_result_assign_mut},
12+
shared::{build_ensures, try_as_result_assign_mut},
1313
ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT,
1414
};
1515

@@ -33,13 +33,14 @@ impl<'a> ContractConditionsHandler<'a> {
3333
#(#inner)*
3434
)
3535
}
36-
ContractConditionsData::Ensures { argument_names, attr } => {
37-
let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names);
36+
ContractConditionsData::Ensures { attr } => {
37+
let (arg_copies, copy_clean, ensures_clause) =
38+
build_ensures(&self.annotated_fn.sig, attr);
3839

3940
// The code that enforces the postconditions and cleans up the shallow
4041
// argument copies (with `mem::forget`).
4142
let exec_postconditions = quote!(
42-
kani::assert(#attr, stringify!(#attr_copy));
43+
kani::assert(#ensures_clause, stringify!(#attr_copy));
4344
#copy_clean
4445
);
4546

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,21 @@ pub fn short_hash_of_token_stream(stream: &proc_macro::TokenStream) -> u64 {
268268
let long_hash = hasher.finish();
269269
long_hash % SIX_HEX_DIGITS_MASK
270270
}
271+
272+
macro_rules! assert_spanned_err {
273+
($condition:expr, $span_source:expr, $msg:expr, $($args:expr),+) => {
274+
if !$condition {
275+
$span_source.span().unwrap().error(format!($msg, $($args),*)).emit();
276+
assert!(false);
277+
}
278+
};
279+
($condition:expr, $span_source:expr, $msg:expr $(,)?) => {
280+
if !$condition {
281+
$span_source.span().unwrap().error($msg).emit();
282+
assert!(false);
283+
}
284+
};
285+
($condition:expr, $span_source:expr) => {
286+
assert_spanned_err!($condition, $span_source, concat!("Failed assertion ", stringify!($condition)))
287+
};
288+
}

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

Lines changed: 4 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33

44
//! Initialization routine for the contract handler
55
6-
use std::collections::{HashMap, HashSet};
7-
86
use proc_macro::{Diagnostic, TokenStream};
9-
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
10-
use quote::quote;
11-
use syn::{spanned::Spanned, visit::Visit, visit_mut::VisitMut, Expr, ExprClosure, ItemFn};
7+
use proc_macro2::{Ident, TokenStream as TokenStream2};
8+
use syn::{spanned::Spanned, ItemFn};
129

1310
use super::{
1411
helpers::{chunks_by, is_token_stream_2_comma, matches_path},
1512
ContractConditionsData, ContractConditionsHandler, ContractConditionsType,
16-
ContractFunctionState, INTERNAL_RESULT_IDENT,
13+
ContractFunctionState,
1714
};
1815

1916
impl<'a> TryFrom<&'a syn::Attribute> for ContractFunctionState {
@@ -82,11 +79,7 @@ impl<'a> ContractConditionsHandler<'a> {
8279
ContractConditionsData::Requires { attr: syn::parse(attr)? }
8380
}
8481
ContractConditionsType::Ensures => {
85-
let mut data: ExprClosure = syn::parse(attr)?;
86-
let argument_names = rename_argument_occurrences(&annotated_fn.sig, &mut data);
87-
let result: Ident = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
88-
let app: Expr = Expr::Verbatim(quote!((#data)(&#result)));
89-
ContractConditionsData::Ensures { argument_names, attr: app }
82+
ContractConditionsData::Ensures { attr: syn::parse(attr)? }
9083
}
9184
ContractConditionsType::Modifies => {
9285
ContractConditionsData::new_modifies(attr, &mut output)
@@ -115,76 +108,3 @@ impl ContractConditionsData {
115108
ContractConditionsData::Modifies { attr }
116109
}
117110
}
118-
119-
/// A supporting function for creating shallow, unsafe copies of the arguments
120-
/// for the postconditions.
121-
///
122-
/// This function:
123-
/// - Collects all [`Ident`]s found in the argument patterns;
124-
/// - Creates new names for them;
125-
/// - Replaces all occurrences of those idents in `attrs` with the new names and;
126-
/// - Returns the mapping of old names to new names.
127-
fn rename_argument_occurrences(
128-
sig: &syn::Signature,
129-
attr: &mut ExprClosure,
130-
) -> HashMap<Ident, Ident> {
131-
let mut arg_ident_collector = ArgumentIdentCollector::new();
132-
arg_ident_collector.visit_signature(&sig);
133-
134-
let mk_new_ident_for = |id: &Ident| Ident::new(&format!("{}_renamed", id), Span::mixed_site());
135-
let arg_idents = arg_ident_collector
136-
.0
137-
.into_iter()
138-
.map(|i| {
139-
let new = mk_new_ident_for(&i);
140-
(i, new)
141-
})
142-
.collect::<HashMap<_, _>>();
143-
144-
let mut ident_rewriter = Renamer(&arg_idents);
145-
ident_rewriter.visit_expr_closure_mut(attr);
146-
arg_idents
147-
}
148-
149-
/// Collect all named identifiers used in the argument patterns of a function.
150-
struct ArgumentIdentCollector(HashSet<Ident>);
151-
152-
impl ArgumentIdentCollector {
153-
fn new() -> Self {
154-
Self(HashSet::new())
155-
}
156-
}
157-
158-
impl<'ast> Visit<'ast> for ArgumentIdentCollector {
159-
fn visit_pat_ident(&mut self, i: &'ast syn::PatIdent) {
160-
self.0.insert(i.ident.clone());
161-
syn::visit::visit_pat_ident(self, i)
162-
}
163-
fn visit_receiver(&mut self, _: &'ast syn::Receiver) {
164-
self.0.insert(Ident::new("self", proc_macro2::Span::call_site()));
165-
}
166-
}
167-
168-
/// Applies the contained renaming (key renamed to value) to every ident pattern
169-
/// and ident expr visited.
170-
struct Renamer<'a>(&'a HashMap<Ident, Ident>);
171-
172-
impl<'a> VisitMut for Renamer<'a> {
173-
fn visit_expr_path_mut(&mut self, i: &mut syn::ExprPath) {
174-
if i.path.segments.len() == 1 {
175-
i.path
176-
.segments
177-
.first_mut()
178-
.and_then(|p| self.0.get(&p.ident).map(|new| p.ident = new.clone()));
179-
}
180-
}
181-
182-
/// This restores shadowing. Without this we would rename all ident
183-
/// occurrences, but not rebinding location. This is because our
184-
/// [`Self::visit_expr_path_mut`] is scope-unaware.
185-
fn visit_pat_ident_mut(&mut self, i: &mut syn::PatIdent) {
186-
if let Some(new) = self.0.get(&i.ident) {
187-
i.ident = new.clone();
188-
}
189-
}
190-
}

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

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -233,15 +233,92 @@
233233
//! }
234234
//! }
235235
//! ```
236+
//!
237+
//! Additionally, there is functionality that allows the referencing of
238+
//! history values within the ensures statement. This means we can
239+
//! precompute a value before the function is called and have access to
240+
//! this value in the later ensures statement. This is done via the
241+
//! `old` monad which lets you access the old state within the present
242+
//! state. Each occurrence of `old` is lifted, so is is necessary that
243+
//! each lifted occurrence is closed with respect to the function arguments.
244+
//! 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:
247+
//!
248+
//! ```
249+
//! #[kani::ensures(|result| old(*ptr + 1) == *ptr)]
250+
//! #[kani::ensures(|result| old(*ptr + 1) == *ptr)]
251+
//! #[kani::requires(*ptr < 100)]
252+
//! #[kani::modifies(ptr)]
253+
//! fn modify(ptr: &mut u32) {
254+
//! *ptr += 1;
255+
//! }
256+
//!
257+
//! #[kani::proof_for_contract(modify)]
258+
//! fn main() {
259+
//! let mut i = kani::any();
260+
//! modify(&mut i);
261+
//! }
262+
//!
263+
//! ```
264+
//!
265+
//! This expands to
266+
//!
267+
//! ```
268+
//! #[allow(dead_code, unused_variables, unused_mut)]
269+
//! #[kanitool::is_contract_generated(check)]
270+
//! fn modify_check_633496(ptr: &mut u32) {
271+
//! let _wrapper_arg_1 =
272+
//! unsafe { kani::internal::Pointer::decouple_lifetime(&ptr) };
273+
//! 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);
278+
//! let result_kani_internal: () = modify_wrapper_633496(ptr, _wrapper_arg_1);
279+
//! kani::assert((|result|
280+
//! (remember_kani_internal_0) ==
281+
//! *ptr_renamed)(&result_kani_internal),
282+
//! "|result| old(*ptr + 1) == *ptr");
283+
//! std::mem::forget(ptr_renamed);
284+
//! kani::assert((|result|
285+
//! (remember_kani_internal_1) ==
286+
//! *ptr_renamed)(&result_kani_internal),
287+
//! "|result| old(*ptr + 1) == *ptr");
288+
//! std::mem::forget(ptr_renamed);
289+
//! result_kani_internal
290+
//! }
291+
//! #[allow(dead_code, unused_variables, unused_mut)]
292+
//! #[kanitool::is_contract_generated(replace)]
293+
//! fn modify_replace_633496(ptr: &mut u32) {
294+
//! 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);
299+
//! let result_kani_internal: () = kani::any_modifies();
300+
//! *unsafe { kani::internal::Pointer::assignable(ptr) } =
301+
//! kani::any_modifies();
302+
//! kani::assume((|result|
303+
//! (remember_kani_internal_0) ==
304+
//! *ptr_renamed)(&result_kani_internal));
305+
//! std::mem::forget(ptr_renamed);
306+
//! kani::assume((|result|
307+
//! (remember_kani_internal_1) ==
308+
//! *ptr_renamed)(&result_kani_internal));
309+
//! std::mem::forget(ptr_renamed);
310+
//! result_kani_internal
311+
//! }
312+
//! ```
236313
237314
use proc_macro::TokenStream;
238315
use proc_macro2::{Ident, TokenStream as TokenStream2};
239316
use quote::{quote, ToTokens};
240-
use std::collections::HashMap;
241-
use syn::{parse_macro_input, Expr, ItemFn};
317+
use syn::{parse_macro_input, Expr, ExprClosure, ItemFn};
242318

243319
mod bootstrap;
244320
mod check;
321+
#[macro_use]
245322
mod helpers;
246323
mod initialize;
247324
mod replace;
@@ -352,11 +429,8 @@ enum ContractConditionsData {
352429
attr: Expr,
353430
},
354431
Ensures {
355-
/// Translation map from original argument names to names of the copies
356-
/// we will be emitting.
357-
argument_names: HashMap<Ident, Ident>,
358432
/// The contents of the attribute.
359-
attr: Expr,
433+
attr: ExprClosure,
360434
},
361435
Modifies {
362436
attr: Vec<Expr>,

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use quote::quote;
88

99
use super::{
1010
helpers::*,
11-
shared::{make_unsafe_argument_copies, try_as_result_assign},
11+
shared::{build_ensures, try_as_result_assign},
1212
ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT,
1313
};
1414

@@ -79,14 +79,15 @@ impl<'a> ContractConditionsHandler<'a> {
7979
#result
8080
)
8181
}
82-
ContractConditionsData::Ensures { attr, argument_names } => {
83-
let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names);
82+
ContractConditionsData::Ensures { attr } => {
83+
let (arg_copies, copy_clean, ensures_clause) =
84+
build_ensures(&self.annotated_fn.sig, attr);
8485
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
8586
quote!(
8687
#arg_copies
8788
#(#before)*
8889
#(#after)*
89-
kani::assume(#attr);
90+
kani::assume(#ensures_clause);
9091
#copy_clean
9192
#result
9293
)

0 commit comments

Comments
 (0)