Skip to content

Commit 9f4fc30

Browse files
pi314mmMatias Scharager
and
Matias Scharager
authored
Change ensures into closures (rust-lang#3207)
This change now separates the front facing "result" name and the internal facing "result_kani_internal" ident where the user can specify with the keyword "result" but then the system replaces this with the internal representation. If the user chooses to use a different variable name than result, this now supports the syntax of `#[kani::ensures(|result_var| expr)]` where result_var can be any arbitrary name. For example, the following test now works: ``` #[kani::ensures(|banana : &u32| *banana == a.wrapping_add(b))] fn simple_addition(a: u32, b: u32) -> u32 { return a.wrapping_add(b); } ``` In addition, the string "result_kani_internal" is now a global constant and can be updated in a single place if needed. Resolves rust-lang#2597 since the user can specify the variable name they want within the ensures binding An important change is that the result is now a pass by reference instead, so as in the example an `&u32` instead of `u32` 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 ab56b9d commit 9f4fc30

File tree

67 files changed

+161
-148
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+161
-148
lines changed

library/kani/src/contracts.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@
5151
//! approximation of the result of division for instance could be this:
5252
//!
5353
//! ```
54-
//! #[ensures(result <= dividend)]
54+
//! #[ensures(|result : &u32| *result <= dividend)]
5555
//! ```
5656
//!
5757
//! This is called a postcondition and it also has access to the arguments and
5858
//! is expressed in regular Rust code. The same restrictions apply as did for
59-
//! [`requires`][macro@requires]. In addition to the arguments the postcondition
60-
//! also has access to the value returned from the function in a variable called
61-
//! `result`.
59+
//! [`requires`][macro@requires]. In addition to the postcondition is expressed
60+
//! as a closure where the value returned from the function is passed to this
61+
//! closure by reference.
6262
//!
6363
//! You may combine as many [`requires`][macro@requires] and
6464
//! [`ensures`][macro@ensures] attributes on a single function as you please.
@@ -67,7 +67,7 @@
6767
//!
6868
//! ```
6969
//! #[kani::requires(divisor != 0)]
70-
//! #[kani::ensures(result <= dividend)]
70+
//! #[kani::ensures(|result : &u32| *result <= dividend)]
7171
//! fn my_div(dividend: u32, divisor: u32) -> u32 {
7272
//! dividend / divisor
7373
//! }

library/kani_macros/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,11 @@ pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream {
131131
/// This is part of the function contract API, for more general information see
132132
/// the [module-level documentation](../kani/contracts/index.html).
133133
///
134-
/// The contents of the attribute is a condition over the input values to the
135-
/// annotated function *and* its return value, accessible as a variable called
136-
/// `result`. All Rust syntax is supported, even calling other functions, but
137-
/// the computations must be side effect free, e.g. it cannot perform I/O or use
138-
/// mutable memory.
134+
/// The contents of the attribute is a closure that captures the input values to
135+
/// the annotated function and the input to the function is the return value of
136+
/// the function passed by reference. All Rust syntax is supported, even calling
137+
/// other functions, but the computations must be side effect free, e.g. it
138+
/// cannot perform I/O or use mutable memory.
139139
///
140140
/// Kani requires each function that uses a contract (this attribute or
141141
/// [`requires`][macro@requires]) to have at least one designated

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
//! Special way we handle the first time we encounter a contract attribute on a
55
//! function.
66
7-
use proc_macro2::Span;
7+
use proc_macro2::{Ident, Span};
88
use quote::quote;
99
use syn::ItemFn;
1010

11-
use super::{helpers::*, shared::identifier_for_generated_function, ContractConditionsHandler};
11+
use super::{
12+
helpers::*, shared::identifier_for_generated_function, ContractConditionsHandler,
13+
INTERNAL_RESULT_IDENT,
14+
};
1215

1316
impl<'a> ContractConditionsHandler<'a> {
1417
/// The complex case. We are the first time a contract is handled on this function, so
@@ -80,6 +83,7 @@ impl<'a> ContractConditionsHandler<'a> {
8083
(quote!(#check_fn_name), quote!(#replace_fn_name))
8184
};
8285

86+
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
8387
self.output.extend(quote!(
8488
#[allow(dead_code, unused_variables)]
8589
#[kanitool::is_contract_generated(recursion_wrapper)]
@@ -89,9 +93,9 @@ impl<'a> ContractConditionsHandler<'a> {
8993
#call_replace(#(#args),*)
9094
} else {
9195
unsafe { REENTRY = true };
92-
let result = #call_check(#(#also_args),*);
96+
let #result = #call_check(#(#also_args),*);
9397
unsafe { REENTRY = false };
94-
result
98+
#result
9599
}
96100
}
97101
));

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use syn::{Expr, FnArg, ItemFn, Token};
1010
use super::{
1111
helpers::*,
1212
shared::{make_unsafe_argument_copies, try_as_result_assign_mut},
13-
ContractConditionsData, ContractConditionsHandler,
13+
ContractConditionsData, ContractConditionsHandler, INTERNAL_RESULT_IDENT,
1414
};
1515

1616
const WRAPPER_ARG_PREFIX: &str = "_wrapper_arg_";
@@ -46,14 +46,15 @@ impl<'a> ContractConditionsHandler<'a> {
4646
assert!(matches!(
4747
inner.pop(),
4848
Some(syn::Stmt::Expr(syn::Expr::Path(pexpr), None))
49-
if pexpr.path.get_ident().map_or(false, |id| id == "result")
49+
if pexpr.path.get_ident().map_or(false, |id| id == INTERNAL_RESULT_IDENT)
5050
));
5151

52+
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
5253
quote!(
5354
#arg_copies
5455
#(#inner)*
5556
#exec_postconditions
56-
result
57+
#result
5758
)
5859
}
5960
ContractConditionsData::Modifies { attr } => {
@@ -95,9 +96,10 @@ impl<'a> ContractConditionsHandler<'a> {
9596
} else {
9697
quote!(#wrapper_name)
9798
};
99+
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
98100
syn::parse_quote!(
99-
let result : #return_type = #wrapper_call(#(#args),*);
100-
result
101+
let #result : #return_type = #wrapper_call(#(#args),*);
102+
#result
101103
)
102104
} else {
103105
self.annotated_fn.block.stmts.clone()

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ use std::collections::{HashMap, HashSet};
77

88
use proc_macro::{Diagnostic, TokenStream};
99
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
10-
use syn::{spanned::Spanned, visit::Visit, visit_mut::VisitMut, Expr, ItemFn, Signature};
10+
use quote::quote;
11+
use syn::{spanned::Spanned, visit::Visit, visit_mut::VisitMut, Expr, ExprClosure, ItemFn};
1112

1213
use super::{
1314
helpers::{chunks_by, is_token_stream_2_comma, matches_path},
1415
ContractConditionsData, ContractConditionsHandler, ContractConditionsType,
15-
ContractFunctionState,
16+
ContractFunctionState, INTERNAL_RESULT_IDENT,
1617
};
1718

1819
impl<'a> TryFrom<&'a syn::Attribute> for ContractFunctionState {
@@ -81,7 +82,11 @@ impl<'a> ContractConditionsHandler<'a> {
8182
ContractConditionsData::Requires { attr: syn::parse(attr)? }
8283
}
8384
ContractConditionsType::Ensures => {
84-
ContractConditionsData::new_ensures(&annotated_fn.sig, syn::parse(attr)?)
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 }
8590
}
8691
ContractConditionsType::Modifies => {
8792
ContractConditionsData::new_modifies(attr, &mut output)
@@ -92,16 +97,6 @@ impl<'a> ContractConditionsHandler<'a> {
9297
}
9398
}
9499
impl ContractConditionsData {
95-
/// Constructs a [`Self::Ensures`] from the signature of the decorated
96-
/// function and the contents of the decorating attribute.
97-
///
98-
/// Renames the [`Ident`]s used in `attr` and stores the translation map in
99-
/// `argument_names`.
100-
fn new_ensures(sig: &Signature, mut attr: Expr) -> Self {
101-
let argument_names = rename_argument_occurrences(sig, &mut attr);
102-
ContractConditionsData::Ensures { argument_names, attr }
103-
}
104-
105100
/// Constructs a [`Self::Modifies`] from the contents of the decorating attribute.
106101
///
107102
/// Responsible for parsing the attribute.
@@ -129,7 +124,10 @@ impl ContractConditionsData {
129124
/// - Creates new names for them;
130125
/// - Replaces all occurrences of those idents in `attrs` with the new names and;
131126
/// - Returns the mapping of old names to new names.
132-
fn rename_argument_occurrences(sig: &syn::Signature, attr: &mut Expr) -> HashMap<Ident, Ident> {
127+
fn rename_argument_occurrences(
128+
sig: &syn::Signature,
129+
attr: &mut ExprClosure,
130+
) -> HashMap<Ident, Ident> {
133131
let mut arg_ident_collector = ArgumentIdentCollector::new();
134132
arg_ident_collector.visit_signature(&sig);
135133

@@ -144,7 +142,7 @@ fn rename_argument_occurrences(sig: &syn::Signature, attr: &mut Expr) -> HashMap
144142
.collect::<HashMap<_, _>>();
145143

146144
let mut ident_rewriter = Renamer(&arg_idents);
147-
ident_rewriter.visit_expr_mut(attr);
145+
ident_rewriter.visit_expr_closure_mut(attr);
148146
arg_idents
149147
}
150148

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

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,9 @@
159159
//! call_replace(fn args...)
160160
//! } else {
161161
//! unsafe { reentry = true };
162-
//! let result = call_check(fn args...);
162+
//! let result_kani_internal = call_check(fn args...);
163163
//! unsafe { reentry = false };
164-
//! result
164+
//! result_kani_internal
165165
//! }
166166
//! }
167167
//! ```
@@ -173,7 +173,7 @@
173173
//!
174174
//! ```
175175
//! #[kani::requires(divisor != 0)]
176-
//! #[kani::ensures(result <= dividend)]
176+
//! #[kani::ensures(|result : &u32| *result <= dividend)]
177177
//! fn div(dividend: u32, divisor: u32) -> u32 {
178178
//! dividend / divisor
179179
//! }
@@ -186,31 +186,35 @@
186186
//! #[kanitool::replaced_with = "div_replace_965916"]
187187
//! fn div(dividend: u32, divisor: u32) -> u32 { dividend / divisor }
188188
//!
189-
//! #[allow(dead_code)]
190-
//! #[allow(unused_variables)]
191-
//! #[kanitool::is_contract_generated(check)]
192-
//! fn div_check_965916(dividend: u32, divisor: u32) -> u32 {
193-
//! let dividend_renamed = kani::internal::untracked_deref(&dividend);
194-
//! let divisor_renamed = kani::internal::untracked_deref(&divisor);
195-
//! let result = { kani::assume(divisor != 0); { dividend / divisor } };
196-
//! kani::assert(result <= dividend_renamed, "result <= dividend");
189+
//! #[allow(dead_code, unused_variables)]
190+
//! #[kanitool :: is_contract_generated(check)] fn
191+
//! div_check_b97df2(dividend : u32, divisor : u32) -> u32
192+
//! {
193+
//! let dividend_renamed = kani::internal::untracked_deref(& dividend);
194+
//! let divisor_renamed = kani::internal::untracked_deref(& divisor);
195+
//! kani::assume(divisor != 0);
196+
//! let result_kani_internal : u32 = div_wrapper_b97df2(dividend, divisor);
197+
//! kani::assert(
198+
//! (| result : & u32 | *result <= dividend_renamed)(& result_kani_internal),
199+
//! stringify!(|result : &u32| *result <= dividend));
197200
//! std::mem::forget(dividend_renamed);
198201
//! std::mem::forget(divisor_renamed);
199-
//! result
202+
//! result_kani_internal
200203
//! }
201204
//!
202-
//! #[allow(dead_code)]
203-
//! #[allow(unused_variables)]
204-
//! #[kanitool::is_contract_generated(replace)]
205-
//! fn div_replace_965916(dividend: u32, divisor: u32) -> u32 {
206-
//! kani::assert(divisor != 0, "divisor != 0");
207-
//! let dividend_renamed = kani::internal::untracked_deref(&dividend);
208-
//! let divisor_renamed = kani::internal::untracked_deref(&divisor);
209-
//! let result = kani::any();
210-
//! kani::assume(result <= dividend_renamed, "result <= dividend");
211-
//! std::mem::forget(dividend_renamed);
205+
//! #[allow(dead_code, unused_variables)]
206+
//! #[kanitool :: is_contract_generated(replace)] fn
207+
//! div_replace_b97df2(dividend : u32, divisor : u32) -> u32
208+
//! {
209+
//! let divisor_renamed = kani::internal::untracked_deref(& divisor);
210+
//! let dividend_renamed = kani::internal::untracked_deref(& dividend);
211+
//! kani::assert(divisor != 0, stringify! (divisor != 0));
212+
//! let result_kani_internal : u32 = kani::any_modifies();
213+
//! kani::assume(
214+
//! (|result : & u32| *result <= dividend_renamed)(&result_kani_internal));
212215
//! std::mem::forget(divisor_renamed);
213-
//! result
216+
//! std::mem::forget(dividend_renamed);
217+
//! result_kani_internal
214218
//! }
215219
//!
216220
//! #[allow(dead_code)]
@@ -220,12 +224,12 @@
220224
//! static mut REENTRY: bool = false;
221225
//!
222226
//! if unsafe { REENTRY } {
223-
//! div_replace_965916(dividend, divisor)
227+
//! div_replace_b97df2(dividend, divisor)
224228
//! } else {
225229
//! unsafe { reentry = true };
226-
//! let result = div_check_965916(dividend, divisor);
230+
//! let result_kani_internal = div_check_b97df2(dividend, divisor);
227231
//! unsafe { reentry = false };
228-
//! result
232+
//! result_kani_internal
229233
//! }
230234
//! }
231235
//! ```
@@ -243,6 +247,8 @@ mod initialize;
243247
mod replace;
244248
mod shared;
245249

250+
const INTERNAL_RESULT_IDENT: &str = "result_kani_internal";
251+
246252
pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream {
247253
contract_main(attr, item, ContractConditionsType::Requires)
248254
}

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33

44
//! Logic used for generating the code that replaces a function with its contract.
55
6-
use proc_macro2::{Ident, TokenStream as TokenStream2};
6+
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
77
use quote::quote;
88

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

1515
impl<'a> ContractConditionsHandler<'a> {
@@ -39,7 +39,8 @@ impl<'a> ContractConditionsHandler<'a> {
3939
fn ensure_bootstrapped_replace_body(&self) -> (Vec<syn::Stmt>, Vec<syn::Stmt>) {
4040
if self.is_first_emit() {
4141
let return_type = return_type_to_type(&self.annotated_fn.sig.output);
42-
(vec![syn::parse_quote!(let result : #return_type = kani::any_modifies();)], vec![])
42+
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
43+
(vec![syn::parse_quote!(let #result : #return_type = kani::any_modifies();)], vec![])
4344
} else {
4445
let stmts = &self.annotated_fn.block.stmts;
4546
let idx = stmts
@@ -70,30 +71,33 @@ impl<'a> ContractConditionsHandler<'a> {
7071
match &self.condition_type {
7172
ContractConditionsData::Requires { attr } => {
7273
let Self { attr_copy, .. } = self;
74+
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
7375
quote!(
7476
kani::assert(#attr, stringify!(#attr_copy));
7577
#(#before)*
7678
#(#after)*
77-
result
79+
#result
7880
)
7981
}
8082
ContractConditionsData::Ensures { attr, argument_names } => {
8183
let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names);
84+
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
8285
quote!(
8386
#arg_copies
8487
#(#before)*
8588
#(#after)*
8689
kani::assume(#attr);
8790
#copy_clean
88-
result
91+
#result
8992
)
9093
}
9194
ContractConditionsData::Modifies { attr } => {
95+
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
9296
quote!(
9397
#(#before)*
9498
#(*unsafe { kani::internal::Pointer::assignable(#attr) } = kani::any_modifies();)*
9599
#(#after)*
96-
result
100+
#result
97101
)
98102
}
99103
}
@@ -126,7 +130,7 @@ impl<'a> ContractConditionsHandler<'a> {
126130
}
127131
}
128132

129-
/// Is this statement `let result : <...> = kani::any_modifies();`.
133+
/// Is this statement `let result_kani_internal : <...> = kani::any_modifies();`.
130134
fn is_replace_return_havoc(stmt: &syn::Stmt) -> bool {
131135
let Some(syn::LocalInit { diverge: None, expr: e, .. }) = try_as_result_assign(stmt) else {
132136
return false;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
1313
use quote::{quote, ToTokens};
1414
use syn::Attribute;
1515

16-
use super::{ContractConditionsHandler, ContractFunctionState};
16+
use super::{ContractConditionsHandler, ContractFunctionState, INTERNAL_RESULT_IDENT};
1717

1818
impl ContractFunctionState {
1919
/// Do we need to emit the `is_contract_generated` tag attribute on the
@@ -114,7 +114,7 @@ macro_rules! try_as_result_assign_pat {
114114
ident: result_ident,
115115
subpat: None,
116116
..
117-
}) if result_ident == "result"
117+
}) if result_ident == INTERNAL_RESULT_IDENT
118118
) => init.$convert(),
119119
_ => None,
120120
}

0 commit comments

Comments
 (0)