|
| 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), |
| 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