Skip to content

Commit 409a83d

Browse files
Refactoring contracts macro logic into separate modules (#2992)
Refactors the `contracts` module into several smaller modules grouped by task. The previous modules was very long and interleaved a lot of logic for various parts of the contracts state machine. This refactoring groups functions by specificity, task correlation and reusability. All code changes here are exclusively moves with no changes to functionality. The split is as follows: - `mod.rs` contains definitions of the data structures used to handle contracts, the entry point for contract handling, as well as a documentation overview. - `bootstrap.rs` is the logic that initialized the central data structures, such as the handler and the functions state - `check.rs` is all the logic required to create the function that checks a contract. - `replace.rs` is all the logic required to create a function that abstracts via contract - `shared.rs` are Kani/contract-specific helper functions that are used in more than one of `bootstrap`, `check` or `replace` - `helpers.rs` are Kani/contract-independent helper functions I was going to make the `old` PR but I shudder at the though of appending more logic to the extremely long `contracts.rs` file so I figured I'd split the logic real quick before adding more. 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: Felipe R. Monteiro <rms.felipe@gmail.com>
1 parent 8a1b550 commit 409a83d

File tree

8 files changed

+1612
-1509
lines changed

8 files changed

+1612
-1509
lines changed

library/kani_macros/src/sysroot/contracts.rs

Lines changed: 0 additions & 1509 deletions
This file was deleted.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Special way we handle the first time we encounter a contract attribute on a
5+
//! function.
6+
7+
use proc_macro2::Span;
8+
use quote::quote;
9+
use syn::ItemFn;
10+
11+
use super::{
12+
helpers::*,
13+
shared::{attach_require_kani_any, identifier_for_generated_function},
14+
ContractConditionsData, ContractConditionsHandler,
15+
};
16+
17+
impl<'a> ContractConditionsHandler<'a> {
18+
/// The complex case. We are the first time a contract is handled on this function, so
19+
/// we're responsible for
20+
///
21+
/// 1. Generating a name for the check function
22+
/// 2. Emitting the original, unchanged item and register the check
23+
/// function on it via attribute
24+
/// 3. Renaming our item to the new name
25+
/// 4. And (minor point) adding #[allow(dead_code)] and
26+
/// #[allow(unused_variables)] to the check function attributes
27+
pub fn handle_untouched(&mut self) {
28+
// We'll be using this to postfix the generated names for the "check"
29+
// and "replace" functions.
30+
let item_hash = self.hash.unwrap();
31+
32+
let original_function_name = self.annotated_fn.sig.ident.clone();
33+
34+
let check_fn_name =
35+
identifier_for_generated_function(&original_function_name, "check", item_hash);
36+
let replace_fn_name =
37+
identifier_for_generated_function(&original_function_name, "replace", item_hash);
38+
let recursion_wrapper_name = identifier_for_generated_function(
39+
&original_function_name,
40+
"recursion_wrapper",
41+
item_hash,
42+
);
43+
44+
// Constructing string literals explicitly here, because `stringify!`
45+
// doesn't work. Let's say we have an identifier `check_fn` and we were
46+
// to do `quote!(stringify!(check_fn))` to try to have it expand to
47+
// `"check_fn"` in the generated code. Then when the next macro parses
48+
// this it will *not* see the literal `"check_fn"` as you may expect but
49+
// instead the *expression* `stringify!(check_fn)`.
50+
let replace_fn_name_str = syn::LitStr::new(&replace_fn_name.to_string(), Span::call_site());
51+
let wrapper_fn_name_str =
52+
syn::LitStr::new(&self.make_wrapper_name().to_string(), Span::call_site());
53+
let recursion_wrapper_name_str =
54+
syn::LitStr::new(&recursion_wrapper_name.to_string(), Span::call_site());
55+
56+
// The order of `attrs` and `kanitool::{checked_with,
57+
// is_contract_generated}` is important here, because macros are
58+
// expanded outside in. This way other contract annotations in `attrs`
59+
// sees those attributes and can use them to determine
60+
// `function_state`.
61+
//
62+
// The same care is taken when we emit check and replace functions.
63+
// emit the check function.
64+
let is_impl_fn = is_probably_impl_fn(&self.annotated_fn);
65+
let ItemFn { attrs, vis, sig, block } = &self.annotated_fn;
66+
self.output.extend(quote!(
67+
#(#attrs)*
68+
#[kanitool::checked_with = #recursion_wrapper_name_str]
69+
#[kanitool::replaced_with = #replace_fn_name_str]
70+
#[kanitool::inner_check = #wrapper_fn_name_str]
71+
#vis #sig {
72+
#block
73+
}
74+
));
75+
76+
let mut wrapper_sig = sig.clone();
77+
attach_require_kani_any(&mut wrapper_sig);
78+
wrapper_sig.ident = recursion_wrapper_name;
79+
80+
let args = pats_to_idents(&mut wrapper_sig.inputs).collect::<Vec<_>>();
81+
let also_args = args.iter();
82+
let (call_check, call_replace) = if is_impl_fn {
83+
(quote!(Self::#check_fn_name), quote!(Self::#replace_fn_name))
84+
} else {
85+
(quote!(#check_fn_name), quote!(#replace_fn_name))
86+
};
87+
88+
self.output.extend(quote!(
89+
#[allow(dead_code, unused_variables)]
90+
#[kanitool::is_contract_generated(recursion_wrapper)]
91+
#wrapper_sig {
92+
static mut REENTRY: bool = false;
93+
if unsafe { REENTRY } {
94+
#call_replace(#(#args),*)
95+
} else {
96+
unsafe { REENTRY = true };
97+
let result = #call_check(#(#also_args),*);
98+
unsafe { REENTRY = false };
99+
result
100+
}
101+
}
102+
));
103+
104+
self.emit_check_function(Some(check_fn_name));
105+
self.emit_replace_function(Some(replace_fn_name));
106+
self.emit_augmented_modifies_wrapper();
107+
}
108+
}
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Logic used for generating the code that checks a contract.
5+
6+
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
7+
use quote::quote;
8+
use syn::{Expr, FnArg, ItemFn, Token};
9+
10+
use super::{
11+
helpers::*,
12+
shared::{make_unsafe_argument_copies, try_as_result_assign_mut},
13+
ContractConditionsData, ContractConditionsHandler,
14+
};
15+
16+
impl<'a> ContractConditionsHandler<'a> {
17+
/// Create the body of a check function.
18+
///
19+
/// Wraps the conditions from this attribute around `self.body`.
20+
///
21+
/// Mutable because a `modifies` clause may need to extend the inner call to
22+
/// the wrapper with new arguments.
23+
pub fn make_check_body(&mut self) -> TokenStream2 {
24+
let mut inner = self.ensure_bootstrapped_check_body();
25+
let Self { attr_copy, .. } = self;
26+
27+
match &self.condition_type {
28+
ContractConditionsData::Requires { attr } => {
29+
quote!(
30+
kani::assume(#attr);
31+
#(#inner)*
32+
)
33+
}
34+
ContractConditionsData::Ensures { argument_names, attr } => {
35+
let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names);
36+
37+
// The code that enforces the postconditions and cleans up the shallow
38+
// argument copies (with `mem::forget`).
39+
let exec_postconditions = quote!(
40+
kani::assert(#attr, stringify!(#attr_copy));
41+
#copy_clean
42+
);
43+
44+
assert!(matches!(
45+
inner.pop(),
46+
Some(syn::Stmt::Expr(syn::Expr::Path(pexpr), None))
47+
if pexpr.path.get_ident().map_or(false, |id| id == "result")
48+
));
49+
50+
quote!(
51+
#arg_copies
52+
#(#inner)*
53+
#exec_postconditions
54+
result
55+
)
56+
}
57+
ContractConditionsData::Modifies { attr } => {
58+
let wrapper_name = self.make_wrapper_name().to_string();
59+
60+
let wrapper_args = if let Some(wrapper_call_args) =
61+
inner.iter_mut().find_map(|stmt| try_as_wrapper_call_args(stmt, &wrapper_name))
62+
{
63+
let wrapper_args = make_wrapper_args(wrapper_call_args.len(), attr.len());
64+
wrapper_call_args
65+
.extend(wrapper_args.clone().map(|a| Expr::Verbatim(quote!(#a))));
66+
wrapper_args
67+
} else {
68+
unreachable!(
69+
"Invariant broken, check function did not contain a call to the wrapper function"
70+
)
71+
};
72+
73+
quote!(
74+
#(let #wrapper_args = unsafe { kani::internal::Pointer::decouple_lifetime(&#attr) };)*
75+
#(#inner)*
76+
)
77+
}
78+
}
79+
}
80+
81+
/// Get the sequence of statements of the previous check body or create the default one.
82+
fn ensure_bootstrapped_check_body(&self) -> Vec<syn::Stmt> {
83+
let wrapper_name = self.make_wrapper_name();
84+
let return_type = return_type_to_type(&self.annotated_fn.sig.output);
85+
if self.is_first_emit() {
86+
let args = exprs_for_args(&self.annotated_fn.sig.inputs);
87+
let wrapper_call = if is_probably_impl_fn(self.annotated_fn) {
88+
quote!(Self::#wrapper_name)
89+
} else {
90+
quote!(#wrapper_name)
91+
};
92+
syn::parse_quote!(
93+
let result : #return_type = #wrapper_call(#(#args),*);
94+
result
95+
)
96+
} else {
97+
self.annotated_fn.block.stmts.clone()
98+
}
99+
}
100+
101+
/// Emit the check function into the output stream.
102+
///
103+
/// See [`Self::make_check_body`] for the most interesting parts of this
104+
/// function.
105+
pub fn emit_check_function(&mut self, override_function_dent: Option<Ident>) {
106+
self.emit_common_header();
107+
108+
if self.function_state.emit_tag_attr() {
109+
// If it's the first time we also emit this marker. Again, order is
110+
// important so this happens as the last emitted attribute.
111+
self.output.extend(quote!(#[kanitool::is_contract_generated(check)]));
112+
}
113+
let body = self.make_check_body();
114+
let mut sig = self.annotated_fn.sig.clone();
115+
if let Some(ident) = override_function_dent {
116+
sig.ident = ident;
117+
}
118+
self.output.extend(quote!(
119+
#sig {
120+
#body
121+
}
122+
))
123+
}
124+
125+
/// Emit a modifies wrapper, possibly augmenting a prior, existing one.
126+
///
127+
/// We only augment if this clause is a `modifies` clause. In that case we
128+
/// expand its signature with one new argument of type `&impl Arbitrary` for
129+
/// each expression in the clause.
130+
pub fn emit_augmented_modifies_wrapper(&mut self) {
131+
if let ContractConditionsData::Modifies { attr } = &self.condition_type {
132+
let wrapper_args = make_wrapper_args(self.annotated_fn.sig.inputs.len(), attr.len());
133+
let sig = &mut self.annotated_fn.sig;
134+
for arg in wrapper_args.clone() {
135+
let lifetime = syn::Lifetime { apostrophe: Span::call_site(), ident: arg.clone() };
136+
sig.inputs.push(FnArg::Typed(syn::PatType {
137+
attrs: vec![],
138+
colon_token: Token![:](Span::call_site()),
139+
pat: Box::new(syn::Pat::Verbatim(quote!(#arg))),
140+
ty: Box::new(syn::Type::Verbatim(quote!(&#lifetime impl kani::Arbitrary))),
141+
}));
142+
sig.generics.params.push(syn::GenericParam::Lifetime(syn::LifetimeParam {
143+
lifetime,
144+
colon_token: None,
145+
bounds: Default::default(),
146+
attrs: vec![],
147+
}));
148+
}
149+
self.output.extend(quote!(#[kanitool::modifies(#(#wrapper_args),*)]))
150+
}
151+
self.emit_common_header();
152+
153+
if self.function_state.emit_tag_attr() {
154+
// If it's the first time we also emit this marker. Again, order is
155+
// important so this happens as the last emitted attribute.
156+
self.output.extend(quote!(#[kanitool::is_contract_generated(wrapper)]));
157+
}
158+
159+
let name = self.make_wrapper_name();
160+
let ItemFn { vis, sig, block, .. } = self.annotated_fn;
161+
162+
let mut sig = sig.clone();
163+
sig.ident = name;
164+
self.output.extend(quote!(
165+
#vis #sig #block
166+
));
167+
}
168+
}
169+
170+
/// Try to interpret this statement as `let result : <...> = <wrapper_fn_name>(args ...);` and
171+
/// return a mutable reference to the parameter list.
172+
fn try_as_wrapper_call_args<'a>(
173+
stmt: &'a mut syn::Stmt,
174+
wrapper_fn_name: &str,
175+
) -> Option<&'a mut syn::punctuated::Punctuated<syn::Expr, syn::token::Comma>> {
176+
let syn::LocalInit { diverge: None, expr: init_expr, .. } = try_as_result_assign_mut(stmt)?
177+
else {
178+
return None;
179+
};
180+
181+
match init_expr.as_mut() {
182+
Expr::Call(syn::ExprCall { func: box_func, args, .. }) => match box_func.as_ref() {
183+
syn::Expr::Path(syn::ExprPath { qself: None, path, .. })
184+
if path.get_ident().map_or(false, |id| id == wrapper_fn_name) =>
185+
{
186+
Some(args)
187+
}
188+
_ => None,
189+
},
190+
_ => None,
191+
}
192+
}
193+
194+
/// Make `num` [`Ident`]s with the names `_wrapper_arg_{i}` with `i` starting at `low` and
195+
/// increasing by one each time.
196+
fn make_wrapper_args(low: usize, num: usize) -> impl Iterator<Item = syn::Ident> + Clone {
197+
(low..).map(|i| Ident::new(&format!("_wrapper_arg_{i}"), Span::mixed_site())).take(num)
198+
}
199+
200+
#[cfg(test)]
201+
mod test {
202+
macro_rules! detect_impl_fn {
203+
($expect_pass:expr, $($tt:tt)*) => {{
204+
let syntax = stringify!($($tt)*);
205+
let ast = syn::parse_str(syntax).unwrap();
206+
assert!($expect_pass == super::is_probably_impl_fn(&ast),
207+
"Incorrect detection.\nExpected is_impl_fun: {}\nInput Expr; {}\nParsed: {:?}",
208+
$expect_pass,
209+
syntax,
210+
ast
211+
);
212+
}}
213+
}
214+
215+
#[test]
216+
fn detect_impl_fn_by_receiver() {
217+
detect_impl_fn!(true, fn self_by_ref(&self, u: usize) -> bool {});
218+
219+
detect_impl_fn!(true, fn self_by_self(self, u: usize) -> bool {});
220+
}
221+
222+
#[test]
223+
fn detect_impl_fn_by_self_ty() {
224+
detect_impl_fn!(true, fn self_by_construct(u: usize) -> Self {});
225+
detect_impl_fn!(true, fn self_by_wrapped_construct(u: usize) -> Arc<Self> {});
226+
227+
detect_impl_fn!(true, fn self_by_other_arg(u: usize, slf: Self) {});
228+
229+
detect_impl_fn!(true, fn self_by_other_wrapped_arg(u: usize, slf: Vec<Self>) {})
230+
}
231+
232+
#[test]
233+
fn detect_impl_fn_by_qself() {
234+
detect_impl_fn!(
235+
true,
236+
fn self_by_mention(u: usize) {
237+
Self::other(u)
238+
}
239+
);
240+
}
241+
242+
#[test]
243+
fn detect_no_impl_fn() {
244+
detect_impl_fn!(
245+
false,
246+
fn self_by_mention(u: usize) {
247+
let self_name = 18;
248+
let self_lit = "self";
249+
let self_lit = "Self";
250+
}
251+
);
252+
}
253+
}

0 commit comments

Comments
 (0)