From 2e6bed3eeae1d21fd07691935c668d7dadc20a8d Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Sat, 30 Sep 2023 12:17:50 -0400 Subject: [PATCH 01/72] WIP --- library/kani_macros/src/sysroot/contracts.rs | 267 ++++++++++++++++--- 1 file changed, 236 insertions(+), 31 deletions(-) diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 242d3e4deba0..5aadcf59a2eb 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -215,13 +215,42 @@ fn identifier_for_generated_function(related_function: &ItemFn, purpose: &str, h } pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream { - requires_ensures_main(attr, item, true) + requires_ensures_main(attr, item, 0) } pub fn ensures(attr: TokenStream, item: TokenStream) -> TokenStream { - requires_ensures_main(attr, item, false) + requires_ensures_main(attr, item, 1) } +pub fn modifies(attr: TokenStream, item: TokenStream) -> TokenStream { + requires_ensures_main(attr, item, 2) +} + +fn is_token_stream_2_comma(t: &proc_macro2::TokenTree) -> bool { + matches!(t, proc_macro2::TokenTree::Punct(p) if p.as_char() == ',') +} + +fn chunks_by<'a, T, C: Default + Extend>( + i: impl IntoIterator + 'a, + mut pred: impl FnMut(&T) -> bool + 'a, +) -> impl Iterator + 'a { + let mut iter = i.into_iter(); + std::iter::from_fn(move || { + let mut new = C::default(); + let mut empty = true; + while let Some(tok) = iter.next() { + empty = false; + if pred(&tok) { + break; + } else { + new.extend([tok]) + } + } + (!empty).then_some(new) + }) +} + + /// Collect all named identifiers used in the argument patterns of a function. struct ArgumentIdentCollector(HashSet); @@ -299,6 +328,7 @@ enum ContractFunctionState { /// This is a replace function that was generated from a previous evaluation /// of a contract attribute. Replace, + ModifiesWrapper, } impl<'a> TryFrom<&'a syn::Attribute> for ContractFunctionState { @@ -436,10 +466,8 @@ struct ContractConditionsHandler<'a> { function_state: ContractFunctionState, /// Information specific to the type of contract attribute we're expanding. condition_type: ContractConditionsType, - /// The contents of the attribute. - attr: Expr, /// Body of the function this attribute was found on. - annotated_fn: &'a ItemFn, + annotated_fn: &'a mut ItemFn, /// An unparsed, unmodified copy of `attr`, used in the error messages. attr_copy: TokenStream2, /// The stream to which we should write the generated code. @@ -449,12 +477,18 @@ struct ContractConditionsHandler<'a> { /// Information needed for generating check and replace handlers for different /// contract attributes. enum ContractConditionsType { - Requires, + Requires { + /// The contents of the attribute. + attr: Expr, + }, Ensures { /// Translation map from original argument names to names of the copies /// we will be emitting. argument_names: HashMap, + /// The contents of the attribute. + attr: Expr, }, + Modifies { attr: Vec }, } impl ContractConditionsType { @@ -463,46 +497,65 @@ impl ContractConditionsType { /// /// Renames the [`Ident`]s used in `attr` and stores the translation map in /// `argument_names`. - fn new_ensures(sig: &Signature, attr: &mut Expr) -> Self { - let argument_names = rename_argument_occurrences(sig, attr); - ContractConditionsType::Ensures { argument_names } + fn new_ensures(sig: &Signature, mut attr: Expr) -> Self { + let argument_names = rename_argument_occurrences(sig, &mut attr); + ContractConditionsType::Ensures { argument_names, attr } } } impl<'a> ContractConditionsHandler<'a> { + fn is_fist_emit(&self) -> bool { + matches!(self.function_state, ContractFunctionState::Untouched) + } + /// Initialize the handler. Constructs the required /// [`ContractConditionsType`] depending on `is_requires`. fn new( function_state: ContractFunctionState, - is_requires: bool, - mut attr: Expr, - annotated_fn: &'a ItemFn, + is_requires: u8, + attr: TokenStream, + annotated_fn: &'a mut ItemFn, attr_copy: TokenStream2, output: &'a mut TokenStream2, - ) -> Self { - let condition_type = if is_requires { - ContractConditionsType::Requires - } else { - ContractConditionsType::new_ensures(&annotated_fn.sig, &mut attr) + ) -> Result { + let condition_type = match is_requires { + 0 => ContractConditionsType::Requires { attr: syn::parse(attr)? }, + 1 => ContractConditionsType::new_ensures(&annotated_fn.sig, syn::parse(attr)?), + 2 => ContractConditionsType::Modifies { + attr: chunks_by(TokenStream2::from(attr), is_token_stream_2_comma) + .map(syn::parse2) + .filter_map(|expr| match expr { + Err(e) => { + output.extend(e.into_compile_error()); + None + } + Ok(expr) => Some(expr) + }) + .collect() + }, + _ => unreachable!(), }; - Self { function_state, condition_type, attr, annotated_fn, attr_copy, output } + Ok(Self { function_state, condition_type, annotated_fn, attr_copy, output }) } /// Create the body of a check function. /// /// Wraps the conditions from this attribute around `self.body`. - fn make_check_body(&self) -> TokenStream2 { - let Self { attr, attr_copy, .. } = self; + /// + /// Mutable because a `modifies` clause may need to extend the inner call to + /// the wrapper with new arguments. + fn make_check_body(&mut self) -> TokenStream2 { + let Self { attr_copy, .. } = self; let ItemFn { sig, block, .. } = self.annotated_fn; let return_type = return_type_to_type(&sig.output); match &self.condition_type { - ContractConditionsType::Requires => quote!( + ContractConditionsType::Requires { attr }=> quote!( kani::assume(#attr); #block ), - ContractConditionsType::Ensures { argument_names } => { + ContractConditionsType::Ensures { argument_names, attr } => { let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names); // The code that enforces the postconditions and cleans up the shallow @@ -527,6 +580,41 @@ impl<'a> ContractConditionsHandler<'a> { result ) } + ContractConditionsType::Modifies { attr } => { + let wrapper_args = make_wrapper_args(attr.len()); + // TODO handle first invocation where this is the actual body. + if !self.is_fist_emit() { + if let Some(wrapper_call_args) = + self.annotated_fn.block.stmts.iter_mut().find_map(try_as_wrapper_call_args) { + wrapper_call_args.extend(wrapper_args.clone().map(|a| Expr::Verbatim(quote!(#a)))); + } else { + unreachable!("Invariant broken, check function did not contain a call to the wrapper function") + } + } + + let inner = self.create_inner_call(wrapper_args.clone()); + let wrapper_args = make_wrapper_args(attr.len()); + + quote!( + #(let #wrapper_args = kani::untracked_deref(#attr);)* + #inner + ) + } + } + } + + fn create_inner_call(&self, additional_args: impl Iterator) -> TokenStream2 { + if self.is_fist_emit() { + let args = self.annotated_fn.sig.inputs.iter().map(pat_to_expr); + quote!( + let result = #wrapper_name(#(#args,)* #(#additional_args),*); + result + ) + } else { + let stmts = &self.annotated_fn.block.stmts; + quote!( + #(#stmts)* + ) } } @@ -539,18 +627,18 @@ impl<'a> ContractConditionsHandler<'a> { /// `use_nondet_result` will only be true if this is the first time we are /// generating a replace function. fn make_replace_body(&self, use_nondet_result: bool) -> TokenStream2 { - let Self { attr, attr_copy, .. } = self; + let Self { attr_copy, .. } = self; let ItemFn { sig, block, .. } = self.annotated_fn; let call_to_prior = if use_nondet_result { quote!(kani::any()) } else { block.to_token_stream() }; let return_type = return_type_to_type(&sig.output); match &self.condition_type { - ContractConditionsType::Requires => quote!( + ContractConditionsType::Requires { attr } => quote!( kani::assert(#attr, stringify!(#attr_copy)); #call_to_prior ), - ContractConditionsType::Ensures { argument_names } => { + ContractConditionsType::Ensures { attr, argument_names } => { let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names); quote!( #arg_copies @@ -560,6 +648,7 @@ impl<'a> ContractConditionsHandler<'a> { result ) } + ContractConditionsType::Modifies { attr } => unreachable!("Replacement with modifies not supported"), } } @@ -624,6 +713,120 @@ impl<'a> ContractConditionsHandler<'a> { } } +fn pat_to_expr(pat: &syn::Pat) -> Expr { + use syn::Pat; + let mk_err = |typ| { + pat.span() + .unwrap() + .error(format!("`{typ}` patterns are not supported for functions with contracts")) + .emit(); + unreachable!() + }; + match pat { + Pat::Const(c) => Expr::Const(c.clone()), + Pat::Ident(id) => Expr::Verbatim(id.ident.to_token_stream()), + Pat::Lit(lit) => Expr::Lit(lit.clone()), + Pat::Reference(rf) => Expr::Reference(syn::ExprReference { + attrs: vec![], + and_token: rf.and_token, + mutability: rf.mutability, + expr: Box::new(pat_to_expr(&rf.pat)), + }), + Pat::Tuple(tup) => Expr::Tuple(syn::ExprTuple { + attrs: vec![], + paren_token: tup.paren_token, + elems: tup.elems.iter().map(pat_to_expr).collect(), + }), + Pat::Slice(slice) => Expr::Reference(syn::ExprReference { + attrs: vec![], + and_token: syn::Token!(&)(Span::call_site()), + mutability: None, + expr: Box::new(Expr::Array(syn::ExprArray { + attrs: vec![], + bracket_token: slice.bracket_token, + elems: slice.elems.iter().map(pat_to_expr).collect(), + })), + }), + Pat::Path(pth) => Expr::Path(pth.clone()), + Pat::Or(_) => mk_err("or"), + Pat::Rest(_) => mk_err("rest"), + Pat::Wild(_) => mk_err("wildcard"), + Pat::Paren(inner) => pat_to_expr(&inner.pat), + Pat::Range(_) => mk_err("range"), + Pat::Struct(strct) => { + if strct.rest.is_some() { + mk_err(".."); + } + Expr::Struct(syn::ExprStruct { + attrs: vec![], + path: strct.path.clone(), + brace_token: strct.brace_token, + dot2_token: None, + rest: None, + qself: strct.qself.clone(), + fields: strct + .fields + .iter() + .map(|field_pat| syn::FieldValue { + attrs: vec![], + member: field_pat.member.clone(), + colon_token: field_pat.colon_token, + expr: pat_to_expr(&field_pat.pat), + }) + .collect(), + }) + } + Pat::Verbatim(_) => mk_err("verbatim"), + Pat::Type(_) => mk_err("type"), + Pat::TupleStruct(_) => mk_err("tuple struct"), + _ => mk_err("unknown"), + } +} + +fn try_as_wrapper_call_args(stmt: &mut syn::Stmt) -> Option<&mut syn::punctuated::Punctuated> { + match stmt { + syn::Stmt::Local(syn::Local { + pat: syn::Pat::Ident(syn::PatIdent { + by_ref: None, + mutability: None, + ident: result_ident, + subpat: None, + .. + }), + init: Some(syn::LocalInit { + diverge: None, + expr: init_expr, + .. + }), + .. + }) if result_ident == "result" => match init_expr.as_mut() { + Expr::Call(syn::ExprCall { + func: box_func, + args, + .. + }) => match box_func.as_ref() { + syn::Expr::Path(syn::ExprPath { + qself: None, + path, + .. + }) if path.get_ident().map_or(false, is_wrapper_fn) => { + Some(args) + } + _ => None, + } + _ => None, + } + _ => None, + } +} + +fn make_wrapper_args(num: usize) -> impl Iterator + Clone { +} + +fn is_wrapper_fn(ident: &syn::Ident) -> bool { + +} + /// If an explicit return type was provided it is returned, otherwise `()`. fn return_type_to_type(return_type: &syn::ReturnType) -> Cow { match return_type { @@ -707,13 +910,12 @@ fn make_unsafe_argument_copies( /// /// See the [module level documentation][self] for a description of how the code /// generation works. -fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: bool) -> TokenStream { +fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) -> TokenStream { let attr_copy = TokenStream2::from(attr.clone()); - let attr = parse_macro_input!(attr as Expr); let mut output = proc_macro2::TokenStream::new(); let item_stream_clone = item.clone(); - let item_fn = parse_macro_input!(item as ItemFn); + let mut item_fn = parse_macro_input!(item as ItemFn); let function_state = ContractFunctionState::from_attributes(&item_fn.attrs); @@ -729,14 +931,17 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: bool return item_fn.into_token_stream().into(); } - let mut handler = ContractConditionsHandler::new( + let mut handler = match ContractConditionsHandler::new( function_state, is_requires, attr, - &item_fn, + &mut item_fn, attr_copy, &mut output, - ); + ) { + Ok(handler) => handler, + Err(e) => return e.into_compile_error().into(), + }; match function_state { ContractFunctionState::Check => { From c772a100e3e6663374d94490e121be768b0171ad Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Sat, 30 Sep 2023 16:12:55 -0700 Subject: [PATCH 02/72] Sketch for new modifies clause --- library/kani_macros/src/sysroot/contracts.rs | 251 ++++++++++++------- 1 file changed, 157 insertions(+), 94 deletions(-) diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 5aadcf59a2eb..9e4f7ed8b699 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -174,8 +174,8 @@ use std::{ collections::{HashMap, HashSet}, }; use syn::{ - parse_macro_input, spanned::Spanned, visit::Visit, visit_mut::VisitMut, Attribute, Expr, - ItemFn, PredicateType, ReturnType, Signature, TraitBound, TypeParamBound, WhereClause, + parse_macro_input, spanned::Spanned, visit::Visit, visit_mut::VisitMut, Attribute, Expr, FnArg, + ItemFn, PredicateType, ReturnType, Signature, Token, TraitBound, TypeParamBound, WhereClause, }; /// Create a unique hash for a token stream (basically a [`std::hash::Hash`] @@ -209,8 +209,12 @@ fn short_hash_of_token_stream(stream: &proc_macro::TokenStream) -> u64 { /// Makes consistent names for a generated function which was created for /// `purpose`, from an attribute that decorates `related_function` with the /// hash `hash`. -fn identifier_for_generated_function(related_function: &ItemFn, purpose: &str, hash: u64) -> Ident { - let identifier = format!("{}_{purpose}_{hash:x}", related_function.sig.ident); +fn identifier_for_generated_function( + related_function_name: &Ident, + purpose: &str, + hash: u64, +) -> Ident { + let identifier = format!("{}_{purpose}_{hash:x}", related_function_name); Ident::new(&identifier, proc_macro2::Span::mixed_site()) } @@ -222,6 +226,7 @@ pub fn ensures(attr: TokenStream, item: TokenStream) -> TokenStream { requires_ensures_main(attr, item, 1) } +#[allow(dead_code)] pub fn modifies(attr: TokenStream, item: TokenStream) -> TokenStream { requires_ensures_main(attr, item, 2) } @@ -250,7 +255,6 @@ fn chunks_by<'a, T, C: Default + Extend>( }) } - /// Collect all named identifiers used in the argument patterns of a function. struct ArgumentIdentCollector(HashSet); @@ -345,6 +349,7 @@ impl<'a> TryFrom<&'a syn::Attribute> for ContractFunctionState { return match ident_str.as_str() { "check" => Ok(Self::Check), "replace" => Ok(Self::Replace), + "wrapper" => Ok(Self::ModifiesWrapper), _ => { Err(Some(lst.span().unwrap().error("Expected `check` or `replace` ident"))) } @@ -472,6 +477,7 @@ struct ContractConditionsHandler<'a> { attr_copy: TokenStream2, /// The stream to which we should write the generated code. output: &'a mut TokenStream2, + hash: Option, } /// Information needed for generating check and replace handlers for different @@ -488,7 +494,9 @@ enum ContractConditionsType { /// The contents of the attribute. attr: Expr, }, - Modifies { attr: Vec }, + Modifies { + attr: Vec, + }, } impl ContractConditionsType { @@ -517,44 +525,46 @@ impl<'a> ContractConditionsHandler<'a> { annotated_fn: &'a mut ItemFn, attr_copy: TokenStream2, output: &'a mut TokenStream2, + hash: Option, ) -> Result { let condition_type = match is_requires { 0 => ContractConditionsType::Requires { attr: syn::parse(attr)? }, 1 => ContractConditionsType::new_ensures(&annotated_fn.sig, syn::parse(attr)?), - 2 => ContractConditionsType::Modifies { + 2 => ContractConditionsType::Modifies { attr: chunks_by(TokenStream2::from(attr), is_token_stream_2_comma) - .map(syn::parse2) - .filter_map(|expr| match expr { - Err(e) => { - output.extend(e.into_compile_error()); - None - } - Ok(expr) => Some(expr) - }) - .collect() + .map(syn::parse2) + .filter_map(|expr| match expr { + Err(e) => { + output.extend(e.into_compile_error()); + None + } + Ok(expr) => Some(expr), + }) + .collect(), }, _ => unreachable!(), }; - Ok(Self { function_state, condition_type, annotated_fn, attr_copy, output }) + Ok(Self { function_state, condition_type, annotated_fn, attr_copy, output, hash }) } /// Create the body of a check function. /// /// Wraps the conditions from this attribute around `self.body`. - /// + /// /// Mutable because a `modifies` clause may need to extend the inner call to /// the wrapper with new arguments. fn make_check_body(&mut self) -> TokenStream2 { let Self { attr_copy, .. } = self; - let ItemFn { sig, block, .. } = self.annotated_fn; - let return_type = return_type_to_type(&sig.output); match &self.condition_type { - ContractConditionsType::Requires { attr }=> quote!( - kani::assume(#attr); - #block - ), + ContractConditionsType::Requires { attr } => { + let block = self.create_inner_call([].into_iter()); + quote!( + kani::assume(#attr); + #(#block)* + ) + } ContractConditionsType::Ensures { argument_names, attr } => { let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names); @@ -565,30 +575,35 @@ impl<'a> ContractConditionsHandler<'a> { #copy_clean ); - // We make a copy here because we'll modify it. Technically not - // necessary but could lead to weird results if - // `make_replace_body` were called after this if we modified in - // place. - let mut call = block.clone(); - let mut inject_conditions = PostconditionInjector(exec_postconditions.clone()); - inject_conditions.visit_block_mut(&mut call); + let mut call = self.create_inner_call([].into_iter()); + + assert!(matches!(call.pop(), Some(syn::Stmt::Expr(syn::Expr::Path(pexpr), None)) if pexpr.path.get_ident().map_or(false, |id| id == "result"))); quote!( #arg_copies - let result : #return_type = #call; + #(#call)* #exec_postconditions result ) } ContractConditionsType::Modifies { attr } => { + let wrapper_name = self.make_wrapper_name().to_string(); let wrapper_args = make_wrapper_args(attr.len()); // TODO handle first invocation where this is the actual body. if !self.is_fist_emit() { - if let Some(wrapper_call_args) = - self.annotated_fn.block.stmts.iter_mut().find_map(try_as_wrapper_call_args) { - wrapper_call_args.extend(wrapper_args.clone().map(|a| Expr::Verbatim(quote!(#a)))); + if let Some(wrapper_call_args) = self + .annotated_fn + .block + .stmts + .iter_mut() + .find_map(|stmt| try_as_wrapper_call_args(stmt, &wrapper_name)) + { + wrapper_call_args + .extend(wrapper_args.clone().map(|a| Expr::Verbatim(quote!(#a)))); } else { - unreachable!("Invariant broken, check function did not contain a call to the wrapper function") + unreachable!( + "Invariant broken, check function did not contain a call to the wrapper function" + ) } } @@ -597,24 +612,31 @@ impl<'a> ContractConditionsHandler<'a> { quote!( #(let #wrapper_args = kani::untracked_deref(#attr);)* - #inner + #(#inner)* ) } } } - fn create_inner_call(&self, additional_args: impl Iterator) -> TokenStream2 { + fn make_wrapper_name(&self) -> Ident { + if let Some(hash) = self.hash { + identifier_for_generated_function(&self.annotated_fn.sig.ident, "wrapper", hash) + } else { + self.annotated_fn.sig.ident.clone() + } + } + + fn create_inner_call(&self, additional_args: impl Iterator) -> Vec { + let wrapper_name = self.make_wrapper_name(); + let return_type = return_type_to_type(&self.annotated_fn.sig.output); if self.is_fist_emit() { - let args = self.annotated_fn.sig.inputs.iter().map(pat_to_expr); - quote!( - let result = #wrapper_name(#(#args,)* #(#additional_args),*); + let args = exprs_for_args(&self.annotated_fn.sig.inputs); + syn::parse_quote!( + let result : #return_type = #wrapper_name(#(#args,)* #(#additional_args),*); result ) } else { - let stmts = &self.annotated_fn.block.stmts; - quote!( - #(#stmts)* - ) + self.annotated_fn.block.stmts.clone() } } @@ -628,7 +650,7 @@ impl<'a> ContractConditionsHandler<'a> { /// generating a replace function. fn make_replace_body(&self, use_nondet_result: bool) -> TokenStream2 { let Self { attr_copy, .. } = self; - let ItemFn { sig, block, .. } = self.annotated_fn; + let ItemFn { sig, block, .. } = &*self.annotated_fn; let call_to_prior = if use_nondet_result { quote!(kani::any()) } else { block.to_token_stream() }; let return_type = return_type_to_type(&sig.output); @@ -648,7 +670,9 @@ impl<'a> ContractConditionsHandler<'a> { result ) } - ContractConditionsType::Modifies { attr } => unreachable!("Replacement with modifies not supported"), + ContractConditionsType::Modifies { .. } => { + quote!(kani::assert(false, "Replacement with modifies is not supported yet.")) + } } } @@ -711,6 +735,43 @@ impl<'a> ContractConditionsHandler<'a> { } self.output.extend(self.annotated_fn.attrs.iter().flat_map(Attribute::to_token_stream)); } + + fn emit_augmented_modifies_wrapper(&mut self) { + if let ContractConditionsType::Modifies { attr } = &self.condition_type { + self.annotated_fn.sig.inputs.extend(make_wrapper_args(attr.len()).map(|warg| { + FnArg::Typed(syn::PatType { + attrs: vec![], + colon_token: Token![:](Span::call_site()), + pat: Box::new(syn::Pat::Verbatim(quote!(#warg))), + ty: Box::new(syn::Type::Verbatim(quote!(&impl kani::Arbitrary))), + }) + })) + } + self.emit_common_header(); + + if self.function_state.emit_tag_attr() { + // If it's the first time we also emit this marker. Again, order is + // important so this happens as the last emitted attribute. + self.output.extend(quote!(#[kanitool::is_contract_generated(wrapper)])); + } + + let name = self.make_wrapper_name(); + let ItemFn { vis, sig, block, .. } = self.annotated_fn; + let mut sig = sig.clone(); + sig.ident = name; + self.output.extend(quote!( + #vis #sig #block + )); + } +} + +fn exprs_for_args<'a, T>( + args: &'a syn::punctuated::Punctuated, +) -> impl Iterator + Clone + 'a { + args.iter().map(|arg| match arg { + FnArg::Receiver(_) => Expr::Verbatim(quote!(self)), + FnArg::Typed(typed) => pat_to_expr(&typed.pat), + }) } fn pat_to_expr(pat: &syn::Pat) -> Expr { @@ -783,48 +844,39 @@ fn pat_to_expr(pat: &syn::Pat) -> Expr { } } -fn try_as_wrapper_call_args(stmt: &mut syn::Stmt) -> Option<&mut syn::punctuated::Punctuated> { +fn try_as_wrapper_call_args<'a>( + stmt: &'a mut syn::Stmt, + wrapper_fn_name: &str, +) -> Option<&'a mut syn::punctuated::Punctuated> { match stmt { - syn::Stmt::Local(syn::Local { - pat: syn::Pat::Ident(syn::PatIdent { - by_ref: None, - mutability: None, - ident: result_ident, - subpat: None, - .. - }), - init: Some(syn::LocalInit { - diverge: None, - expr: init_expr, - .. - }), - .. - }) if result_ident == "result" => match init_expr.as_mut() { - Expr::Call(syn::ExprCall { - func: box_func, - args, - .. - }) => match box_func.as_ref() { - syn::Expr::Path(syn::ExprPath { - qself: None, - path, - .. - }) if path.get_ident().map_or(false, is_wrapper_fn) => { - Some(args) - } - _ => None, - } - _ => None, - } - _ => None, - } -} - -fn make_wrapper_args(num: usize) -> impl Iterator + Clone { + syn::Stmt::Local(syn::Local { + pat: + syn::Pat::Ident(syn::PatIdent { + by_ref: None, + mutability: None, + ident: result_ident, + subpat: None, + .. + }), + init: Some(syn::LocalInit { diverge: None, expr: init_expr, .. }), + .. + }) if result_ident == "result" => match init_expr.as_mut() { + Expr::Call(syn::ExprCall { func: box_func, args, .. }) => match box_func.as_ref() { + syn::Expr::Path(syn::ExprPath { qself: None, path, .. }) + if path.get_ident().map_or(false, |id| id == wrapper_fn_name) => + { + Some(args) + } + _ => None, + }, + _ => None, + }, + _ => None, + } } -fn is_wrapper_fn(ident: &syn::Ident) -> bool { - +fn make_wrapper_args(num: usize) -> impl Iterator + Clone { + (0..num).map(|i| Ident::new(&format!("wrapper_arg_{i}"), Span::mixed_site())) } /// If an explicit return type was provided it is returned, otherwise `()`. @@ -931,6 +983,11 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) return item_fn.into_token_stream().into(); } + let hash = matches!(function_state, ContractFunctionState::Untouched) + .then(|| short_hash_of_token_stream(&item_stream_clone)); + + let original_function_name = item_fn.sig.ident.clone(); + let mut handler = match ContractConditionsHandler::new( function_state, is_requires, @@ -938,12 +995,14 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) &mut item_fn, attr_copy, &mut output, + hash, ) { Ok(handler) => handler, Err(e) => return e.into_compile_error().into(), }; match function_state { + ContractFunctionState::ModifiesWrapper => handler.emit_augmented_modifies_wrapper(), ContractFunctionState::Check => { // The easy cases first: If we are on a check or replace function // emit them again but with additional conditions layered on. @@ -951,11 +1010,11 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) // Since we are already on the check function, it will have an // appropriate, unique generated name which we are just going to // pass on. - handler.emit_check_function(item_fn.sig.ident.clone()); + handler.emit_check_function(original_function_name); } ContractFunctionState::Replace => { // Analogous to above - handler.emit_replace_function(item_fn.sig.ident.clone(), false); + handler.emit_replace_function(original_function_name, false); } ContractFunctionState::Original => { unreachable!("Impossible: This is handled via short circuiting earlier.") @@ -973,10 +1032,12 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) // We'll be using this to postfix the generated names for the "check" // and "replace" functions. - let item_hash = short_hash_of_token_stream(&item_stream_clone); + let item_hash = hash.unwrap(); - let check_fn_name = identifier_for_generated_function(&item_fn, "check", item_hash); - let replace_fn_name = identifier_for_generated_function(&item_fn, "replace", item_hash); + let check_fn_name = + identifier_for_generated_function(&original_function_name, "check", item_hash); + let replace_fn_name = + identifier_for_generated_function(&original_function_name, "replace", item_hash); // Constructing string literals explicitly here, because `stringify!` // doesn't work. Let's say we have an identifier `check_fn` and we were @@ -996,18 +1057,20 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) // // The same care is taken when we emit check and replace functions. // emit the check function. - let ItemFn { attrs, vis, sig, block } = &item_fn; - handler.output.extend(quote!( + let ItemFn { attrs, vis, sig, block } = &handler.annotated_fn; + let reemit_tokens = quote!( #(#attrs)* #[kanitool::checked_with = #check_fn_name_str] #[kanitool::replaced_with = #replace_fn_name_str] #vis #sig { #block } - )); + ); + handler.output.extend(reemit_tokens); handler.emit_check_function(check_fn_name); handler.emit_replace_function(replace_fn_name, true); + handler.emit_augmented_modifies_wrapper(); } } From ed2e79d75d2e65bd405648ccb5a8ee4b078af2a0 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Sun, 1 Oct 2023 12:15:20 -0400 Subject: [PATCH 03/72] Add explicit lifetimes to arguments and reorder code --- library/kani_macros/src/sysroot/contracts.rs | 438 +++++++++---------- 1 file changed, 207 insertions(+), 231 deletions(-) diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 9e4f7ed8b699..bc00a7ba5e67 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -175,49 +175,9 @@ use std::{ }; use syn::{ parse_macro_input, spanned::Spanned, visit::Visit, visit_mut::VisitMut, Attribute, Expr, FnArg, - ItemFn, PredicateType, ReturnType, Signature, Token, TraitBound, TypeParamBound, WhereClause, + ItemFn, PredicateType, ReturnType, Signature, Token, TraitBound, TypeParamBound, WhereClause, GenericArgument, }; -/// Create a unique hash for a token stream (basically a [`std::hash::Hash`] -/// impl for `proc_macro2::TokenStream`). -fn hash_of_token_stream(hasher: &mut H, stream: proc_macro2::TokenStream) { - use proc_macro2::TokenTree; - use std::hash::Hash; - for token in stream { - match token { - TokenTree::Ident(i) => i.hash(hasher), - TokenTree::Punct(p) => p.as_char().hash(hasher), - TokenTree::Group(g) => { - std::mem::discriminant(&g.delimiter()).hash(hasher); - hash_of_token_stream(hasher, g.stream()); - } - TokenTree::Literal(lit) => lit.to_string().hash(hasher), - } - } -} - -/// Hash this `TokenStream` and return an integer that is at most digits -/// long when hex formatted. -fn short_hash_of_token_stream(stream: &proc_macro::TokenStream) -> u64 { - use std::hash::Hasher; - let mut hasher = std::collections::hash_map::DefaultHasher::default(); - hash_of_token_stream(&mut hasher, proc_macro2::TokenStream::from(stream.clone())); - let long_hash = hasher.finish(); - long_hash % 0x1_000_000 // six hex digits -} - -/// Makes consistent names for a generated function which was created for -/// `purpose`, from an attribute that decorates `related_function` with the -/// hash `hash`. -fn identifier_for_generated_function( - related_function_name: &Ident, - purpose: &str, - hash: u64, -) -> Ident { - let identifier = format!("{}_{purpose}_{hash:x}", related_function_name); - Ident::new(&identifier, proc_macro2::Span::mixed_site()) -} - pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream { requires_ensures_main(attr, item, 0) } @@ -226,97 +186,31 @@ pub fn ensures(attr: TokenStream, item: TokenStream) -> TokenStream { requires_ensures_main(attr, item, 1) } -#[allow(dead_code)] -pub fn modifies(attr: TokenStream, item: TokenStream) -> TokenStream { - requires_ensures_main(attr, item, 2) -} - -fn is_token_stream_2_comma(t: &proc_macro2::TokenTree) -> bool { - matches!(t, proc_macro2::TokenTree::Punct(p) if p.as_char() == ',') -} - -fn chunks_by<'a, T, C: Default + Extend>( - i: impl IntoIterator + 'a, - mut pred: impl FnMut(&T) -> bool + 'a, -) -> impl Iterator + 'a { - let mut iter = i.into_iter(); - std::iter::from_fn(move || { - let mut new = C::default(); - let mut empty = true; - while let Some(tok) = iter.next() { - empty = false; - if pred(&tok) { - break; +/// This is very similar to the kani_attribute macro, but it instead creates +/// key-value style attributes which I find a little easier to parse. +macro_rules! passthrough { + ($name:ident, $allow_dead_code:ident) => { + pub fn $name(attr: TokenStream, item: TokenStream) -> TokenStream { + let args = proc_macro2::TokenStream::from(attr); + let fn_item = proc_macro2::TokenStream::from(item); + let name = Ident::new(stringify!($name), proc_macro2::Span::call_site()); + let extra_attrs = if $allow_dead_code { + quote!(#[allow(dead_code)]) } else { - new.extend([tok]) - } - } - (!empty).then_some(new) - }) -} - -/// Collect all named identifiers used in the argument patterns of a function. -struct ArgumentIdentCollector(HashSet); - -impl ArgumentIdentCollector { - fn new() -> Self { - Self(HashSet::new()) - } -} - -impl<'ast> Visit<'ast> for ArgumentIdentCollector { - fn visit_pat_ident(&mut self, i: &'ast syn::PatIdent) { - self.0.insert(i.ident.clone()); - syn::visit::visit_pat_ident(self, i) - } - fn visit_receiver(&mut self, _: &'ast syn::Receiver) { - self.0.insert(Ident::new("self", proc_macro2::Span::call_site())); - } -} - -/// Applies the contained renaming (key renamed to value) to every ident pattern -/// and ident expr visited. -struct Renamer<'a>(&'a HashMap); - -impl<'a> VisitMut for Renamer<'a> { - fn visit_expr_path_mut(&mut self, i: &mut syn::ExprPath) { - if i.path.segments.len() == 1 { - i.path - .segments - .first_mut() - .and_then(|p| self.0.get(&p.ident).map(|new| p.ident = new.clone())); - } - } - - /// This restores shadowing. Without this we would rename all ident - /// occurrences, but not rebinding location. This is because our - /// [`Self::visit_expr_path_mut`] is scope-unaware. - fn visit_pat_ident_mut(&mut self, i: &mut syn::PatIdent) { - if let Some(new) = self.0.get(&i.ident) { - i.ident = new.clone(); + quote!() + }; + quote!( + #extra_attrs + #[kanitool::#name = stringify!(#args)] + #fn_item + ) + .into() } } } -/// Does the provided path have the same chain of identifiers as `mtch` (match) -/// and no arguments anywhere? -/// -/// So for instance (using some pseudo-syntax for the [`syn::Path`]s) -/// `matches_path(std::vec::Vec, &["std", "vec", "Vec"]) == true` but -/// `matches_path(std::Vec::::contains, &["std", "Vec", "contains"]) != -/// true`. -/// -/// This is intended to be used to match the internal `kanitool` family of -/// attributes which we know to have a regular structure and no arguments. -fn matches_path(path: &syn::Path, mtch: &[E]) -> bool -where - Ident: std::cmp::PartialEq, -{ - path.segments.len() == mtch.len() - && path.segments.iter().all(|s| s.arguments.is_empty()) - && path.leading_colon.is_none() - && path.segments.iter().zip(mtch).all(|(actual, expected)| actual.ident == *expected) -} +passthrough!(stub_verified, false); +passthrough!(proof_for_contract, true); /// Classifies the state a function is in in the contract handling pipeline. #[derive(Clone, Copy, PartialEq, Eq)] @@ -390,80 +284,6 @@ impl ContractFunctionState { } } -/// A visitor which injects a copy of the token stream it holds before every -/// `return` expression. -/// -/// This is intended to be used with postconditions and for that purpose it also -/// performs a rewrite where the return value is first bound to `result` so the -/// postconditions can access it. -/// -/// # Example -/// -/// The expression `return x;` turns into -/// -/// ```rs -/// { // Always opens a new block -/// let result = x; -/// -/// return result; -/// } -/// ``` -struct PostconditionInjector(TokenStream2); - -impl VisitMut for PostconditionInjector { - /// We leave this empty to stop the recursion here. We don't want to look - /// inside the closure, because the return statements contained within are - /// for a different function. - fn visit_expr_closure_mut(&mut self, _: &mut syn::ExprClosure) {} - - fn visit_expr_mut(&mut self, i: &mut Expr) { - if let syn::Expr::Return(r) = i { - let tokens = self.0.clone(); - let mut output = TokenStream2::new(); - if let Some(expr) = &mut r.expr { - // In theory the return expression can contain itself a `return` - // so we need to recurse here. - self.visit_expr_mut(expr); - output.extend(quote!(let result = #expr;)); - *expr = Box::new(Expr::Verbatim(quote!(result))); - } - *i = syn::Expr::Verbatim(quote!({ - #output - #tokens - #i - })) - } else { - syn::visit_mut::visit_expr_mut(self, i) - } - } -} - -/// A supporting function for creating shallow, unsafe copies of the arguments -/// for the postconditions. -/// -/// This function: -/// - Collects all [`Ident`]s found in the argument patterns; -/// - Creates new names for them; -/// - Replaces all occurrences of those idents in `attrs` with the new names and; -/// - Returns the mapping of old names to new names. -fn rename_argument_occurrences(sig: &syn::Signature, attr: &mut Expr) -> HashMap { - let mut arg_ident_collector = ArgumentIdentCollector::new(); - arg_ident_collector.visit_signature(&sig); - - let mk_new_ident_for = |id: &Ident| Ident::new(&format!("{}_renamed", id), Span::mixed_site()); - let arg_idents = arg_ident_collector - .0 - .into_iter() - .map(|i| { - let new = mk_new_ident_for(&i); - (i, new) - }) - .collect::>(); - - let mut ident_rewriter = Renamer(&arg_idents); - ident_rewriter.visit_expr_mut(attr); - arg_idents -} /// The information needed to generate the bodies of check and replacement /// functions that integrate the conditions from this contract attribute. @@ -738,14 +558,33 @@ impl<'a> ContractConditionsHandler<'a> { fn emit_augmented_modifies_wrapper(&mut self) { if let ContractConditionsType::Modifies { attr } = &self.condition_type { - self.annotated_fn.sig.inputs.extend(make_wrapper_args(attr.len()).map(|warg| { - FnArg::Typed(syn::PatType { - attrs: vec![], - colon_token: Token![:](Span::call_site()), - pat: Box::new(syn::Pat::Verbatim(quote!(#warg))), - ty: Box::new(syn::Type::Verbatim(quote!(&impl kani::Arbitrary))), - }) - })) + let wrapper_args = make_wrapper_args(attr.len()); + let sig = &mut self.annotated_fn.sig; + for arg in wrapper_args.clone() { + let lifetime = syn::Lifetime { + apostrophe: Span::call_site(), + ident: arg.clone(), + }; + sig.inputs.push( + FnArg::Typed(syn::PatType { + attrs: vec![], + colon_token: Token![:](Span::call_site()), + pat: Box::new(syn::Pat::Verbatim(quote!(#arg))), + ty: Box::new(syn::Type::Verbatim(quote!(&#lifetime impl kani::Arbitrary))), + }) + ); + sig.generics.params.push( + syn::GenericParam::Lifetime(syn::LifetimeParam { + lifetime, + colon_token: None, + bounds: Default::default(), + attrs: vec![], + }) + ); + } + self.output.extend( + quote!(#[kanitool::modifies(#(#wrapper_args),*)]) + ) } self.emit_common_header(); @@ -753,10 +592,11 @@ impl<'a> ContractConditionsHandler<'a> { // If it's the first time we also emit this marker. Again, order is // important so this happens as the last emitted attribute. self.output.extend(quote!(#[kanitool::is_contract_generated(wrapper)])); - } - + } + let name = self.make_wrapper_name(); let ItemFn { vis, sig, block, .. } = self.annotated_fn; + let mut sig = sig.clone(); sig.ident = name; self.output.extend(quote!( @@ -1077,28 +917,164 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) output.into() } -/// This is very similar to the kani_attribute macro, but it instead creates -/// key-value style attributes which I find a little easier to parse. -macro_rules! passthrough { - ($name:ident, $allow_dead_code:ident) => { - pub fn $name(attr: TokenStream, item: TokenStream) -> TokenStream { - let args = proc_macro2::TokenStream::from(attr); - let fn_item = proc_macro2::TokenStream::from(item); - let name = Ident::new(stringify!($name), proc_macro2::Span::call_site()); - let extra_attrs = if $allow_dead_code { - quote!(#[allow(dead_code)]) + + +/// Create a unique hash for a token stream (basically a [`std::hash::Hash`] +/// impl for `proc_macro2::TokenStream`). +fn hash_of_token_stream(hasher: &mut H, stream: proc_macro2::TokenStream) { + use proc_macro2::TokenTree; + use std::hash::Hash; + for token in stream { + match token { + TokenTree::Ident(i) => i.hash(hasher), + TokenTree::Punct(p) => p.as_char().hash(hasher), + TokenTree::Group(g) => { + std::mem::discriminant(&g.delimiter()).hash(hasher); + hash_of_token_stream(hasher, g.stream()); + } + TokenTree::Literal(lit) => lit.to_string().hash(hasher), + } + } +} + +/// Hash this `TokenStream` and return an integer that is at most digits +/// long when hex formatted. +fn short_hash_of_token_stream(stream: &proc_macro::TokenStream) -> u64 { + use std::hash::Hasher; + let mut hasher = std::collections::hash_map::DefaultHasher::default(); + hash_of_token_stream(&mut hasher, proc_macro2::TokenStream::from(stream.clone())); + let long_hash = hasher.finish(); + long_hash % 0x1_000_000 // six hex digits +} + +/// Makes consistent names for a generated function which was created for +/// `purpose`, from an attribute that decorates `related_function` with the +/// hash `hash`. +fn identifier_for_generated_function( + related_function_name: &Ident, + purpose: &str, + hash: u64, +) -> Ident { + let identifier = format!("{}_{purpose}_{hash:x}", related_function_name); + Ident::new(&identifier, proc_macro2::Span::mixed_site()) +} + +#[allow(dead_code)] +pub fn modifies(attr: TokenStream, item: TokenStream) -> TokenStream { + requires_ensures_main(attr, item, 2) +} + +fn is_token_stream_2_comma(t: &proc_macro2::TokenTree) -> bool { + matches!(t, proc_macro2::TokenTree::Punct(p) if p.as_char() == ',') +} + +fn chunks_by<'a, T, C: Default + Extend>( + i: impl IntoIterator + 'a, + mut pred: impl FnMut(&T) -> bool + 'a, +) -> impl Iterator + 'a { + let mut iter = i.into_iter(); + std::iter::from_fn(move || { + let mut new = C::default(); + let mut empty = true; + while let Some(tok) = iter.next() { + empty = false; + if pred(&tok) { + break; } else { - quote!() - }; - quote!( - #extra_attrs - #[kanitool::#name = stringify!(#args)] - #fn_item - ) - .into() + new.extend([tok]) + } } + (!empty).then_some(new) + }) +} + +/// Collect all named identifiers used in the argument patterns of a function. +struct ArgumentIdentCollector(HashSet); + +impl ArgumentIdentCollector { + fn new() -> Self { + Self(HashSet::new()) } } -passthrough!(stub_verified, false); -passthrough!(proof_for_contract, true); +impl<'ast> Visit<'ast> for ArgumentIdentCollector { + fn visit_pat_ident(&mut self, i: &'ast syn::PatIdent) { + self.0.insert(i.ident.clone()); + syn::visit::visit_pat_ident(self, i) + } + fn visit_receiver(&mut self, _: &'ast syn::Receiver) { + self.0.insert(Ident::new("self", proc_macro2::Span::call_site())); + } +} + +/// Applies the contained renaming (key renamed to value) to every ident pattern +/// and ident expr visited. +struct Renamer<'a>(&'a HashMap); + +impl<'a> VisitMut for Renamer<'a> { + fn visit_expr_path_mut(&mut self, i: &mut syn::ExprPath) { + if i.path.segments.len() == 1 { + i.path + .segments + .first_mut() + .and_then(|p| self.0.get(&p.ident).map(|new| p.ident = new.clone())); + } + } + + /// This restores shadowing. Without this we would rename all ident + /// occurrences, but not rebinding location. This is because our + /// [`Self::visit_expr_path_mut`] is scope-unaware. + fn visit_pat_ident_mut(&mut self, i: &mut syn::PatIdent) { + if let Some(new) = self.0.get(&i.ident) { + i.ident = new.clone(); + } + } +} + +/// A supporting function for creating shallow, unsafe copies of the arguments +/// for the postconditions. +/// +/// This function: +/// - Collects all [`Ident`]s found in the argument patterns; +/// - Creates new names for them; +/// - Replaces all occurrences of those idents in `attrs` with the new names and; +/// - Returns the mapping of old names to new names. +fn rename_argument_occurrences(sig: &syn::Signature, attr: &mut Expr) -> HashMap { + let mut arg_ident_collector = ArgumentIdentCollector::new(); + arg_ident_collector.visit_signature(&sig); + + let mk_new_ident_for = |id: &Ident| Ident::new(&format!("{}_renamed", id), Span::mixed_site()); + let arg_idents = arg_ident_collector + .0 + .into_iter() + .map(|i| { + let new = mk_new_ident_for(&i); + (i, new) + }) + .collect::>(); + + let mut ident_rewriter = Renamer(&arg_idents); + ident_rewriter.visit_expr_mut(attr); + arg_idents +} + +/// Does the provided path have the same chain of identifiers as `mtch` (match) +/// and no arguments anywhere? +/// +/// So for instance (using some pseudo-syntax for the [`syn::Path`]s) +/// `matches_path(std::vec::Vec, &["std", "vec", "Vec"]) == true` but +/// `matches_path(std::Vec::::contains, &["std", "Vec", "contains"]) != +/// true`. +/// +/// This is intended to be used to match the internal `kanitool` family of +/// attributes which we know to have a regular structure and no arguments. +fn matches_path(path: &syn::Path, mtch: &[E]) -> bool +where + Ident: std::cmp::PartialEq, +{ + path.segments.len() == mtch.len() + && path.segments.iter().all(|s| s.arguments.is_empty()) + && path.leading_colon.is_none() + && path.segments.iter().zip(mtch).all(|(actual, expected)| actual.ident == *expected) +} + From 5a8f746628408f304adc54791aceb228ff36b31b Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Sun, 1 Oct 2023 17:13:58 -0400 Subject: [PATCH 04/72] Sketch for the internal mechanisms --- cprover_bindings/src/goto_program/mod.rs | 2 +- cprover_bindings/src/goto_program/symbol.rs | 71 +++++++++ .../src/goto_program/symbol_table.rs | 11 +- cprover_bindings/src/goto_program/typ.rs | 11 ++ cprover_bindings/src/irep/irep.rs | 9 ++ cprover_bindings/src/irep/irep_id.rs | 2 + cprover_bindings/src/irep/to_irep.rs | 66 ++++++-- .../codegen_cprover_gotoc/codegen/function.rs | 94 +++++++++++- .../compiler_interface.rs | 92 +++++++++-- kani-compiler/src/kani_middle/attributes.rs | 143 ++++++++++++++++-- kani-compiler/src/kani_middle/contracts.rs | 43 ++++++ kani-compiler/src/kani_middle/resolve.rs | 2 +- kani-driver/src/call_goto_instrument.rs | 27 ++++ kani-driver/src/harness_runner.rs | 18 ++- kani_metadata/src/artifact.rs | 4 + library/kani_macros/src/lib.rs | 2 +- library/kani_macros/src/sysroot/contracts.rs | 52 +++---- 17 files changed, 576 insertions(+), 73 deletions(-) create mode 100644 kani-compiler/src/kani_middle/contracts.rs diff --git a/cprover_bindings/src/goto_program/mod.rs b/cprover_bindings/src/goto_program/mod.rs index 15c0282f9a40..8b61722344fb 100644 --- a/cprover_bindings/src/goto_program/mod.rs +++ b/cprover_bindings/src/goto_program/mod.rs @@ -22,6 +22,6 @@ pub use expr::{ }; pub use location::Location; pub use stmt::{Stmt, StmtBody, SwitchCase}; -pub use symbol::{Symbol, SymbolValues}; +pub use symbol::{FunctionContract, Lambda, Symbol, SymbolValues}; pub use symbol_table::SymbolTable; pub use typ::{CIntType, DatatypeComponent, Parameter, Type}; diff --git a/cprover_bindings/src/goto_program/symbol.rs b/cprover_bindings/src/goto_program/symbol.rs index 7f74abaa9816..f65eec7d2513 100644 --- a/cprover_bindings/src/goto_program/symbol.rs +++ b/cprover_bindings/src/goto_program/symbol.rs @@ -13,6 +13,8 @@ pub struct Symbol { pub location: Location, pub typ: Type, pub value: SymbolValues, + /// Contracts to be enforced (only supported for functions) + pub contract: Option>, /// Optional debugging information @@ -44,6 +46,57 @@ pub struct Symbol { pub is_weak: bool, } +/// The equivalent of a "mathematical function" in CBMC. Semantically this is an +/// anonymous function object, similar to a closure, but without closing over an +/// environment. +/// +/// This is only valid for use as a function contract. It may not perform side +/// effects, a property that is enforced on the CBMC side. +/// +/// The precise nomenclature is that in CBMC a contract value has *type* +/// `mathematical_function` and values of that type are `lambda`s. Since this +/// struct represents such values it is named `Lambda`. +#[derive(Debug, Clone)] +pub struct Lambda { + pub arguments: Vec, + pub body: Expr, +} + +impl Lambda { + pub fn as_contract_for( + fn_ty: &Type, + return_var_name: Option, + body: Expr, + ) -> Self { + let arguments = match fn_ty { + Type::Code { parameters, return_type } => { + [Parameter::new(None, return_var_name, (**return_type).clone())] + .into_iter() + .chain(parameters.iter().cloned()) + .collect() + } + _ => panic!( + "Contract lambdas can only be generated for `Code` types, received {fn_ty:?}" + ), + }; + Self { arguments, body } + } +} + +/// The CBMC representation of a function contract with three types of clauses. +/// See https://diffblue.github.io/cbmc/contracts-user.html for the meaning of +/// each type of clause. +#[derive(Clone, Debug)] +pub struct FunctionContract { + pub(crate) assigns: Vec, +} + +impl FunctionContract { + pub fn new(assigns: Vec) -> Self { + Self { assigns } + } +} + /// Currently, only C is understood by CBMC. // TODO: #[derive(Clone, Debug)] @@ -84,6 +137,7 @@ impl Symbol { base_name, pretty_name, + contract: None, module: None, mode: SymbolModes::C, // global properties @@ -107,6 +161,18 @@ impl Symbol { } } + /// Add this contract to the symbol (symbol must be a function) or fold the + /// conditions into an existing contract. + pub fn attach_contract(&mut self, contract: FunctionContract) { + assert!(self.typ.is_code()); + match self.contract { + Some(ref mut prior) => { + prior.assigns.extend(contract.assigns); + } + None => self.contract = Some(Box::new(contract)), + } + } + /// The symbol that defines the type of the struct or union. /// For a struct foo this is the symbol "tag-foo" that maps to the type struct foo. pub fn aggr_ty>(t: Type, pretty_name: T) -> Symbol { @@ -319,6 +385,11 @@ impl Symbol { self.is_auxiliary = hidden; self } + + pub fn with_is_property(mut self, v: bool) -> Self { + self.is_property = v; + self + } } /// Predicates diff --git a/cprover_bindings/src/goto_program/symbol_table.rs b/cprover_bindings/src/goto_program/symbol_table.rs index 1e635ec925da..9bcd14cb624e 100644 --- a/cprover_bindings/src/goto_program/symbol_table.rs +++ b/cprover_bindings/src/goto_program/symbol_table.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT use super::super::{env, MachineModel}; -use super::{BuiltinFn, Stmt, Symbol}; +use super::{BuiltinFn, FunctionContract, Stmt, Symbol}; use crate::InternedString; use std::collections::BTreeMap; /// This is a typesafe implementation of the CBMC symbol table, based on the CBMC code at: @@ -79,6 +79,15 @@ impl SymbolTable { let name = name.into(); self.symbol_table.get_mut(&name).unwrap().update_fn_declaration_with_definition(body); } + + pub fn attach_contract>( + &mut self, + name: T, + contract: FunctionContract, + ) { + let sym = self.symbol_table.get_mut(&name.into()).unwrap(); + sym.attach_contract(contract); + } } /// Getters diff --git a/cprover_bindings/src/goto_program/typ.rs b/cprover_bindings/src/goto_program/typ.rs index 9d1649b99cd1..dd07c150bb3f 100644 --- a/cprover_bindings/src/goto_program/typ.rs +++ b/cprover_bindings/src/goto_program/typ.rs @@ -228,6 +228,17 @@ impl Parameter { } } +/// Constructor +impl Parameter { + pub fn new>( + base_name: Option, + identifier: Option, + typ: Type, + ) -> Self { + Self { base_name: base_name.map(Into::into), identifier: identifier.map(Into::into), typ } + } +} + impl CIntType { pub fn sizeof_in_bits(&self, st: &SymbolTable) -> u64 { match self { diff --git a/cprover_bindings/src/irep/irep.rs b/cprover_bindings/src/irep/irep.rs index 68e094000884..0d0c6dc4ace7 100644 --- a/cprover_bindings/src/irep/irep.rs +++ b/cprover_bindings/src/irep/irep.rs @@ -6,6 +6,7 @@ use super::super::goto_program::{Location, Type}; use super::super::MachineModel; use super::{IrepId, ToIrep}; use crate::cbmc_string::InternedString; +use crate::linear_map; use linear_map::LinearMap; use num::BigInt; use std::fmt::Debug; @@ -141,4 +142,12 @@ impl Irep { pub fn zero() -> Irep { Irep::just_id(IrepId::Id0) } + + pub fn tuple(sub: Vec) -> Self { + Irep { + id: IrepId::Tuple, + sub, + named_sub: linear_map![(IrepId::Type, Irep::just_id(IrepId::Tuple))], + } + } } diff --git a/cprover_bindings/src/irep/irep_id.rs b/cprover_bindings/src/irep/irep_id.rs index 3ad8f71a7e86..cad6eb563bf4 100644 --- a/cprover_bindings/src/irep/irep_id.rs +++ b/cprover_bindings/src/irep/irep_id.rs @@ -593,6 +593,7 @@ pub enum IrepId { CSpecLoopInvariant, CSpecRequires, CSpecEnsures, + CSpecAssigns, VirtualFunction, ElementType, WorkingDirectory, @@ -1462,6 +1463,7 @@ impl ToString for IrepId { IrepId::CSpecLoopInvariant => "#spec_loop_invariant", IrepId::CSpecRequires => "#spec_requires", IrepId::CSpecEnsures => "#spec_ensures", + IrepId::CSpecAssigns => "#spec_assigns", IrepId::VirtualFunction => "virtual_function", IrepId::ElementType => "element_type", IrepId::WorkingDirectory => "working_directory", diff --git a/cprover_bindings/src/irep/to_irep.rs b/cprover_bindings/src/irep/to_irep.rs index 41c501896cd1..75faa7fc3d08 100644 --- a/cprover_bindings/src/irep/to_irep.rs +++ b/cprover_bindings/src/irep/to_irep.rs @@ -6,8 +6,9 @@ use super::super::goto_program; use super::super::MachineModel; use super::{Irep, IrepId}; use crate::linear_map; +use crate::InternedString; use goto_program::{ - BinaryOperator, CIntType, DatatypeComponent, Expr, ExprValue, Location, Parameter, + BinaryOperator, CIntType, DatatypeComponent, Expr, ExprValue, Lambda, Location, Parameter, SelfOperator, Stmt, StmtBody, SwitchCase, SymbolValues, Type, UnaryOperator, }; @@ -16,10 +17,10 @@ pub trait ToIrep { } /// Utility functions -fn arguments_irep(arguments: &[Expr], mm: &MachineModel) -> Irep { +fn arguments_irep<'a>(arguments: impl Iterator, mm: &MachineModel) -> Irep { Irep { id: IrepId::Arguments, - sub: arguments.iter().map(|x| x.to_irep(mm)).collect(), + sub: arguments.map(|x| x.to_irep(mm)).collect(), named_sub: linear_map![], } } @@ -169,6 +170,16 @@ impl ToIrep for Expr { } } +impl Irep { + pub fn symbol(identifier: InternedString) -> Self { + Irep { + id: IrepId::Symbol, + sub: vec![], + named_sub: linear_map![(IrepId::Identifier, Irep::just_string_id(identifier))], + } + } +} + impl ToIrep for ExprValue { fn to_irep(&self, mm: &MachineModel) -> Irep { match self { @@ -245,7 +256,7 @@ impl ToIrep for ExprValue { } ExprValue::FunctionCall { function, arguments } => side_effect_irep( IrepId::FunctionCall, - vec![function.to_irep(mm), arguments_irep(arguments, mm)], + vec![function.to_irep(mm), arguments_irep(arguments.iter(), mm)], ), ExprValue::If { c, t, e } => Irep { id: IrepId::If, @@ -297,14 +308,7 @@ impl ToIrep for ExprValue { sub: values.iter().map(|x| x.to_irep(mm)).collect(), named_sub: linear_map![], }, - ExprValue::Symbol { identifier } => Irep { - id: IrepId::Symbol, - sub: vec![], - named_sub: linear_map![( - IrepId::Identifier, - Irep::just_string_id(identifier.to_string()), - )], - }, + ExprValue::Symbol { identifier } => Irep::symbol(*identifier), ExprValue::Typecast(e) => { Irep { id: IrepId::Typecast, sub: vec![e.to_irep(mm)], named_sub: linear_map![] } } @@ -456,7 +460,7 @@ impl ToIrep for StmtBody { vec![ lhs.as_ref().map_or(Irep::nil(), |x| x.to_irep(mm)), function.to_irep(mm), - arguments_irep(arguments, mm), + arguments_irep(arguments.iter(), mm), ], ), StmtBody::Goto(dest) => code_irep(IrepId::Goto, vec![]) @@ -499,10 +503,44 @@ impl ToIrep for SwitchCase { } } +impl ToIrep for Lambda { + fn to_irep(&self, mm: &MachineModel) -> Irep { + let (ops_ireps, types) = self + .arguments + .iter() + .map(|param| { + let ty_rep = param.typ().to_irep(mm); + ( + Irep::symbol(param.identifier().unwrap_or("_".into())) + .with_named_sub(IrepId::Type, ty_rep.clone()), + ty_rep, + ) + }) + .unzip(); + let typ = Irep { + id: IrepId::MathematicalFunction, + sub: vec![Irep::just_sub(types), self.body.typ().to_irep(mm)], + named_sub: Default::default(), + }; + Irep { + id: IrepId::Lambda, + sub: vec![Irep::tuple(ops_ireps), self.body.to_irep(mm)], + named_sub: linear_map!((IrepId::Type, typ)), + } + } +} + impl goto_program::Symbol { pub fn to_irep(&self, mm: &MachineModel) -> super::Symbol { + let mut typ = self.typ.to_irep(mm); + if let Some(contract) = &self.contract { + typ = typ.with_named_sub( + IrepId::CSpecAssigns, + Irep::just_sub(contract.assigns.iter().map(|req| req.to_irep(mm)).collect()), + ); + } super::Symbol { - typ: self.typ.to_irep(mm), + typ, value: match &self.value { SymbolValues::Expr(e) => e.to_irep(mm), SymbolValues::Stmt(s) => s.to_irep(mm), diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index 77d68af745ae..42a86ec75249 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -4,10 +4,10 @@ //! This file contains functions related to codegenning MIR functions into gotoc use crate::codegen_cprover_gotoc::GotocCtx; -use cbmc::goto_program::{Expr, Stmt, Symbol}; +use cbmc::goto_program::{Expr, FunctionContract, Stmt, Symbol}; use cbmc::InternString; use rustc_middle::mir::traversal::reverse_postorder; -use rustc_middle::mir::{Body, HasLocalDecls, Local}; +use rustc_middle::mir::{self, Body, HasLocalDecls, Local}; use rustc_middle::ty::{self, Instance}; use std::collections::BTreeMap; use std::iter::FromIterator; @@ -222,6 +222,96 @@ impl<'tcx> GotocCtx<'tcx> { ); } + /// Convert the Kani level contract into a CBMC level contract by creating a + /// lambda that calls the contract implementation function. + /// + /// For instance say we are processing a contract on `f` + /// + /// ```rs + /// as_goto_contract(..., GFnContract { requires: , .. }) + /// = FunctionContract { + /// requires: [ + /// Lambda { + /// arguments: , + /// body: Call(codegen_fn_expr(contract_impl_fn), [args of f..., return arg]) + /// } + /// ], + /// ... + /// } + /// ``` + /// + /// A spec lambda in GOTO receives as its first argument the return value of + /// the annotated function. However at the top level we must receive `self` + /// as first argument, because rust requires it. As a result the generated + /// lambda takes the return value as first argument and then immediately + /// calls the generated spec function, but passing the return value as the + /// last argument. + fn as_goto_contract(&mut self, assigns_contract: Vec) -> FunctionContract { + use cbmc::goto_program::Lambda; + + let goto_annotated_fn_name = self.current_fn().name(); + let goto_annotated_fn_typ = self + .symbol_table + .lookup(&goto_annotated_fn_name) + .unwrap_or_else(|| panic!("Function '{goto_annotated_fn_name}' is not declared")) + .typ + .clone(); + + let assigns = assigns_contract + .into_iter() + .map(|local| { + Lambda::as_contract_for( + &goto_annotated_fn_typ, + None, + self.codegen_place(&local.into()).unwrap().goto_expr.dereference(), + ) + }) + .collect(); + + FunctionContract::new(assigns) + } + + /// Convert the contract to a CBMC contract, then attach it to `instance`. + /// `instance` must have previously been declared. + /// + /// This does not overwrite prior contracts but merges with them. + pub fn attach_contract(&mut self, instance: Instance<'tcx>, contract: Vec) { + // This should be safe, since the contract is pretty much evaluated as + // though it was the first (or last) assertion in the function. + self.set_current_fn(instance); + let goto_contract = self.as_goto_contract(contract); + let name = self.current_fn().name(); + + // CBMC has two ways of attaching the contract and it seems the + // difference is whether dfcc is used or not. With dfcc it's stored in + // `contract::`, otherwise directly on the type of the + // function. + // + // Actually the issue sees to haver been something else and ataching to + // the symbol directly seems ot also work if dfcc is used. + let create_separate_contract_sym = false; + + let contract_target_name = if create_separate_contract_sym { + let contract_sym_name = format!("contract::{}", name); + self.ensure(&contract_sym_name, |ctx, fname| { + Symbol::function( + fname, + ctx.fn_typ(), + None, + format!("contract::{}", ctx.current_fn().readable_name()), + ctx.codegen_span(&ctx.current_fn().mir().span), + ) + .with_is_property(true) + }); + contract_sym_name + } else { + name + }; + + self.symbol_table.attach_contract(contract_target_name, goto_contract); + self.reset_current_fn() + } + pub fn declare_function(&mut self, instance: Instance<'tcx>) { debug!("declaring {}; {:?}", instance, instance); self.set_current_fn(instance); diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index b3262c23c9c8..01fb160dbc3a 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -6,7 +6,7 @@ use crate::args::ReachabilityType; use crate::codegen_cprover_gotoc::GotocCtx; use crate::kani_middle::analysis; -use crate::kani_middle::attributes::is_test_harness_description; +use crate::kani_middle::attributes::{is_test_harness_description, KaniAttributes}; use crate::kani_middle::metadata::gen_test_metadata; use crate::kani_middle::provide; use crate::kani_middle::reachability::{ @@ -31,14 +31,14 @@ use rustc_codegen_ssa::{CodegenResults, CrateInfo}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_errors::{ErrorGuaranteed, DEFAULT_LOCALE_RESOURCE}; -use rustc_hir::def_id::LOCAL_CRATE; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::definitions::DefPathHash; use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME}; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::mir::mono::MonoItem; use rustc_middle::query::{ExternProviders, Providers}; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{Instance, InstanceDef, ParamEnv, TyCtxt}; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; use rustc_session::cstore::MetadataLoaderDyn; use rustc_session::output::out_filename; @@ -82,6 +82,7 @@ impl GotocCodegenBackend { starting_items: &[MonoItem<'tcx>], symtab_goto: &Path, machine_model: &MachineModel, + check_contract: Option, ) -> (GotocCtx<'tcx>, Vec>) { let items = with_timer( || collect_reachable_items(tcx, starting_items), @@ -94,7 +95,7 @@ impl GotocCodegenBackend { let mut gcx = GotocCtx::new(tcx, (*self.queries.lock().unwrap()).clone(), machine_model); check_reachable_items(gcx.tcx, &gcx.queries, &items); - with_timer( + let contract_info = with_timer( || { // we first declare all items for item in &items { @@ -144,6 +145,47 @@ impl GotocCodegenBackend { MonoItem::GlobalAsm(_) => {} // We have already warned above } } + + // Attaching the contract gets its own loop, because the + // functions used in the contract expressions must have been + // declared and created before since we rip out the + // implementation from the contract function + let mut contract_info = None; + for item in &items { + if let MonoItem::Fn(instance @ Instance { def: InstanceDef::Item(did), .. }) = + item + { + if check_contract == Some(*did) { + let attrs = KaniAttributes::for_item(tcx, *did); + let assigns_contract = + attrs.modifies_contract().unwrap_or_else(Vec::new); + + let get_instance = |did| { + Instance::expect_resolve( + tcx, + ParamEnv::reveal_all(), + did, + instance.args, + ) + }; + let gcx = &mut gcx; + let mut attach_contract = + |target| gcx.attach_contract(target, assigns_contract); + let name_for_inst = |inst| tcx.symbol_name(inst).to_string(); + + let Ok(inner_check_id) = attrs.inner_check().unwrap() else { + continue; + }; + let inner_check_inst = get_instance(inner_check_id); + attach_contract(inner_check_inst); + assert!( + contract_info.replace(name_for_inst(inner_check_inst)).is_none() + ); + } + } + } + assert_eq!(contract_info.is_some(), check_contract.is_some()); + contract_info }, "codegen", ); @@ -178,12 +220,22 @@ impl GotocCodegenBackend { if let Some(restrictions) = vtable_restrictions { write_file(&symtab_goto, ArtifactType::VTableRestriction, &restrictions, pretty); } + + write_file(symtab_goto, ArtifactType::ContractMetadata, &contract_info, pretty); } (gcx, items) } } +fn contract_metadata_for_harness( + tcx: TyCtxt, + def_id: DefId, +) -> Result, ErrorGuaranteed> { + let attrs = KaniAttributes::for_item(tcx, def_id); + Ok(attrs.interpret_the_for_contract_attribute().transpose()?.map(|(_, id, _)| id)) +} + impl CodegenBackend for GotocCodegenBackend { fn metadata_loader(&self) -> Box { Box::new(rustc_codegen_ssa::back::metadata::DefaultMetadataLoader) @@ -239,8 +291,18 @@ impl CodegenBackend for GotocCodegenBackend { for harness in harnesses { let model_path = queries.harness_model_path(&tcx.def_path_hash(harness.def_id())).unwrap(); - let (gcx, items) = - self.codegen_items(tcx, &[harness], model_path, &results.machine_model); + let Ok(contract_metadata) = + contract_metadata_for_harness(tcx, harness.def_id()) + else { + continue; + }; + let (gcx, items) = self.codegen_items( + tcx, + &[harness], + model_path, + &results.machine_model, + contract_metadata, + ); results.extend(gcx, items, None); } } @@ -262,8 +324,13 @@ impl CodegenBackend for GotocCodegenBackend { // We will be able to remove this once we optimize all calls to CBMC utilities. // https://github.com/model-checking/kani/issues/1971 let model_path = base_filename.with_extension(ArtifactType::SymTabGoto); - let (gcx, items) = - self.codegen_items(tcx, &harnesses, &model_path, &results.machine_model); + let (gcx, items) = self.codegen_items( + tcx, + &harnesses, + &model_path, + &results.machine_model, + Default::default(), + ); results.extend(gcx, items, None); for (test_fn, test_desc) in harnesses.iter().zip(descriptions.iter()) { @@ -287,8 +354,13 @@ impl CodegenBackend for GotocCodegenBackend { || entry_fn == Some(def_id) }); let model_path = base_filename.with_extension(ArtifactType::SymTabGoto); - let (gcx, items) = - self.codegen_items(tcx, &local_reachable, &model_path, &results.machine_model); + let (gcx, items) = self.codegen_items( + tcx, + &local_reachable, + &model_path, + &results.machine_model, + Default::default(), + ); results.extend(gcx, items, None); } } diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 2c9c6d7ee54c..8b480bb37309 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -6,12 +6,22 @@ use std::collections::BTreeMap; use kani_metadata::{CbmcSolver, HarnessAttributes, Stub}; use rustc_ast::{ - attr, AttrArgs, AttrArgsEq, AttrKind, Attribute, ExprKind, LitKind, MetaItem, MetaItemKind, + attr, + token::Token, + token::TokenKind, + tokenstream::{TokenStream, TokenTree}, + AttrArgs, AttrArgsEq, AttrKind, Attribute, ExprKind, LitKind, MetaItem, MetaItemKind, NestedMetaItem, }; use rustc_errors::ErrorGuaranteed; -use rustc_hir::{def::DefKind, def_id::DefId}; -use rustc_middle::ty::{Instance, TyCtxt, TyKind}; +use rustc_hir::{ + def::DefKind, + def_id::{DefId, LocalDefId}, +}; +use rustc_middle::{ + mir::Local, + ty::{Instance, TyCtxt, TyKind}, +}; use rustc_session::Session; use rustc_span::{Span, Symbol}; use std::str::FromStr; @@ -48,6 +58,8 @@ enum KaniAttributeKind { /// Attribute on a function that was auto-generated from expanding a /// function contract. IsContractGenerated, + Modifies, + InnerCheck, } impl KaniAttributeKind { @@ -64,6 +76,8 @@ impl KaniAttributeKind { KaniAttributeKind::Unstable | KaniAttributeKind::ReplacedWith | KaniAttributeKind::CheckedWith + | KaniAttributeKind::Modifies + | KaniAttributeKind::InnerCheck | KaniAttributeKind::IsContractGenerated => false, } } @@ -170,7 +184,7 @@ impl<'tcx> KaniAttributes<'tcx> { /// Parse and extract the `proof_for_contract(TARGET)` attribute. The /// returned symbol and DefId are respectively the name and id of `TARGET`, /// the span in the span for the attribute (contents). - fn interpret_the_for_contract_attribute( + pub(crate) fn interpret_the_for_contract_attribute( &self, ) -> Option> { self.expect_maybe_one(KaniAttributeKind::ProofForContract).map(|target| { @@ -199,18 +213,50 @@ impl<'tcx> KaniAttributes<'tcx> { .map(|target| expect_key_string_value(self.tcx.sess, target)) } - /// Extract the name of the sibling function this function's contract is - /// stubbed as (if any). - /// - /// `None` indicates this function does not use a contract, `Some(Err(_))` - /// indicates a contract does exist but an error occurred during resolution. + pub fn inner_check(&self) -> Option> { + self.eval_sibling_attribute(KaniAttributeKind::InnerCheck) + } + pub fn replaced_with(&self) -> Option> { self.expect_maybe_one(KaniAttributeKind::ReplacedWith) .map(|target| expect_key_string_value(self.tcx.sess, target)) } - /// Resolve a function that is known to reside in the same module as the one - /// these attributes belong to (`self.item`). + fn eval_sibling_attribute( + &self, + kind: KaniAttributeKind, + ) -> Option> { + use rustc_hir::{Item, ItemKind, Mod, Node}; + self.expect_maybe_one(kind).map(|target| { + let name = expect_key_string_value(self.tcx.sess, target)?; + let hir_map = self.tcx.hir(); + let hir_id = hir_map.local_def_id_to_hir_id(self.item.expect_local()); + let find_in_mod = |md: &Mod<'_>| { + md.item_ids + .iter() + .find(|it| hir_map.item(**it).ident.name == name) + .unwrap() + .hir_id() + }; + + let result = match hir_map.get_parent(hir_id) { + Node::Item(Item { kind, .. }) => match kind { + ItemKind::Mod(m) => find_in_mod(m), + ItemKind::Impl(imp) => { + imp.items.iter().find(|it| it.ident.name == name).unwrap().id.hir_id() + } + other => panic!("Odd parent item kind {other:?}"), + }, + Node::Crate(m) => find_in_mod(m), + other => panic!("Odd parent node type {other:?}"), + } + .expect_owner() + .def_id + .to_def_id(); + Ok(result) + }) + } + fn resolve_sibling(&self, path_str: &str) -> Result> { resolve_fn( self.tcx, @@ -288,6 +334,12 @@ impl<'tcx> KaniAttributes<'tcx> { // to communicate with one another. So by the time it gets // here we don't care if it's valid or not. } + KaniAttributeKind::Modifies => { + self.modifies_contract(); + } + KaniAttributeKind::InnerCheck => { + self.inner_check(); + } } } } @@ -389,6 +441,8 @@ impl<'tcx> KaniAttributes<'tcx> { } KaniAttributeKind::CheckedWith | KaniAttributeKind::IsContractGenerated + | KaniAttributeKind::Modifies + | KaniAttributeKind::InnerCheck | KaniAttributeKind::ReplacedWith => { self.tcx.sess.span_err(self.tcx.def_span(self.item), format!("Contracts are not supported on harnesses. (Found the kani-internal contract attribute `{}`)", kind.as_ref())); } @@ -490,6 +544,73 @@ impl<'tcx> KaniAttributes<'tcx> { resolve::resolve_fn(self.tcx, current_module.to_local_def_id(), &replacement).unwrap(); Stub { original: original_str.to_string(), replacement } } + + pub fn modifies_contract(&self) -> Option> { + let local_def_id = self.item.expect_local(); + self.map.get(&KaniAttributeKind::Modifies).map(|attr| { + attr.iter() + .flat_map(|clause| match &clause.get_normal_item().args { + AttrArgs::Delimited(lvals) => { + parse_modify_values(self.tcx, local_def_id, &lvals.tokens) + } + _ => unreachable!(), + }) + .collect() + }) + } +} + +macro_rules! comma_tok { + () => { + TokenTree::Token(Token { kind: TokenKind::Comma, .. }, _) + }; +} + +fn parse_modify_values<'a>( + tcx: TyCtxt<'a>, + local_def_id: LocalDefId, + t: &'a TokenStream, +) -> impl Iterator + 'a { + let mir = tcx.optimized_mir(local_def_id); + let mut iter = t.trees(); + std::iter::from_fn(move || { + let tree = iter.next()?; + let wrong_token_err = + || tcx.sess.span_err(tree.span(), "Unexpected token. Expected identifier."); + let result = match tree { + TokenTree::Token(token, _) => { + if let TokenKind::Ident(id, _) = &token.kind { + let hir = tcx.hir(); + let bid = hir.body_owned_by(local_def_id); + Some( + hir.body_param_names(bid) + .zip(mir.args_iter()) + .find(|(name, _decl)| name.name == *id) + .unwrap() + .1, + ) + } else { + wrong_token_err(); + None + } + } + _ => { + wrong_token_err(); + None + } + }; + match iter.next() { + None | Some(comma_tok!()) => (), + Some(not_comma) => { + tcx.sess.span_err( + not_comma.span(), + "Unexpected token, expected end of attribute or comma", + ); + iter.by_ref().skip_while(|t| !matches!(t, comma_tok!())).count(); + } + } + result + }) } /// An efficient check for the existence for a particular [`KaniAttributeKind`]. diff --git a/kani-compiler/src/kani_middle/contracts.rs b/kani-compiler/src/kani_middle/contracts.rs new file mode 100644 index 000000000000..0e763f0c89c0 --- /dev/null +++ b/kani-compiler/src/kani_middle/contracts.rs @@ -0,0 +1,43 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +//! Basic type definitions for function contracts. + +/// Generic representation for a function contract. This is so that we can reuse +/// this type for different resolution stages if the implementation functions +/// (`C`). +/// +/// Note that currently only the `assigns` clause is actually used, whereas +/// requires and ensures are handled by the frontend. We leave this struct here +/// since in theory a CBMC code gen for any clause has been implemented thus +/// this parallels the structure expected by CBMC. +#[derive(Default)] +pub struct GFnContract { + requires: Vec, + ensures: Vec, + assigns: Vec, + frees: Vec, +} + +impl GFnContract { + /// Read access to all precondition clauses. + pub fn requires(&self) -> &[C] { + &self.requires + } + + /// Read access to all postcondition clauses. + pub fn ensures(&self) -> &[C] { + &self.ensures + } + + pub fn assigns(&self) -> &[A] { + &self.assigns + } + + pub fn frees(&self) -> &[F] { + &self.frees + } + + pub fn new(requires: Vec, ensures: Vec, assigns: Vec, frees: Vec) -> Self { + Self { requires, ensures, assigns, frees } + } +} diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index 0cc3ee0b7f3a..84d485610b68 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -52,7 +52,7 @@ pub fn resolve_fn<'tcx>( /// paths. /// /// Note: This function was written to be generic, however, it has only been tested for functions. -fn resolve_path<'tcx>( +pub(crate) fn resolve_path<'tcx>( tcx: TyCtxt<'tcx>, current_module: LocalDefId, path_str: &str, diff --git a/kani-driver/src/call_goto_instrument.rs b/kani-driver/src/call_goto_instrument.rs index 8a97d9e15747..444298fb1769 100644 --- a/kani-driver/src/call_goto_instrument.rs +++ b/kani-driver/src/call_goto_instrument.rs @@ -22,6 +22,7 @@ impl KaniSession { output: &Path, project: &Project, harness: &HarnessMetadata, + contract_info: Option, ) -> Result<()> { // We actually start by calling goto-cc to start the specialization: self.specialize_to_proof_harness(input, output, &harness.mangled_name)?; @@ -37,6 +38,8 @@ impl KaniSession { self.goto_sanity_check(output)?; } + self.instrument_contracts(harness, output, contract_info)?; + if self.args.checks.undefined_function_on() { self.add_library(output)?; self.undefined_functions(output)?; @@ -160,6 +163,30 @@ impl KaniSession { self.call_goto_instrument(args) } + /// Make CBMC enforce a function contract. + pub fn instrument_contracts( + &self, + harness: &HarnessMetadata, + file: &Path, + check: Option, + ) -> Result<()> { + if check.is_none() { + return Ok(()); + } + + let mut args: Vec = + vec!["--dfcc".into(), (&harness.mangled_name).into()]; + + if let Some(function) = check { + println!("enforcing function contract for {function}"); + args.extend(["--enforce-contract".into(), function.into()]); + } + + args.extend([file.into(), file.into()]); + + self.call_goto_instrument(args) + } + /// Generate a .demangled.c file from the .c file using the `prettyName`s from the symbol table /// /// Currently, only top-level function names and (most) type names are demangled. diff --git a/kani-driver/src/harness_runner.rs b/kani-driver/src/harness_runner.rs index 2cd8bb7cbd70..563d3f2f86c6 100644 --- a/kani-driver/src/harness_runner.rs +++ b/kani-driver/src/harness_runner.rs @@ -57,7 +57,15 @@ impl<'sess, 'pr> HarnessRunner<'sess, 'pr> { let report_dir = self.project.outdir.join(format!("report-{harness_filename}")); let goto_file = self.project.get_harness_artifact(&harness, ArtifactType::Goto).unwrap(); - self.sess.instrument_model(goto_file, goto_file, &self.project, &harness)?; + let contract_info = self.get_contract_info(harness)?; + + self.sess.instrument_model( + goto_file, + goto_file, + &self.project, + &harness, + contract_info, + )?; if self.sess.args.synthesize_loop_contracts { self.sess.synthesize_loop_contracts(goto_file, &goto_file, &harness)?; @@ -72,6 +80,14 @@ impl<'sess, 'pr> HarnessRunner<'sess, 'pr> { Ok(results) } + fn get_contract_info(&self, harness: &'pr HarnessMetadata) -> Result> { + let contract_info_artifact = + self.project.get_harness_artifact(&harness, ArtifactType::ContractMetadata).unwrap(); + + let reader = std::io::BufReader::new(std::fs::File::open(contract_info_artifact)?); + Ok(serde_json::from_reader(reader)?) + } + /// Return an error if the user is trying to verify a harness with stubs without enabling the /// experimental feature. fn check_stubbing(&self, harnesses: &[&HarnessMetadata]) -> Result<()> { diff --git a/kani_metadata/src/artifact.rs b/kani_metadata/src/artifact.rs index 54ff7a025e19..6cebbf79ca66 100644 --- a/kani_metadata/src/artifact.rs +++ b/kani_metadata/src/artifact.rs @@ -25,6 +25,8 @@ pub enum ArtifactType { /// A `json` file that stores the name to prettyName mapping for symbols /// (used to demangle names from the C dump). PrettyNameMap, + + ContractMetadata, } impl ArtifactType { @@ -37,6 +39,7 @@ impl ArtifactType { ArtifactType::TypeMap => "type_map.json", ArtifactType::VTableRestriction => "restrictions.json", ArtifactType::PrettyNameMap => "pretty_name_map.json", + ArtifactType::ContractMetadata => "power-of-the-law.json", } } } @@ -64,6 +67,7 @@ pub fn convert_type(path: &Path, from: ArtifactType, to: ArtifactType) -> PathBu | ArtifactType::SymTabGoto | ArtifactType::TypeMap | ArtifactType::VTableRestriction + | ArtifactType::ContractMetadata | ArtifactType::PrettyNameMap => { result.set_extension(""); result.set_extension(&to); diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index 89482a6266ca..0ca34d521230 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -169,7 +169,7 @@ pub fn stub_verified(attr: TokenStream, item: TokenStream) -> TokenStream { /// This module implements Kani attributes in a way that only Kani's compiler can understand. /// This code should only be activated when pre-building Kani's sysroot. -#[cfg(kani_sysroot)] +//#[cfg(kani_sysroot)] mod sysroot { use proc_macro_error::{abort, abort_call_site}; diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index bc00a7ba5e67..c352038e0bf8 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -175,7 +175,8 @@ use std::{ }; use syn::{ parse_macro_input, spanned::Spanned, visit::Visit, visit_mut::VisitMut, Attribute, Expr, FnArg, - ItemFn, PredicateType, ReturnType, Signature, Token, TraitBound, TypeParamBound, WhereClause, GenericArgument, + GenericArgument, ItemFn, PredicateType, ReturnType, Signature, Token, TraitBound, + TypeParamBound, WhereClause, }; pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream { @@ -284,7 +285,6 @@ impl ContractFunctionState { } } - /// The information needed to generate the bodies of check and replacement /// functions that integrate the conditions from this contract attribute. struct ContractConditionsHandler<'a> { @@ -397,7 +397,9 @@ impl<'a> ContractConditionsHandler<'a> { let mut call = self.create_inner_call([].into_iter()); - assert!(matches!(call.pop(), Some(syn::Stmt::Expr(syn::Expr::Path(pexpr), None)) if pexpr.path.get_ident().map_or(false, |id| id == "result"))); + assert!( + matches!(call.pop(), Some(syn::Stmt::Expr(syn::Expr::Path(pexpr), None)) if pexpr.path.get_ident().map_or(false, |id| id == "result")) + ); quote!( #arg_copies @@ -561,30 +563,21 @@ impl<'a> ContractConditionsHandler<'a> { let wrapper_args = make_wrapper_args(attr.len()); let sig = &mut self.annotated_fn.sig; for arg in wrapper_args.clone() { - let lifetime = syn::Lifetime { - apostrophe: Span::call_site(), - ident: arg.clone(), - }; - sig.inputs.push( - FnArg::Typed(syn::PatType { - attrs: vec![], - colon_token: Token![:](Span::call_site()), - pat: Box::new(syn::Pat::Verbatim(quote!(#arg))), - ty: Box::new(syn::Type::Verbatim(quote!(&#lifetime impl kani::Arbitrary))), - }) - ); - sig.generics.params.push( - syn::GenericParam::Lifetime(syn::LifetimeParam { - lifetime, - colon_token: None, - bounds: Default::default(), - attrs: vec![], - }) - ); + let lifetime = syn::Lifetime { apostrophe: Span::call_site(), ident: arg.clone() }; + sig.inputs.push(FnArg::Typed(syn::PatType { + attrs: vec![], + colon_token: Token![:](Span::call_site()), + pat: Box::new(syn::Pat::Verbatim(quote!(#arg))), + ty: Box::new(syn::Type::Verbatim(quote!(&#lifetime impl kani::Arbitrary))), + })); + sig.generics.params.push(syn::GenericParam::Lifetime(syn::LifetimeParam { + lifetime, + colon_token: None, + bounds: Default::default(), + attrs: vec![], + })); } - self.output.extend( - quote!(#[kanitool::modifies(#(#wrapper_args),*)]) - ) + self.output.extend(quote!(#[kanitool::modifies(#(#wrapper_args),*)])) } self.emit_common_header(); @@ -592,8 +585,8 @@ impl<'a> ContractConditionsHandler<'a> { // If it's the first time we also emit this marker. Again, order is // important so this happens as the last emitted attribute. self.output.extend(quote!(#[kanitool::is_contract_generated(wrapper)])); - } - + } + let name = self.make_wrapper_name(); let ItemFn { vis, sig, block, .. } = self.annotated_fn; @@ -917,8 +910,6 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) output.into() } - - /// Create a unique hash for a token stream (basically a [`std::hash::Hash`] /// impl for `proc_macro2::TokenStream`). fn hash_of_token_stream(hasher: &mut H, stream: proc_macro2::TokenStream) { @@ -1077,4 +1068,3 @@ where && path.leading_colon.is_none() && path.segments.iter().zip(mtch).all(|(actual, expected)| actual.ident == *expected) } - From 2759469b78f07bed957f9670343cb9a5b87c43cc Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Mon, 2 Oct 2023 10:52:56 -0400 Subject: [PATCH 05/72] Debugging proc-macro code --- .../compiler_interface.rs | 2 +- kani-driver/src/project.rs | 10 ++-- library/kani_macros/Cargo.toml | 2 +- library/kani_macros/src/lib.rs | 8 ++- library/kani_macros/src/sysroot/contracts.rs | 56 ++++++++++++------- .../function-contract/assigns_expr_pass.rs | 15 +++++ .../function-contract/assigns_fail.expected | 7 +++ .../function-contract/assigns_fail.rs | 15 +++++ .../function-contract/assigns_pass.expected | 7 +++ .../function-contract/assigns_pass.rs | 16 ++++++ 10 files changed, 110 insertions(+), 28 deletions(-) create mode 100644 tests/expected/function-contract/assigns_expr_pass.rs create mode 100644 tests/expected/function-contract/assigns_fail.expected create mode 100644 tests/expected/function-contract/assigns_fail.rs create mode 100644 tests/expected/function-contract/assigns_pass.expected create mode 100644 tests/expected/function-contract/assigns_pass.rs diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 01fb160dbc3a..30f1d84cf074 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -169,7 +169,7 @@ impl GotocCodegenBackend { ) }; let gcx = &mut gcx; - let mut attach_contract = + let attach_contract = |target| gcx.attach_contract(target, assigns_contract); let name_for_inst = |inst| tcx.symbol_name(inst).to_string(); diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index c776a106ae47..35ec8a9864d3 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -130,10 +130,12 @@ impl Project { // All other harness artifacts that may have been generated as part of the build. artifacts.extend( - [SymTab, TypeMap, VTableRestriction, PrettyNameMap].iter().filter_map(|typ| { - let artifact = Artifact::try_from(&symtab_out, *typ).ok()?; - Some(artifact) - }), + [SymTab, TypeMap, VTableRestriction, PrettyNameMap, ContractMetadata] + .iter() + .filter_map(|typ| { + let artifact = Artifact::try_from(&symtab_out, *typ).ok()?; + Some(artifact) + }), ); artifacts.push(symtab_out); artifacts.push(goto); diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index 42c30f20c29a..69ea97f5b194 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -15,4 +15,4 @@ proc-macro = true proc-macro2 = "1.0" proc-macro-error = "1.0.4" quote = "1.0.20" -syn = { version = "2.0.18", features = ["full", "visit-mut", "visit"] } +syn = { version = "2.0.18", features = ["full", "visit-mut", "visit", "extra-traits"] } diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index 0ca34d521230..f980a0602b34 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -167,6 +167,11 @@ pub fn stub_verified(attr: TokenStream, item: TokenStream) -> TokenStream { attr_impl::stub_verified(attr, item) } +#[proc_macro_attribute] +pub fn modifies(attr: TokenStream, item: TokenStream) -> TokenStream { + attr_impl::modifies(attr, item) +} + /// This module implements Kani attributes in a way that only Kani's compiler can understand. /// This code should only be activated when pre-building Kani's sysroot. //#[cfg(kani_sysroot)] @@ -175,7 +180,7 @@ mod sysroot { mod contracts; - pub use contracts::{ensures, proof_for_contract, requires, stub_verified}; + pub use contracts::{ensures, modifies, proof_for_contract, requires, stub_verified}; use super::*; @@ -348,6 +353,7 @@ mod regular { no_op!(unwind); no_op!(requires); no_op!(ensures); + no_op!(modifies); no_op!(proof_for_contract); no_op!(stub_verified); } diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index c352038e0bf8..8863cfa03cd2 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -433,7 +433,7 @@ impl<'a> ContractConditionsHandler<'a> { let wrapper_args = make_wrapper_args(attr.len()); quote!( - #(let #wrapper_args = kani::untracked_deref(#attr);)* + #(let #wrapper_args = kani::untracked_deref(&#attr);)* #(#inner)* ) } @@ -444,7 +444,13 @@ impl<'a> ContractConditionsHandler<'a> { if let Some(hash) = self.hash { identifier_for_generated_function(&self.annotated_fn.sig.ident, "wrapper", hash) } else { - self.annotated_fn.sig.ident.clone() + let str_name = self.annotated_fn.sig.ident.to_string(); + let splits = str_name.rsplitn(3, '_').collect::>(); + let [hash, _, base] = splits.as_slice() else { + unreachable!("Odd name for function {str_name}, splits were {}", splits.len()); + }; + + Ident::new(&format!("{base}_wrapper_{hash}"), Span::call_site()) } } @@ -681,35 +687,40 @@ fn try_as_wrapper_call_args<'a>( stmt: &'a mut syn::Stmt, wrapper_fn_name: &str, ) -> Option<&'a mut syn::punctuated::Punctuated> { + println!("Checking statement {stmt:?}"); match stmt { syn::Stmt::Local(syn::Local { - pat: - syn::Pat::Ident(syn::PatIdent { - by_ref: None, - mutability: None, - ident: result_ident, - subpat: None, - .. - }), + pat: syn::Pat::Type(syn::PatType { pat: inner_pat, .. }), init: Some(syn::LocalInit { diverge: None, expr: init_expr, .. }), .. - }) if result_ident == "result" => match init_expr.as_mut() { - Expr::Call(syn::ExprCall { func: box_func, args, .. }) => match box_func.as_ref() { - syn::Expr::Path(syn::ExprPath { qself: None, path, .. }) - if path.get_ident().map_or(false, |id| id == wrapper_fn_name) => - { - Some(args) - } + }) if matches!(inner_pat.as_ref(), + syn::Pat::Ident(syn::PatIdent { + by_ref: None, + mutability: None, + ident: result_ident, + subpat: None, + .. + }) if result_ident == "result" + ) => + { + match init_expr.as_mut() { + Expr::Call(syn::ExprCall { func: box_func, args, .. }) => match box_func.as_ref() { + syn::Expr::Path(syn::ExprPath { qself: None, path, .. }) + if path.get_ident().map_or(false, |id| id == wrapper_fn_name) => + { + Some(args) + } + _ => None, + }, _ => None, - }, - _ => None, - }, + } + } _ => None, } } fn make_wrapper_args(num: usize) -> impl Iterator + Clone { - (0..num).map(|i| Ident::new(&format!("wrapper_arg_{i}"), Span::mixed_site())) + (0..num).map(|i| Ident::new(&format!("_wrapper_arg_{i}"), Span::mixed_site())) } /// If an explicit return type was provided it is returned, otherwise `()`. @@ -881,6 +892,8 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) let replace_fn_name_str = syn::LitStr::new(&replace_fn_name.to_string(), Span::call_site()); let check_fn_name_str = syn::LitStr::new(&check_fn_name.to_string(), Span::call_site()); + let wrapper_fn_name_str = + syn::LitStr::new(&handler.make_wrapper_name().to_string(), Span::call_site()); // The order of `attrs` and `kanitool::{checked_with, // is_contract_generated}` is important here, because macros are @@ -895,6 +908,7 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) #(#attrs)* #[kanitool::checked_with = #check_fn_name_str] #[kanitool::replaced_with = #replace_fn_name_str] + #[kanitool::inner_check = #wrapper_fn_name_str] #vis #sig { #block } diff --git a/tests/expected/function-contract/assigns_expr_pass.rs b/tests/expected/function-contract/assigns_expr_pass.rs new file mode 100644 index 000000000000..a196d94b59b0 --- /dev/null +++ b/tests/expected/function-contract/assigns_expr_pass.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::requires(**ptr < 100)] +#[kani::modifies((*ptr).as_ref())] +fn modify(ptr: &mut Box) { + *ptr += 1; +} + +#[kani::proof_for_contract(modify)] +fn main() { + let mut i = Box::new(kani::any()); + modify(&mut i); +} \ No newline at end of file diff --git a/tests/expected/function-contract/assigns_fail.expected b/tests/expected/function-contract/assigns_fail.expected new file mode 100644 index 000000000000..fd8d816451cd --- /dev/null +++ b/tests/expected/function-contract/assigns_fail.expected @@ -0,0 +1,7 @@ +assigns\ +- Status: FAILURE\ +- Description: "Check that *ptr is assignable" + +Failed Checks: Check that *ptr is assignable + +VERIFICATION:- FAILED \ No newline at end of file diff --git a/tests/expected/function-contract/assigns_fail.rs b/tests/expected/function-contract/assigns_fail.rs new file mode 100644 index 000000000000..3fee15716655 --- /dev/null +++ b/tests/expected/function-contract/assigns_fail.rs @@ -0,0 +1,15 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::requires(*ptr < 100)] +fn modify(ptr: &mut u32) { + *ptr += 1; +} + +#[kani::proof_for_contract(modify)] +fn main() { + let _ = Box::new(()); + let mut i = kani::any(); + modify(&mut i); +} \ No newline at end of file diff --git a/tests/expected/function-contract/assigns_pass.expected b/tests/expected/function-contract/assigns_pass.expected new file mode 100644 index 000000000000..fd8d816451cd --- /dev/null +++ b/tests/expected/function-contract/assigns_pass.expected @@ -0,0 +1,7 @@ +assigns\ +- Status: FAILURE\ +- Description: "Check that *ptr is assignable" + +Failed Checks: Check that *ptr is assignable + +VERIFICATION:- FAILED \ No newline at end of file diff --git a/tests/expected/function-contract/assigns_pass.rs b/tests/expected/function-contract/assigns_pass.rs new file mode 100644 index 000000000000..2690fb54117f --- /dev/null +++ b/tests/expected/function-contract/assigns_pass.rs @@ -0,0 +1,16 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::requires(*ptr < 100)] +#[kani::modifies(ptr)] +fn modify(ptr: &mut u32) { + *ptr += 1; +} + +#[kani::proof_for_contract(modify)] +fn main() { + let _ = Box::new(()); + let mut i = kani::any(); + modify(&mut i); +} \ No newline at end of file From 9dd55a4c1ef037f60907b5b1a058518a8b2ce578 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 2 Nov 2023 12:36:33 -0700 Subject: [PATCH 06/72] Fixed the assigns bug --- .../codegen_cprover_gotoc/codegen/function.rs | 2 +- .../compiler_interface.rs | 36 +++++++------------ library/kani_macros/src/sysroot/contracts.rs | 1 - 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index 42a86ec75249..fbc0977d497b 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -7,7 +7,7 @@ use crate::codegen_cprover_gotoc::GotocCtx; use cbmc::goto_program::{Expr, FunctionContract, Stmt, Symbol}; use cbmc::InternString; use rustc_middle::mir::traversal::reverse_postorder; -use rustc_middle::mir::{self, Body, HasLocalDecls, Local}; +use rustc_middle::mir::{Body, HasLocalDecls, Local}; use rustc_middle::ty::{self, Instance}; use std::collections::BTreeMap; use std::iter::FromIterator; diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 30f1d84cf074..467e157d180b 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -38,7 +38,7 @@ use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::mir::mono::MonoItem; use rustc_middle::query::{ExternProviders, Providers}; -use rustc_middle::ty::{Instance, InstanceDef, ParamEnv, TyCtxt}; +use rustc_middle::ty::{Instance, InstanceDef, TyCtxt}; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; use rustc_session::cstore::MetadataLoaderDyn; use rustc_session::output::out_filename; @@ -82,7 +82,7 @@ impl GotocCodegenBackend { starting_items: &[MonoItem<'tcx>], symtab_goto: &Path, machine_model: &MachineModel, - check_contract: Option, + mut check_contract: Option, ) -> (GotocCtx<'tcx>, Vec>) { let items = with_timer( || collect_reachable_items(tcx, starting_items), @@ -146,6 +146,11 @@ impl GotocCodegenBackend { } } + if let Some(did) = &mut check_contract { + let attrs = KaniAttributes::for_item(tcx, *did); + *did = attrs.inner_check().unwrap().unwrap() + } + // Attaching the contract gets its own loop, because the // functions used in the contract expressions must have been // declared and created before since we rip out the @@ -157,29 +162,12 @@ impl GotocCodegenBackend { { if check_contract == Some(*did) { let attrs = KaniAttributes::for_item(tcx, *did); - let assigns_contract = - attrs.modifies_contract().unwrap_or_else(Vec::new); - - let get_instance = |did| { - Instance::expect_resolve( - tcx, - ParamEnv::reveal_all(), - did, - instance.args, - ) - }; - let gcx = &mut gcx; - let attach_contract = - |target| gcx.attach_contract(target, assigns_contract); - let name_for_inst = |inst| tcx.symbol_name(inst).to_string(); - - let Ok(inner_check_id) = attrs.inner_check().unwrap() else { - continue; - }; - let inner_check_inst = get_instance(inner_check_id); - attach_contract(inner_check_inst); + let assigns_contract = attrs.modifies_contract().unwrap(); + gcx.attach_contract(*instance, assigns_contract); assert!( - contract_info.replace(name_for_inst(inner_check_inst)).is_none() + contract_info + .replace(tcx.symbol_name(*instance).to_string()) + .is_none() ); } } diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 8863cfa03cd2..df946ca9b707 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -687,7 +687,6 @@ fn try_as_wrapper_call_args<'a>( stmt: &'a mut syn::Stmt, wrapper_fn_name: &str, ) -> Option<&'a mut syn::punctuated::Punctuated> { - println!("Checking statement {stmt:?}"); match stmt { syn::Stmt::Local(syn::Local { pat: syn::Pat::Type(syn::PatType { pat: inner_pat, .. }), From ba27ab62ddcff5a98e98a6f93aed7552aac01442 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 2 Nov 2023 16:19:07 -0700 Subject: [PATCH 07/72] Using lifetime decoupling for `modifies` --- library/kani/src/lib.rs | 34 +++++++++++++++++++ library/kani_macros/src/sysroot/contracts.rs | 8 ++--- .../function-contract/assigns_expr_pass.rs | 4 +-- tests/expected/function-contract/test.rs | 14 ++++++++ 4 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 tests/expected/function-contract/test.rs diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 1da6a4655018..63190419426c 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -92,6 +92,40 @@ macro_rules! implies { }; } +#[doc(hidden)] +pub trait DecoupleLifetime<'a> { + type Inner; + unsafe fn decouple_lifetime(self) -> &'a Self::Inner; +} + +impl<'a, 'b, T> DecoupleLifetime<'a> for &'b T { + type Inner = T; + unsafe fn decouple_lifetime(self) -> &'a Self::Inner { + std::mem::transmute(self) + } +} + +impl<'a, 'b, T> DecoupleLifetime<'a> for &'b mut T { + type Inner = T; + unsafe fn decouple_lifetime(self) -> &'a Self::Inner { + std::mem::transmute(self) + } +} + +impl<'a, T> DecoupleLifetime<'a> for *const T { + type Inner = T; + unsafe fn decouple_lifetime(self) -> &'a Self::Inner { + &*self as &'a T + } +} + +impl<'a, T> DecoupleLifetime<'a> for *mut T { + type Inner = T; + unsafe fn decouple_lifetime(self) -> &'a Self::Inner { + &*self as &'a T + } +} + /// A way to break the ownerhip rules. Only used by contracts where we can /// guarantee it is done safely. #[inline(never)] diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index df946ca9b707..d4d8bf7f67b7 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -332,7 +332,7 @@ impl ContractConditionsType { } impl<'a> ContractConditionsHandler<'a> { - fn is_fist_emit(&self) -> bool { + fn is_first_emit(&self) -> bool { matches!(self.function_state, ContractFunctionState::Untouched) } @@ -412,7 +412,7 @@ impl<'a> ContractConditionsHandler<'a> { let wrapper_name = self.make_wrapper_name().to_string(); let wrapper_args = make_wrapper_args(attr.len()); // TODO handle first invocation where this is the actual body. - if !self.is_fist_emit() { + if !self.is_first_emit() { if let Some(wrapper_call_args) = self .annotated_fn .block @@ -433,7 +433,7 @@ impl<'a> ContractConditionsHandler<'a> { let wrapper_args = make_wrapper_args(attr.len()); quote!( - #(let #wrapper_args = kani::untracked_deref(&#attr);)* + #(let #wrapper_args = unsafe { kani::DecoupleLifetime::decouple_lifetime(&#attr) };)* #(#inner)* ) } @@ -457,7 +457,7 @@ impl<'a> ContractConditionsHandler<'a> { fn create_inner_call(&self, additional_args: impl Iterator) -> Vec { let wrapper_name = self.make_wrapper_name(); let return_type = return_type_to_type(&self.annotated_fn.sig.output); - if self.is_fist_emit() { + if self.is_first_emit() { let args = exprs_for_args(&self.annotated_fn.sig.inputs); syn::parse_quote!( let result : #return_type = #wrapper_name(#(#args,)* #(#additional_args),*); diff --git a/tests/expected/function-contract/assigns_expr_pass.rs b/tests/expected/function-contract/assigns_expr_pass.rs index a196d94b59b0..107c137886ba 100644 --- a/tests/expected/function-contract/assigns_expr_pass.rs +++ b/tests/expected/function-contract/assigns_expr_pass.rs @@ -3,9 +3,9 @@ // kani-flags: -Zfunction-contracts #[kani::requires(**ptr < 100)] -#[kani::modifies((*ptr).as_ref())] +#[kani::modifies(*ptr.as_ref())] fn modify(ptr: &mut Box) { - *ptr += 1; + *ptr.as_mut() += 1; } #[kani::proof_for_contract(modify)] diff --git a/tests/expected/function-contract/test.rs b/tests/expected/function-contract/test.rs new file mode 100644 index 000000000000..9f1b268c5975 --- /dev/null +++ b/tests/expected/function-contract/test.rs @@ -0,0 +1,14 @@ +fn modify_check_6e1104(ptr: &mut Box) { + let _wrapper_arg_0 = kani::untracked_deref(&*ptr.as_ref()); + kani::assume(**ptr < 100); + let result: () = modify_wrapper_6e1104(ptr, _wrapper_arg_0); + result +} +fn modify_replace_6e1104(ptr: &mut Box) { + kani::assert(false, "Replacement with modifies is not supported yet.") +} +fn modify_wrapper_6e1104<'_wrapper_arg_0>(ptr: &mut Box, + _wrapper_arg_0: &'_wrapper_arg_0 impl kani::Arbitrary) { + *ptr.as_mut() += 1; +} +fn main() { let mut i = Box::new(kani::any()); modify_check_6e1104(&mut i); } \ No newline at end of file From bbc4e99f63ae74319633e94133645126d0a49002 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 2 Nov 2023 18:43:25 -0700 Subject: [PATCH 08/72] Fix lifetime decoupling trait --- library/kani/src/lib.rs | 18 +++++++++--------- tests/expected/function-contract/test.rs | 14 -------------- 2 files changed, 9 insertions(+), 23 deletions(-) delete mode 100644 tests/expected/function-contract/test.rs diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 63190419426c..52633a24a89a 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -95,34 +95,34 @@ macro_rules! implies { #[doc(hidden)] pub trait DecoupleLifetime<'a> { type Inner; - unsafe fn decouple_lifetime(self) -> &'a Self::Inner; + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner; } impl<'a, 'b, T> DecoupleLifetime<'a> for &'b T { type Inner = T; - unsafe fn decouple_lifetime(self) -> &'a Self::Inner { - std::mem::transmute(self) + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + std::mem::transmute(*self) } } impl<'a, 'b, T> DecoupleLifetime<'a> for &'b mut T { type Inner = T; - unsafe fn decouple_lifetime(self) -> &'a Self::Inner { - std::mem::transmute(self) + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + *std::mem::transmute::<&'_ &'b mut T, &'_ &'a T>(self) } } impl<'a, T> DecoupleLifetime<'a> for *const T { type Inner = T; - unsafe fn decouple_lifetime(self) -> &'a Self::Inner { - &*self as &'a T + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + &**self as &'a T } } impl<'a, T> DecoupleLifetime<'a> for *mut T { type Inner = T; - unsafe fn decouple_lifetime(self) -> &'a Self::Inner { - &*self as &'a T + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + &**self as &'a T } } diff --git a/tests/expected/function-contract/test.rs b/tests/expected/function-contract/test.rs deleted file mode 100644 index 9f1b268c5975..000000000000 --- a/tests/expected/function-contract/test.rs +++ /dev/null @@ -1,14 +0,0 @@ -fn modify_check_6e1104(ptr: &mut Box) { - let _wrapper_arg_0 = kani::untracked_deref(&*ptr.as_ref()); - kani::assume(**ptr < 100); - let result: () = modify_wrapper_6e1104(ptr, _wrapper_arg_0); - result -} -fn modify_replace_6e1104(ptr: &mut Box) { - kani::assert(false, "Replacement with modifies is not supported yet.") -} -fn modify_wrapper_6e1104<'_wrapper_arg_0>(ptr: &mut Box, - _wrapper_arg_0: &'_wrapper_arg_0 impl kani::Arbitrary) { - *ptr.as_mut() += 1; -} -fn main() { let mut i = Box::new(kani::any()); modify_check_6e1104(&mut i); } \ No newline at end of file From 00157cbfb603c26520fb1231b094e3003315c2f6 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 2 Nov 2023 18:49:26 -0700 Subject: [PATCH 09/72] Make assigns existence optional again --- .../src/codegen_cprover_gotoc/compiler_interface.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 467e157d180b..e40dc048bcf6 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -162,7 +162,10 @@ impl GotocCodegenBackend { { if check_contract == Some(*did) { let attrs = KaniAttributes::for_item(tcx, *did); - let assigns_contract = attrs.modifies_contract().unwrap(); + let Some(assigns_contract) = attrs.modifies_contract() else { + debug!(?instance, "had no assigns contract specified"); + continue; + }; gcx.attach_contract(*instance, assigns_contract); assert!( contract_info @@ -172,7 +175,6 @@ impl GotocCodegenBackend { } } } - assert_eq!(contract_info.is_some(), check_contract.is_some()); contract_info }, "codegen", From 559cf813788cde6db4aed57ab29c87a51a749906 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 3 Nov 2023 12:07:44 -0700 Subject: [PATCH 10/72] Reviving havoc protection for reentry tracker --- .../compiler_interface.rs | 51 +++++---- kani-compiler/src/kani_middle/attributes.rs | 12 +- kani-driver/src/call_goto_instrument.rs | 13 ++- kani-driver/src/harness_runner.rs | 2 +- library/kani_macros/src/sysroot/contracts.rs | 105 ++++++++++++------ 5 files changed, 125 insertions(+), 58 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index a1dda0147b5c..526730a865d8 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -37,8 +37,8 @@ use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME}; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::mir::mono::MonoItem; +use rustc_middle::ty::{self, Instance, InstanceDef, ParamEnv, TyCtxt}; use rustc_middle::util::Providers; -use rustc_middle::ty::{Instance, InstanceDef, TyCtxt}; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; use rustc_session::cstore::MetadataLoaderDyn; use rustc_session::output::out_filename; @@ -82,7 +82,7 @@ impl GotocCodegenBackend { starting_items: &[MonoItem<'tcx>], symtab_goto: &Path, machine_model: &MachineModel, - mut check_contract: Option, + check_contract: Option, ) -> (GotocCtx<'tcx>, Vec>) { let items = with_timer( || collect_reachable_items(tcx, starting_items), @@ -146,25 +146,38 @@ impl GotocCodegenBackend { } } - if let Some(did) = &mut check_contract { - let attrs = KaniAttributes::for_item(tcx, *did); - *did = attrs.inner_check().unwrap().unwrap() - } + check_contract.map(|check_contract_def| { + let check_contract_attrs = KaniAttributes::for_item(tcx, check_contract_def); + let wrapper_def = check_contract_attrs.inner_check().unwrap().unwrap(); - let mut instances_of_check = items.iter().copied().filter_map(|i| match i { - MonoItem::Fn(instance @ Instance { def: InstanceDef::Item(did), .. }) => (check_contract == Some(did)).then_some(instance), - _ => None - }); - let instance_of_check = instances_of_check.next().unwrap(); - assert!(instances_of_check.next().is_none()); - let attrs = KaniAttributes::for_item(tcx, instance_of_check.def_id()); - let assigns_contract = attrs.modifies_contract().unwrap_or_else(|| { - debug!(?instance_of_check, "had no assigns contract specified"); - vec![] - }); - gcx.attach_contract(instance_of_check, assigns_contract); + let mut instances_of_check = items.iter().copied().filter_map(|i| match i { + MonoItem::Fn(instance @ Instance { def: InstanceDef::Item(did), .. }) => { + (wrapper_def == did).then_some(instance) + } + _ => None, + }); + let instance_of_check = instances_of_check.next().unwrap(); + assert!(instances_of_check.next().is_none()); + let attrs = KaniAttributes::for_item(tcx, instance_of_check.def_id()); + let assigns_contract = attrs.modifies_contract().unwrap_or_else(|| { + debug!(?instance_of_check, "had no assigns contract specified"); + vec![] + }); + gcx.attach_contract(instance_of_check, assigns_contract); + + let tracker_def = check_contract_attrs.recursion_tracker().unwrap().unwrap(); + let tracker_instance = Instance::expect_resolve( + tcx, + ParamEnv::reveal_all(), + tracker_def, + ty::List::empty(), + ); - tcx.symbol_name(instance_of_check).to_string() + ( + tcx.symbol_name(instance_of_check).to_string(), + tcx.symbol_name(tracker_instance).to_string(), + ) + }) }, "codegen", ); diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 8b480bb37309..c6e8c2663b2a 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -60,6 +60,7 @@ enum KaniAttributeKind { IsContractGenerated, Modifies, InnerCheck, + RecursionTracker, } impl KaniAttributeKind { @@ -77,6 +78,7 @@ impl KaniAttributeKind { | KaniAttributeKind::ReplacedWith | KaniAttributeKind::CheckedWith | KaniAttributeKind::Modifies + | KaniAttributeKind::RecursionTracker | KaniAttributeKind::InnerCheck | KaniAttributeKind::IsContractGenerated => false, } @@ -222,6 +224,10 @@ impl<'tcx> KaniAttributes<'tcx> { .map(|target| expect_key_string_value(self.tcx.sess, target)) } + pub fn recursion_tracker(&self) -> Option> { + self.eval_sibling_attribute(KaniAttributeKind::RecursionTracker) + } + fn eval_sibling_attribute( &self, kind: KaniAttributeKind, @@ -340,6 +346,9 @@ impl<'tcx> KaniAttributes<'tcx> { KaniAttributeKind::InnerCheck => { self.inner_check(); } + KaniAttributeKind::RecursionTracker => { + self.recursion_tracker(); + } } } } @@ -443,6 +452,7 @@ impl<'tcx> KaniAttributes<'tcx> { | KaniAttributeKind::IsContractGenerated | KaniAttributeKind::Modifies | KaniAttributeKind::InnerCheck + | KaniAttributeKind::RecursionTracker | KaniAttributeKind::ReplacedWith => { self.tcx.sess.span_err(self.tcx.def_span(self.item), format!("Contracts are not supported on harnesses. (Found the kani-internal contract attribute `{}`)", kind.as_ref())); } @@ -498,7 +508,7 @@ impl<'tcx> KaniAttributes<'tcx> { .span_note( self.tcx.def_span(def_id), format!( - "Try adding a contract to this function or use the unsound `{}` attribute instead.", + "Try adding a contract to this function or use the unsound `{}` attribute instead.", KaniAttributeKind::Stub.as_ref(), ) ) diff --git a/kani-driver/src/call_goto_instrument.rs b/kani-driver/src/call_goto_instrument.rs index 444298fb1769..c6b36a7353f0 100644 --- a/kani-driver/src/call_goto_instrument.rs +++ b/kani-driver/src/call_goto_instrument.rs @@ -22,7 +22,7 @@ impl KaniSession { output: &Path, project: &Project, harness: &HarnessMetadata, - contract_info: Option, + contract_info: Option<(String, String)>, ) -> Result<()> { // We actually start by calling goto-cc to start the specialization: self.specialize_to_proof_harness(input, output, &harness.mangled_name)?; @@ -168,7 +168,7 @@ impl KaniSession { &self, harness: &HarnessMetadata, file: &Path, - check: Option, + check: Option<(String, String)>, ) -> Result<()> { if check.is_none() { return Ok(()); @@ -177,9 +177,14 @@ impl KaniSession { let mut args: Vec = vec!["--dfcc".into(), (&harness.mangled_name).into()]; - if let Some(function) = check { + if let Some((function, recursion_tracker)) = check { println!("enforcing function contract for {function}"); - args.extend(["--enforce-contract".into(), function.into()]); + args.extend([ + "--enforce-contract".into(), + function.into(), + "--nondet-static-exclude".into(), + recursion_tracker.into(), + ]); } args.extend([file.into(), file.into()]); diff --git a/kani-driver/src/harness_runner.rs b/kani-driver/src/harness_runner.rs index 563d3f2f86c6..b7cfcd3c35c7 100644 --- a/kani-driver/src/harness_runner.rs +++ b/kani-driver/src/harness_runner.rs @@ -80,7 +80,7 @@ impl<'sess, 'pr> HarnessRunner<'sess, 'pr> { Ok(results) } - fn get_contract_info(&self, harness: &'pr HarnessMetadata) -> Result> { + fn get_contract_info(&self, harness: &'pr HarnessMetadata) -> Result> { let contract_info_artifact = self.project.get_harness_artifact(&harness, ArtifactType::ContractMetadata).unwrap(); diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 3fc360690842..c50b88f42752 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -239,26 +239,24 @@ use std::{ }; use syn::{ parse_macro_input, spanned::Spanned, visit::Visit, visit_mut::VisitMut, Attribute, Expr, FnArg, - ItemFn, PredicateType, ReturnType, Signature, Token, TraitBound, - TypeParamBound, WhereClause, + ItemFn, PredicateType, ReturnType, Signature, Token, TraitBound, TypeParamBound, WhereClause, }; #[allow(dead_code)] pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream { - requires_ensures_main(attr, item, 0) + requires_ensures_main(attr, item, ContractConditionsType::Requires) } #[allow(dead_code)] pub fn ensures(attr: TokenStream, item: TokenStream) -> TokenStream { - requires_ensures_main(attr, item, 1) + requires_ensures_main(attr, item, ContractConditionsType::Ensures) } #[allow(dead_code)] pub fn modifies(attr: TokenStream, item: TokenStream) -> TokenStream { - requires_ensures_main(attr, item, 2) + requires_ensures_main(attr, item, ContractConditionsType::Modifies) } - /// This is very similar to the kani_attribute macro, but it instead creates /// key-value style attributes which I find a little easier to parse. macro_rules! passthrough { @@ -283,7 +281,21 @@ macro_rules! passthrough { } passthrough!(stub_verified, false); -passthrough!(proof_for_contract, true); + +pub fn proof_for_contract(attr: TokenStream, item: TokenStream) -> TokenStream { + let args = proc_macro2::TokenStream::from(attr); + let ItemFn { attrs, vis, sig, block } = parse_macro_input!(item as ItemFn); + quote!( + #[allow(dead_code)] + #[kanitool::proof_for_contract = stringify!(#args)] + #(#attrs)* + #vis #sig { + let _ = std::boxed::Box::new(0_usize); + #block + } + ) + .into() +} /// Classifies the state a function is in in the contract handling pipeline. #[derive(Clone, Copy, PartialEq, Eq)] @@ -362,7 +374,7 @@ impl ContractFunctionState { struct ContractConditionsHandler<'a> { function_state: ContractFunctionState, /// Information specific to the type of contract attribute we're expanding. - condition_type: ContractConditionsType, + condition_type: ContractConditionsData, /// Body of the function this attribute was found on. annotated_fn: &'a mut ItemFn, /// An unparsed, unmodified copy of `attr`, used in the error messages. @@ -372,9 +384,16 @@ struct ContractConditionsHandler<'a> { hash: Option, } +#[derive(Copy, Clone, Eq, PartialEq)] +enum ContractConditionsType { + Requires, + Ensures, + Modifies, +} + /// Information needed for generating check and replace handlers for different /// contract attributes. -enum ContractConditionsType { +enum ContractConditionsData { Requires { /// The contents of the attribute. attr: Expr, @@ -391,7 +410,7 @@ enum ContractConditionsType { }, } -impl ContractConditionsType { +impl ContractConditionsData { /// Constructs a [`Self::Ensures`] from the signature of the decorated /// function and the contents of the decorating attribute. /// @@ -399,7 +418,7 @@ impl ContractConditionsType { /// `argument_names`. fn new_ensures(sig: &Signature, mut attr: Expr) -> Self { let argument_names = rename_argument_occurrences(sig, &mut attr); - ContractConditionsType::Ensures { argument_names, attr } + ContractConditionsData::Ensures { argument_names, attr } } } @@ -412,7 +431,7 @@ impl<'a> ContractConditionsHandler<'a> { /// [`ContractConditionsType`] depending on `is_requires`. fn new( function_state: ContractFunctionState, - is_requires: u8, + is_requires: ContractConditionsType, attr: TokenStream, annotated_fn: &'a mut ItemFn, attr_copy: TokenStream2, @@ -420,9 +439,13 @@ impl<'a> ContractConditionsHandler<'a> { hash: Option, ) -> Result { let condition_type = match is_requires { - 0 => ContractConditionsType::Requires { attr: syn::parse(attr)? }, - 1 => ContractConditionsType::new_ensures(&annotated_fn.sig, syn::parse(attr)?), - 2 => ContractConditionsType::Modifies { + ContractConditionsType::Requires => { + ContractConditionsData::Requires { attr: syn::parse(attr)? } + } + ContractConditionsType::Ensures => { + ContractConditionsData::new_ensures(&annotated_fn.sig, syn::parse(attr)?) + } + ContractConditionsType::Modifies => ContractConditionsData::Modifies { attr: chunks_by(TokenStream2::from(attr), is_token_stream_2_comma) .map(syn::parse2) .filter_map(|expr| match expr { @@ -434,7 +457,6 @@ impl<'a> ContractConditionsHandler<'a> { }) .collect(), }, - _ => unreachable!(), }; Ok(Self { function_state, condition_type, annotated_fn, attr_copy, output, hash }) @@ -450,14 +472,14 @@ impl<'a> ContractConditionsHandler<'a> { let Self { attr_copy, .. } = self; match &self.condition_type { - ContractConditionsType::Requires { attr } => { + ContractConditionsData::Requires { attr } => { let block = self.create_inner_call([].into_iter()); quote!( kani::assume(#attr); #(#block)* ) } - ContractConditionsType::Ensures { argument_names, attr } => { + ContractConditionsData::Ensures { argument_names, attr } => { let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names); // The code that enforces the postconditions and cleans up the shallow @@ -480,7 +502,7 @@ impl<'a> ContractConditionsHandler<'a> { result ) } - ContractConditionsType::Modifies { attr } => { + ContractConditionsData::Modifies { attr } => { let wrapper_name = self.make_wrapper_name().to_string(); let wrapper_args = make_wrapper_args(attr.len()); // TODO handle first invocation where this is the actual body. @@ -556,11 +578,11 @@ impl<'a> ContractConditionsHandler<'a> { let return_type = return_type_to_type(&sig.output); match &self.condition_type { - ContractConditionsType::Requires { attr } => quote!( + ContractConditionsData::Requires { attr } => quote!( kani::assert(#attr, stringify!(#attr_copy)); #call_to_prior ), - ContractConditionsType::Ensures { attr, argument_names } => { + ContractConditionsData::Ensures { attr, argument_names } => { let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names); quote!( #arg_copies @@ -570,7 +592,7 @@ impl<'a> ContractConditionsHandler<'a> { result ) } - ContractConditionsType::Modifies { .. } => { + ContractConditionsData::Modifies { .. } => { quote!(kani::assert(false, "Replacement with modifies is not supported yet.")) } } @@ -637,7 +659,7 @@ impl<'a> ContractConditionsHandler<'a> { } fn emit_augmented_modifies_wrapper(&mut self) { - if let ContractConditionsType::Modifies { attr } = &self.condition_type { + if let ContractConditionsData::Modifies { attr } = &self.condition_type { let wrapper_args = make_wrapper_args(attr.len()); let sig = &mut self.annotated_fn.sig; for arg in wrapper_args.clone() { @@ -877,7 +899,11 @@ fn make_unsafe_argument_copies( /// /// See the [module level documentation][self] for a description of how the code /// generation works. -fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) -> TokenStream { +fn requires_ensures_main( + attr: TokenStream, + item: TokenStream, + is_requires: ContractConditionsType, +) -> TokenStream { let attr_copy = TokenStream2::from(attr.clone()); let mut output = proc_macro2::TokenStream::new(); @@ -949,10 +975,20 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) // and "replace" functions. let item_hash = hash.unwrap(); - let check_fn_name = identifier_for_generated_function(&original_function_name, "check", item_hash); - let replace_fn_name = identifier_for_generated_function(&original_function_name, "replace", item_hash); - let recursion_wrapper_name = - identifier_for_generated_function(&original_function_name, "recursion_wrapper", item_hash); + let check_fn_name = + identifier_for_generated_function(&original_function_name, "check", item_hash); + let replace_fn_name = + identifier_for_generated_function(&original_function_name, "replace", item_hash); + let recursion_wrapper_name = identifier_for_generated_function( + &original_function_name, + "recursion_wrapper", + item_hash, + ); + let recursion_tracker_name = identifier_for_generated_function( + &original_function_name, + "recursion_tracker", + item_hash, + ); // Constructing string literals explicitly here, because `stringify!` // doesn't work. Let's say we have an identifier `check_fn` and we were @@ -966,6 +1002,8 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) syn::LitStr::new(&handler.make_wrapper_name().to_string(), Span::call_site()); let recursion_wrapper_name_str = syn::LitStr::new(&recursion_wrapper_name.to_string(), Span::call_site()); + let recursion_tracker_name_str = + syn::LitStr::new(&recursion_tracker_name.to_string(), Span::call_site()); // The order of `attrs` and `kanitool::{checked_with, // is_contract_generated}` is important here, because macros are @@ -982,8 +1020,8 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) #[kanitool::checked_with = #recursion_wrapper_name_str] #[kanitool::replaced_with = #replace_fn_name_str] #[kanitool::inner_check = #wrapper_fn_name_str] + #[kanitool::recursion_tracker = #recursion_tracker_name_str] #vis #sig { - let _ = std::boxed::Box::new(0_usize); #block } )); @@ -1001,16 +1039,17 @@ fn requires_ensures_main(attr: TokenStream, item: TokenStream, is_requires: u8) }; handler.output.extend(quote!( + + static mut #recursion_tracker_name: bool = false; #[allow(dead_code, unused_variables)] #[kanitool::is_contract_generated(recursion_wrapper)] #wrapper_sig { - static mut REENTRY: bool = false; - if unsafe { REENTRY } { + if unsafe { #recursion_tracker_name } { #call_replace(#(#args),*) } else { - unsafe { REENTRY = true }; + unsafe { #recursion_tracker_name = true }; let result = #call_check(#(#also_args),*); - unsafe { REENTRY = false }; + unsafe { #recursion_tracker_name = false }; result } } From f6d939c43e80b8faf9bf6fc26c381d594ba653bc Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Tue, 7 Nov 2023 15:12:05 -0800 Subject: [PATCH 11/72] Fixed recursion tracker havocking --- .../codegen_cprover_gotoc/compiler_interface.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 526730a865d8..39bce65d1e1b 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -5,13 +5,13 @@ use crate::args::ReachabilityType; use crate::codegen_cprover_gotoc::GotocCtx; -use crate::kani_middle::analysis; use crate::kani_middle::attributes::{is_test_harness_description, KaniAttributes}; use crate::kani_middle::metadata::gen_test_metadata; use crate::kani_middle::provide; use crate::kani_middle::reachability::{ collect_reachable_items, filter_const_crate_items, filter_crate_items, }; +use crate::kani_middle::{analysis, SourceLocation}; use crate::kani_middle::{check_reachable_items, dump_mir_items}; use crate::kani_queries::QueryDb; use cbmc::goto_program::Location; @@ -166,17 +166,12 @@ impl GotocCodegenBackend { gcx.attach_contract(instance_of_check, assigns_contract); let tracker_def = check_contract_attrs.recursion_tracker().unwrap().unwrap(); - let tracker_instance = Instance::expect_resolve( - tcx, - ParamEnv::reveal_all(), - tracker_def, - ty::List::empty(), - ); - ( - tcx.symbol_name(instance_of_check).to_string(), - tcx.symbol_name(tracker_instance).to_string(), - ) + let span = tcx.def_span(tracker_def); + let location = SourceLocation::new(tcx, &span); + let full_name = format!("{}:{}", location.filename, tcx.item_name(tracker_def)); + + (tcx.symbol_name(instance_of_check).to_string(), full_name) }) }, "codegen", From 187c56f3f729dc7baf47d54e934a56d0ee339127 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 10 Nov 2023 17:10:57 -0800 Subject: [PATCH 12/72] Fmt --- tests/expected/function-contract/assigns_expr_pass.rs | 2 +- tests/expected/function-contract/assigns_fail.rs | 2 +- tests/expected/function-contract/assigns_pass.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/expected/function-contract/assigns_expr_pass.rs b/tests/expected/function-contract/assigns_expr_pass.rs index 107c137886ba..e90c201624fa 100644 --- a/tests/expected/function-contract/assigns_expr_pass.rs +++ b/tests/expected/function-contract/assigns_expr_pass.rs @@ -12,4 +12,4 @@ fn modify(ptr: &mut Box) { fn main() { let mut i = Box::new(kani::any()); modify(&mut i); -} \ No newline at end of file +} diff --git a/tests/expected/function-contract/assigns_fail.rs b/tests/expected/function-contract/assigns_fail.rs index 3fee15716655..26517526038b 100644 --- a/tests/expected/function-contract/assigns_fail.rs +++ b/tests/expected/function-contract/assigns_fail.rs @@ -12,4 +12,4 @@ fn main() { let _ = Box::new(()); let mut i = kani::any(); modify(&mut i); -} \ No newline at end of file +} diff --git a/tests/expected/function-contract/assigns_pass.rs b/tests/expected/function-contract/assigns_pass.rs index 2690fb54117f..f76f1a6a6c22 100644 --- a/tests/expected/function-contract/assigns_pass.rs +++ b/tests/expected/function-contract/assigns_pass.rs @@ -13,4 +13,4 @@ fn main() { let _ = Box::new(()); let mut i = kani::any(); modify(&mut i); -} \ No newline at end of file +} From 54727e279dcfbfb0075bfc73c50a4cb7ebec7eaa Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Sat, 11 Nov 2023 16:11:29 -0800 Subject: [PATCH 13/72] Cargo fix --- kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 39bce65d1e1b..180efa8af96e 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -37,7 +37,7 @@ use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME}; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::mir::mono::MonoItem; -use rustc_middle::ty::{self, Instance, InstanceDef, ParamEnv, TyCtxt}; +use rustc_middle::ty::{Instance, InstanceDef, TyCtxt}; use rustc_middle::util::Providers; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; use rustc_session::cstore::MetadataLoaderDyn; From b7c8e6341c9fbf24b5cb7950a758db69d8593727 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Mon, 13 Nov 2023 16:03:59 -0800 Subject: [PATCH 14/72] Fix two test cases + support impl call --- library/kani_macros/src/sysroot/contracts.rs | 13 +++++++++++-- .../function-contract/assigns_expr_pass.expected | 1 + .../expected/function-contract/assigns_expr_pass.rs | 2 +- .../function-contract/assigns_pass.expected | 6 ------ 4 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 tests/expected/function-contract/assigns_expr_pass.expected diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index c50b88f42752..0708190d6d14 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -492,7 +492,11 @@ impl<'a> ContractConditionsHandler<'a> { let mut call = self.create_inner_call([].into_iter()); assert!( - matches!(call.pop(), Some(syn::Stmt::Expr(syn::Expr::Path(pexpr), None)) if pexpr.path.get_ident().map_or(false, |id| id == "result")) + matches!( + call.pop(), + Some(syn::Stmt::Expr(syn::Expr::Path(pexpr), None)) + if pexpr.path.get_ident().map_or(false, |id| id == "result") + ) ); quote!( @@ -553,8 +557,13 @@ impl<'a> ContractConditionsHandler<'a> { let return_type = return_type_to_type(&self.annotated_fn.sig.output); if self.is_first_emit() { let args = exprs_for_args(&self.annotated_fn.sig.inputs); + let wrapper_call = if is_probably_impl_fn(self.annotated_fn) { + quote!(Self::#wrapper_name) + } else { + quote!(#wrapper_name) + }; syn::parse_quote!( - let result : #return_type = #wrapper_name(#(#args,)* #(#additional_args),*); + let result : #return_type = #wrapper_call(#(#args,)* #(#additional_args),*); result ) } else { diff --git a/tests/expected/function-contract/assigns_expr_pass.expected b/tests/expected/function-contract/assigns_expr_pass.expected new file mode 100644 index 000000000000..34c886c358cb --- /dev/null +++ b/tests/expected/function-contract/assigns_expr_pass.expected @@ -0,0 +1 @@ +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/assigns_expr_pass.rs b/tests/expected/function-contract/assigns_expr_pass.rs index e90c201624fa..18fa5ff76897 100644 --- a/tests/expected/function-contract/assigns_expr_pass.rs +++ b/tests/expected/function-contract/assigns_expr_pass.rs @@ -3,7 +3,7 @@ // kani-flags: -Zfunction-contracts #[kani::requires(**ptr < 100)] -#[kani::modifies(*ptr.as_ref())] +#[kani::modifies(ptr.as_ref())] fn modify(ptr: &mut Box) { *ptr.as_mut() += 1; } diff --git a/tests/expected/function-contract/assigns_pass.expected b/tests/expected/function-contract/assigns_pass.expected index fd8d816451cd..82927d2c52cf 100644 --- a/tests/expected/function-contract/assigns_pass.expected +++ b/tests/expected/function-contract/assigns_pass.expected @@ -1,7 +1 @@ -assigns\ -- Status: FAILURE\ -- Description: "Check that *ptr is assignable" - -Failed Checks: Check that *ptr is assignable - VERIFICATION:- FAILED \ No newline at end of file From 27ebf4f555bdb199ca6b55e25481683d11f7dbf8 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Mon, 13 Nov 2023 16:15:12 -0800 Subject: [PATCH 15/72] Fix another test case --- kani-compiler/src/kani_middle/attributes.rs | 29 +++++++++++++++++++- library/kani_macros/src/sysroot/contracts.rs | 15 ++++------ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index c6e8c2663b2a..a9e908dfa0be 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -224,8 +224,35 @@ impl<'tcx> KaniAttributes<'tcx> { .map(|target| expect_key_string_value(self.tcx.sess, target)) } + /// Retrieves the global, static recursion tracker variable. pub fn recursion_tracker(&self) -> Option> { - self.eval_sibling_attribute(KaniAttributeKind::RecursionTracker) + self.expect_maybe_one(KaniAttributeKind::RecursionTracker).map(|target| { + let name = expect_key_string_value(self.tcx.sess, target)?; + let all_items = self.tcx.hir_crate_items(()); + let hir_map = self.tcx.hir(); + + // I don't like the way this is currently implemented. I search + // through all items defined in the crate for one with the correct + // name. That works but this should do something better like + // `eval_sibling_attribute`, which is less likely to have any + // conflicts or alternatively use resolution for a path. + // + // The efficient thing to do is figure out where (relative to the + // annotated item) rustc actually stores the tracker (which `mod`) + // and the retrieve it (like `eval_sibling_attribute` does). + + let owner = all_items + .items() + .find(|it| hir_map.opt_name(it.hir_id()) == Some(name)) + .ok_or_else(|| { + self.tcx.sess.span_err( + self.tcx.def_span(self.item), + format!("Could not find recursion tracker '{name}' in crate"), + ) + })?; + + Ok(owner.owner_id.def_id.to_def_id()) + }) } fn eval_sibling_attribute( diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 0708190d6d14..94b62c83d192 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -491,13 +491,11 @@ impl<'a> ContractConditionsHandler<'a> { let mut call = self.create_inner_call([].into_iter()); - assert!( - matches!( - call.pop(), - Some(syn::Stmt::Expr(syn::Expr::Path(pexpr), None)) - if pexpr.path.get_ident().map_or(false, |id| id == "result") - ) - ); + assert!(matches!( + call.pop(), + Some(syn::Stmt::Expr(syn::Expr::Path(pexpr), None)) + if pexpr.path.get_ident().map_or(false, |id| id == "result") + )); quote!( #arg_copies @@ -1048,11 +1046,10 @@ fn requires_ensures_main( }; handler.output.extend(quote!( - - static mut #recursion_tracker_name: bool = false; #[allow(dead_code, unused_variables)] #[kanitool::is_contract_generated(recursion_wrapper)] #wrapper_sig { + static mut #recursion_tracker_name: bool = false; if unsafe { #recursion_tracker_name } { #call_replace(#(#args),*) } else { From d9e921c74eefebed715a06f15a9d4a8087578013 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Mon, 13 Nov 2023 17:40:24 -0800 Subject: [PATCH 16/72] Simple havoc --- library/kani_macros/src/sysroot/contracts.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 94b62c83d192..26e2c855d533 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -536,6 +536,9 @@ impl<'a> ContractConditionsHandler<'a> { } } + /// Create a new name for the assigns wrapper function *or* get the name of + /// the wrapper we must have already generated. This is so that we can + /// recognize a call to that wrapper inside the check function. fn make_wrapper_name(&self) -> Ident { if let Some(hash) = self.hash { identifier_for_generated_function(&self.annotated_fn.sig.ident, "wrapper", hash) @@ -599,8 +602,12 @@ impl<'a> ContractConditionsHandler<'a> { result ) } - ContractConditionsData::Modifies { .. } => { - quote!(kani::assert(false, "Replacement with modifies is not supported yet.")) + ContractConditionsData::Modifies { attr } => { + let args = make_wrapper_args(attr.len()); + quote!( + let result = #call_to_prior; + #(*#args = kani::any();)* + ) } } } @@ -665,6 +672,11 @@ impl<'a> ContractConditionsHandler<'a> { self.output.extend(self.annotated_fn.attrs.iter().flat_map(Attribute::to_token_stream)); } + /// Emit a modifies wrapper, possibly augmenting a prior, existing one. + /// + /// We only augment if this clause is a `modifies` clause. In that case we + /// expand its signature with one new argument of type `&impl Arbitrary` for + /// each expression in the clause. fn emit_augmented_modifies_wrapper(&mut self) { if let ContractConditionsData::Modifies { attr } = &self.condition_type { let wrapper_args = make_wrapper_args(attr.len()); From ffcdc76b25fcfb9ae8980173cfa47f8313aa0cdd Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Sun, 19 Nov 2023 00:01:31 -0500 Subject: [PATCH 17/72] Fixing Formatting and clippy --- kani-driver/src/args/mod.rs | 8 ++++---- kani-driver/src/args_toml.rs | 2 +- library/kani_macros/src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 6651f72db7c2..9b29b323c9d7 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -725,7 +725,7 @@ mod tests { #[test] fn check_multiple_harnesses() { let args = - StandaloneArgs::try_parse_from("kani input.rs --harness a --harness b".split(" ")) + StandaloneArgs::try_parse_from("kani input.rs --harness a --harness b".split(' ')) .unwrap(); assert_eq!(args.verify_opts.harnesses, vec!["a".to_owned(), "b".to_owned()]); } @@ -733,7 +733,7 @@ mod tests { #[test] fn check_multiple_harnesses_without_flag_fail() { let result = StandaloneArgs::try_parse_from( - "kani input.rs --harness harness_1 harness_2".split(" "), + "kani input.rs --harness harness_1 harness_2".split(' '), ); assert!(result.is_err()); assert_eq!(result.unwrap_err().kind(), ErrorKind::UnknownArgument); @@ -785,7 +785,7 @@ mod tests { // We don't support --dry-run anymore but we print a friendly reminder for now. let args = vec!["kani", "file.rs", "--dry-run"]; let err = - StandaloneArgs::try_parse_from(&args).unwrap().verify_opts.validate().unwrap_err(); + StandaloneArgs::try_parse_from(args).unwrap().verify_opts.validate().unwrap_err(); assert_eq!(err.kind(), ErrorKind::ValueValidation); } @@ -793,7 +793,7 @@ mod tests { #[test] fn check_invalid_input_fails() { let args = vec!["kani", "."]; - let err = StandaloneArgs::try_parse_from(&args).unwrap().validate().unwrap_err(); + let err = StandaloneArgs::try_parse_from(args).unwrap().validate().unwrap_err(); assert_eq!(err.kind(), ErrorKind::InvalidValue); } diff --git a/kani-driver/src/args_toml.rs b/kani-driver/src/args_toml.rs index aaa5b3260083..f467d4a25cb3 100644 --- a/kani-driver/src/args_toml.rs +++ b/kani-driver/src/args_toml.rs @@ -312,6 +312,6 @@ mod tests { #[test] fn check_unstable_entry_invalid() { let name = String::from("feature"); - assert!(matches!(unstable_entry(&name, &Value::String("".to_string())), Err(_))); + assert!(unstable_entry(&name, &Value::String("".to_string())).is_err()); } } diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index f980a0602b34..91e587b03d5c 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -174,7 +174,7 @@ pub fn modifies(attr: TokenStream, item: TokenStream) -> TokenStream { /// This module implements Kani attributes in a way that only Kani's compiler can understand. /// This code should only be activated when pre-building Kani's sysroot. -//#[cfg(kani_sysroot)] +#[cfg(kani_sysroot)] mod sysroot { use proc_macro_error::{abort, abort_call_site}; From b83343844e231ec588db4b213b77d1d1ffc497f8 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Sun, 19 Nov 2023 00:07:43 -0500 Subject: [PATCH 18/72] Kani fmt --- kani-driver/src/args/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 9b29b323c9d7..1254fbf7ad1c 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -784,8 +784,7 @@ mod tests { fn check_dry_run_fails() { // We don't support --dry-run anymore but we print a friendly reminder for now. let args = vec!["kani", "file.rs", "--dry-run"]; - let err = - StandaloneArgs::try_parse_from(args).unwrap().verify_opts.validate().unwrap_err(); + let err = StandaloneArgs::try_parse_from(args).unwrap().verify_opts.validate().unwrap_err(); assert_eq!(err.kind(), ErrorKind::ValueValidation); } From 64f7e5fe525922faaebd5e1b7a657926ec6487e5 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Tue, 21 Nov 2023 14:16:23 -0800 Subject: [PATCH 19/72] Fix simlpe havoc --- library/kani_macros/src/sysroot/contracts.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 26e2c855d533..3cccc2509d4c 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -603,10 +603,10 @@ impl<'a> ContractConditionsHandler<'a> { ) } ContractConditionsData::Modifies { attr } => { - let args = make_wrapper_args(attr.len()); quote!( let result = #call_to_prior; - #(*#args = kani::any();)* + #(*#attr = kani::any();)* + result ) } } From 0c3cea7d79d3e48164ecd9f45a6f398218ebfea6 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 22 Nov 2023 12:24:11 -0800 Subject: [PATCH 20/72] Fix static exclude for recursion tracker --- .../compiler_interface.rs | 81 ++++++++++++------- kani-compiler/src/kani_middle/attributes.rs | 36 +-------- kani-driver/src/call_goto_instrument.rs | 2 +- library/kani_macros/src/sysroot/contracts.rs | 16 +--- 4 files changed, 59 insertions(+), 76 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 180efa8af96e..06e407146c04 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -61,6 +61,7 @@ use tempfile::Builder as TempFileBuilder; use tracing::{debug, error, info}; pub type UnsupportedConstructs = FxHashMap>; + #[derive(Clone)] pub struct GotocCodegenBackend { /// The query is shared with `KaniCompiler` and it is initialized as part of `rustc` @@ -146,33 +147,7 @@ impl GotocCodegenBackend { } } - check_contract.map(|check_contract_def| { - let check_contract_attrs = KaniAttributes::for_item(tcx, check_contract_def); - let wrapper_def = check_contract_attrs.inner_check().unwrap().unwrap(); - - let mut instances_of_check = items.iter().copied().filter_map(|i| match i { - MonoItem::Fn(instance @ Instance { def: InstanceDef::Item(did), .. }) => { - (wrapper_def == did).then_some(instance) - } - _ => None, - }); - let instance_of_check = instances_of_check.next().unwrap(); - assert!(instances_of_check.next().is_none()); - let attrs = KaniAttributes::for_item(tcx, instance_of_check.def_id()); - let assigns_contract = attrs.modifies_contract().unwrap_or_else(|| { - debug!(?instance_of_check, "had no assigns contract specified"); - vec![] - }); - gcx.attach_contract(instance_of_check, assigns_contract); - - let tracker_def = check_contract_attrs.recursion_tracker().unwrap().unwrap(); - - let span = tcx.def_span(tracker_def); - let location = SourceLocation::new(tcx, &span); - let full_name = format!("{}:{}", location.filename, tcx.item_name(tracker_def)); - - (tcx.symbol_name(instance_of_check).to_string(), full_name) - }) + check_contract.map(|check_id| gcx.handle_check_contract(check_id, &items)) }, "codegen", ); @@ -215,6 +190,51 @@ impl GotocCodegenBackend { } } +impl<'tcx> GotocCtx<'tcx> { + fn handle_check_contract( + &mut self, + function_under_contract: DefId, + items: &[MonoItem<'tcx>], + ) -> (String, String) { + let tcx = self.tcx; + let function_under_contract_attrs = KaniAttributes::for_item(tcx, function_under_contract); + let wrapped_fn = function_under_contract_attrs.inner_check().unwrap().unwrap(); + + let mut instance_under_contract = items.iter().copied().filter_map(|i| match i { + MonoItem::Fn(instance @ Instance { def: InstanceDef::Item(did), .. }) => { + (wrapped_fn == did).then_some(instance) + } + _ => None, + }); + let instance_of_check = instance_under_contract.next().unwrap(); + assert!( + instance_under_contract.next().is_none(), + "Only one instance of a checked function may be in scope" + ); + let attrs_of_wrapped_fn = KaniAttributes::for_item(tcx, wrapped_fn); + let assigns_contract = attrs_of_wrapped_fn.modifies_contract().unwrap_or_else(|| { + debug!(?instance_of_check, "had no assigns contract specified"); + vec![] + }); + self.attach_contract(instance_of_check, assigns_contract); + + let wrapper_name = tcx.symbol_name(instance_of_check).to_string(); + + let recursion_wrapper_id = + function_under_contract_attrs.checked_with_id().unwrap().unwrap(); + let span_of_recursion_wrapper = tcx.def_span(recursion_wrapper_id); + let location_of_recursion_wrapper = SourceLocation::new(tcx, &span_of_recursion_wrapper); + + let full_name = format!( + "{}:{}::REENTRY", + location_of_recursion_wrapper.filename, + tcx.item_name(recursion_wrapper_id), + ); + + (wrapper_name, full_name) + } +} + fn contract_metadata_for_harness( tcx: TyCtxt, def_id: DefId, @@ -317,8 +337,11 @@ impl CodegenBackend for GotocCodegenBackend { results.extend(gcx, items, None); for (test_fn, test_desc) in harnesses.iter().zip(descriptions.iter()) { - let instance = - if let MonoItem::Fn(instance) = test_fn { instance } else { continue }; + let instance = if let MonoItem::Fn(instance) = test_fn { + instance + } else { + continue; + }; let metadata = gen_test_metadata(tcx, *test_desc, *instance, &base_filename); let test_model_path = &metadata.goto_file.as_ref().unwrap(); std::fs::copy(&model_path, test_model_path).expect(&format!( diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index a9e908dfa0be..d94b964dc4ae 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -60,7 +60,6 @@ enum KaniAttributeKind { IsContractGenerated, Modifies, InnerCheck, - RecursionTracker, } impl KaniAttributeKind { @@ -78,7 +77,6 @@ impl KaniAttributeKind { | KaniAttributeKind::ReplacedWith | KaniAttributeKind::CheckedWith | KaniAttributeKind::Modifies - | KaniAttributeKind::RecursionTracker | KaniAttributeKind::InnerCheck | KaniAttributeKind::IsContractGenerated => false, } @@ -225,34 +223,8 @@ impl<'tcx> KaniAttributes<'tcx> { } /// Retrieves the global, static recursion tracker variable. - pub fn recursion_tracker(&self) -> Option> { - self.expect_maybe_one(KaniAttributeKind::RecursionTracker).map(|target| { - let name = expect_key_string_value(self.tcx.sess, target)?; - let all_items = self.tcx.hir_crate_items(()); - let hir_map = self.tcx.hir(); - - // I don't like the way this is currently implemented. I search - // through all items defined in the crate for one with the correct - // name. That works but this should do something better like - // `eval_sibling_attribute`, which is less likely to have any - // conflicts or alternatively use resolution for a path. - // - // The efficient thing to do is figure out where (relative to the - // annotated item) rustc actually stores the tracker (which `mod`) - // and the retrieve it (like `eval_sibling_attribute` does). - - let owner = all_items - .items() - .find(|it| hir_map.opt_name(it.hir_id()) == Some(name)) - .ok_or_else(|| { - self.tcx.sess.span_err( - self.tcx.def_span(self.item), - format!("Could not find recursion tracker '{name}' in crate"), - ) - })?; - - Ok(owner.owner_id.def_id.to_def_id()) - }) + pub fn checked_with_id(&self) -> Option> { + self.eval_sibling_attribute(KaniAttributeKind::CheckedWith) } fn eval_sibling_attribute( @@ -373,9 +345,6 @@ impl<'tcx> KaniAttributes<'tcx> { KaniAttributeKind::InnerCheck => { self.inner_check(); } - KaniAttributeKind::RecursionTracker => { - self.recursion_tracker(); - } } } } @@ -479,7 +448,6 @@ impl<'tcx> KaniAttributes<'tcx> { | KaniAttributeKind::IsContractGenerated | KaniAttributeKind::Modifies | KaniAttributeKind::InnerCheck - | KaniAttributeKind::RecursionTracker | KaniAttributeKind::ReplacedWith => { self.tcx.sess.span_err(self.tcx.def_span(self.item), format!("Contracts are not supported on harnesses. (Found the kani-internal contract attribute `{}`)", kind.as_ref())); } diff --git a/kani-driver/src/call_goto_instrument.rs b/kani-driver/src/call_goto_instrument.rs index c6b36a7353f0..6879e6c53d83 100644 --- a/kani-driver/src/call_goto_instrument.rs +++ b/kani-driver/src/call_goto_instrument.rs @@ -178,7 +178,7 @@ impl KaniSession { vec!["--dfcc".into(), (&harness.mangled_name).into()]; if let Some((function, recursion_tracker)) = check { - println!("enforcing function contract for {function}"); + println!("enforcing function contract for {function} with tracker {recursion_tracker}"); args.extend([ "--enforce-contract".into(), function.into(), diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 3cccc2509d4c..a8af106e6d01 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -1003,11 +1003,6 @@ fn requires_ensures_main( "recursion_wrapper", item_hash, ); - let recursion_tracker_name = identifier_for_generated_function( - &original_function_name, - "recursion_tracker", - item_hash, - ); // Constructing string literals explicitly here, because `stringify!` // doesn't work. Let's say we have an identifier `check_fn` and we were @@ -1021,8 +1016,6 @@ fn requires_ensures_main( syn::LitStr::new(&handler.make_wrapper_name().to_string(), Span::call_site()); let recursion_wrapper_name_str = syn::LitStr::new(&recursion_wrapper_name.to_string(), Span::call_site()); - let recursion_tracker_name_str = - syn::LitStr::new(&recursion_tracker_name.to_string(), Span::call_site()); // The order of `attrs` and `kanitool::{checked_with, // is_contract_generated}` is important here, because macros are @@ -1039,7 +1032,6 @@ fn requires_ensures_main( #[kanitool::checked_with = #recursion_wrapper_name_str] #[kanitool::replaced_with = #replace_fn_name_str] #[kanitool::inner_check = #wrapper_fn_name_str] - #[kanitool::recursion_tracker = #recursion_tracker_name_str] #vis #sig { #block } @@ -1061,13 +1053,13 @@ fn requires_ensures_main( #[allow(dead_code, unused_variables)] #[kanitool::is_contract_generated(recursion_wrapper)] #wrapper_sig { - static mut #recursion_tracker_name: bool = false; - if unsafe { #recursion_tracker_name } { + static mut REENTRY: bool = false; + if unsafe { REENTRY } { #call_replace(#(#args),*) } else { - unsafe { #recursion_tracker_name = true }; + unsafe { REENTRY = true }; let result = #call_check(#(#also_args),*); - unsafe { #recursion_tracker_name = false }; + unsafe { REENTRY = false }; result } } From 4b451cfc60627dc57d68262f8efa1aa06cf801da Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 22 Nov 2023 13:26:36 -0800 Subject: [PATCH 21/72] Some changes had accidentally been reverted --- library/kani/src/lib.rs | 46 +++++++++++++------ library/kani_macros/src/sysroot/contracts.rs | 4 +- .../function-contract/assigns_pass.expected | 2 +- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 52633a24a89a..769074b3c99a 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -93,36 +93,54 @@ macro_rules! implies { } #[doc(hidden)] -pub trait DecoupleLifetime<'a> { +pub trait Pointer<'a> { type Inner; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner; + unsafe fn decouple_lifetime(self) -> &'a Self::Inner; + + unsafe fn assignable(self) -> &'a mut Self::Inner; } -impl<'a, 'b, T> DecoupleLifetime<'a> for &'b T { +impl<'a, 'b, T> Pointer<'a> for &'b T { type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - std::mem::transmute(*self) + unsafe fn decouple_lifetime(self) -> &'a Self::Inner { + std::mem::transmute(self) + } + + unsafe fn assignable(self) -> &'a mut Self::Inner { + std::mem::transmute(self as *const T) } } -impl<'a, 'b, T> DecoupleLifetime<'a> for &'b mut T { +impl<'a, 'b, T> Pointer<'a> for &'b mut T { type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - *std::mem::transmute::<&'_ &'b mut T, &'_ &'a T>(self) + unsafe fn decouple_lifetime(self) -> &'a Self::Inner { + std::mem::transmute(self as *mut T) + } + + unsafe fn assignable(self) -> &'a mut Self::Inner { + std::mem::transmute(self) } } -impl<'a, T> DecoupleLifetime<'a> for *const T { +impl<'a, T> Pointer<'a> for *const T { type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - &**self as &'a T + unsafe fn decouple_lifetime(self) -> &'a Self::Inner { + &*self as &'a T + } + + unsafe fn assignable(self) -> &'a mut Self::Inner { + std::mem::transmute(self) } } -impl<'a, T> DecoupleLifetime<'a> for *mut T { +impl<'a, T> Pointer<'a> for *mut T { type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - &**self as &'a T + unsafe fn decouple_lifetime(self) -> &'a Self::Inner { + &*self as &'a T + } + + unsafe fn assignable(self) -> &'a mut Self::Inner { + std::mem::transmute(self) } } diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index a8af106e6d01..8f4dc83c1a40 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -529,7 +529,7 @@ impl<'a> ContractConditionsHandler<'a> { let wrapper_args = make_wrapper_args(attr.len()); quote!( - #(let #wrapper_args = unsafe { kani::DecoupleLifetime::decouple_lifetime(&#attr) };)* + #(let #wrapper_args = unsafe { kani::Pointer::decouple_lifetime(#attr) };)* #(#inner)* ) } @@ -605,7 +605,7 @@ impl<'a> ContractConditionsHandler<'a> { ContractConditionsData::Modifies { attr } => { quote!( let result = #call_to_prior; - #(*#attr = kani::any();)* + #(*unsafe { kani::Pointer::assignable(#attr) } = kani::any();)* result ) } diff --git a/tests/expected/function-contract/assigns_pass.expected b/tests/expected/function-contract/assigns_pass.expected index 82927d2c52cf..880f00714b32 100644 --- a/tests/expected/function-contract/assigns_pass.expected +++ b/tests/expected/function-contract/assigns_pass.expected @@ -1 +1 @@ -VERIFICATION:- FAILED \ No newline at end of file +VERIFICATION:- SUCCESSFUL \ No newline at end of file From 0b6ba1e8e64af57c569b95c9d8500e500ef7629e Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 22 Nov 2023 13:36:44 -0800 Subject: [PATCH 22/72] Formatting --- library/kani/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 769074b3c99a..0ba29399ba8c 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -96,7 +96,6 @@ macro_rules! implies { pub trait Pointer<'a> { type Inner; unsafe fn decouple_lifetime(self) -> &'a Self::Inner; - unsafe fn assignable(self) -> &'a mut Self::Inner; } From 9362624eae22eae6aec29a5981d34526a22f9c8f Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 22 Nov 2023 14:09:34 -0800 Subject: [PATCH 23/72] Allow pointer transmutes --- library/kani/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 0ba29399ba8c..4019cd1809ca 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -105,6 +105,7 @@ impl<'a, 'b, T> Pointer<'a> for &'b T { std::mem::transmute(self) } + #[allow(clippy::transmute_ptr_to_ref)] unsafe fn assignable(self) -> &'a mut Self::Inner { std::mem::transmute(self as *const T) } @@ -112,6 +113,8 @@ impl<'a, 'b, T> Pointer<'a> for &'b T { impl<'a, 'b, T> Pointer<'a> for &'b mut T { type Inner = T; + + #[allow(clippy::transmute_ptr_to_ref)] unsafe fn decouple_lifetime(self) -> &'a Self::Inner { std::mem::transmute(self as *mut T) } @@ -127,6 +130,7 @@ impl<'a, T> Pointer<'a> for *const T { &*self as &'a T } + #[allow(clippy::transmute_ptr_to_ref)] unsafe fn assignable(self) -> &'a mut Self::Inner { std::mem::transmute(self) } @@ -138,6 +142,7 @@ impl<'a, T> Pointer<'a> for *mut T { &*self as &'a T } + #[allow(clippy::transmute_ptr_to_ref)] unsafe fn assignable(self) -> &'a mut Self::Inner { std::mem::transmute(self) } From 7148bac2817ff9b1d18445a713604f053aeb3efa Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 22 Nov 2023 17:44:53 -0500 Subject: [PATCH 24/72] turns out these need to be refs --- library/kani/src/lib.rs | 21 +++++++++++--------- library/kani_macros/src/sysroot/contracts.rs | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 4019cd1809ca..3cb1e9100d59 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -95,14 +95,17 @@ macro_rules! implies { #[doc(hidden)] pub trait Pointer<'a> { type Inner; - unsafe fn decouple_lifetime(self) -> &'a Self::Inner; + + /// We're using a reference to self here, because the user can use just a plain function + /// argument, for instance one of type `&mut _`, in the `modifies` clause which would move it. + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner; unsafe fn assignable(self) -> &'a mut Self::Inner; } impl<'a, 'b, T> Pointer<'a> for &'b T { type Inner = T; - unsafe fn decouple_lifetime(self) -> &'a Self::Inner { - std::mem::transmute(self) + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + std::mem::transmute(*self) } #[allow(clippy::transmute_ptr_to_ref)] @@ -115,8 +118,8 @@ impl<'a, 'b, T> Pointer<'a> for &'b mut T { type Inner = T; #[allow(clippy::transmute_ptr_to_ref)] - unsafe fn decouple_lifetime(self) -> &'a Self::Inner { - std::mem::transmute(self as *mut T) + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + *std::mem::transmute::<_, &&'a T>(self) } unsafe fn assignable(self) -> &'a mut Self::Inner { @@ -126,8 +129,8 @@ impl<'a, 'b, T> Pointer<'a> for &'b mut T { impl<'a, T> Pointer<'a> for *const T { type Inner = T; - unsafe fn decouple_lifetime(self) -> &'a Self::Inner { - &*self as &'a T + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + &**self as &'a T } #[allow(clippy::transmute_ptr_to_ref)] @@ -138,8 +141,8 @@ impl<'a, T> Pointer<'a> for *const T { impl<'a, T> Pointer<'a> for *mut T { type Inner = T; - unsafe fn decouple_lifetime(self) -> &'a Self::Inner { - &*self as &'a T + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + &**self as &'a T } #[allow(clippy::transmute_ptr_to_ref)] diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 8f4dc83c1a40..f576da4ff767 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -529,7 +529,7 @@ impl<'a> ContractConditionsHandler<'a> { let wrapper_args = make_wrapper_args(attr.len()); quote!( - #(let #wrapper_args = unsafe { kani::Pointer::decouple_lifetime(#attr) };)* + #(let #wrapper_args = unsafe { kani::Pointer::decouple_lifetime(&#attr) };)* #(#inner)* ) } From ba4a73e5c509fc6efaf6006d975704748ae17329 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 22 Nov 2023 18:13:39 -0500 Subject: [PATCH 25/72] Gracefully handle if the contract artifact is absent --- kani-driver/src/harness_runner.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/kani-driver/src/harness_runner.rs b/kani-driver/src/harness_runner.rs index b7cfcd3c35c7..dd742e4297c5 100644 --- a/kani-driver/src/harness_runner.rs +++ b/kani-driver/src/harness_runner.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use kani_metadata::{ArtifactType, HarnessMetadata}; use rayon::prelude::*; use std::path::Path; @@ -81,8 +81,12 @@ impl<'sess, 'pr> HarnessRunner<'sess, 'pr> { } fn get_contract_info(&self, harness: &'pr HarnessMetadata) -> Result> { - let contract_info_artifact = - self.project.get_harness_artifact(&harness, ArtifactType::ContractMetadata).unwrap(); + let contract_info_artifact = self + .project + .get_harness_artifact(&harness, ArtifactType::ContractMetadata) + .ok_or_else(|| { + anyhow!("no contract metadata file is present for harness {}", harness.pretty_name) + })?; let reader = std::io::BufReader::new(std::fs::File::open(contract_info_artifact)?); Ok(serde_json::from_reader(reader)?) From 9610a9e165319a0a2c986a1d480048c1e6e133db Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 22 Nov 2023 18:22:50 -0500 Subject: [PATCH 26/72] Clippy ... --- library/kani/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 3cb1e9100d59..89bb2cba4aa0 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -119,7 +119,7 @@ impl<'a, 'b, T> Pointer<'a> for &'b mut T { #[allow(clippy::transmute_ptr_to_ref)] unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - *std::mem::transmute::<_, &&'a T>(self) + std::mem::transmute::<_, &&'a T>(self) } unsafe fn assignable(self) -> &'a mut Self::Inner { From 03ef3ceb8ea3281efe0a934af5e0d10045619ab6 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 22 Nov 2023 19:48:07 -0500 Subject: [PATCH 27/72] Ignoring this error for now --- kani-driver/src/harness_runner.rs | 5 ++++- kani-driver/src/project.rs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/kani-driver/src/harness_runner.rs b/kani-driver/src/harness_runner.rs index dd742e4297c5..d54b2080ffbc 100644 --- a/kani-driver/src/harness_runner.rs +++ b/kani-driver/src/harness_runner.rs @@ -57,7 +57,10 @@ impl<'sess, 'pr> HarnessRunner<'sess, 'pr> { let report_dir = self.project.outdir.join(format!("report-{harness_filename}")); let goto_file = self.project.get_harness_artifact(&harness, ArtifactType::Goto).unwrap(); - let contract_info = self.get_contract_info(harness)?; + // TODO: Fix upstream. + // This error is ignored for now, because it fails on cargo kani projects. + // I suspect that the metadata file is just not being registered correctly. + let contract_info = self.get_contract_info(harness).ok().flatten(); self.sess.instrument_model( goto_file, diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index 94f20c9d7f0b..80ca248874a1 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -227,6 +227,7 @@ pub fn cargo_project(session: &KaniSession, keep_going: bool) -> Result .iter() .map(|artifact| convert_type(&artifact, Metadata, SymTabGoto)) .collect::>(); + artifacts.push(Artifact::try_new(goto.as_path(), ContractMetadata)?); session.link_goto_binary(&all_gotos, &goto)?; let goto_artifact = Artifact::try_new(&goto, Goto)?; From 7b788c701cb1d192a3f3743a6cb4f9a2ba4a9e67 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 29 Nov 2023 13:44:37 -0800 Subject: [PATCH 28/72] Changing how contracts communicate --- .../compiler_interface.rs | 34 +++++++++------ .../src/codegen_cprover_gotoc/mod.rs | 2 +- kani-compiler/src/kani_compiler.rs | 41 +++++++++++++------ kani-compiler/src/kani_middle/metadata.rs | 3 ++ kani-driver/src/call_goto_instrument.rs | 21 ++++------ kani-driver/src/harness_runner.rs | 26 +----------- kani-driver/src/metadata.rs | 1 + kani-driver/src/project.rs | 12 +++--- kani_metadata/src/artifact.rs | 4 -- kani_metadata/src/harness.rs | 8 ++++ 10 files changed, 79 insertions(+), 73 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 06e407146c04..d80bd1100a8a 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -19,9 +19,9 @@ use cbmc::irep::goto_binary_serde::write_goto_binary_file; use cbmc::RoundingMode; use cbmc::{InternedString, MachineModel}; use kani_metadata::artifact::convert_type; -use kani_metadata::CompilerArtifactStub; use kani_metadata::UnsupportedFeature; use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; +use kani_metadata::{AssignsContract, CompilerArtifactStub}; use rustc_codegen_ssa::back::archive::{ get_native_object_symbols, ArArchiveBuilder, ArchiveBuilder, }; @@ -62,6 +62,8 @@ use tracing::{debug, error, info}; pub type UnsupportedConstructs = FxHashMap>; +pub type ContractInfoChannel = std::sync::mpsc::Sender<(DefPathHash, AssignsContract)>; + #[derive(Clone)] pub struct GotocCodegenBackend { /// The query is shared with `KaniCompiler` and it is initialized as part of `rustc` @@ -69,11 +71,13 @@ pub struct GotocCodegenBackend { /// Since we don't have any guarantees on when the compiler creates the Backend object, neither /// in which thread it will be used, we prefer to explicitly synchronize any query access. queries: Arc>, + + contract_channel: ContractInfoChannel, } impl GotocCodegenBackend { - pub fn new(queries: Arc>) -> Self { - GotocCodegenBackend { queries } + pub fn new(queries: Arc>, contract_channel: ContractInfoChannel) -> Self { + GotocCodegenBackend { queries, contract_channel } } /// Generate code that is reachable from the given starting points. @@ -84,7 +88,7 @@ impl GotocCodegenBackend { symtab_goto: &Path, machine_model: &MachineModel, check_contract: Option, - ) -> (GotocCtx<'tcx>, Vec>) { + ) -> (GotocCtx<'tcx>, Vec>, Option) { let items = with_timer( || collect_reachable_items(tcx, starting_items), "codegen reachability analysis", @@ -182,11 +186,9 @@ impl GotocCodegenBackend { if let Some(restrictions) = vtable_restrictions { write_file(&symtab_goto, ArtifactType::VTableRestriction, &restrictions, pretty); } - - write_file(symtab_goto, ArtifactType::ContractMetadata, &contract_info, pretty); } - (gcx, items) + (gcx, items, contract_info) } } @@ -195,7 +197,7 @@ impl<'tcx> GotocCtx<'tcx> { &mut self, function_under_contract: DefId, items: &[MonoItem<'tcx>], - ) -> (String, String) { + ) -> AssignsContract { let tcx = self.tcx; let function_under_contract_attrs = KaniAttributes::for_item(tcx, function_under_contract); let wrapped_fn = function_under_contract_attrs.inner_check().unwrap().unwrap(); @@ -231,7 +233,7 @@ impl<'tcx> GotocCtx<'tcx> { tcx.item_name(recursion_wrapper_id), ); - (wrapper_name, full_name) + AssignsContract { recursion_tracker: full_name, contracted_function_name: wrapper_name } } } @@ -299,7 +301,7 @@ impl CodegenBackend for GotocCodegenBackend { else { continue; }; - let (gcx, items) = self.codegen_items( + let (gcx, items, contract_info) = self.codegen_items( tcx, &[harness], model_path, @@ -307,6 +309,11 @@ impl CodegenBackend for GotocCodegenBackend { contract_metadata, ); results.extend(gcx, items, None); + if let Some(assigns_contract) = contract_info { + self.contract_channel + .send((tcx.def_path_hash(harness.def_id()), assigns_contract)) + .unwrap(); + } } } ReachabilityType::Tests => { @@ -327,7 +334,7 @@ impl CodegenBackend for GotocCodegenBackend { // We will be able to remove this once we optimize all calls to CBMC utilities. // https://github.com/model-checking/kani/issues/1971 let model_path = base_filename.with_extension(ArtifactType::SymTabGoto); - let (gcx, items) = self.codegen_items( + let (gcx, items, contract_info) = self.codegen_items( tcx, &harnesses, &model_path, @@ -336,6 +343,8 @@ impl CodegenBackend for GotocCodegenBackend { ); results.extend(gcx, items, None); + assert!(contract_info.is_none()); + for (test_fn, test_desc) in harnesses.iter().zip(descriptions.iter()) { let instance = if let MonoItem::Fn(instance) = test_fn { instance @@ -360,13 +369,14 @@ impl CodegenBackend for GotocCodegenBackend { || entry_fn == Some(def_id) }); let model_path = base_filename.with_extension(ArtifactType::SymTabGoto); - let (gcx, items) = self.codegen_items( + let (gcx, items, contract_info) = self.codegen_items( tcx, &local_reachable, &model_path, &results.machine_model, Default::default(), ); + assert!(contract_info.is_none()); results.extend(gcx, items, None); } } diff --git a/kani-compiler/src/codegen_cprover_gotoc/mod.rs b/kani-compiler/src/codegen_cprover_gotoc/mod.rs index 7454d748d660..155e93604863 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/mod.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/mod.rs @@ -6,6 +6,6 @@ mod context; mod overrides; mod utils; -pub use compiler_interface::{GotocCodegenBackend, UnsupportedConstructs}; +pub use compiler_interface::{ContractInfoChannel, GotocCodegenBackend, UnsupportedConstructs}; pub use context::GotocCtx; pub use context::VtableCtx; diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index 56cd02ce542d..d34ff1c9f6e7 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -17,7 +17,7 @@ use crate::args::{Arguments, ReachabilityType}; #[cfg(feature = "cprover")] -use crate::codegen_cprover_gotoc::GotocCodegenBackend; +use crate::codegen_cprover_gotoc::{ContractInfoChannel, GotocCodegenBackend}; use crate::kani_middle::attributes::is_proof_harness; use crate::kani_middle::check_crate_items; use crate::kani_middle::metadata::gen_proof_metadata; @@ -26,7 +26,7 @@ use crate::kani_middle::stubbing::{self, harness_stub_map}; use crate::kani_queries::QueryDb; use crate::session::init_session; use clap::Parser; -use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; +use kani_metadata::{ArtifactType, AssignsContract, HarnessMetadata, KaniMetadata}; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_driver::{Callbacks, Compilation, RunCompiler}; use rustc_hir::def_id::LOCAL_CRATE; @@ -56,8 +56,11 @@ pub fn run(args: Vec) -> ExitCode { /// Configure the cprover backend that generate goto-programs. #[cfg(feature = "cprover")] -fn backend(queries: Arc>) -> Box { - Box::new(GotocCodegenBackend::new(queries)) +fn backend( + queries: Arc>, + contract_channel: ContractInfoChannel, +) -> Box { + Box::new(GotocCodegenBackend::new(queries, contract_channel)) } /// Fallback backend. It will trigger an error if no backend has been enabled. @@ -191,12 +194,20 @@ impl KaniCompiler { } CompilationStage::CodegenWithStubs { target_harnesses, all_harnesses, .. } => { assert!(!target_harnesses.is_empty(), "expected at least one target harness"); - let target_harness = &target_harnesses[0]; - let extra_arg = - stubbing::mk_rustc_arg(&all_harnesses[&target_harness].stub_map); + let target_harness_name = &target_harnesses[0]; + let target_harness = &all_harnesses[target_harness_name]; + let extra_arg = stubbing::mk_rustc_arg(&target_harness.stub_map); let mut args = orig_args.clone(); args.push(extra_arg); - self.run_compilation_session(&args)?; + let contract_spec = self.run_compilation_session(&args)?; + let CompilationStage::CodegenWithStubs { all_harnesses, .. } = &mut self.stage + else { + unreachable!() + }; + for (target, spec) in contract_spec { + let target_harness = all_harnesses.get_mut(&target).unwrap(); + target_harness.metadata.contract = spec.into(); + } } CompilationStage::Done { metadata: Some((kani_metadata, crate_info)) } => { // Only store metadata for harnesses for now. @@ -249,7 +260,7 @@ impl KaniCompiler { } else { CompilationStage::Done { metadata: Some(( - generate_metadata(&crate_info, all_harnesses), + generate_metadata(&crate_info, &all_harnesses), crate_info.clone(), )), } @@ -262,12 +273,17 @@ impl KaniCompiler { } /// Run the Rust compiler with the given arguments and pass `&mut self` to handle callbacks. - fn run_compilation_session(&mut self, args: &[String]) -> Result<(), ErrorGuaranteed> { + fn run_compilation_session( + &mut self, + args: &[String], + ) -> Result, ErrorGuaranteed> { debug!(?args, "run_compilation_session"); let queries = self.queries.clone(); let mut compiler = RunCompiler::new(args, self); - compiler.set_make_codegen_backend(Some(Box::new(move |_cfg| backend(queries)))); - compiler.run() + let (send, receive) = std::sync::mpsc::channel(); + compiler.set_make_codegen_backend(Some(Box::new(move |_cfg| backend(queries, send)))); + compiler.run(); + Ok(receive.iter().collect()) } /// Gather and process all harnesses from this crate that shall be compiled. @@ -459,6 +475,7 @@ mod tests { original_end_line: 20, goto_file: None, attributes: HarnessAttributes::default(), + contract: Default::default(), } } diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index fbf69c2d4fa4..4d193b9f347b 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -3,6 +3,7 @@ //! This module handles Kani metadata generation. For example, generating HarnessMetadata for a //! given function. +use std::default::Default; use std::path::Path; use crate::kani_middle::attributes::test_harness_name; @@ -39,6 +40,7 @@ pub fn gen_proof_metadata(tcx: TyCtxt, def_id: DefId, base_name: &Path) -> Harne attributes, // TODO: This no longer needs to be an Option. goto_file: Some(model_file), + contract: Default::default(), } } @@ -67,5 +69,6 @@ pub fn gen_test_metadata<'tcx>( attributes: HarnessAttributes::default(), // TODO: This no longer needs to be an Option. goto_file: Some(model_file), + contract: Default::default(), } } diff --git a/kani-driver/src/call_goto_instrument.rs b/kani-driver/src/call_goto_instrument.rs index 6879e6c53d83..e002a93ac6d9 100644 --- a/kani-driver/src/call_goto_instrument.rs +++ b/kani-driver/src/call_goto_instrument.rs @@ -12,7 +12,7 @@ use crate::metadata::collect_and_link_function_pointer_restrictions; use crate::project::Project; use crate::session::KaniSession; use crate::util::alter_extension; -use kani_metadata::{ArtifactType, HarnessMetadata}; +use kani_metadata::{ArtifactType, AssignsContract, HarnessMetadata}; impl KaniSession { /// Instrument and optimize a goto binary in-place. @@ -22,7 +22,6 @@ impl KaniSession { output: &Path, project: &Project, harness: &HarnessMetadata, - contract_info: Option<(String, String)>, ) -> Result<()> { // We actually start by calling goto-cc to start the specialization: self.specialize_to_proof_harness(input, output, &harness.mangled_name)?; @@ -38,7 +37,7 @@ impl KaniSession { self.goto_sanity_check(output)?; } - self.instrument_contracts(harness, output, contract_info)?; + self.instrument_contracts(harness, output)?; if self.args.checks.undefined_function_on() { self.add_library(output)?; @@ -164,12 +163,8 @@ impl KaniSession { } /// Make CBMC enforce a function contract. - pub fn instrument_contracts( - &self, - harness: &HarnessMetadata, - file: &Path, - check: Option<(String, String)>, - ) -> Result<()> { + pub fn instrument_contracts(&self, harness: &HarnessMetadata, file: &Path) -> Result<()> { + let check = harness.contract.as_ref(); if check.is_none() { return Ok(()); } @@ -177,13 +172,13 @@ impl KaniSession { let mut args: Vec = vec!["--dfcc".into(), (&harness.mangled_name).into()]; - if let Some((function, recursion_tracker)) = check { - println!("enforcing function contract for {function} with tracker {recursion_tracker}"); + if let Some(assigns) = check { + println!("enforcing function contract for {assigns:?}"); args.extend([ "--enforce-contract".into(), - function.into(), + assigns.contracted_function_name.as_str().into(), "--nondet-static-exclude".into(), - recursion_tracker.into(), + assigns.recursion_tracker.as_str().into(), ]); } diff --git a/kani-driver/src/harness_runner.rs b/kani-driver/src/harness_runner.rs index d54b2080ffbc..bef2d2c1712a 100644 --- a/kani-driver/src/harness_runner.rs +++ b/kani-driver/src/harness_runner.rs @@ -57,18 +57,8 @@ impl<'sess, 'pr> HarnessRunner<'sess, 'pr> { let report_dir = self.project.outdir.join(format!("report-{harness_filename}")); let goto_file = self.project.get_harness_artifact(&harness, ArtifactType::Goto).unwrap(); - // TODO: Fix upstream. - // This error is ignored for now, because it fails on cargo kani projects. - // I suspect that the metadata file is just not being registered correctly. - let contract_info = self.get_contract_info(harness).ok().flatten(); - - self.sess.instrument_model( - goto_file, - goto_file, - &self.project, - &harness, - contract_info, - )?; + + self.sess.instrument_model(goto_file, goto_file, &self.project, &harness)?; if self.sess.args.synthesize_loop_contracts { self.sess.synthesize_loop_contracts(goto_file, &goto_file, &harness)?; @@ -83,18 +73,6 @@ impl<'sess, 'pr> HarnessRunner<'sess, 'pr> { Ok(results) } - fn get_contract_info(&self, harness: &'pr HarnessMetadata) -> Result> { - let contract_info_artifact = self - .project - .get_harness_artifact(&harness, ArtifactType::ContractMetadata) - .ok_or_else(|| { - anyhow!("no contract metadata file is present for harness {}", harness.pretty_name) - })?; - - let reader = std::io::BufReader::new(std::fs::File::open(contract_info_artifact)?); - Ok(serde_json::from_reader(reader)?) - } - /// Return an error if the user is trying to verify a harness with stubs without enabling the /// experimental feature. fn check_stubbing(&self, harnesses: &[&HarnessMetadata]) -> Result<()> { diff --git a/kani-driver/src/metadata.rs b/kani-driver/src/metadata.rs index 871be621c6b0..fabf5dacf3e8 100644 --- a/kani-driver/src/metadata.rs +++ b/kani-driver/src/metadata.rs @@ -184,6 +184,7 @@ pub fn mock_proof_harness( original_end_line: 0, attributes: HarnessAttributes { unwind_value, proof: true, ..Default::default() }, goto_file: model_file, + contract: Default::default(), } } diff --git a/kani-driver/src/project.rs b/kani-driver/src/project.rs index 80ca248874a1..b99a7e9aff07 100644 --- a/kani-driver/src/project.rs +++ b/kani-driver/src/project.rs @@ -130,12 +130,10 @@ impl Project { // All other harness artifacts that may have been generated as part of the build. artifacts.extend( - [SymTab, TypeMap, VTableRestriction, PrettyNameMap, ContractMetadata] - .iter() - .filter_map(|typ| { - let artifact = Artifact::try_from(&symtab_out, *typ).ok()?; - Some(artifact) - }), + [SymTab, TypeMap, VTableRestriction, PrettyNameMap].iter().filter_map(|typ| { + let artifact = Artifact::try_from(&symtab_out, *typ).ok()?; + Some(artifact) + }), ); artifacts.push(symtab_out); artifacts.push(goto); @@ -227,7 +225,7 @@ pub fn cargo_project(session: &KaniSession, keep_going: bool) -> Result .iter() .map(|artifact| convert_type(&artifact, Metadata, SymTabGoto)) .collect::>(); - artifacts.push(Artifact::try_new(goto.as_path(), ContractMetadata)?); + session.link_goto_binary(&all_gotos, &goto)?; let goto_artifact = Artifact::try_new(&goto, Goto)?; diff --git a/kani_metadata/src/artifact.rs b/kani_metadata/src/artifact.rs index 814d970dcdd9..078cad14f679 100644 --- a/kani_metadata/src/artifact.rs +++ b/kani_metadata/src/artifact.rs @@ -25,8 +25,6 @@ pub enum ArtifactType { /// A `json` file that stores the name to prettyName mapping for symbols /// (used to demangle names from the C dump). PrettyNameMap, - - ContractMetadata, } impl ArtifactType { @@ -39,7 +37,6 @@ impl ArtifactType { ArtifactType::TypeMap => "type_map.json", ArtifactType::VTableRestriction => "restrictions.json", ArtifactType::PrettyNameMap => "pretty_name_map.json", - ArtifactType::ContractMetadata => "power-of-the-law.json", } } } @@ -67,7 +64,6 @@ pub fn convert_type(path: &Path, from: ArtifactType, to: ArtifactType) -> PathBu | ArtifactType::SymTabGoto | ArtifactType::TypeMap | ArtifactType::VTableRestriction - | ArtifactType::ContractMetadata | ArtifactType::PrettyNameMap => { result.set_extension(""); result.set_extension(to); diff --git a/kani_metadata/src/harness.rs b/kani_metadata/src/harness.rs index 2356f9bdf42f..7336ee2e6cea 100644 --- a/kani_metadata/src/harness.rs +++ b/kani_metadata/src/harness.rs @@ -5,6 +5,12 @@ use crate::CbmcSolver; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct AssignsContract { + pub contracted_function_name: String, + pub recursion_tracker: String, +} + /// We emit this structure for each annotated proof harness (`#[kani::proof]`) we find. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct HarnessMetadata { @@ -24,6 +30,8 @@ pub struct HarnessMetadata { pub goto_file: Option, /// The `#[kani::<>]` attributes added to a harness. pub attributes: HarnessAttributes, + /// + pub contract: Option, } /// The attributes added by the user to control how a harness is executed. From 981a465e89855d39cab3ba5ea3b3197b24227bdf Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 29 Nov 2023 14:24:44 -0800 Subject: [PATCH 29/72] Formatting --- .../compiler_interface.rs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index c84dcf790652..9d1af3dcd181 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -302,11 +302,16 @@ impl CodegenBackend for GotocCodegenBackend { .unwrap(); let Ok(contract_metadata) = contract_metadata_for_harness(tcx, harness.def_id()) - else { - continue; - }; - let (gcx, items, contract_info) = - self.codegen_items(tcx, &[harness], model_path, &results.machine_model, contract_metadata); + else { + continue; + }; + let (gcx, items, contract_info) = self.codegen_items( + tcx, + &[harness], + model_path, + &results.machine_model, + contract_metadata, + ); results.extend(gcx, items, None); if let Some(assigns_contract) = contract_info { self.contract_channel @@ -333,8 +338,13 @@ impl CodegenBackend for GotocCodegenBackend { // We will be able to remove this once we optimize all calls to CBMC utilities. // https://github.com/model-checking/kani/issues/1971 let model_path = base_filename.with_extension(ArtifactType::SymTabGoto); - let (gcx, items, contract_info) = - self.codegen_items(tcx, &harnesses, &model_path, &results.machine_model, Default::default()); + let (gcx, items, contract_info) = self.codegen_items( + tcx, + &harnesses, + &model_path, + &results.machine_model, + Default::default(), + ); results.extend(gcx, items, None); assert!(contract_info.is_none()); From f9b4ef3e8bd2b72de6f1a2b7fbb37aa7b0ec46a2 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 29 Nov 2023 14:30:14 -0800 Subject: [PATCH 30/72] Fix Warnings --- kani-driver/src/call_goto_instrument.rs | 2 +- kani-driver/src/harness_runner.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kani-driver/src/call_goto_instrument.rs b/kani-driver/src/call_goto_instrument.rs index e002a93ac6d9..0b3db37faf89 100644 --- a/kani-driver/src/call_goto_instrument.rs +++ b/kani-driver/src/call_goto_instrument.rs @@ -12,7 +12,7 @@ use crate::metadata::collect_and_link_function_pointer_restrictions; use crate::project::Project; use crate::session::KaniSession; use crate::util::alter_extension; -use kani_metadata::{ArtifactType, AssignsContract, HarnessMetadata}; +use kani_metadata::{ArtifactType, HarnessMetadata}; impl KaniSession { /// Instrument and optimize a goto binary in-place. diff --git a/kani-driver/src/harness_runner.rs b/kani-driver/src/harness_runner.rs index bef2d2c1712a..f0a7def68d32 100644 --- a/kani-driver/src/harness_runner.rs +++ b/kani-driver/src/harness_runner.rs @@ -1,7 +1,7 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use anyhow::{anyhow, bail, Result}; +use anyhow::{bail, Result}; use kani_metadata::{ArtifactType, HarnessMetadata}; use rayon::prelude::*; use std::path::Path; From 12a993d9a78d788fee13ee3cbcb232bd0391b96c Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 30 Nov 2023 11:12:49 -0800 Subject: [PATCH 31/72] Consistent use of file paths for recursion tracker --- .../src/codegen_cprover_gotoc/compiler_interface.rs | 8 +++++--- kani-compiler/src/kani_compiler.rs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 9d1af3dcd181..ec59e04ead77 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -5,13 +5,13 @@ use crate::args::ReachabilityType; use crate::codegen_cprover_gotoc::GotocCtx; +use crate::kani_middle::analysis; use crate::kani_middle::attributes::{is_test_harness_description, KaniAttributes}; use crate::kani_middle::metadata::gen_test_metadata; use crate::kani_middle::provide; use crate::kani_middle::reachability::{ collect_reachable_items, filter_const_crate_items, filter_crate_items, }; -use crate::kani_middle::{analysis, SourceLocation}; use crate::kani_middle::{check_reachable_items, dump_mir_items}; use crate::kani_queries::QueryDb; use cbmc::goto_program::Location; @@ -226,11 +226,13 @@ impl<'tcx> GotocCtx<'tcx> { let recursion_wrapper_id = function_under_contract_attrs.checked_with_id().unwrap().unwrap(); let span_of_recursion_wrapper = tcx.def_span(recursion_wrapper_id); - let location_of_recursion_wrapper = SourceLocation::new(tcx, &span_of_recursion_wrapper); + let location_of_recursion_wrapper = self.codegen_span(&span_of_recursion_wrapper); let full_name = format!( "{}:{}::REENTRY", - location_of_recursion_wrapper.filename, + location_of_recursion_wrapper + .filename() + .expect("recursion location wrapper should have a file name"), tcx.item_name(recursion_wrapper_id), ); diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index c412d7b9ea71..5f7d46c52770 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -187,7 +187,7 @@ impl KaniCompiler { debug!(next=?self.stage, "run"); match &self.stage { CompilationStage::Init => { - self.run_compilation_session(&orig_args)?; + assert!(self.run_compilation_session(&orig_args)?.is_empty()); } CompilationStage::CodegenNoStubs { .. } => { unreachable!("This stage should always run in the same session as Init"); From 18434422114f30922c53bcefb6e3aa5473cd3041 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 30 Nov 2023 11:36:08 -0800 Subject: [PATCH 32/72] =?UTF-8?q?Simplify=20check=20function=20editing?= =?UTF-8?q?=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/kani_macros/src/sysroot/contracts.rs | 42 ++++++++------------ 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index f576da4ff767..14f8a30f4a32 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -469,14 +469,14 @@ impl<'a> ContractConditionsHandler<'a> { /// Mutable because a `modifies` clause may need to extend the inner call to /// the wrapper with new arguments. fn make_check_body(&mut self) -> TokenStream2 { + let mut inner = self.ensure_inner_call(); let Self { attr_copy, .. } = self; match &self.condition_type { ContractConditionsData::Requires { attr } => { - let block = self.create_inner_call([].into_iter()); quote!( kani::assume(#attr); - #(#block)* + #(#inner)* ) } ContractConditionsData::Ensures { argument_names, attr } => { @@ -489,17 +489,15 @@ impl<'a> ContractConditionsHandler<'a> { #copy_clean ); - let mut call = self.create_inner_call([].into_iter()); - assert!(matches!( - call.pop(), + inner.pop(), Some(syn::Stmt::Expr(syn::Expr::Path(pexpr), None)) if pexpr.path.get_ident().map_or(false, |id| id == "result") )); quote!( #arg_copies - #(#call)* + #(#inner)* #exec_postconditions result ) @@ -507,25 +505,19 @@ impl<'a> ContractConditionsHandler<'a> { ContractConditionsData::Modifies { attr } => { let wrapper_name = self.make_wrapper_name().to_string(); let wrapper_args = make_wrapper_args(attr.len()); - // TODO handle first invocation where this is the actual body. - if !self.is_first_emit() { - if let Some(wrapper_call_args) = self - .annotated_fn - .block - .stmts - .iter_mut() - .find_map(|stmt| try_as_wrapper_call_args(stmt, &wrapper_name)) - { - wrapper_call_args - .extend(wrapper_args.clone().map(|a| Expr::Verbatim(quote!(#a)))); - } else { - unreachable!( - "Invariant broken, check function did not contain a call to the wrapper function" - ) - } + + if let Some(wrapper_call_args) = inner + .iter_mut() + .find_map(|stmt| try_as_wrapper_call_args(stmt, &wrapper_name)) + { + wrapper_call_args + .extend(wrapper_args.clone().map(|a| Expr::Verbatim(quote!(#a)))); + } else { + unreachable!( + "Invariant broken, check function did not contain a call to the wrapper function" + ) } - let inner = self.create_inner_call(wrapper_args.clone()); let wrapper_args = make_wrapper_args(attr.len()); quote!( @@ -553,7 +545,7 @@ impl<'a> ContractConditionsHandler<'a> { } } - fn create_inner_call(&self, additional_args: impl Iterator) -> Vec { + fn ensure_inner_call(&self) -> Vec { let wrapper_name = self.make_wrapper_name(); let return_type = return_type_to_type(&self.annotated_fn.sig.output); if self.is_first_emit() { @@ -564,7 +556,7 @@ impl<'a> ContractConditionsHandler<'a> { quote!(#wrapper_name) }; syn::parse_quote!( - let result : #return_type = #wrapper_call(#(#args,)* #(#additional_args),*); + let result : #return_type = #wrapper_call(#(#args),*); result ) } else { From 00518b0fd866fd2a4350baa6dcc10dcb74c88071 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 30 Nov 2023 13:29:20 -0800 Subject: [PATCH 33/72] Fix ordering for havoc and postconditions Adds test cases to ensure the order is correct --- library/kani_macros/src/sysroot/contracts.rs | 199 +++++++++++++----- .../function-contract/havoc_pass.expected | 38 ++++ .../havoc_pass_reordered.expected | 38 ++++ .../function-contract/havoc_pass_reordered.rs | 25 +++ 4 files changed, 251 insertions(+), 49 deletions(-) create mode 100644 tests/expected/function-contract/havoc_pass.expected create mode 100644 tests/expected/function-contract/havoc_pass_reordered.expected create mode 100644 tests/expected/function-contract/havoc_pass_reordered.rs diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 14f8a30f4a32..d3d2a9f0d1c2 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -469,7 +469,7 @@ impl<'a> ContractConditionsHandler<'a> { /// Mutable because a `modifies` clause may need to extend the inner call to /// the wrapper with new arguments. fn make_check_body(&mut self) -> TokenStream2 { - let mut inner = self.ensure_inner_call(); + let mut inner = self.ensure_bootstrapped_check_body(); let Self { attr_copy, .. } = self; match &self.condition_type { @@ -506,9 +506,8 @@ impl<'a> ContractConditionsHandler<'a> { let wrapper_name = self.make_wrapper_name().to_string(); let wrapper_args = make_wrapper_args(attr.len()); - if let Some(wrapper_call_args) = inner - .iter_mut() - .find_map(|stmt| try_as_wrapper_call_args(stmt, &wrapper_name)) + if let Some(wrapper_call_args) = + inner.iter_mut().find_map(|stmt| try_as_wrapper_call_args(stmt, &wrapper_name)) { wrapper_call_args .extend(wrapper_args.clone().map(|a| Expr::Verbatim(quote!(#a)))); @@ -545,7 +544,7 @@ impl<'a> ContractConditionsHandler<'a> { } } - fn ensure_inner_call(&self) -> Vec { + fn ensure_bootstrapped_check_body(&self) -> Vec { let wrapper_name = self.make_wrapper_name(); let return_type = return_type_to_type(&self.annotated_fn.sig.output); if self.is_first_emit() { @@ -564,6 +563,49 @@ impl<'a> ContractConditionsHandler<'a> { } } + /// Split an existing replace body of the form + /// + /// ```ignore + /// // multiple preconditions and argument copies like like + /// kani::assert(.. precondition); + /// let arg_name = kani::untracked_deref(&arg_value); + /// // single result havoc + /// let result : ResultType = kani::any(); + /// + /// // multiple argument havockings + /// *unsafe { kani::Pointer::assignable(argument) } = kani::any(); + /// // multiple postconditions + /// kani::assume(postcond); + /// // multiple argument copy (used in postconditions) cleanups + /// std::mem::forget(arg_name); + /// // single return + /// result + /// ``` + /// + /// Such that the first vector contains everything up to and including the single result havoc + /// and the second one the rest, excluding the return. + /// + /// If this is the first time we're emitting replace we create the return havoc and nothing else. + fn ensure_bootstrapped_replace_body(&self) -> (Vec, Vec) { + if self.is_first_emit() { + let return_type = return_type_to_type(&self.annotated_fn.sig.output); + (vec![syn::parse_quote!(let result : #return_type = kani::any();)], vec![]) + } else { + let stmts = &self.annotated_fn.block.stmts; + let idx = stmts + .iter() + .enumerate() + .find_map(|(i, elem)| is_replace_return_havoc(elem).then_some(i)) + .unwrap_or_else(|| { + panic!("ICE: Could not find result let binding in statement sequence {stmts:?}") + }); + // We want the result assign statement to end up as the last statement in the first + // vector, hence the `+1`. + let (before, after) = stmts.split_at(idx + 1); + (before.to_vec(), after.split_last().unwrap().1.to_vec()) + } + } + /// Create the body of a stub for this contract. /// /// Wraps the conditions from this attribute around a prior call. If @@ -572,23 +614,25 @@ impl<'a> ContractConditionsHandler<'a> { /// /// `use_nondet_result` will only be true if this is the first time we are /// generating a replace function. - fn make_replace_body(&self, use_nondet_result: bool) -> TokenStream2 { - let Self { attr_copy, .. } = self; - let ItemFn { sig, block, .. } = &*self.annotated_fn; - let call_to_prior = - if use_nondet_result { quote!(kani::any()) } else { block.to_token_stream() }; - let return_type = return_type_to_type(&sig.output); + fn make_replace_body(&self) -> TokenStream2 { + let (before, after) = self.ensure_bootstrapped_replace_body(); match &self.condition_type { - ContractConditionsData::Requires { attr } => quote!( - kani::assert(#attr, stringify!(#attr_copy)); - #call_to_prior - ), + ContractConditionsData::Requires { attr } => { + let Self { attr_copy, .. } = self; + quote!( + kani::assert(#attr, stringify!(#attr_copy)); + #(#before)* + #(#after)* + result + ) + }, ContractConditionsData::Ensures { attr, argument_names } => { let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names); quote!( #arg_copies - let result: #return_type = #call_to_prior; + #(#before)* + #(#after)* kani::assume(#attr); #copy_clean result @@ -596,8 +640,9 @@ impl<'a> ContractConditionsHandler<'a> { } ContractConditionsData::Modifies { attr } => { quote!( - let result = #call_to_prior; + #(#before)* #(*unsafe { kani::Pointer::assignable(#attr) } = kani::any();)* + #(#after)* result ) } @@ -630,7 +675,7 @@ impl<'a> ContractConditionsHandler<'a> { /// /// See [`Self::make_replace_body`] for the most interesting parts of this /// function. - fn emit_replace_function(&mut self, replace_function_ident: Ident, is_first_emit: bool) { + fn emit_replace_function(&mut self, replace_function_ident: Ident) { self.emit_common_header(); if self.function_state.emit_tag_attr() { @@ -639,10 +684,10 @@ impl<'a> ContractConditionsHandler<'a> { self.output.extend(quote!(#[kanitool::is_contract_generated(replace)])); } let mut sig = self.annotated_fn.sig.clone(); - if is_first_emit { + if self.is_first_emit() { attach_require_kani_any(&mut sig); } - let body = self.make_replace_body(is_first_emit); + let body = self.make_replace_body(); sig.ident = replace_function_ident; // Finally emit the check function itself. @@ -709,6 +754,77 @@ impl<'a> ContractConditionsHandler<'a> { } } +/// Used as the "single source of truth" for [`try_as_result_assign`] and [`try_as_result_assign_mut`] +/// since we can't abstract over mutability. Input is the object to match on and the name of the +/// function used to convert an `Option` into the result type (e.g. `as_ref` and `as_mut` +/// respectively). +/// +/// We start with a `match` as a top-level here, since if we made this a pattern macro (the "clean" +/// thing to do) then we cant use the `if` inside there which we need because box patterns are +/// unstable. +macro_rules! try_as_result_assign_pat { + ($input:expr, $convert:ident) => { + match $input { + syn::Stmt::Local(syn::Local { + pat: syn::Pat::Type(syn::PatType { + pat: inner_pat, + attrs, + .. + }), + init, + .. + }) if attrs.is_empty() + && matches!( + inner_pat.as_ref(), + syn::Pat::Ident(syn::PatIdent { + by_ref: None, + mutability: None, + ident: result_ident, + subpat: None, + .. + }) if result_ident == "result" + ) => init.$convert(), + _ => None, + } + }; +} + +fn try_as_result_assign(stmt: &syn::Stmt) -> Option<&syn::LocalInit> { + try_as_result_assign_pat!(stmt, as_ref) +} + +fn try_as_result_assign_mut(stmt: &mut syn::Stmt) -> Option<&mut syn::LocalInit> { + try_as_result_assign_pat!(stmt, as_mut) +} + +fn is_replace_return_havoc(stmt: &syn::Stmt) -> bool { + let Some(syn::LocalInit { diverge: None, expr: e, .. }) = try_as_result_assign(stmt) else { + return false; + }; + + matches!( + e.as_ref(), + Expr::Call(syn::ExprCall { + func, + args, + .. + }) + if args.is_empty() + && matches!( + func.as_ref(), + Expr::Path(syn::ExprPath { + qself: None, + path, + attrs, + }) + if path.segments.len() == 2 + && path.segments[0].ident == "kani" + && path.segments[1].ident == "any" + && attrs.is_empty() + ) + ) +} + fn exprs_for_args<'a, T>( args: &'a syn::punctuated::Punctuated, ) -> impl Iterator + Clone + 'a { @@ -787,38 +903,23 @@ fn pat_to_expr(pat: &syn::Pat) -> Expr { _ => mk_err("unknown"), } } - fn try_as_wrapper_call_args<'a>( stmt: &'a mut syn::Stmt, wrapper_fn_name: &str, ) -> Option<&'a mut syn::punctuated::Punctuated> { - match stmt { - syn::Stmt::Local(syn::Local { - pat: syn::Pat::Type(syn::PatType { pat: inner_pat, .. }), - init: Some(syn::LocalInit { diverge: None, expr: init_expr, .. }), - .. - }) if matches!(inner_pat.as_ref(), - syn::Pat::Ident(syn::PatIdent { - by_ref: None, - mutability: None, - ident: result_ident, - subpat: None, - .. - }) if result_ident == "result" - ) => - { - match init_expr.as_mut() { - Expr::Call(syn::ExprCall { func: box_func, args, .. }) => match box_func.as_ref() { - syn::Expr::Path(syn::ExprPath { qself: None, path, .. }) - if path.get_ident().map_or(false, |id| id == wrapper_fn_name) => - { - Some(args) - } - _ => None, - }, - _ => None, + let syn::LocalInit { diverge: None, expr: init_expr, .. } = try_as_result_assign_mut(stmt)? else { + return None; + }; + + match init_expr.as_mut() { + Expr::Call(syn::ExprCall { func: box_func, args, .. }) => match box_func.as_ref() { + syn::Expr::Path(syn::ExprPath { qself: None, path, .. }) + if path.get_ident().map_or(false, |id| id == wrapper_fn_name) => + { + Some(args) } - } + _ => None, + }, _ => None, } } @@ -966,7 +1067,7 @@ fn requires_ensures_main( } ContractFunctionState::Replace => { // Analogous to above - handler.emit_replace_function(original_function_name, false); + handler.emit_replace_function(original_function_name); } ContractFunctionState::Original => { unreachable!("Impossible: This is handled via short circuiting earlier.") @@ -1058,7 +1159,7 @@ fn requires_ensures_main( )); handler.emit_check_function(check_fn_name); - handler.emit_replace_function(replace_fn_name, true); + handler.emit_replace_function(replace_fn_name); handler.emit_augmented_modifies_wrapper(); } } diff --git a/tests/expected/function-contract/havoc_pass.expected b/tests/expected/function-contract/havoc_pass.expected new file mode 100644 index 000000000000..02ce972ef370 --- /dev/null +++ b/tests/expected/function-contract/havoc_pass.expected @@ -0,0 +1,38 @@ +copy_replace.assertion\ +- Status: SUCCESS\ +- Description: "equality"\ +in function copy_replace + +VERIFICATION:- SUCCESSFUL + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that var_4 is assignable"\ +in function copy + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that var_3 is assignable"\ +in function copy\ + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that var_5 is assignable"\ +in function copy + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that *var_5 is assignable"\ +in function copy\ + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that var_7 is assignable"\ +in function copy + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that *var_7 is assignable"\ +in function copy + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/havoc_pass_reordered.expected b/tests/expected/function-contract/havoc_pass_reordered.expected new file mode 100644 index 000000000000..02ce972ef370 --- /dev/null +++ b/tests/expected/function-contract/havoc_pass_reordered.expected @@ -0,0 +1,38 @@ +copy_replace.assertion\ +- Status: SUCCESS\ +- Description: "equality"\ +in function copy_replace + +VERIFICATION:- SUCCESSFUL + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that var_4 is assignable"\ +in function copy + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that var_3 is assignable"\ +in function copy\ + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that var_5 is assignable"\ +in function copy + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that *var_5 is assignable"\ +in function copy\ + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that var_7 is assignable"\ +in function copy + +copy.assigns\ +- Status: SUCCESS\ +- Description: "Check that *var_7 is assignable"\ +in function copy + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/havoc_pass_reordered.rs b/tests/expected/function-contract/havoc_pass_reordered.rs new file mode 100644 index 000000000000..224e0c1180ff --- /dev/null +++ b/tests/expected/function-contract/havoc_pass_reordered.rs @@ -0,0 +1,25 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +// These two are reordered in comparison to `havoc_pass` and we expect the test case to pass still +#[kani::modifies(dst)] +#[kani::ensures(*dst == src)] +fn copy(src: u32, dst: &mut u32) { + *dst = src; +} + + +#[kani::proof_for_contract(copy)] +fn copy_harness() { + copy(kani::any(), &mut kani::any()); +} + +#[kani::proof] +#[kani::stub_verified(copy)] +fn copy_replace() { + let src = kani::any(); + let mut dst = kani::any(); + copy(src, &mut dst); + kani::assert(src == dst, "equality"); +} \ No newline at end of file From 862a5d7ae2bdbbf95f566c1db42683186d675ae7 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 30 Nov 2023 14:14:45 -0800 Subject: [PATCH 34/72] Extra test cases --- .../function-contract/.unsafe_assigns_rc | 33 ++++++++++++++ .../function-contract/.vec_assigns_pass | 45 +++++++++++++++++++ .../function-contract/stmt_expr_assigns.rs | 18 ++++++++ 3 files changed, 96 insertions(+) create mode 100644 tests/expected/function-contract/.unsafe_assigns_rc create mode 100644 tests/expected/function-contract/.vec_assigns_pass create mode 100644 tests/expected/function-contract/stmt_expr_assigns.rs diff --git a/tests/expected/function-contract/.unsafe_assigns_rc b/tests/expected/function-contract/.unsafe_assigns_rc new file mode 100644 index 000000000000..ea2988219ee8 --- /dev/null +++ b/tests/expected/function-contract/.unsafe_assigns_rc @@ -0,0 +1,33 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +use std::rc::Rc; +use std::ops::Deref; + +#[kani::modifies({ + let intref : &u32 = ptr.deref().deref(); + intref +})] +fn modify(ptr: Rc<&mut u32>) { + unsafe { + **(Rc::as_ptr(&ptr) as *mut &mut u32) = 1; + } +} + +#[kani::proof_for_contract(modify)] +fn main() { + let mut i : u32 = kani::any(); + let ptr = Rc::new(&mut i); + modify(ptr.clone()); +} + + +// #[kani::proof] +// #[kani::stub_verified(modify)] +// fn replace_modify() { +// let begin = kani::any_where(|i| *i < 100); +// let i = Rc::new(RefCell::new(begin)); +// modify(i.clone()); +// kani::assert(*i.borrow() == begin + 1, "end"); +// } \ No newline at end of file diff --git a/tests/expected/function-contract/.vec_assigns_pass b/tests/expected/function-contract/.vec_assigns_pass new file mode 100644 index 000000000000..ec39c1d698c2 --- /dev/null +++ b/tests/expected/function-contract/.vec_assigns_pass @@ -0,0 +1,45 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::requires(v.len() > 1)] +#[kani::modifies(&v[0])] +#[kani::ensures(v[0] == src)] +fn modify(v: &mut Vec, src: u32) { + v[0] = src +} + +#[kani::unwind(10)] +#[kani::proof_for_contract(modify)] +fn main() { + let v_len = kani::any_where(|i| *i < 4); + let mut v: Vec = vec![kani::any()]; + for _ in 0..v_len { + v.push(kani::any()); + } + modify(&mut v, kani::any()); +} + +#[kani::unwind(10)] +#[kani::proof] +#[kani::stub_verified(modify)] +fn modify_replace() { + let v_len = kani::any_where(|i| *i < 4); + let mut v: Vec = vec![kani::any()]; + let mut compare = vec![]; + for _ in 0..v_len { + let elem = kani::any(); + v.push(elem); + compare.push(elem); + } + let src = kani::any(); + modify(&mut v, src); + kani::assert( + v[0] == src, + "element set" + ); + kani::assert( + v[1..] == compare[..], + "vector tail equality" + ); +} diff --git a/tests/expected/function-contract/stmt_expr_assigns.rs b/tests/expected/function-contract/stmt_expr_assigns.rs new file mode 100644 index 000000000000..f8e7f9e4a836 --- /dev/null +++ b/tests/expected/function-contract/stmt_expr_assigns.rs @@ -0,0 +1,18 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::requires(**ptr < 100)] +#[kani::modifies({ + let r = ptr.as_ref(); + r +})] +fn modify(ptr: &mut Box) { + *ptr.as_mut() += 1; +} + +#[kani::proof_for_contract(modify)] +fn main() { + let mut i = Box::new(kani::any()); + modify(&mut i); +} From e229ad086e508d2f213a2e64a8c03aca0705cdf9 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 30 Nov 2023 14:15:21 -0800 Subject: [PATCH 35/72] Formatting --- library/kani_macros/src/sysroot/contracts.rs | 5 +++-- tests/expected/function-contract/havoc_pass_reordered.rs | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index d3d2a9f0d1c2..352d4bca0a6d 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -626,7 +626,7 @@ impl<'a> ContractConditionsHandler<'a> { #(#after)* result ) - }, + } ContractConditionsData::Ensures { attr, argument_names } => { let (arg_copies, copy_clean) = make_unsafe_argument_copies(&argument_names); quote!( @@ -907,7 +907,8 @@ fn try_as_wrapper_call_args<'a>( stmt: &'a mut syn::Stmt, wrapper_fn_name: &str, ) -> Option<&'a mut syn::punctuated::Punctuated> { - let syn::LocalInit { diverge: None, expr: init_expr, .. } = try_as_result_assign_mut(stmt)? else { + let syn::LocalInit { diverge: None, expr: init_expr, .. } = try_as_result_assign_mut(stmt)? + else { return None; }; diff --git a/tests/expected/function-contract/havoc_pass_reordered.rs b/tests/expected/function-contract/havoc_pass_reordered.rs index 224e0c1180ff..13f3b1d057c9 100644 --- a/tests/expected/function-contract/havoc_pass_reordered.rs +++ b/tests/expected/function-contract/havoc_pass_reordered.rs @@ -9,7 +9,6 @@ fn copy(src: u32, dst: &mut u32) { *dst = src; } - #[kani::proof_for_contract(copy)] fn copy_harness() { copy(kani::any(), &mut kani::any()); @@ -22,4 +21,4 @@ fn copy_replace() { let mut dst = kani::any(); copy(src, &mut dst); kani::assert(src == dst, "equality"); -} \ No newline at end of file +} From 9d95def1335d9df466f6bc02f3a7069b5ae37e60 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 1 Dec 2023 11:40:12 -0500 Subject: [PATCH 36/72] Grouping test cases and adding more Added field and havocking test cases --- .../.vec_pass.fixme-2909} | 0 .../expr_pass.expected} | 0 .../expr_pass.rs} | 0 .../modifies/expr_replace_fail.expected | 7 +++++ .../modifies/expr_replace_fail.rs | 18 +++++++++++++ .../modifies/expr_replace_pass.rs | 19 +++++++++++++ .../modifies/field_pass.expected | 1 + .../function-contract/modifies/field_pass.rs | 20 ++++++++++++++ .../modifies/field_replace_fail.rs | 23 ++++++++++++++++ .../modifies/field_replace_pass.rs | 27 +++++++++++++++++++ .../{ => modifies}/havoc_pass.expected | 0 .../havoc_pass.rs} | 0 .../havoc_pass_reordered.expected | 0 .../modifies/havoc_pass_reordered.rs | 24 +++++++++++++++++ .../simple_fail.expected} | 0 .../simple_fail.rs} | 0 .../simple_pass.expected} | 0 .../simple_pass.rs} | 0 18 files changed, 139 insertions(+) rename tests/expected/function-contract/{.vec_assigns_pass => modifies/.vec_pass.fixme-2909} (100%) rename tests/expected/function-contract/{assigns_expr_pass.expected => modifies/expr_pass.expected} (100%) rename tests/expected/function-contract/{assigns_expr_pass.rs => modifies/expr_pass.rs} (100%) create mode 100644 tests/expected/function-contract/modifies/expr_replace_fail.expected create mode 100644 tests/expected/function-contract/modifies/expr_replace_fail.rs create mode 100644 tests/expected/function-contract/modifies/expr_replace_pass.rs create mode 100644 tests/expected/function-contract/modifies/field_pass.expected create mode 100644 tests/expected/function-contract/modifies/field_pass.rs create mode 100644 tests/expected/function-contract/modifies/field_replace_fail.rs create mode 100644 tests/expected/function-contract/modifies/field_replace_pass.rs rename tests/expected/function-contract/{ => modifies}/havoc_pass.expected (100%) rename tests/expected/function-contract/{havoc_pass_reordered.rs => modifies/havoc_pass.rs} (100%) rename tests/expected/function-contract/{ => modifies}/havoc_pass_reordered.expected (100%) create mode 100644 tests/expected/function-contract/modifies/havoc_pass_reordered.rs rename tests/expected/function-contract/{assigns_fail.expected => modifies/simple_fail.expected} (100%) rename tests/expected/function-contract/{assigns_fail.rs => modifies/simple_fail.rs} (100%) rename tests/expected/function-contract/{assigns_pass.expected => modifies/simple_pass.expected} (100%) rename tests/expected/function-contract/{assigns_pass.rs => modifies/simple_pass.rs} (100%) diff --git a/tests/expected/function-contract/.vec_assigns_pass b/tests/expected/function-contract/modifies/.vec_pass.fixme-2909 similarity index 100% rename from tests/expected/function-contract/.vec_assigns_pass rename to tests/expected/function-contract/modifies/.vec_pass.fixme-2909 diff --git a/tests/expected/function-contract/assigns_expr_pass.expected b/tests/expected/function-contract/modifies/expr_pass.expected similarity index 100% rename from tests/expected/function-contract/assigns_expr_pass.expected rename to tests/expected/function-contract/modifies/expr_pass.expected diff --git a/tests/expected/function-contract/assigns_expr_pass.rs b/tests/expected/function-contract/modifies/expr_pass.rs similarity index 100% rename from tests/expected/function-contract/assigns_expr_pass.rs rename to tests/expected/function-contract/modifies/expr_pass.rs diff --git a/tests/expected/function-contract/modifies/expr_replace_fail.expected b/tests/expected/function-contract/modifies/expr_replace_fail.expected new file mode 100644 index 000000000000..4ea7c000a8bc --- /dev/null +++ b/tests/expected/function-contract/modifies/expr_replace_fail.expected @@ -0,0 +1,7 @@ +main.assertion\ +- Status: FAILURE\ +- Description: "Increment" + +Failed Checks: Increment + +VERIFICATION:- FAILED diff --git a/tests/expected/function-contract/modifies/expr_replace_fail.rs b/tests/expected/function-contract/modifies/expr_replace_fail.rs new file mode 100644 index 000000000000..63ac2d8a8027 --- /dev/null +++ b/tests/expected/function-contract/modifies/expr_replace_fail.rs @@ -0,0 +1,18 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::requires(**ptr < 100)] +#[kani::modifies(ptr.as_ref())] +fn modify(ptr: &mut Box) { + *ptr.as_mut() += 1; +} + +#[kani::proof] +#[kani::stub_verified(modify)] +fn main() { + let val = kani::any_where(|i| *i < 100); + let mut i = Box::new(val); + modify(&mut i); + kani::assert(*i == val + 1, "Increment"); +} diff --git a/tests/expected/function-contract/modifies/expr_replace_pass.rs b/tests/expected/function-contract/modifies/expr_replace_pass.rs new file mode 100644 index 000000000000..187b97c3cefb --- /dev/null +++ b/tests/expected/function-contract/modifies/expr_replace_pass.rs @@ -0,0 +1,19 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::requires(**ptr < 100)] +#[kani::modifies(ptr.as_ref())] +#[kani::ensures(*ptr.as_ref() == prior + 1)] +fn modify(ptr: &mut Box, prior: u32) { + *ptr.as_mut() += 1; +} + +#[kani::proof] +#[kani::stub_verified(modify)] +fn main() { + let val = kani::any_where(|i| *i < 100); + let mut i = Box::new(val); + modify(&mut i, val); + kani::assert(*i == val + 1, "Increment"); +} diff --git a/tests/expected/function-contract/modifies/field_pass.expected b/tests/expected/function-contract/modifies/field_pass.expected new file mode 100644 index 000000000000..34c886c358cb --- /dev/null +++ b/tests/expected/function-contract/modifies/field_pass.expected @@ -0,0 +1 @@ +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/field_pass.rs b/tests/expected/function-contract/modifies/field_pass.rs new file mode 100644 index 000000000000..997f67474350 --- /dev/null +++ b/tests/expected/function-contract/modifies/field_pass.rs @@ -0,0 +1,20 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +struct S<'a> { + distraction: usize, + target: &'a mut u32, +} +#[kani::requires(*s.target < 100)] +#[kani::modifies(s.target)] +fn modify(s: S) { + *s.target += 1; +} + +#[kani::proof_for_contract(modify)] +fn main() { + let mut i = kani::any(); + let s = S { distraction: 0, target: &mut i }; + modify(s); +} diff --git a/tests/expected/function-contract/modifies/field_replace_fail.rs b/tests/expected/function-contract/modifies/field_replace_fail.rs new file mode 100644 index 000000000000..261e4ebd4974 --- /dev/null +++ b/tests/expected/function-contract/modifies/field_replace_fail.rs @@ -0,0 +1,23 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +struct S<'a> { + distraction: usize, + target: &'a mut u32, +} +#[kani::requires(*s.target < 100)] +#[kani::modifies(s.target)] +fn modify(s: S) { + *s.target += 1; +} + +#[kani::proof] +#[kani::stub_verified(modify)] +fn main() { + let mut i = kani::any_where(|i| *i < 100); + let i_copy = i; + let s = S { distraction: 0, target: &mut i }; + modify(s); + kani::assert(i == i_copy + 1, "Increment havocked"); +} diff --git a/tests/expected/function-contract/modifies/field_replace_pass.rs b/tests/expected/function-contract/modifies/field_replace_pass.rs new file mode 100644 index 000000000000..a6ae4ea4a7e0 --- /dev/null +++ b/tests/expected/function-contract/modifies/field_replace_pass.rs @@ -0,0 +1,27 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +struct S<'a> { + distraction: &'a mut u32, + target: &'a mut u32, +} +#[kani::requires(*s.target < 100)] +#[kani::modifies(s.target)] +#[kani::ensures(*s.target == prior + 1)] +fn modify(s: S, prior: u32) { + *s.target += 1; +} + +#[kani::proof] +#[kani::stub_verified(modify)] +fn main() { + let mut i = kani::any_where(|i| *i < 100); + let i_copy = i; + let mut distraction = kani::any(); + let distraction_copy = distraction; + let s = S { distraction: &mut distraction, target: &mut i }; + modify(s, i_copy); + kani::assert(i == i_copy + 1, "Increment"); + kani::assert(distraction == distraction_copy, "Unchanged"); +} diff --git a/tests/expected/function-contract/havoc_pass.expected b/tests/expected/function-contract/modifies/havoc_pass.expected similarity index 100% rename from tests/expected/function-contract/havoc_pass.expected rename to tests/expected/function-contract/modifies/havoc_pass.expected diff --git a/tests/expected/function-contract/havoc_pass_reordered.rs b/tests/expected/function-contract/modifies/havoc_pass.rs similarity index 100% rename from tests/expected/function-contract/havoc_pass_reordered.rs rename to tests/expected/function-contract/modifies/havoc_pass.rs diff --git a/tests/expected/function-contract/havoc_pass_reordered.expected b/tests/expected/function-contract/modifies/havoc_pass_reordered.expected similarity index 100% rename from tests/expected/function-contract/havoc_pass_reordered.expected rename to tests/expected/function-contract/modifies/havoc_pass_reordered.expected diff --git a/tests/expected/function-contract/modifies/havoc_pass_reordered.rs b/tests/expected/function-contract/modifies/havoc_pass_reordered.rs new file mode 100644 index 000000000000..13f3b1d057c9 --- /dev/null +++ b/tests/expected/function-contract/modifies/havoc_pass_reordered.rs @@ -0,0 +1,24 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +// These two are reordered in comparison to `havoc_pass` and we expect the test case to pass still +#[kani::modifies(dst)] +#[kani::ensures(*dst == src)] +fn copy(src: u32, dst: &mut u32) { + *dst = src; +} + +#[kani::proof_for_contract(copy)] +fn copy_harness() { + copy(kani::any(), &mut kani::any()); +} + +#[kani::proof] +#[kani::stub_verified(copy)] +fn copy_replace() { + let src = kani::any(); + let mut dst = kani::any(); + copy(src, &mut dst); + kani::assert(src == dst, "equality"); +} diff --git a/tests/expected/function-contract/assigns_fail.expected b/tests/expected/function-contract/modifies/simple_fail.expected similarity index 100% rename from tests/expected/function-contract/assigns_fail.expected rename to tests/expected/function-contract/modifies/simple_fail.expected diff --git a/tests/expected/function-contract/assigns_fail.rs b/tests/expected/function-contract/modifies/simple_fail.rs similarity index 100% rename from tests/expected/function-contract/assigns_fail.rs rename to tests/expected/function-contract/modifies/simple_fail.rs diff --git a/tests/expected/function-contract/assigns_pass.expected b/tests/expected/function-contract/modifies/simple_pass.expected similarity index 100% rename from tests/expected/function-contract/assigns_pass.expected rename to tests/expected/function-contract/modifies/simple_pass.expected diff --git a/tests/expected/function-contract/assigns_pass.rs b/tests/expected/function-contract/modifies/simple_pass.rs similarity index 100% rename from tests/expected/function-contract/assigns_pass.rs rename to tests/expected/function-contract/modifies/simple_pass.rs From 15a596b2471506a7082827ba7dc8374d3e25a62a Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 1 Dec 2023 13:45:44 -0800 Subject: [PATCH 37/72] Documentation, dead code removal and unique arguments in `modifies` --- cprover_bindings/src/goto_program/symbol.rs | 7 +- .../codegen_cprover_gotoc/codegen/function.rs | 26 +----- .../compiler_interface.rs | 17 ++++ kani-compiler/src/kani_middle/attributes.rs | 11 +++ kani-compiler/src/kani_middle/contracts.rs | 43 ---------- kani_metadata/src/harness.rs | 5 +- library/kani/src/lib.rs | 8 ++ library/kani_macros/src/sysroot/contracts.rs | 85 ++++++++++++++----- 8 files changed, 108 insertions(+), 94 deletions(-) delete mode 100644 kani-compiler/src/kani_middle/contracts.rs diff --git a/cprover_bindings/src/goto_program/symbol.rs b/cprover_bindings/src/goto_program/symbol.rs index f65eec7d2513..0e5eccf0cd5a 100644 --- a/cprover_bindings/src/goto_program/symbol.rs +++ b/cprover_bindings/src/goto_program/symbol.rs @@ -83,9 +83,9 @@ impl Lambda { } } -/// The CBMC representation of a function contract with three types of clauses. -/// See https://diffblue.github.io/cbmc/contracts-user.html for the meaning of -/// each type of clause. +/// The CBMC representation of a function contract. Represents +/// https://diffblue.github.io/cbmc/contracts-user.html but currently only assigns clauses are +/// supported. #[derive(Clone, Debug)] pub struct FunctionContract { pub(crate) assigns: Vec, @@ -386,6 +386,7 @@ impl Symbol { self } + /// Set `is_property` to true. pub fn with_is_property(mut self, v: bool) -> Self { self.is_property = v; self diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index fbc0977d497b..359f296433fb 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -223,29 +223,7 @@ impl<'tcx> GotocCtx<'tcx> { } /// Convert the Kani level contract into a CBMC level contract by creating a - /// lambda that calls the contract implementation function. - /// - /// For instance say we are processing a contract on `f` - /// - /// ```rs - /// as_goto_contract(..., GFnContract { requires: , .. }) - /// = FunctionContract { - /// requires: [ - /// Lambda { - /// arguments: , - /// body: Call(codegen_fn_expr(contract_impl_fn), [args of f..., return arg]) - /// } - /// ], - /// ... - /// } - /// ``` - /// - /// A spec lambda in GOTO receives as its first argument the return value of - /// the annotated function. However at the top level we must receive `self` - /// as first argument, because rust requires it. As a result the generated - /// lambda takes the return value as first argument and then immediately - /// calls the generated spec function, but passing the return value as the - /// last argument. + /// CBMC lambda. fn as_goto_contract(&mut self, assigns_contract: Vec) -> FunctionContract { use cbmc::goto_program::Lambda; @@ -274,7 +252,7 @@ impl<'tcx> GotocCtx<'tcx> { /// Convert the contract to a CBMC contract, then attach it to `instance`. /// `instance` must have previously been declared. /// - /// This does not overwrite prior contracts but merges with them. + /// This merges with any previously attached contracts. pub fn attach_contract(&mut self, instance: Instance<'tcx>, contract: Vec) { // This should be safe, since the contract is pretty much evaluated as // though it was the first (or last) assertion in the function. diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index ec59e04ead77..f38fff0df7d5 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -82,6 +82,8 @@ impl GotocCodegenBackend { } /// Generate code that is reachable from the given starting points. + /// + /// Invariant: iff `check_contract.is_some()` then `return.2.is_some()` fn codegen_items<'tcx>( &self, tcx: TyCtxt<'tcx>, @@ -194,6 +196,21 @@ impl GotocCodegenBackend { } impl<'tcx> GotocCtx<'tcx> { + /// Given the `proof_for_contract` target `function_under_contract` and the reachable `items`, + /// find or create the `AssignsContract` that needs to be enforced and attach it to the symbol + /// for which it needs to be enforced. + /// + /// 1. Gets the `#[kanitool::inner_check = "..."]` target, then resolves exactly one instance + /// of it. Panics there are more or less than one instance. + /// 2. Expects that a `#[kanitool::modifies(...)]` is placed on the `inner_check` function, + /// turns it into a CBMC contract and attaches it to the symbol for the previously resolved + /// instance. + /// 3. Returns the mangled of the symbol it attached the contract to. + /// 4. Resolves the `#[kanitool::checked_with = "..."]` target from `function_under_contract` + /// which has `static mut REENTRY : bool` declared inside. + /// 5. Returns the full path to this constant that `--nondet-static-exclude` expects which is + /// comprised of the file path that `checked_with` is located in, the name of the + /// `checked_with` function and the name of the constant (`REENTRY`). fn handle_check_contract( &mut self, function_under_contract: DefId, diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 1243b431caa9..509a1e291eca 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -227,6 +227,11 @@ impl<'tcx> KaniAttributes<'tcx> { self.eval_sibling_attribute(KaniAttributeKind::CheckedWith) } + /// Find the `mod` that `self.item` is defined in, then search in the items defined in this + /// `mod` for an item that is named after the `name` in the `#[kanitool:: = ""]` + /// annotation on `self.item`. + /// + /// This is similar to [`resolve_fn`] but more efficient since it only looks inside one `mod`. fn eval_sibling_attribute( &self, kind: KaniAttributeKind, @@ -550,6 +555,8 @@ impl<'tcx> KaniAttributes<'tcx> { Stub { original: original_str.to_string(), replacement } } + /// Parse and interpret the `kanitool::modifies(var1, var2, ...)` annotation into the vector + /// `[var1, var2, ...]`. pub fn modifies_contract(&self) -> Option> { let local_def_id = self.item.expect_local(); self.map.get(&KaniAttributeKind::Modifies).map(|attr| { @@ -565,12 +572,16 @@ impl<'tcx> KaniAttributes<'tcx> { } } +/// Pattern macro for the comma token used in attributes. macro_rules! comma_tok { () => { TokenTree::Token(Token { kind: TokenKind::Comma, .. }, _) }; } +/// Parse the a token stream inside an attribute (like `kanitool::modifies`) as a comma separated +/// sequence of function parameter names on `local_def_id` (must refer to a function). Then +/// translates the names into [`Local`]s. fn parse_modify_values<'a>( tcx: TyCtxt<'a>, local_def_id: LocalDefId, diff --git a/kani-compiler/src/kani_middle/contracts.rs b/kani-compiler/src/kani_middle/contracts.rs deleted file mode 100644 index 0e763f0c89c0..000000000000 --- a/kani-compiler/src/kani_middle/contracts.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright Kani Contributors -// SPDX-License-Identifier: Apache-2.0 OR MIT -//! Basic type definitions for function contracts. - -/// Generic representation for a function contract. This is so that we can reuse -/// this type for different resolution stages if the implementation functions -/// (`C`). -/// -/// Note that currently only the `assigns` clause is actually used, whereas -/// requires and ensures are handled by the frontend. We leave this struct here -/// since in theory a CBMC code gen for any clause has been implemented thus -/// this parallels the structure expected by CBMC. -#[derive(Default)] -pub struct GFnContract { - requires: Vec, - ensures: Vec, - assigns: Vec, - frees: Vec, -} - -impl GFnContract { - /// Read access to all precondition clauses. - pub fn requires(&self) -> &[C] { - &self.requires - } - - /// Read access to all postcondition clauses. - pub fn ensures(&self) -> &[C] { - &self.ensures - } - - pub fn assigns(&self) -> &[A] { - &self.assigns - } - - pub fn frees(&self) -> &[F] { - &self.frees - } - - pub fn new(requires: Vec, ensures: Vec, assigns: Vec, frees: Vec) -> Self { - Self { requires, ensures, assigns, frees } - } -} diff --git a/kani_metadata/src/harness.rs b/kani_metadata/src/harness.rs index 7336ee2e6cea..3dd6c82ebd39 100644 --- a/kani_metadata/src/harness.rs +++ b/kani_metadata/src/harness.rs @@ -5,9 +5,12 @@ use crate::CbmcSolver; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +/// A CBMC-level `assigns` contract that needs to be enforced on a function. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct AssignsContract { + /// The target of the contract pub contracted_function_name: String, + /// A static global variable used to track recursion that must not be havocked. pub recursion_tracker: String, } @@ -30,7 +33,7 @@ pub struct HarnessMetadata { pub goto_file: Option, /// The `#[kani::<>]` attributes added to a harness. pub attributes: HarnessAttributes, - /// + /// A CBMC-level assigns contract that should be enforced when running this harness. pub contract: Option, } diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 0b4dbeda8bc7..50578ae815bd 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -92,13 +92,21 @@ macro_rules! implies { }; } +/// Helper trait for code generation for `modifies` contracts. +/// +/// We allow the user to provide us with a pointer-like object that we convert as needed. #[doc(hidden)] pub trait Pointer<'a> { + /// Type of the pointed-to data type Inner; + /// Used for checking assigns contracts where we pass immutable references to the function. + /// /// We're using a reference to self here, because the user can use just a plain function /// argument, for instance one of type `&mut _`, in the `modifies` clause which would move it. unsafe fn decouple_lifetime(&self) -> &'a Self::Inner; + + /// used for havocking on replecement of a `modifies` clause. unsafe fn assignable(self) -> &'a mut Self::Inner; } diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 352d4bca0a6d..28ad4431efbc 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -384,6 +384,9 @@ struct ContractConditionsHandler<'a> { hash: Option, } +/// Which kind of contract attribute are we dealing with? +/// +/// Pre-parsing version of [`ContractConditionsData`]. #[derive(Copy, Clone, Eq, PartialEq)] enum ContractConditionsType { Requires, @@ -391,8 +394,9 @@ enum ContractConditionsType { Modifies, } -/// Information needed for generating check and replace handlers for different -/// contract attributes. +/// Clause-specific information mostly generated by parsing the attribute. +/// +/// [`ContractConditionsType`] is the corresponding pre-parse version. enum ContractConditionsData { Requires { /// The contents of the attribute. @@ -420,6 +424,24 @@ impl ContractConditionsData { let argument_names = rename_argument_occurrences(sig, &mut attr); ContractConditionsData::Ensures { argument_names, attr } } + + /// Constructs a [`Self::Modifies`] from the contents of the decorating attribute. + /// + /// Responsible for parsing the attribute. + fn new_modifies(attr: TokenStream, output: &mut TokenStream2) -> Self { + let attr = chunks_by(TokenStream2::from(attr), is_token_stream_2_comma) + .map(syn::parse2) + .filter_map(|expr| match expr { + Err(e) => { + output.extend(e.into_compile_error()); + None + } + Ok(expr) => Some(expr), + }) + .collect(); + + ContractConditionsData::Modifies { attr } + } } impl<'a> ContractConditionsHandler<'a> { @@ -445,18 +467,7 @@ impl<'a> ContractConditionsHandler<'a> { ContractConditionsType::Ensures => { ContractConditionsData::new_ensures(&annotated_fn.sig, syn::parse(attr)?) } - ContractConditionsType::Modifies => ContractConditionsData::Modifies { - attr: chunks_by(TokenStream2::from(attr), is_token_stream_2_comma) - .map(syn::parse2) - .filter_map(|expr| match expr { - Err(e) => { - output.extend(e.into_compile_error()); - None - } - Ok(expr) => Some(expr), - }) - .collect(), - }, + ContractConditionsType::Modifies => ContractConditionsData::new_modifies(attr, output), }; Ok(Self { function_state, condition_type, annotated_fn, attr_copy, output, hash }) @@ -504,20 +515,19 @@ impl<'a> ContractConditionsHandler<'a> { } ContractConditionsData::Modifies { attr } => { let wrapper_name = self.make_wrapper_name().to_string(); - let wrapper_args = make_wrapper_args(attr.len()); - if let Some(wrapper_call_args) = + let wrapper_args = if let Some(wrapper_call_args) = inner.iter_mut().find_map(|stmt| try_as_wrapper_call_args(stmt, &wrapper_name)) { + let wrapper_args = make_wrapper_args(wrapper_call_args.len(), attr.len()); wrapper_call_args .extend(wrapper_args.clone().map(|a| Expr::Verbatim(quote!(#a)))); + wrapper_args } else { unreachable!( "Invariant broken, check function did not contain a call to the wrapper function" ) - } - - let wrapper_args = make_wrapper_args(attr.len()); + }; quote!( #(let #wrapper_args = unsafe { kani::Pointer::decouple_lifetime(&#attr) };)* @@ -544,6 +554,7 @@ impl<'a> ContractConditionsHandler<'a> { } } + /// Get the sequence of statements of the previous check body or create the default one. fn ensure_bootstrapped_check_body(&self) -> Vec { let wrapper_name = self.make_wrapper_name(); let return_type = return_type_to_type(&self.annotated_fn.sig.output); @@ -716,7 +727,7 @@ impl<'a> ContractConditionsHandler<'a> { /// each expression in the clause. fn emit_augmented_modifies_wrapper(&mut self) { if let ContractConditionsData::Modifies { attr } = &self.condition_type { - let wrapper_args = make_wrapper_args(attr.len()); + let wrapper_args = make_wrapper_args(self.annotated_fn.sig.inputs.len(), attr.len()); let sig = &mut self.annotated_fn.sig; for arg in wrapper_args.clone() { let lifetime = syn::Lifetime { apostrophe: Span::call_site(), ident: arg.clone() }; @@ -789,14 +800,31 @@ macro_rules! try_as_result_assign_pat { }; } +/// Try to parse this statement as `let result : <...> = ;` and return `init`. +/// +/// This is the shape of statement we create in replace functions to havoc (with `init` being +/// `kani::any()`) and we need to recognize it for when we edit the replace function and integrate +/// additional conditions. +/// +/// It's a thin wrapper around [`try_as_result_assign_pat!`] to create an immutable match. fn try_as_result_assign(stmt: &syn::Stmt) -> Option<&syn::LocalInit> { try_as_result_assign_pat!(stmt, as_ref) } +/// Try to parse this statement as `let result : <...> = ;` and return a mutable reference to +/// `init`. +/// +/// This is the shape of statement we create in check functions (with `init` being a call to check +/// function with additional pointer arguments for the `modifies` clause) and we need to recognize +/// it to then edit this call if we find another `modifies` clause and add its additional arguments. +/// additional conditions. +/// +/// It's a thin wrapper around [`try_as_result_assign_pat!`] to create a mutable match. fn try_as_result_assign_mut(stmt: &mut syn::Stmt) -> Option<&mut syn::LocalInit> { try_as_result_assign_pat!(stmt, as_mut) } +/// Is this statement `let result : <...> = kani::any();`. fn is_replace_return_havoc(stmt: &syn::Stmt) -> bool { let Some(syn::LocalInit { diverge: None, expr: e, .. }) = try_as_result_assign(stmt) else { return false; @@ -825,6 +853,9 @@ fn is_replace_return_havoc(stmt: &syn::Stmt) -> bool { ) } +/// For each argument create an expression that passes this argument along unmodified. +/// +/// Reconstructs structs that may have been deconstructed with patterns. fn exprs_for_args<'a, T>( args: &'a syn::punctuated::Punctuated, ) -> impl Iterator + Clone + 'a { @@ -834,6 +865,9 @@ fn exprs_for_args<'a, T>( }) } +/// Create an expression that reconstructs a struct that was matched in a pattern. +/// +/// Does not support enums, wildcards, pattern alternatives (`|`), range patterns, or verbatim. fn pat_to_expr(pat: &syn::Pat) -> Expr { use syn::Pat; let mk_err = |typ| { @@ -898,11 +932,14 @@ fn pat_to_expr(pat: &syn::Pat) -> Expr { }) } Pat::Verbatim(_) => mk_err("verbatim"), - Pat::Type(_) => mk_err("type"), + Pat::Type(pt) => pat_to_expr(pt.pat.as_ref()), Pat::TupleStruct(_) => mk_err("tuple struct"), _ => mk_err("unknown"), } } + +/// Try to interpret this statement as `let result : <...> = (args ...);` and +/// return a mutable reference to the parameter list. fn try_as_wrapper_call_args<'a>( stmt: &'a mut syn::Stmt, wrapper_fn_name: &str, @@ -925,8 +962,10 @@ fn try_as_wrapper_call_args<'a>( } } -fn make_wrapper_args(num: usize) -> impl Iterator + Clone { - (0..num).map(|i| Ident::new(&format!("_wrapper_arg_{i}"), Span::mixed_site())) +/// Make `num` [`Ident`]s with the names `_wrapper_arg_{i}` with `i` starting at `low` and +/// increasing by one each time. +fn make_wrapper_args(low: usize, num: usize) -> impl Iterator + Clone { + (low..).map(|i| Ident::new(&format!("_wrapper_arg_{i}"), Span::mixed_site())).take(num) } /// If an explicit return type was provided it is returned, otherwise `()`. From af780f1d344a510ce36d07bf614919f0b17e7126 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 1 Dec 2023 13:55:02 -0800 Subject: [PATCH 38/72] Test case for unique generated argument names --- .../modifies/unique_arguments.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/expected/function-contract/modifies/unique_arguments.rs diff --git a/tests/expected/function-contract/modifies/unique_arguments.rs b/tests/expected/function-contract/modifies/unique_arguments.rs new file mode 100644 index 000000000000..a0f034cd12c8 --- /dev/null +++ b/tests/expected/function-contract/modifies/unique_arguments.rs @@ -0,0 +1,31 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +#[kani::modifies(a)] +#[kani::modifies(b)] +#[kani::ensures(*a == 1)] +#[kani::ensures(*b == 2)] +fn two_pointers(a: &mut u32, b: &mut u32) { + *a = 1; + *b = 2; +} + + +#[kani::proof_for_contract(two_pointers)] +fn test_contract() { + two_pointers(&mut kani::any(), &mut kani::any()); +} + + +#[kani::proof] +#[kani::stub_verified(two_pointers)] +fn test_stubbing() { + let mut a = kani::any(); + let mut b = kani::any(); + + two_pointers(&mut a, &mut b); + + kani::assert(a == 1, "a is 1"); + kani::assert(b == 2, "b is 2"); +} From e76d0b3a6690d59cde1fb4107d5041093916284a Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 1 Dec 2023 13:58:48 -0800 Subject: [PATCH 39/72] Added missing `expected` files --- .../modifies/expr_replace_pass.expected | 5 +++++ .../modifies/field_replace_fail.expected | 7 +++++++ .../modifies/field_replace_pass.expected | 9 +++++++++ .../function-contract/modifies/unique_arguemnts.expected | 9 +++++++++ 4 files changed, 30 insertions(+) create mode 100644 tests/expected/function-contract/modifies/expr_replace_pass.expected create mode 100644 tests/expected/function-contract/modifies/field_replace_fail.expected create mode 100644 tests/expected/function-contract/modifies/field_replace_pass.expected create mode 100644 tests/expected/function-contract/modifies/unique_arguemnts.expected diff --git a/tests/expected/function-contract/modifies/expr_replace_pass.expected b/tests/expected/function-contract/modifies/expr_replace_pass.expected new file mode 100644 index 000000000000..405875334763 --- /dev/null +++ b/tests/expected/function-contract/modifies/expr_replace_pass.expected @@ -0,0 +1,5 @@ +main.assertion\ +- Status: SUCCESS\ +- Description: "Increment"\ + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/field_replace_fail.expected b/tests/expected/function-contract/modifies/field_replace_fail.expected new file mode 100644 index 000000000000..91643c68b3c5 --- /dev/null +++ b/tests/expected/function-contract/modifies/field_replace_fail.expected @@ -0,0 +1,7 @@ +main.assertion\ +- Status: FAILURE\ +- Description: "Increment havocked" + +Failed Checks: Increment havocked + +VERIFICATION:- FAILED diff --git a/tests/expected/function-contract/modifies/field_replace_pass.expected b/tests/expected/function-contract/modifies/field_replace_pass.expected new file mode 100644 index 000000000000..64dedf3f511a --- /dev/null +++ b/tests/expected/function-contract/modifies/field_replace_pass.expected @@ -0,0 +1,9 @@ +main.assertion\ +- Status: SUCCESS\ +- Description: "Increment"\ + +main.assertion\ +- Status: SUCCESS\ +- Description: "Unchanged" + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/unique_arguemnts.expected b/tests/expected/function-contract/modifies/unique_arguemnts.expected new file mode 100644 index 000000000000..d84097d648e9 --- /dev/null +++ b/tests/expected/function-contract/modifies/unique_arguemnts.expected @@ -0,0 +1,9 @@ +test_stubbing.assertion\ +- Status: SUCCESS\ +- Description: "a is 1" + +test_stubbing.assertion\ +- Status: SUCCESS\ +- Description: "b is 2" + +VERIFICATION:- SUCCESSFUL From 907a7f95e851879c00dee61e17e5455216ed250f Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 1 Dec 2023 14:03:25 -0800 Subject: [PATCH 40/72] Another missing `expected` file --- tests/expected/function-contract/modifies/simple_fail.expected | 2 +- tests/expected/function-contract/modifies/simple_pass.expected | 2 +- .../{stmt_expr_assigns.rs => modifies/stmt_expr.rs} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename tests/expected/function-contract/{stmt_expr_assigns.rs => modifies/stmt_expr.rs} (100%) diff --git a/tests/expected/function-contract/modifies/simple_fail.expected b/tests/expected/function-contract/modifies/simple_fail.expected index fd8d816451cd..ffaee2293931 100644 --- a/tests/expected/function-contract/modifies/simple_fail.expected +++ b/tests/expected/function-contract/modifies/simple_fail.expected @@ -4,4 +4,4 @@ assigns\ Failed Checks: Check that *ptr is assignable -VERIFICATION:- FAILED \ No newline at end of file +VERIFICATION:- FAILED diff --git a/tests/expected/function-contract/modifies/simple_pass.expected b/tests/expected/function-contract/modifies/simple_pass.expected index 880f00714b32..34c886c358cb 100644 --- a/tests/expected/function-contract/modifies/simple_pass.expected +++ b/tests/expected/function-contract/modifies/simple_pass.expected @@ -1 +1 @@ -VERIFICATION:- SUCCESSFUL \ No newline at end of file +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/stmt_expr_assigns.rs b/tests/expected/function-contract/modifies/stmt_expr.rs similarity index 100% rename from tests/expected/function-contract/stmt_expr_assigns.rs rename to tests/expected/function-contract/modifies/stmt_expr.rs From 1c715088ddf6c923cc0c51297897a217425fbd1a Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 1 Dec 2023 14:06:48 -0800 Subject: [PATCH 41/72] Revert some clippy changes --- kani-driver/src/args/mod.rs | 9 +++++---- kani-driver/src/args_toml.rs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/kani-driver/src/args/mod.rs b/kani-driver/src/args/mod.rs index 1254fbf7ad1c..6651f72db7c2 100644 --- a/kani-driver/src/args/mod.rs +++ b/kani-driver/src/args/mod.rs @@ -725,7 +725,7 @@ mod tests { #[test] fn check_multiple_harnesses() { let args = - StandaloneArgs::try_parse_from("kani input.rs --harness a --harness b".split(' ')) + StandaloneArgs::try_parse_from("kani input.rs --harness a --harness b".split(" ")) .unwrap(); assert_eq!(args.verify_opts.harnesses, vec!["a".to_owned(), "b".to_owned()]); } @@ -733,7 +733,7 @@ mod tests { #[test] fn check_multiple_harnesses_without_flag_fail() { let result = StandaloneArgs::try_parse_from( - "kani input.rs --harness harness_1 harness_2".split(' '), + "kani input.rs --harness harness_1 harness_2".split(" "), ); assert!(result.is_err()); assert_eq!(result.unwrap_err().kind(), ErrorKind::UnknownArgument); @@ -784,7 +784,8 @@ mod tests { fn check_dry_run_fails() { // We don't support --dry-run anymore but we print a friendly reminder for now. let args = vec!["kani", "file.rs", "--dry-run"]; - let err = StandaloneArgs::try_parse_from(args).unwrap().verify_opts.validate().unwrap_err(); + let err = + StandaloneArgs::try_parse_from(&args).unwrap().verify_opts.validate().unwrap_err(); assert_eq!(err.kind(), ErrorKind::ValueValidation); } @@ -792,7 +793,7 @@ mod tests { #[test] fn check_invalid_input_fails() { let args = vec!["kani", "."]; - let err = StandaloneArgs::try_parse_from(args).unwrap().validate().unwrap_err(); + let err = StandaloneArgs::try_parse_from(&args).unwrap().validate().unwrap_err(); assert_eq!(err.kind(), ErrorKind::InvalidValue); } diff --git a/kani-driver/src/args_toml.rs b/kani-driver/src/args_toml.rs index f467d4a25cb3..aaa5b3260083 100644 --- a/kani-driver/src/args_toml.rs +++ b/kani-driver/src/args_toml.rs @@ -312,6 +312,6 @@ mod tests { #[test] fn check_unstable_entry_invalid() { let name = String::from("feature"); - assert!(unstable_entry(&name, &Value::String("".to_string())).is_err()); + assert!(matches!(unstable_entry(&name, &Value::String("".to_string())), Err(_))); } } From e4f3cfd837a3f460b2f99b74a1bf131ccf387e85 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 1 Dec 2023 14:07:51 -0800 Subject: [PATCH 42/72] Remove not strictly necessary dep --- library/kani_macros/Cargo.toml | 2 +- library/kani_macros/src/sysroot/contracts.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/kani_macros/Cargo.toml b/library/kani_macros/Cargo.toml index 0928f564bb88..dc36a6d16284 100644 --- a/library/kani_macros/Cargo.toml +++ b/library/kani_macros/Cargo.toml @@ -15,4 +15,4 @@ proc-macro = true proc-macro2 = "1.0" proc-macro-error = "1.0.4" quote = "1.0.20" -syn = { version = "2.0.18", features = ["full", "visit-mut", "visit", "extra-traits"] } +syn = { version = "2.0.18", features = ["full", "visit-mut", "visit"] } diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 28ad4431efbc..36ead85cdff5 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -608,7 +608,7 @@ impl<'a> ContractConditionsHandler<'a> { .enumerate() .find_map(|(i, elem)| is_replace_return_havoc(elem).then_some(i)) .unwrap_or_else(|| { - panic!("ICE: Could not find result let binding in statement sequence {stmts:?}") + panic!("ICE: Could not find result let binding in statement sequence") }); // We want the result assign statement to end up as the last statement in the first // vector, hence the `+1`. From 9f3d102436cda871215d61a9098ca70e3467b0dd Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 1 Dec 2023 14:31:07 -0800 Subject: [PATCH 43/72] Some basic write-sets documentation --- library/kani/src/contracts.rs | 42 ++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/library/kani/src/contracts.rs b/library/kani/src/contracts.rs index a211a8ba905c..c893c386ee5d 100644 --- a/library/kani/src/contracts.rs +++ b/library/kani/src/contracts.rs @@ -118,14 +118,22 @@ //! //! ## Specification Attributes Overview //! -//! There are currently two specification attributes available for describing -//! function behavior: [`requires`][macro@requires] for preconditions and +//! The basic two two specification attributes available for describing +//! function behavior are [`requires`][macro@requires] for preconditions and //! [`ensures`][macro@ensures] for postconditions. Both admit arbitrary Rust //! expressions as their bodies which may also reference the function arguments //! but must not mutate memory or perform I/O. The postcondition may //! additionally reference the return value of the function as the variable //! `result`. //! +//! In addition Kani provides the [`modifies`](macro@modifies) attribute. This +//! works a bit different int aht it does not contain conditions but a comma +//! separated sequence of expressions that evaluate to pointers. This attribute +//! constrains to which memory locations the function is allowed to write. Each +//! expression can contain arbitrary Rust syntax, though it may not perform side +//! effects and it is also currently unsound if the expression can panic. For more +//! information see the [write sets](#write-sets) section. +//! //! During verified stubbing the return value of a function with a contract is //! replaced by a call to `kani::any`. As such the return value must implement //! the `kani::Arbitrary` trait. @@ -189,4 +197,32 @@ //! If you feel strongly about this issue you can join the discussion on issue //! [#2823](https://github.com/model-checking/kani/issues/2823) to enable //! opt-out of inductive verification. -pub use super::{ensures, proof_for_contract, requires, stub_verified}; +//! +//! ## Write Sets +//! +//! The [`modifies`](macro@modifies) attribute is used to describe which +//! locations in memory a function may assign to. The attribute contains a comma +//! separated series of expressions that reference the function arguments. +//! Syntactically any expression id permissible, though it may not perform side +//! effects (I/O, mutation) or panic. As an example consider this super simple +//! function: +//! +//! ``` +//! #[kani::modifies(ptr, my_box.as_ref())] +//! fn a_function(ptr: &mut u32, my_box: &mut Box) { +//! *ptr = 80; +//! *my_box.as_mut() = 90; +//! } +//! ``` +//! +//! Because the function performs an observable side-effect (setting both the +//! value behind the pointer and the value pointed-to by the box) we need to +//! provide a `modifies` attribute. Otherwise Kani will reject a contract on +//! this function. +//! +//! An expression used in a `modifies` clause must return a pointer to the +//! location that you would like to allow to be modified. This can be any basic +//! Rust pointer type (`&T`, `&mut T`, `*const T` or `*mut T`). In addition `T` +//! must implement [`Arbitrary`](super::Arbitrary). This is used to assign +//! `kani::any()` to the location when the function is used in a `stub_verified`. +pub use super::{ensures, proof_for_contract, requires, stub_verified, modifies}; From 0573d09e197d92069433459dea27d1e5d5175a3c Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 1 Dec 2023 14:33:46 -0800 Subject: [PATCH 44/72] Missing formatting --- library/kani/src/contracts.rs | 2 +- tests/expected/function-contract/modifies/unique_arguments.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/library/kani/src/contracts.rs b/library/kani/src/contracts.rs index c893c386ee5d..90fda5ac90b5 100644 --- a/library/kani/src/contracts.rs +++ b/library/kani/src/contracts.rs @@ -225,4 +225,4 @@ //! Rust pointer type (`&T`, `&mut T`, `*const T` or `*mut T`). In addition `T` //! must implement [`Arbitrary`](super::Arbitrary). This is used to assign //! `kani::any()` to the location when the function is used in a `stub_verified`. -pub use super::{ensures, proof_for_contract, requires, stub_verified, modifies}; +pub use super::{ensures, modifies, proof_for_contract, requires, stub_verified}; diff --git a/tests/expected/function-contract/modifies/unique_arguments.rs b/tests/expected/function-contract/modifies/unique_arguments.rs index a0f034cd12c8..396ba4c5b036 100644 --- a/tests/expected/function-contract/modifies/unique_arguments.rs +++ b/tests/expected/function-contract/modifies/unique_arguments.rs @@ -11,13 +11,11 @@ fn two_pointers(a: &mut u32, b: &mut u32) { *b = 2; } - #[kani::proof_for_contract(two_pointers)] fn test_contract() { two_pointers(&mut kani::any(), &mut kani::any()); } - #[kani::proof] #[kani::stub_verified(two_pointers)] fn test_stubbing() { From 384c2f6564c0cdbf3fa42599f6b84ed6b134ab40 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 1 Dec 2023 14:39:13 -0800 Subject: [PATCH 45/72] Forgot to commit this one --- tests/expected/function-contract/modifies/stmt_expr.expected | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/expected/function-contract/modifies/stmt_expr.expected diff --git a/tests/expected/function-contract/modifies/stmt_expr.expected b/tests/expected/function-contract/modifies/stmt_expr.expected new file mode 100644 index 000000000000..34c886c358cb --- /dev/null +++ b/tests/expected/function-contract/modifies/stmt_expr.expected @@ -0,0 +1 @@ +VERIFICATION:- SUCCESSFUL From f7578a8034942996308d2d719d8ccba64c34e802 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 1 Dec 2023 14:52:50 -0800 Subject: [PATCH 46/72] Typo --- .../{unique_arguemnts.expected => unique_arguments.expected} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/expected/function-contract/modifies/{unique_arguemnts.expected => unique_arguments.expected} (100%) diff --git a/tests/expected/function-contract/modifies/unique_arguemnts.expected b/tests/expected/function-contract/modifies/unique_arguments.expected similarity index 100% rename from tests/expected/function-contract/modifies/unique_arguemnts.expected rename to tests/expected/function-contract/modifies/unique_arguments.expected From 1fffd53e63de978a8b055ea07e8e41e40ba67638 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Mon, 4 Dec 2023 13:59:17 -0800 Subject: [PATCH 47/72] Tinkering with the vector test case. --- .../{.vec_pass.fixme-2909 => vec_pass.fixme-2909.rs} | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) rename tests/expected/function-contract/modifies/{.vec_pass.fixme-2909 => vec_pass.fixme-2909.rs} (80%) diff --git a/tests/expected/function-contract/modifies/.vec_pass.fixme-2909 b/tests/expected/function-contract/modifies/vec_pass.fixme-2909.rs similarity index 80% rename from tests/expected/function-contract/modifies/.vec_pass.fixme-2909 rename to tests/expected/function-contract/modifies/vec_pass.fixme-2909.rs index ec39c1d698c2..f2fc4fac3bed 100644 --- a/tests/expected/function-contract/modifies/.vec_pass.fixme-2909 +++ b/tests/expected/function-contract/modifies/vec_pass.fixme-2909.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Zfunction-contracts -#[kani::requires(v.len() > 1)] +#[kani::requires(v.len() > 0)] #[kani::modifies(&v[0])] #[kani::ensures(v[0] == src)] fn modify(v: &mut Vec, src: u32) { @@ -24,9 +24,10 @@ fn main() { #[kani::proof] #[kani::stub_verified(modify)] fn modify_replace() { - let v_len = kani::any_where(|i| *i < 4); - let mut v: Vec = vec![kani::any()]; - let mut compare = vec![]; + let v_len : usize = kani::any_where(|i| *i < 4); + let mut v: Vec = Vec::with_capacity(v_len + 1); + v.push(kani::any()); + let mut compare = Vec::with_capacity(v_len); for _ in 0..v_len { let elem = kani::any(); v.push(elem); From 34e3a1b5760b215d5a7f1b4aaafb24706bf9f144 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 14 Dec 2023 13:49:33 -0500 Subject: [PATCH 48/72] Addressing code review --- .../codegen_cprover_gotoc/codegen/function.rs | 28 +------------------ kani-compiler/src/kani_middle/attributes.rs | 9 ++++++ .../function-contract/modifies/expr_pass.rs | 3 ++ .../modifies/expr_replace_fail.rs | 4 +++ .../modifies/expr_replace_pass.rs | 2 +- .../function-contract/modifies/havoc_pass.rs | 1 - .../modifies/havoc_pass_reordered.rs | 2 +- .../function-contract/modifies/simple_fail.rs | 1 - .../function-contract/modifies/simple_pass.rs | 1 - .../modifies/vec_pass.fixme-2909.rs | 26 +++++------------ 10 files changed, 26 insertions(+), 51 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index 359f296433fb..8eded8b15ea4 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -260,33 +260,7 @@ impl<'tcx> GotocCtx<'tcx> { let goto_contract = self.as_goto_contract(contract); let name = self.current_fn().name(); - // CBMC has two ways of attaching the contract and it seems the - // difference is whether dfcc is used or not. With dfcc it's stored in - // `contract::`, otherwise directly on the type of the - // function. - // - // Actually the issue sees to haver been something else and ataching to - // the symbol directly seems ot also work if dfcc is used. - let create_separate_contract_sym = false; - - let contract_target_name = if create_separate_contract_sym { - let contract_sym_name = format!("contract::{}", name); - self.ensure(&contract_sym_name, |ctx, fname| { - Symbol::function( - fname, - ctx.fn_typ(), - None, - format!("contract::{}", ctx.current_fn().readable_name()), - ctx.codegen_span(&ctx.current_fn().mir().span), - ) - .with_is_property(true) - }); - contract_sym_name - } else { - name - }; - - self.symbol_table.attach_contract(contract_target_name, goto_contract); + self.symbol_table.attach_contract(name, goto_contract); self.reset_current_fn() } diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 509a1e291eca..d82c7760360f 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -58,7 +58,16 @@ enum KaniAttributeKind { /// Attribute on a function that was auto-generated from expanding a /// function contract. IsContractGenerated, + /// Identifies a set of pointer arguments that should be added to the write + /// set when checking a function contract. Placed on the inner check function. + /// + /// Emitted by the expansion of a `modifies` function contract clause. Modifies, + /// A function used as the inner code of a contract check. + /// + /// Contains the original body of the contracted function. The signature is + /// expanded with additional pointer arguments that are not used in the function + /// but referenced by the `modifies` annotation. InnerCheck, } diff --git a/tests/expected/function-contract/modifies/expr_pass.rs b/tests/expected/function-contract/modifies/expr_pass.rs index 18fa5ff76897..d76e20126c45 100644 --- a/tests/expected/function-contract/modifies/expr_pass.rs +++ b/tests/expected/function-contract/modifies/expr_pass.rs @@ -2,6 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Zfunction-contracts +// Test that a modifies clause works when a (function call) +// expression is provided + #[kani::requires(**ptr < 100)] #[kani::modifies(ptr.as_ref())] fn modify(ptr: &mut Box) { diff --git a/tests/expected/function-contract/modifies/expr_replace_fail.rs b/tests/expected/function-contract/modifies/expr_replace_fail.rs index 63ac2d8a8027..b5677a817243 100644 --- a/tests/expected/function-contract/modifies/expr_replace_fail.rs +++ b/tests/expected/function-contract/modifies/expr_replace_fail.rs @@ -2,6 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Zfunction-contracts +// Tests that providing the "modifies" clause havocks the pointer such +// that the increment can no longer be observed (in the absence of an +// "ensures" clause) + #[kani::requires(**ptr < 100)] #[kani::modifies(ptr.as_ref())] fn modify(ptr: &mut Box) { diff --git a/tests/expected/function-contract/modifies/expr_replace_pass.rs b/tests/expected/function-contract/modifies/expr_replace_pass.rs index 187b97c3cefb..8be1ef2cbaee 100644 --- a/tests/expected/function-contract/modifies/expr_replace_pass.rs +++ b/tests/expected/function-contract/modifies/expr_replace_pass.rs @@ -4,7 +4,7 @@ #[kani::requires(**ptr < 100)] #[kani::modifies(ptr.as_ref())] -#[kani::ensures(*ptr.as_ref() == prior + 1)] +#[kani::ensures(**ptr == prior + 1)] fn modify(ptr: &mut Box, prior: u32) { *ptr.as_mut() += 1; } diff --git a/tests/expected/function-contract/modifies/havoc_pass.rs b/tests/expected/function-contract/modifies/havoc_pass.rs index 13f3b1d057c9..aa5bcada1a26 100644 --- a/tests/expected/function-contract/modifies/havoc_pass.rs +++ b/tests/expected/function-contract/modifies/havoc_pass.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Zfunction-contracts -// These two are reordered in comparison to `havoc_pass` and we expect the test case to pass still #[kani::modifies(dst)] #[kani::ensures(*dst == src)] fn copy(src: u32, dst: &mut u32) { diff --git a/tests/expected/function-contract/modifies/havoc_pass_reordered.rs b/tests/expected/function-contract/modifies/havoc_pass_reordered.rs index 13f3b1d057c9..dc5f370179e5 100644 --- a/tests/expected/function-contract/modifies/havoc_pass_reordered.rs +++ b/tests/expected/function-contract/modifies/havoc_pass_reordered.rs @@ -3,8 +3,8 @@ // kani-flags: -Zfunction-contracts // These two are reordered in comparison to `havoc_pass` and we expect the test case to pass still -#[kani::modifies(dst)] #[kani::ensures(*dst == src)] +#[kani::modifies(dst)] fn copy(src: u32, dst: &mut u32) { *dst = src; } diff --git a/tests/expected/function-contract/modifies/simple_fail.rs b/tests/expected/function-contract/modifies/simple_fail.rs index 26517526038b..8d1b16e9a976 100644 --- a/tests/expected/function-contract/modifies/simple_fail.rs +++ b/tests/expected/function-contract/modifies/simple_fail.rs @@ -9,7 +9,6 @@ fn modify(ptr: &mut u32) { #[kani::proof_for_contract(modify)] fn main() { - let _ = Box::new(()); let mut i = kani::any(); modify(&mut i); } diff --git a/tests/expected/function-contract/modifies/simple_pass.rs b/tests/expected/function-contract/modifies/simple_pass.rs index f76f1a6a6c22..3c90ef8c789f 100644 --- a/tests/expected/function-contract/modifies/simple_pass.rs +++ b/tests/expected/function-contract/modifies/simple_pass.rs @@ -10,7 +10,6 @@ fn modify(ptr: &mut u32) { #[kani::proof_for_contract(modify)] fn main() { - let _ = Box::new(()); let mut i = kani::any(); modify(&mut i); } diff --git a/tests/expected/function-contract/modifies/vec_pass.fixme-2909.rs b/tests/expected/function-contract/modifies/vec_pass.fixme-2909.rs index f2fc4fac3bed..b904dcea6d72 100644 --- a/tests/expected/function-contract/modifies/vec_pass.fixme-2909.rs +++ b/tests/expected/function-contract/modifies/vec_pass.fixme-2909.rs @@ -9,8 +9,8 @@ fn modify(v: &mut Vec, src: u32) { v[0] = src } -#[kani::unwind(10)] -#[kani::proof_for_contract(modify)] +//#[kani::unwind(10)] +//#[kani::proof_for_contract(modify)] fn main() { let v_len = kani::any_where(|i| *i < 4); let mut v: Vec = vec![kani::any()]; @@ -24,23 +24,11 @@ fn main() { #[kani::proof] #[kani::stub_verified(modify)] fn modify_replace() { - let v_len : usize = kani::any_where(|i| *i < 4); - let mut v: Vec = Vec::with_capacity(v_len + 1); - v.push(kani::any()); - let mut compare = Vec::with_capacity(v_len); - for _ in 0..v_len { - let elem = kani::any(); - v.push(elem); - compare.push(elem); - } + let v_len = kani::any_where(|i| *i < 4 && *i > 0); + let mut v: Vec = vec![kani::any(); v_len].to_vec(); + let compare = v[1..].to_vec(); let src = kani::any(); modify(&mut v, src); - kani::assert( - v[0] == src, - "element set" - ); - kani::assert( - v[1..] == compare[..], - "vector tail equality" - ); + kani::assert(v[0] == src, "element set"); + kani::assert(compare == v[1..v_len], "vector tail equality"); } From f01b619716b044659bbce2193476bcaf1a937ec0 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 14 Dec 2023 13:55:34 -0500 Subject: [PATCH 49/72] Apply suggestions from code review Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com> --- kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs | 4 ++-- library/kani/src/contracts.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index f38fff0df7d5..944a8586e73c 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -201,11 +201,11 @@ impl<'tcx> GotocCtx<'tcx> { /// for which it needs to be enforced. /// /// 1. Gets the `#[kanitool::inner_check = "..."]` target, then resolves exactly one instance - /// of it. Panics there are more or less than one instance. + /// of it. Panics if there are more or less than one instance. /// 2. Expects that a `#[kanitool::modifies(...)]` is placed on the `inner_check` function, /// turns it into a CBMC contract and attaches it to the symbol for the previously resolved /// instance. - /// 3. Returns the mangled of the symbol it attached the contract to. + /// 3. Returns the mangled name of the symbol it attached the contract to. /// 4. Resolves the `#[kanitool::checked_with = "..."]` target from `function_under_contract` /// which has `static mut REENTRY : bool` declared inside. /// 5. Returns the full path to this constant that `--nondet-static-exclude` expects which is diff --git a/library/kani/src/contracts.rs b/library/kani/src/contracts.rs index 90fda5ac90b5..07e1b4cfaa19 100644 --- a/library/kani/src/contracts.rs +++ b/library/kani/src/contracts.rs @@ -127,7 +127,7 @@ //! `result`. //! //! In addition Kani provides the [`modifies`](macro@modifies) attribute. This -//! works a bit different int aht it does not contain conditions but a comma +//! works a bit different in that it does not contain conditions but a comma //! separated sequence of expressions that evaluate to pointers. This attribute //! constrains to which memory locations the function is allowed to write. Each //! expression can contain arbitrary Rust syntax, though it may not perform side @@ -203,7 +203,7 @@ //! The [`modifies`](macro@modifies) attribute is used to describe which //! locations in memory a function may assign to. The attribute contains a comma //! separated series of expressions that reference the function arguments. -//! Syntactically any expression id permissible, though it may not perform side +//! Syntactically any expression is permissible, though it may not perform side //! effects (I/O, mutation) or panic. As an example consider this super simple //! function: //! From f283759fc32e310637319e70fe26adc8960aa983 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 14 Dec 2023 13:56:09 -0500 Subject: [PATCH 50/72] Addressing code review leftovers --- cprover_bindings/src/goto_program/symbol.rs | 2 +- kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs | 1 + .../modifies/{vec_pass.fixme-2909.rs => vec_pass.rs} | 0 3 files changed, 2 insertions(+), 1 deletion(-) rename tests/expected/function-contract/modifies/{vec_pass.fixme-2909.rs => vec_pass.rs} (100%) diff --git a/cprover_bindings/src/goto_program/symbol.rs b/cprover_bindings/src/goto_program/symbol.rs index 0e5eccf0cd5a..b1082a8f1f80 100644 --- a/cprover_bindings/src/goto_program/symbol.rs +++ b/cprover_bindings/src/goto_program/symbol.rs @@ -386,7 +386,7 @@ impl Symbol { self } - /// Set `is_property` to true. + /// Set `is_property`. pub fn with_is_property(mut self, v: bool) -> Self { self.is_property = v; self diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index 8eded8b15ea4..94dae51cf0e2 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -256,6 +256,7 @@ impl<'tcx> GotocCtx<'tcx> { pub fn attach_contract(&mut self, instance: Instance<'tcx>, contract: Vec) { // This should be safe, since the contract is pretty much evaluated as // though it was the first (or last) assertion in the function. + assert!(self.current_fn.is_none()); self.set_current_fn(instance); let goto_contract = self.as_goto_contract(contract); let name = self.current_fn().name(); diff --git a/tests/expected/function-contract/modifies/vec_pass.fixme-2909.rs b/tests/expected/function-contract/modifies/vec_pass.rs similarity index 100% rename from tests/expected/function-contract/modifies/vec_pass.fixme-2909.rs rename to tests/expected/function-contract/modifies/vec_pass.rs From 00bfe6ede316dc992c53fe5750702d6028870e1a Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 14 Dec 2023 18:01:34 -0500 Subject: [PATCH 51/72] Added missing expected file --- .../modifies/vec_pass.expected | 20 +++++++++++++++++++ .../function-contract/modifies/vec_pass.rs | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 tests/expected/function-contract/modifies/vec_pass.expected diff --git a/tests/expected/function-contract/modifies/vec_pass.expected b/tests/expected/function-contract/modifies/vec_pass.expected new file mode 100644 index 000000000000..7ba6fc1af589 --- /dev/null +++ b/tests/expected/function-contract/modifies/vec_pass.expected @@ -0,0 +1,20 @@ +modify.assertion\ +- Status: SUCCESS\ +- Description: "v.len() > 0"\ +in function modify + +modify_replace.assertion\ +- Status: SUCCESS\ +- Description: "element set"\ +in function modify_replace + +modify_replace.assertion\ +- Status: SUCCESS\ +- Description: "vector tail equality"\ +in function modify_replace + +assertion\ +- Status: SUCCESS\ +- Description: "v [0] == src" + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/vec_pass.rs b/tests/expected/function-contract/modifies/vec_pass.rs index b904dcea6d72..1e40a2a08eb7 100644 --- a/tests/expected/function-contract/modifies/vec_pass.rs +++ b/tests/expected/function-contract/modifies/vec_pass.rs @@ -9,8 +9,8 @@ fn modify(v: &mut Vec, src: u32) { v[0] = src } -//#[kani::unwind(10)] -//#[kani::proof_for_contract(modify)] +#[kani::unwind(10)] +#[kani::proof_for_contract(modify)] fn main() { let v_len = kani::any_where(|i| *i < 4); let mut v: Vec = vec![kani::any()]; From 9c6b876142c9aec43553f27b1bc52eba6cce0785 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 14 Dec 2023 20:20:36 -0500 Subject: [PATCH 52/72] Fixed the expected file --- tests/expected/function-contract/modifies/vec_pass.expected | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/expected/function-contract/modifies/vec_pass.expected b/tests/expected/function-contract/modifies/vec_pass.expected index 7ba6fc1af589..d31486f2dcc6 100644 --- a/tests/expected/function-contract/modifies/vec_pass.expected +++ b/tests/expected/function-contract/modifies/vec_pass.expected @@ -15,6 +15,6 @@ in function modify_replace assertion\ - Status: SUCCESS\ -- Description: "v [0] == src" +- Description: "v[0] == src" VERIFICATION:- SUCCESSFUL From a5dfccd11b0197c641cb6c4dfb0783828ed0e5cd Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 15 Dec 2023 12:28:31 -0800 Subject: [PATCH 53/72] Added test cases for global variable modifications --- .../modifies/global_fail.expected | 9 ++++++++ .../function-contract/modifies/global_fail.rs | 17 ++++++++++++++ .../modifies/global_pass.expected | 1 + .../function-contract/modifies/global_pass.rs | 18 +++++++++++++++ .../modifies/global_replace_fail.expected | 8 +++++++ .../modifies/global_replace_fail.rs | 22 +++++++++++++++++++ .../modifies/global_replace_pass.expected | 6 +++++ .../modifies/global_replace_pass.rs | 22 +++++++++++++++++++ 8 files changed, 103 insertions(+) create mode 100644 tests/expected/function-contract/modifies/global_fail.expected create mode 100644 tests/expected/function-contract/modifies/global_fail.rs create mode 100644 tests/expected/function-contract/modifies/global_pass.expected create mode 100644 tests/expected/function-contract/modifies/global_pass.rs create mode 100644 tests/expected/function-contract/modifies/global_replace_fail.expected create mode 100644 tests/expected/function-contract/modifies/global_replace_fail.rs create mode 100644 tests/expected/function-contract/modifies/global_replace_pass.expected create mode 100644 tests/expected/function-contract/modifies/global_replace_pass.rs diff --git a/tests/expected/function-contract/modifies/global_fail.expected b/tests/expected/function-contract/modifies/global_fail.expected new file mode 100644 index 000000000000..04e0359ad8a4 --- /dev/null +++ b/tests/expected/function-contract/modifies/global_fail.expected @@ -0,0 +1,9 @@ +assigns\ +- Status: FAILURE\ +- Description: "Check that *var_1 is assignable"\ +in function modify_wrapper + +Failed Checks: Check that *var_1 is assignable\ +in modify_wrapper + +VERIFICATION:- FAILED diff --git a/tests/expected/function-contract/modifies/global_fail.rs b/tests/expected/function-contract/modifies/global_fail.rs new file mode 100644 index 000000000000..48c095a4964e --- /dev/null +++ b/tests/expected/function-contract/modifies/global_fail.rs @@ -0,0 +1,17 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +static mut PTR: u32 = 0; + +#[kani::requires(PTR < 100)] +unsafe fn modify() { + PTR += 1; +} + +#[kani::proof_for_contract(modify)] +fn main() { + unsafe { + modify(); + } +} diff --git a/tests/expected/function-contract/modifies/global_pass.expected b/tests/expected/function-contract/modifies/global_pass.expected new file mode 100644 index 000000000000..34c886c358cb --- /dev/null +++ b/tests/expected/function-contract/modifies/global_pass.expected @@ -0,0 +1 @@ +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/global_pass.rs b/tests/expected/function-contract/modifies/global_pass.rs new file mode 100644 index 000000000000..f4579c0b5e29 --- /dev/null +++ b/tests/expected/function-contract/modifies/global_pass.rs @@ -0,0 +1,18 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +static mut PTR: u32 = 0; + +#[kani::requires(PTR < 100)] +#[kani::modifies(&mut PTR)] +unsafe fn modify() { + PTR += 1; +} + +#[kani::proof_for_contract(modify)] +fn main() { + unsafe { + modify(); + } +} diff --git a/tests/expected/function-contract/modifies/global_replace_fail.expected b/tests/expected/function-contract/modifies/global_replace_fail.expected new file mode 100644 index 000000000000..8c0761731a4b --- /dev/null +++ b/tests/expected/function-contract/modifies/global_replace_fail.expected @@ -0,0 +1,8 @@ +main.assertion\ +- Status: FAILURE\ +- Description: "not havocked"\ +in function main + +Failed Checks: not havocked + +VERIFICATION:- FAILED diff --git a/tests/expected/function-contract/modifies/global_replace_fail.rs b/tests/expected/function-contract/modifies/global_replace_fail.rs new file mode 100644 index 000000000000..25169918b120 --- /dev/null +++ b/tests/expected/function-contract/modifies/global_replace_fail.rs @@ -0,0 +1,22 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +static mut PTR: u32 = 0; + +#[kani::requires(PTR < 100)] +#[kani::modifies(&mut PTR)] +unsafe fn modify() { + PTR += 1; +} + +#[kani::proof] +#[kani::stub_verified(modify)] +fn main() { + unsafe { + PTR = kani::any_where(|i| *i < 100); + let compare = PTR; + modify(); + kani::assert(compare + 1 == PTR, "not havocked"); + } +} diff --git a/tests/expected/function-contract/modifies/global_replace_pass.expected b/tests/expected/function-contract/modifies/global_replace_pass.expected new file mode 100644 index 000000000000..164f00c88d22 --- /dev/null +++ b/tests/expected/function-contract/modifies/global_replace_pass.expected @@ -0,0 +1,6 @@ +main.assertion\ +- Status: SUCCESS\ +- Description: "replaced"\ +in function main + +VERIFICATION:- SUCCESSFUL diff --git a/tests/expected/function-contract/modifies/global_replace_pass.rs b/tests/expected/function-contract/modifies/global_replace_pass.rs new file mode 100644 index 000000000000..333348f25ce4 --- /dev/null +++ b/tests/expected/function-contract/modifies/global_replace_pass.rs @@ -0,0 +1,22 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT +// kani-flags: -Zfunction-contracts + +static mut PTR: u32 = 0; + +#[kani::modifies(&mut PTR)] +#[kani::ensures(PTR == src)] +unsafe fn modify(src: u32) { + PTR = src; +} + +#[kani::proof] +#[kani::stub_verified(modify)] +fn main() { + unsafe { + PTR = kani::any(); + let new = kani::any(); + modify(new); + kani::assert(new == PTR, "replaced"); + } +} From c5b6a2b2e2b61e4b8c73ca0f37b9b5c6d41023a3 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Sat, 23 Dec 2023 16:41:15 +0100 Subject: [PATCH 54/72] Apply suggestions from code review Co-authored-by: Felipe R. Monteiro --- kani-compiler/src/kani_middle/attributes.rs | 2 +- library/kani/src/contracts.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index d82c7760360f..dee9c8f182a4 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -588,7 +588,7 @@ macro_rules! comma_tok { }; } -/// Parse the a token stream inside an attribute (like `kanitool::modifies`) as a comma separated +/// Parse the token stream inside an attribute (like `kanitool::modifies`) as a comma separated /// sequence of function parameter names on `local_def_id` (must refer to a function). Then /// translates the names into [`Local`]s. fn parse_modify_values<'a>( diff --git a/library/kani/src/contracts.rs b/library/kani/src/contracts.rs index 07e1b4cfaa19..4f963038cab9 100644 --- a/library/kani/src/contracts.rs +++ b/library/kani/src/contracts.rs @@ -118,7 +118,7 @@ //! //! ## Specification Attributes Overview //! -//! The basic two two specification attributes available for describing +//! The basic two specification attributes available for describing //! function behavior are [`requires`][macro@requires] for preconditions and //! [`ensures`][macro@ensures] for postconditions. Both admit arbitrary Rust //! expressions as their bodies which may also reference the function arguments From 6892afc9cf754fdbc4a174545eae7887e5d80b40 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Sat, 23 Dec 2023 16:42:03 +0100 Subject: [PATCH 55/72] Suggestions from @feliperodri --- .../src/goto_program/symbol_table.rs | 2 ++ library/kani_macros/src/lib.rs | 18 +++++++++++++++++- library/kani_macros/src/sysroot/contracts.rs | 3 ++- .../function-contract/.unsafe_assigns_rc | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/cprover_bindings/src/goto_program/symbol_table.rs b/cprover_bindings/src/goto_program/symbol_table.rs index 9bcd14cb624e..8125c8df3cd9 100644 --- a/cprover_bindings/src/goto_program/symbol_table.rs +++ b/cprover_bindings/src/goto_program/symbol_table.rs @@ -80,6 +80,8 @@ impl SymbolTable { self.symbol_table.get_mut(&name).unwrap().update_fn_declaration_with_definition(body); } + /// Attach a contract to the symbol identified by `name`. If a prior + /// contract exists it is extended with additional clauses. pub fn attach_contract>( &mut self, name: T, diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index 91e587b03d5c..491d5c2c9df1 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -167,6 +167,22 @@ pub fn stub_verified(attr: TokenStream, item: TokenStream) -> TokenStream { attr_impl::stub_verified(attr, item) } +/// Declaration of an explicit write-set for the annotated function. +/// +/// This is part of the function contract API, for more general information see +/// the [module-level documentation](../kani/contracts/index.html). +/// +/// The contents of the attribute is a series of comma-separated expressions referencing the +/// arguments of the function. Each expression is expected to return a pointer type, i.e. `*const T`, +/// `*mut T`, `&T` or `&mut T`. The pointed-to type must implement +/// [`Arbitrary`](../kani/arbitrary/trait.Arbitrary.html). +/// +/// All Rust syntax is supported, even calling other functions, but the computations must be side +/// effect free, e.g. it cannot perform I/O or use mutable memory. +/// +/// Kani requires each function that uses a contract to have at least one designated +/// [`proof_for_contract`][macro@proof_for_contract] harness for checking the +/// contract. #[proc_macro_attribute] pub fn modifies(attr: TokenStream, item: TokenStream) -> TokenStream { attr_impl::modifies(attr, item) @@ -174,7 +190,7 @@ pub fn modifies(attr: TokenStream, item: TokenStream) -> TokenStream { /// This module implements Kani attributes in a way that only Kani's compiler can understand. /// This code should only be activated when pre-building Kani's sysroot. -#[cfg(kani_sysroot)] +//#[cfg(kani_sysroot)] mod sysroot { use proc_macro_error::{abort, abort_call_site}; diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 36ead85cdff5..a712fa1eb682 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -1319,11 +1319,12 @@ fn hash_of_token_stream(hasher: &mut H, stream: proc_macro /// Hash this `TokenStream` and return an integer that is at most digits /// long when hex formatted. fn short_hash_of_token_stream(stream: &proc_macro::TokenStream) -> u64 { + const SIX_HEX_DIGITS_MASK: u64 = 0x1_000_000; use std::hash::Hasher; let mut hasher = std::collections::hash_map::DefaultHasher::default(); hash_of_token_stream(&mut hasher, proc_macro2::TokenStream::from(stream.clone())); let long_hash = hasher.finish(); - long_hash % 0x1_000_000 // six hex digits + long_hash % SIX_HEX_DIGITS_MASK } /// Makes consistent names for a generated function which was created for diff --git a/tests/expected/function-contract/.unsafe_assigns_rc b/tests/expected/function-contract/.unsafe_assigns_rc index ea2988219ee8..53b011f36717 100644 --- a/tests/expected/function-contract/.unsafe_assigns_rc +++ b/tests/expected/function-contract/.unsafe_assigns_rc @@ -30,4 +30,4 @@ fn main() { // let i = Rc::new(RefCell::new(begin)); // modify(i.clone()); // kani::assert(*i.borrow() == begin + 1, "end"); -// } \ No newline at end of file +// } From 455373c53c143a1c1541c07296db0856fa376b61 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Tue, 26 Dec 2023 10:06:01 +0100 Subject: [PATCH 56/72] Update contract code to stable MIR --- .../codegen_cprover_gotoc/codegen/function.rs | 7 ++-- .../compiler_interface.rs | 32 +++++++++---------- kani-compiler/src/kani_compiler.rs | 2 +- kani-compiler/src/kani_middle/attributes.rs | 9 +++--- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index dab832e8879e..26d8a17c9f33 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -221,7 +221,7 @@ impl<'tcx> GotocCtx<'tcx> { Lambda::as_contract_for( &goto_annotated_fn_typ, None, - self.codegen_place(&local.into()).unwrap().goto_expr.dereference(), + self.codegen_place_stable(&local.into()).unwrap().goto_expr.dereference(), ) }) .collect(); @@ -233,11 +233,12 @@ impl<'tcx> GotocCtx<'tcx> { /// `instance` must have previously been declared. /// /// This merges with any previously attached contracts. - pub fn attach_contract(&mut self, instance: Instance<'tcx>, contract: Vec) { + pub fn attach_contract(&mut self, instance: Instance, contract: Vec) { // This should be safe, since the contract is pretty much evaluated as // though it was the first (or last) assertion in the function. assert!(self.current_fn.is_none()); - self.set_current_fn(instance); + let body = instance.body().unwrap(); + self.set_current_fn(instance, &body); let goto_contract = self.as_goto_contract(contract); let name = self.current_fn().name(); diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 80a288a2620f..2dedd67de53f 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -31,13 +31,11 @@ use rustc_codegen_ssa::{CodegenResults, CrateInfo}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_errors::{ErrorGuaranteed, DEFAULT_LOCALE_RESOURCE}; -use rustc_hir::def_id::LOCAL_CRATE; -use rustc_hir::definitions::DefPathHash; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_metadata::creader::MetadataLoaderDyn; use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME}; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; -use rustc_middle::mir::mono::MonoItem; use rustc_middle::ty::TyCtxt; use rustc_middle::util::Providers; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; @@ -65,7 +63,7 @@ use tracing::{debug, error, info}; pub type UnsupportedConstructs = FxHashMap>; -pub type ContractInfoChannel = std::sync::mpsc::Sender<(DefPathHash, AssignsContract)>; +pub type ContractInfoChannel = std::sync::mpsc::Sender<(InternedString, AssignsContract)>; #[derive(Clone)] pub struct GotocCodegenBackend { @@ -213,15 +211,17 @@ impl<'tcx> GotocCtx<'tcx> { fn handle_check_contract( &mut self, function_under_contract: DefId, - items: &[MonoItem<'tcx>], + items: &[MonoItem], ) -> AssignsContract { let tcx = self.tcx; let function_under_contract_attrs = KaniAttributes::for_item(tcx, function_under_contract); let wrapped_fn = function_under_contract_attrs.inner_check().unwrap().unwrap(); - let mut instance_under_contract = items.iter().copied().filter_map(|i| match i { - MonoItem::Fn(instance @ Instance { def: InstanceDef::Item(did), .. }) => { - (wrapped_fn == did).then_some(instance) + let mut instance_under_contract = items.iter().filter_map(|i| match i { + MonoItem::Fn(instance @ Instance { def, .. }) + if wrapped_fn == rustc_internal::internal(def.def_id()) => + { + Some(instance.clone()) } _ => None, }); @@ -237,7 +237,7 @@ impl<'tcx> GotocCtx<'tcx> { }); self.attach_contract(instance_of_check, assigns_contract); - let wrapper_name = tcx.symbol_name(instance_of_check).to_string(); + let wrapper_name = self.symbol_name_stable(instance_of_check); let recursion_wrapper_id = function_under_contract_attrs.checked_with_id().unwrap().unwrap(); @@ -315,12 +315,12 @@ impl CodegenBackend for GotocCodegenBackend { items.contains(&instance.mangled_name().intern()) }); for harness in harnesses { - let model_path = queries - .harness_model_path(&harness.mangled_name()) - .unwrap(); - let Ok(contract_metadata) = - contract_metadata_for_harness(tcx, harness.def_id()) - else { + let model_path = + queries.harness_model_path(&harness.mangled_name()).unwrap(); + let Ok(contract_metadata) = contract_metadata_for_harness( + tcx, + rustc_internal::internal(harness.def.def_id()), + ) else { continue; }; let (gcx, items, contract_info) = self.codegen_items( @@ -333,7 +333,7 @@ impl CodegenBackend for GotocCodegenBackend { results.extend(gcx, items, None); if let Some(assigns_contract) = contract_info { self.contract_channel - .send((tcx.def_path_hash(harness.def_id()), assigns_contract)) + .send((harness.name().intern(), assigns_contract)) .unwrap(); } } diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index ff8e5bef484b..f635c2e85b41 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -278,7 +278,7 @@ impl KaniCompiler { fn run_compilation_session( &mut self, args: &[String], - ) -> Result, ErrorGuaranteed> { + ) -> Result, ErrorGuaranteed> { debug!(?args, "run_compilation_session"); let queries = self.queries.clone(); let mut compiler = RunCompiler::new(args, self); diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index c2f83deee728..89211848c5be 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -18,14 +18,12 @@ use rustc_hir::{ def::DefKind, def_id::{DefId, LocalDefId}, }; -use rustc_middle::{ - mir::Local, - ty::{Instance, TyCtxt, TyKind}, -}; +use rustc_middle::ty::{Instance, TyCtxt, TyKind}; use rustc_session::Session; use rustc_smir::rustc_internal; use rustc_span::{Span, Symbol}; use stable_mir::mir::mono::Instance as InstanceStable; +use stable_mir::mir::Local; use stable_mir::CrateDef; use std::str::FromStr; use strum_macros::{AsRefStr, EnumString}; @@ -619,7 +617,8 @@ fn parse_modify_values<'a>( .zip(mir.args_iter()) .find(|(name, _decl)| name.name == *id) .unwrap() - .1, + .1 + .as_usize(), ) } else { wrong_token_err(); From 0980cbc20722ff13ad88a035cbb082bdf14a8e07 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Mon, 29 Jan 2024 16:48:42 -0500 Subject: [PATCH 57/72] Addressing code review comments Moved contracts code gen functions to separate module Moved `Pointer` trait to `internal` module Remove channel based modifies contract communication --- cprover_bindings/src/irep/to_irep.rs | 12 +- .../codegen_cprover_gotoc/codegen/contract.rs | 117 ++++++++++++++++++ .../codegen_cprover_gotoc/codegen/function.rs | 46 +------ .../src/codegen_cprover_gotoc/codegen/mod.rs | 1 + .../compiler_interface.rs | 109 +++------------- .../src/codegen_cprover_gotoc/mod.rs | 2 +- kani-compiler/src/kani_compiler.rs | 50 ++++---- kani-compiler/src/kani_middle/attributes.rs | 9 +- kani-compiler/src/kani_middle/mod.rs | 2 +- kani-compiler/src/kani_queries/mod.rs | 39 ++++++ kani-driver/src/call_goto_instrument.rs | 1 - library/kani/src/internal.rs | 75 +++++++++++ library/kani/src/lib.rs | 79 +----------- library/kani_macros/src/sysroot/contracts.rs | 20 +-- .../function-contract/modifies/expr_pass.rs | 1 + .../modifies/expr_replace_fail.rs | 2 +- 16 files changed, 309 insertions(+), 256 deletions(-) create mode 100644 kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs create mode 100644 library/kani/src/internal.rs diff --git a/cprover_bindings/src/irep/to_irep.rs b/cprover_bindings/src/irep/to_irep.rs index 75faa7fc3d08..16b8b69c8fe7 100644 --- a/cprover_bindings/src/irep/to_irep.rs +++ b/cprover_bindings/src/irep/to_irep.rs @@ -504,15 +504,21 @@ impl ToIrep for SwitchCase { } impl ToIrep for Lambda { + /// At the moment this function assumes that this lambda is used for a + /// `modifies` contract. It should work for any other lambda body, but + /// the parameter names use "modifies" in their generated names. fn to_irep(&self, mm: &MachineModel) -> Irep { let (ops_ireps, types) = self .arguments .iter() - .map(|param| { + .enumerate() + .map(|(index, param)| { let ty_rep = param.typ().to_irep(mm); ( - Irep::symbol(param.identifier().unwrap_or("_".into())) - .with_named_sub(IrepId::Type, ty_rep.clone()), + Irep::symbol( + param.identifier().unwrap_or_else(|| format!("_modifies_{index}").into()), + ) + .with_named_sub(IrepId::Type, ty_rep.clone()), ty_rep, ) }) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs new file mode 100644 index 000000000000..d9f231f0a97e --- /dev/null +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -0,0 +1,117 @@ +use crate::codegen_cprover_gotoc::GotocCtx; +use crate::kani_middle::attributes::KaniAttributes; +use cbmc::goto_program::FunctionContract; +use cbmc::goto_program::Lambda; +use kani_metadata::AssignsContract; +use rustc_hir::def_id::DefId as InternalDefId; +use rustc_smir::rustc_internal; +use stable_mir::mir::mono::{Instance, MonoItem}; +use stable_mir::mir::Local; +use stable_mir::CrateDef; +use tracing::debug; + +impl<'tcx> GotocCtx<'tcx> { + /// Given the `proof_for_contract` target `function_under_contract` and the reachable `items`, + /// find or create the `AssignsContract` that needs to be enforced and attach it to the symbol + /// for which it needs to be enforced. + /// + /// 1. Gets the `#[kanitool::inner_check = "..."]` target, then resolves exactly one instance + /// of it. Panics if there are more or less than one instance. + /// 2. Expects that a `#[kanitool::modifies(...)]` is placed on the `inner_check` function, + /// turns it into a CBMC contract and attaches it to the symbol for the previously resolved + /// instance. + /// 3. Returns the mangled name of the symbol it attached the contract to. + /// 4. Resolves the `#[kanitool::checked_with = "..."]` target from `function_under_contract` + /// which has `static mut REENTRY : bool` declared inside. + /// 5. Returns the full path to this constant that `--nondet-static-exclude` expects which is + /// comprised of the file path that `checked_with` is located in, the name of the + /// `checked_with` function and the name of the constant (`REENTRY`). + pub fn handle_check_contract( + &mut self, + function_under_contract: InternalDefId, + items: &[MonoItem], + ) -> AssignsContract { + let tcx = self.tcx; + let function_under_contract_attrs = KaniAttributes::for_item(tcx, function_under_contract); + let wrapped_fn = function_under_contract_attrs.inner_check().unwrap().unwrap(); + + let mut instance_under_contract = items.iter().filter_map(|i| match i { + MonoItem::Fn(instance @ Instance { def, .. }) + if wrapped_fn == rustc_internal::internal(def.def_id()) => + { + Some(instance.clone()) + } + _ => None, + }); + let instance_of_check = instance_under_contract.next().unwrap(); + assert!( + instance_under_contract.next().is_none(), + "Only one instance of a checked function may be in scope" + ); + let attrs_of_wrapped_fn = KaniAttributes::for_item(tcx, wrapped_fn); + let assigns_contract = attrs_of_wrapped_fn.modifies_contract().unwrap_or_else(|| { + debug!(?instance_of_check, "had no assigns contract specified"); + vec![] + }); + self.attach_modifies_contract(instance_of_check, assigns_contract); + + let wrapper_name = self.symbol_name_stable(instance_of_check); + + let recursion_wrapper_id = + function_under_contract_attrs.checked_with_id().unwrap().unwrap(); + let span_of_recursion_wrapper = tcx.def_span(recursion_wrapper_id); + let location_of_recursion_wrapper = self.codegen_span(&span_of_recursion_wrapper); + + let full_name = format!( + "{}:{}::REENTRY", + location_of_recursion_wrapper + .filename() + .expect("recursion location wrapper should have a file name"), + tcx.item_name(recursion_wrapper_id), + ); + + AssignsContract { recursion_tracker: full_name, contracted_function_name: wrapper_name } + } + + /// Convert the Kani level contract into a CBMC level contract by creating a + /// CBMC lambda. + fn codegen_modifies_contract(&mut self, modified_places: Vec) -> FunctionContract { + let goto_annotated_fn_name = self.current_fn().name(); + let goto_annotated_fn_typ = self + .symbol_table + .lookup(&goto_annotated_fn_name) + .unwrap_or_else(|| panic!("Function '{goto_annotated_fn_name}' is not declared")) + .typ + .clone(); + + let assigns = modified_places + .into_iter() + .map(|local| { + Lambda::as_contract_for( + &goto_annotated_fn_typ, + None, + self.codegen_place_stable(&local.into()).unwrap().goto_expr.dereference(), + ) + }) + .collect(); + + FunctionContract::new(assigns) + } + + /// Convert the contract to a CBMC contract, then attach it to `instance`. + /// `instance` must have previously been declared. + /// + /// This merges with any previously attached contracts. + pub fn attach_modifies_contract(&mut self, instance: Instance, modified_places: Vec) { + // This should be safe, since the contract is pretty much evaluated as + // though it was the first (or last) assertion in the function. + assert!(self.current_fn.is_none()); + let body = instance.body().unwrap(); + self.set_current_fn(instance, &body); + let goto_contract = self.codegen_modifies_contract(modified_places); + let name = self.current_fn().name(); + + self.symbol_table.attach_contract(name, goto_contract); + self.reset_current_fn() + } +} diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs index 26d8a17c9f33..52bf778ab235 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs @@ -4,7 +4,7 @@ //! This file contains functions related to codegenning MIR functions into gotoc use crate::codegen_cprover_gotoc::GotocCtx; -use cbmc::goto_program::{Expr, FunctionContract, Stmt, Symbol}; +use cbmc::goto_program::{Expr, Stmt, Symbol}; use cbmc::InternString; use rustc_middle::mir::traversal::reverse_postorder; use stable_mir::mir::mono::Instance; @@ -202,50 +202,6 @@ impl<'tcx> GotocCtx<'tcx> { ); } - /// Convert the Kani level contract into a CBMC level contract by creating a - /// CBMC lambda. - fn as_goto_contract(&mut self, assigns_contract: Vec) -> FunctionContract { - use cbmc::goto_program::Lambda; - - let goto_annotated_fn_name = self.current_fn().name(); - let goto_annotated_fn_typ = self - .symbol_table - .lookup(&goto_annotated_fn_name) - .unwrap_or_else(|| panic!("Function '{goto_annotated_fn_name}' is not declared")) - .typ - .clone(); - - let assigns = assigns_contract - .into_iter() - .map(|local| { - Lambda::as_contract_for( - &goto_annotated_fn_typ, - None, - self.codegen_place_stable(&local.into()).unwrap().goto_expr.dereference(), - ) - }) - .collect(); - - FunctionContract::new(assigns) - } - - /// Convert the contract to a CBMC contract, then attach it to `instance`. - /// `instance` must have previously been declared. - /// - /// This merges with any previously attached contracts. - pub fn attach_contract(&mut self, instance: Instance, contract: Vec) { - // This should be safe, since the contract is pretty much evaluated as - // though it was the first (or last) assertion in the function. - assert!(self.current_fn.is_none()); - let body = instance.body().unwrap(); - self.set_current_fn(instance, &body); - let goto_contract = self.as_goto_contract(contract); - let name = self.current_fn().name(); - - self.symbol_table.attach_contract(name, goto_contract); - self.reset_current_fn() - } - pub fn declare_function(&mut self, instance: Instance) { debug!("declaring {}; {:?}", instance.name(), instance); let body = instance.body().unwrap(); diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/mod.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/mod.rs index 938a784765e0..238bdb27b069 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/mod.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/mod.rs @@ -17,6 +17,7 @@ mod statement; mod static_var; // Visible for all codegen module. +pub mod contract; mod ty_stable; pub(super) mod typ; diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 2dedd67de53f..c8c48b51db2f 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -31,7 +31,7 @@ use rustc_codegen_ssa::{CodegenResults, CrateInfo}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_errors::{ErrorGuaranteed, DEFAULT_LOCALE_RESOURCE}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::def_id::{DefId as InternalDefId, LOCAL_CRATE}; use rustc_metadata::creader::MetadataLoaderDyn; use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME}; use rustc_metadata::EncodedMetadata; @@ -45,7 +45,7 @@ use rustc_smir::rustc_internal; use rustc_target::abi::Endian; use rustc_target::spec::PanicStrategy; use stable_mir::mir::mono::{Instance, MonoItem}; -use stable_mir::CrateDef; +use stable_mir::{CrateDef, DefId}; use std::any::Any; use std::collections::BTreeMap; use std::collections::HashSet; @@ -63,8 +63,6 @@ use tracing::{debug, error, info}; pub type UnsupportedConstructs = FxHashMap>; -pub type ContractInfoChannel = std::sync::mpsc::Sender<(InternedString, AssignsContract)>; - #[derive(Clone)] pub struct GotocCodegenBackend { /// The query is shared with `KaniCompiler` and it is initialized as part of `rustc` @@ -72,13 +70,11 @@ pub struct GotocCodegenBackend { /// Since we don't have any guarantees on when the compiler creates the Backend object, neither /// in which thread it will be used, we prefer to explicitly synchronize any query access. queries: Arc>, - - contract_channel: ContractInfoChannel, } impl GotocCodegenBackend { - pub fn new(queries: Arc>, contract_channel: ContractInfoChannel) -> Self { - GotocCodegenBackend { queries, contract_channel } + pub fn new(queries: Arc>) -> Self { + GotocCodegenBackend { queries } } /// Generate code that is reachable from the given starting points. @@ -90,7 +86,7 @@ impl GotocCodegenBackend { starting_items: &[MonoItem], symtab_goto: &Path, machine_model: &MachineModel, - check_contract: Option, + check_contract: Option, ) -> (GotocCtx<'tcx>, Vec, Option) { let items = with_timer( || collect_reachable_items(tcx, starting_items), @@ -192,78 +188,6 @@ impl GotocCodegenBackend { } } -impl<'tcx> GotocCtx<'tcx> { - /// Given the `proof_for_contract` target `function_under_contract` and the reachable `items`, - /// find or create the `AssignsContract` that needs to be enforced and attach it to the symbol - /// for which it needs to be enforced. - /// - /// 1. Gets the `#[kanitool::inner_check = "..."]` target, then resolves exactly one instance - /// of it. Panics if there are more or less than one instance. - /// 2. Expects that a `#[kanitool::modifies(...)]` is placed on the `inner_check` function, - /// turns it into a CBMC contract and attaches it to the symbol for the previously resolved - /// instance. - /// 3. Returns the mangled name of the symbol it attached the contract to. - /// 4. Resolves the `#[kanitool::checked_with = "..."]` target from `function_under_contract` - /// which has `static mut REENTRY : bool` declared inside. - /// 5. Returns the full path to this constant that `--nondet-static-exclude` expects which is - /// comprised of the file path that `checked_with` is located in, the name of the - /// `checked_with` function and the name of the constant (`REENTRY`). - fn handle_check_contract( - &mut self, - function_under_contract: DefId, - items: &[MonoItem], - ) -> AssignsContract { - let tcx = self.tcx; - let function_under_contract_attrs = KaniAttributes::for_item(tcx, function_under_contract); - let wrapped_fn = function_under_contract_attrs.inner_check().unwrap().unwrap(); - - let mut instance_under_contract = items.iter().filter_map(|i| match i { - MonoItem::Fn(instance @ Instance { def, .. }) - if wrapped_fn == rustc_internal::internal(def.def_id()) => - { - Some(instance.clone()) - } - _ => None, - }); - let instance_of_check = instance_under_contract.next().unwrap(); - assert!( - instance_under_contract.next().is_none(), - "Only one instance of a checked function may be in scope" - ); - let attrs_of_wrapped_fn = KaniAttributes::for_item(tcx, wrapped_fn); - let assigns_contract = attrs_of_wrapped_fn.modifies_contract().unwrap_or_else(|| { - debug!(?instance_of_check, "had no assigns contract specified"); - vec![] - }); - self.attach_contract(instance_of_check, assigns_contract); - - let wrapper_name = self.symbol_name_stable(instance_of_check); - - let recursion_wrapper_id = - function_under_contract_attrs.checked_with_id().unwrap().unwrap(); - let span_of_recursion_wrapper = tcx.def_span(recursion_wrapper_id); - let location_of_recursion_wrapper = self.codegen_span(&span_of_recursion_wrapper); - - let full_name = format!( - "{}:{}::REENTRY", - location_of_recursion_wrapper - .filename() - .expect("recursion location wrapper should have a file name"), - tcx.item_name(recursion_wrapper_id), - ); - - AssignsContract { recursion_tracker: full_name, contracted_function_name: wrapper_name } - } -} - -fn contract_metadata_for_harness( - tcx: TyCtxt, - def_id: DefId, -) -> Result, ErrorGuaranteed> { - let attrs = KaniAttributes::for_item(tcx, def_id); - Ok(attrs.interpret_the_for_contract_attribute().transpose()?.map(|(_, id, _)| id)) -} - impl CodegenBackend for GotocCodegenBackend { fn metadata_loader(&self) -> Box { Box::new(rustc_codegen_ssa::back::metadata::DefaultMetadataLoader) @@ -317,12 +241,8 @@ impl CodegenBackend for GotocCodegenBackend { for harness in harnesses { let model_path = queries.harness_model_path(&harness.mangled_name()).unwrap(); - let Ok(contract_metadata) = contract_metadata_for_harness( - tcx, - rustc_internal::internal(harness.def.def_id()), - ) else { - continue; - }; + let contract_metadata = + contract_metadata_for_harness(tcx, harness.def.def_id()).unwrap(); let (gcx, items, contract_info) = self.codegen_items( tcx, &[MonoItem::Fn(harness)], @@ -332,9 +252,10 @@ impl CodegenBackend for GotocCodegenBackend { ); results.extend(gcx, items, None); if let Some(assigns_contract) = contract_info { - self.contract_channel - .send((harness.name().intern(), assigns_contract)) - .unwrap(); + self.queries + .lock() + .unwrap() + .add_modifies_contract(harness.name().intern(), assigns_contract); } } } @@ -497,6 +418,14 @@ impl CodegenBackend for GotocCodegenBackend { } } +fn contract_metadata_for_harness( + tcx: TyCtxt, + def_id: DefId, +) -> Result, ErrorGuaranteed> { + let attrs = KaniAttributes::for_def_id(tcx, def_id); + Ok(attrs.interpret_the_for_contract_attribute().transpose()?.map(|(_, id, _)| id)) +} + fn check_target(session: &Session) { // The requirement below is needed to build a valid CBMC machine model // in function `machine_model_from_session` from diff --git a/kani-compiler/src/codegen_cprover_gotoc/mod.rs b/kani-compiler/src/codegen_cprover_gotoc/mod.rs index 155e93604863..7454d748d660 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/mod.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/mod.rs @@ -6,6 +6,6 @@ mod context; mod overrides; mod utils; -pub use compiler_interface::{ContractInfoChannel, GotocCodegenBackend, UnsupportedConstructs}; +pub use compiler_interface::{GotocCodegenBackend, UnsupportedConstructs}; pub use context::GotocCtx; pub use context::VtableCtx; diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index f635c2e85b41..68db28bd4b2c 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -17,7 +17,7 @@ use crate::args::{Arguments, ReachabilityType}; #[cfg(feature = "cprover")] -use crate::codegen_cprover_gotoc::{ContractInfoChannel, GotocCodegenBackend}; +use crate::codegen_cprover_gotoc::GotocCodegenBackend; use crate::kani_middle::attributes::is_proof_harness; use crate::kani_middle::check_crate_items; use crate::kani_middle::metadata::gen_proof_metadata; @@ -27,7 +27,7 @@ use crate::kani_queries::QueryDb; use crate::session::init_session; use cbmc::{InternString, InternedString}; use clap::Parser; -use kani_metadata::{ArtifactType, AssignsContract, HarnessMetadata, KaniMetadata}; +use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_driver::{Callbacks, Compilation, RunCompiler}; use rustc_hir::def_id::LOCAL_CRATE; @@ -58,11 +58,8 @@ pub fn run(args: Vec) -> ExitCode { /// Configure the cprover backend that generate goto-programs. #[cfg(feature = "cprover")] -fn backend( - queries: Arc>, - contract_channel: ContractInfoChannel, -) -> Box { - Box::new(GotocCodegenBackend::new(queries, contract_channel)) +fn backend(queries: Arc>) -> Box { + Box::new(GotocCodegenBackend::new(queries)) } /// Fallback backend. It will trigger an error if no backend has been enabled. @@ -187,10 +184,23 @@ impl KaniCompiler { pub fn run(&mut self, orig_args: Vec) -> Result<(), ErrorGuaranteed> { loop { debug!(next=?self.stage, "run"); - match &self.stage { - CompilationStage::Init => { - assert!(self.run_compilation_session(&orig_args)?.is_empty()); + // Because this modifies `self.stage` we need to run this before + // borrowing `&self.stage` immutably + if let CompilationStage::Done { metadata: Some((metadata, _)), .. } = &mut self.stage { + let mut qdb = self.queries.lock().unwrap(); + for harness in + metadata.proof_harnesses.iter_mut().chain(metadata.test_harnesses.iter_mut()) + { + if let Some(modifies_contract) = + qdb.get_modifies_contracts(&harness.mangled_name) + { + harness.contract = modifies_contract.into(); + } } + qdb.assert_modifies_contracts_received(); + } + match &self.stage { + CompilationStage::Init => self.run_compilation_session(&orig_args)?, CompilationStage::CodegenNoStubs { .. } => { unreachable!("This stage should always run in the same session as Init"); } @@ -201,15 +211,7 @@ impl KaniCompiler { let extra_arg = stubbing::mk_rustc_arg(&target_harness.stub_map); let mut args = orig_args.clone(); args.push(extra_arg); - let contract_spec = self.run_compilation_session(&args)?; - let CompilationStage::CodegenWithStubs { all_harnesses, .. } = &mut self.stage - else { - unreachable!() - }; - for (target, spec) in contract_spec { - let target_harness = all_harnesses.get_mut(&target).unwrap(); - target_harness.metadata.contract = spec.into(); - } + self.run_compilation_session(&args)? } CompilationStage::Done { metadata: Some((kani_metadata, crate_info)) } => { // Only store metadata for harnesses for now. @@ -275,17 +277,13 @@ impl KaniCompiler { } /// Run the Rust compiler with the given arguments and pass `&mut self` to handle callbacks. - fn run_compilation_session( - &mut self, - args: &[String], - ) -> Result, ErrorGuaranteed> { + fn run_compilation_session(&mut self, args: &[String]) -> Result<(), ErrorGuaranteed> { debug!(?args, "run_compilation_session"); let queries = self.queries.clone(); let mut compiler = RunCompiler::new(args, self); - let (send, receive) = std::sync::mpsc::channel(); - compiler.set_make_codegen_backend(Some(Box::new(move |_cfg| backend(queries, send)))); + compiler.set_make_codegen_backend(Some(Box::new(move |_cfg| backend(queries)))); compiler.run()?; - Ok(receive.iter().collect()) + Ok(()) } /// Gather and process all harnesses from this crate that shall be compiled. diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 57a0f288779e..f5c4ae615f8d 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -24,7 +24,7 @@ use rustc_smir::rustc_internal; use rustc_span::{Span, Symbol}; use stable_mir::mir::mono::Instance as InstanceStable; use stable_mir::mir::Local; -use stable_mir::CrateDef; +use stable_mir::{CrateDef, DefId as StableDefId}; use std::str::FromStr; use strum_macros::{AsRefStr, EnumString}; @@ -136,7 +136,12 @@ impl<'tcx> KaniAttributes<'tcx> { /// Perform preliminary parsing and checking for the attributes on this /// function pub fn for_instance(tcx: TyCtxt<'tcx>, instance: InstanceStable) -> Self { - KaniAttributes::for_item(tcx, rustc_internal::internal(instance.def.def_id())) + KaniAttributes::for_def_id(tcx, instance.def.def_id()) + } + + /// Look up the attributes by a stable MIR DefID + pub fn for_def_id(tcx: TyCtxt<'tcx>, def_id: StableDefId) -> Self { + KaniAttributes::for_item(tcx, rustc_internal::internal(def_id)) } pub fn for_item(tcx: TyCtxt<'tcx>, def_id: DefId) -> Self { diff --git a/kani-compiler/src/kani_middle/mod.rs b/kani-compiler/src/kani_middle/mod.rs index 5d1b148b963f..fcc0e47a2991 100644 --- a/kani-compiler/src/kani_middle/mod.rs +++ b/kani-compiler/src/kani_middle/mod.rs @@ -84,7 +84,7 @@ pub fn check_reachable_items(tcx: TyCtxt, queries: &QueryDb, items: &[MonoItem]) }; if !def_ids.contains(&def_id) { // Check if any unstable attribute was reached. - KaniAttributes::for_item(tcx, rustc_internal::internal(def_id)) + KaniAttributes::for_def_id(tcx, def_id) .check_unstable_features(&queries.args().unstable_features); def_ids.insert(def_id); } diff --git a/kani-compiler/src/kani_queries/mod.rs b/kani-compiler/src/kani_queries/mod.rs index 6bda0a20bd2f..8aef160c3d9c 100644 --- a/kani-compiler/src/kani_queries/mod.rs +++ b/kani-compiler/src/kani_queries/mod.rs @@ -3,6 +3,8 @@ //! Define the communication between KaniCompiler and the codegen implementation. use cbmc::{InternString, InternedString}; +use kani_metadata::AssignsContract; +use std::fmt::{Display, Formatter, Write}; use std::{ collections::HashMap, path::PathBuf, @@ -17,6 +19,7 @@ pub struct QueryDb { args: Option, /// Information about all target harnesses. pub harnesses_info: HashMap, + modifies_contracts: HashMap, } impl QueryDb { @@ -41,4 +44,40 @@ impl QueryDb { pub fn args(&self) -> &Arguments { self.args.as_ref().expect("Arguments have not been initialized") } + + pub fn add_modifies_contract(&mut self, name: InternedString, contract: AssignsContract) { + assert!( + self.modifies_contracts.insert(name, contract).is_none(), + "Invariant broken, tried adding second modifies contracts to: {name}", + ) + } + + pub fn get_modifies_contracts(&mut self, key: &str) -> Option { + self.modifies_contracts.remove(&key.intern()) + } + + pub fn assert_modifies_contracts_received(&self) { + assert!( + self.modifies_contracts.is_empty(), + "Invariant broken: The modifies contracts for {} have not been retrieved", + PrintList(self.modifies_contracts.keys()) + ) + } +} + +struct PrintList(I); + +impl + Clone> Display for PrintList { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_char('[')?; + let mut is_first = true; + for e in self.0.clone() { + if is_first { + f.write_str(", ")?; + is_first = false; + } + e.fmt(f)?; + } + f.write_char(']') + } } diff --git a/kani-driver/src/call_goto_instrument.rs b/kani-driver/src/call_goto_instrument.rs index 0b3db37faf89..d219b5d228ae 100644 --- a/kani-driver/src/call_goto_instrument.rs +++ b/kani-driver/src/call_goto_instrument.rs @@ -173,7 +173,6 @@ impl KaniSession { vec!["--dfcc".into(), (&harness.mangled_name).into()]; if let Some(assigns) = check { - println!("enforcing function contract for {assigns:?}"); args.extend([ "--enforce-contract".into(), assigns.contracted_function_name.as_str().into(), diff --git a/library/kani/src/internal.rs b/library/kani/src/internal.rs new file mode 100644 index 000000000000..e4a8ff99dfdb --- /dev/null +++ b/library/kani/src/internal.rs @@ -0,0 +1,75 @@ +/// Helper trait for code generation for `modifies` contracts. +/// +/// We allow the user to provide us with a pointer-like object that we convert as needed. +#[doc(hidden)] +pub trait Pointer<'a> { + /// Type of the pointed-to data + type Inner; + + /// Used for checking assigns contracts where we pass immutable references to the function. + /// + /// We're using a reference to self here, because the user can use just a plain function + /// argument, for instance one of type `&mut _`, in the `modifies` clause which would move it. + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner; + + /// used for havocking on replecement of a `modifies` clause. + unsafe fn assignable(self) -> &'a mut Self::Inner; +} + +impl<'a, 'b, T> Pointer<'a> for &'b T { + type Inner = T; + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + std::mem::transmute(*self) + } + + #[allow(clippy::transmute_ptr_to_ref)] + unsafe fn assignable(self) -> &'a mut Self::Inner { + std::mem::transmute(self as *const T) + } +} + +impl<'a, 'b, T> Pointer<'a> for &'b mut T { + type Inner = T; + + #[allow(clippy::transmute_ptr_to_ref)] + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + std::mem::transmute::<_, &&'a T>(self) + } + + unsafe fn assignable(self) -> &'a mut Self::Inner { + std::mem::transmute(self) + } +} + +impl<'a, T> Pointer<'a> for *const T { + type Inner = T; + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + &**self as &'a T + } + + #[allow(clippy::transmute_ptr_to_ref)] + unsafe fn assignable(self) -> &'a mut Self::Inner { + std::mem::transmute(self) + } +} + +impl<'a, T> Pointer<'a> for *mut T { + type Inner = T; + unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { + &**self as &'a T + } + + #[allow(clippy::transmute_ptr_to_ref)] + unsafe fn assignable(self) -> &'a mut Self::Inner { + std::mem::transmute(self) + } +} + +/// A way to break the ownerhip rules. Only used by contracts where we can +/// guarantee it is done safely. +#[inline(never)] +#[doc(hidden)] +#[rustc_diagnostic_item = "KaniUntrackedDeref"] +pub fn untracked_deref(_: &T) -> T { + todo!() +} diff --git a/library/kani/src/lib.rs b/library/kani/src/lib.rs index 50578ae815bd..865344df4d4b 100644 --- a/library/kani/src/lib.rs +++ b/library/kani/src/lib.rs @@ -25,6 +25,9 @@ pub mod slice; pub mod tuple; pub mod vec; +#[doc(hidden)] +pub mod internal; + mod models; pub use arbitrary::Arbitrary; @@ -92,82 +95,6 @@ macro_rules! implies { }; } -/// Helper trait for code generation for `modifies` contracts. -/// -/// We allow the user to provide us with a pointer-like object that we convert as needed. -#[doc(hidden)] -pub trait Pointer<'a> { - /// Type of the pointed-to data - type Inner; - - /// Used for checking assigns contracts where we pass immutable references to the function. - /// - /// We're using a reference to self here, because the user can use just a plain function - /// argument, for instance one of type `&mut _`, in the `modifies` clause which would move it. - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner; - - /// used for havocking on replecement of a `modifies` clause. - unsafe fn assignable(self) -> &'a mut Self::Inner; -} - -impl<'a, 'b, T> Pointer<'a> for &'b T { - type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - std::mem::transmute(*self) - } - - #[allow(clippy::transmute_ptr_to_ref)] - unsafe fn assignable(self) -> &'a mut Self::Inner { - std::mem::transmute(self as *const T) - } -} - -impl<'a, 'b, T> Pointer<'a> for &'b mut T { - type Inner = T; - - #[allow(clippy::transmute_ptr_to_ref)] - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - std::mem::transmute::<_, &&'a T>(self) - } - - unsafe fn assignable(self) -> &'a mut Self::Inner { - std::mem::transmute(self) - } -} - -impl<'a, T> Pointer<'a> for *const T { - type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - &**self as &'a T - } - - #[allow(clippy::transmute_ptr_to_ref)] - unsafe fn assignable(self) -> &'a mut Self::Inner { - std::mem::transmute(self) - } -} - -impl<'a, T> Pointer<'a> for *mut T { - type Inner = T; - unsafe fn decouple_lifetime(&self) -> &'a Self::Inner { - &**self as &'a T - } - - #[allow(clippy::transmute_ptr_to_ref)] - unsafe fn assignable(self) -> &'a mut Self::Inner { - std::mem::transmute(self) - } -} - -/// A way to break the ownerhip rules. Only used by contracts where we can -/// guarantee it is done safely. -#[inline(never)] -#[doc(hidden)] -#[rustc_diagnostic_item = "KaniUntrackedDeref"] -pub fn untracked_deref(_: &T) -> T { - todo!() -} - /// Creates an assertion of the specified condition and message. /// /// # Example: diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index a712fa1eb682..73ce50d179d4 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -88,7 +88,7 @@ //! ``` //! //! All named arguments of the annotated function are unsafely shallow-copied -//! with the `kani::untracked_deref` function to circumvent the borrow checker +//! with the `kani::internal::untracked_deref` function to circumvent the borrow checker //! for postconditions. The case where this is relevant is if you want to return //! a mutable borrow from the function which means any immutable borrow in the //! postcondition would be illegal. We must ensure that those copies are not @@ -190,8 +190,8 @@ //! #[allow(unused_variables)] //! #[kanitool::is_contract_generated(check)] //! fn div_check_965916(dividend: u32, divisor: u32) -> u32 { -//! let dividend_renamed = kani::untracked_deref(÷nd); -//! let divisor_renamed = kani::untracked_deref(&divisor); +//! let dividend_renamed = kani::internal::untracked_deref(÷nd); +//! let divisor_renamed = kani::internal::untracked_deref(&divisor); //! let result = { kani::assume(divisor != 0); { dividend / divisor } }; //! kani::assert(result <= dividend_renamed, "result <= dividend"); //! std::mem::forget(dividend_renamed); @@ -204,8 +204,8 @@ //! #[kanitool::is_contract_generated(replace)] //! fn div_replace_965916(dividend: u32, divisor: u32) -> u32 { //! kani::assert(divisor != 0, "divisor != 0"); -//! let dividend_renamed = kani::untracked_deref(÷nd); -//! let divisor_renamed = kani::untracked_deref(&divisor); +//! let dividend_renamed = kani::internal::untracked_deref(÷nd); +//! let divisor_renamed = kani::internal::untracked_deref(&divisor); //! let result = kani::any(); //! kani::assume(result <= dividend_renamed, "result <= dividend"); //! std::mem::forget(dividend_renamed); @@ -530,7 +530,7 @@ impl<'a> ContractConditionsHandler<'a> { }; quote!( - #(let #wrapper_args = unsafe { kani::Pointer::decouple_lifetime(&#attr) };)* + #(let #wrapper_args = unsafe { kani::internal::Pointer::decouple_lifetime(&#attr) };)* #(#inner)* ) } @@ -579,12 +579,12 @@ impl<'a> ContractConditionsHandler<'a> { /// ```ignore /// // multiple preconditions and argument copies like like /// kani::assert(.. precondition); - /// let arg_name = kani::untracked_deref(&arg_value); + /// let arg_name = kani::internal::untracked_deref(&arg_value); /// // single result havoc /// let result : ResultType = kani::any(); /// /// // multiple argument havockings - /// *unsafe { kani::Pointer::assignable(argument) } = kani::any(); + /// *unsafe { kani::internal::Pointer::assignable(argument) } = kani::any(); /// // multiple postconditions /// kani::assume(postcond); /// // multiple argument copy (used in postconditions) cleanups @@ -652,7 +652,7 @@ impl<'a> ContractConditionsHandler<'a> { ContractConditionsData::Modifies { attr } => { quote!( #(#before)* - #(*unsafe { kani::Pointer::assignable(#attr) } = kani::any();)* + #(*unsafe { kani::internal::Pointer::assignable(#attr) } = kani::any();)* #(#after)* result ) @@ -1042,7 +1042,7 @@ fn make_unsafe_argument_copies( let also_arg_names = renaming_map.values(); let arg_values = renaming_map.keys(); ( - quote!(#(let #arg_names = kani::untracked_deref(&#arg_values);)*), + quote!(#(let #arg_names = kani::internal::untracked_deref(&#arg_values);)*), quote!(#(std::mem::forget(#also_arg_names);)*), ) } diff --git a/tests/expected/function-contract/modifies/expr_pass.rs b/tests/expected/function-contract/modifies/expr_pass.rs index d76e20126c45..65e561df48a2 100644 --- a/tests/expected/function-contract/modifies/expr_pass.rs +++ b/tests/expected/function-contract/modifies/expr_pass.rs @@ -7,6 +7,7 @@ #[kani::requires(**ptr < 100)] #[kani::modifies(ptr.as_ref())] +#[kani::ensures(**ptr < 101)] fn modify(ptr: &mut Box) { *ptr.as_mut() += 1; } diff --git a/tests/expected/function-contract/modifies/expr_replace_fail.rs b/tests/expected/function-contract/modifies/expr_replace_fail.rs index b5677a817243..09fa840d8696 100644 --- a/tests/expected/function-contract/modifies/expr_replace_fail.rs +++ b/tests/expected/function-contract/modifies/expr_replace_fail.rs @@ -18,5 +18,5 @@ fn main() { let val = kani::any_where(|i| *i < 100); let mut i = Box::new(val); modify(&mut i); - kani::assert(*i == val + 1, "Increment"); + assert!(*i == val + 1, "Increment"); } From fac45b6594d01881912f121e3911435630069e83 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Mon, 29 Jan 2024 16:53:16 -0500 Subject: [PATCH 58/72] Copyright --- kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs | 2 ++ library/kani/src/internal.rs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index d9f231f0a97e..3e4f37ea9af2 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -1,3 +1,5 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT use crate::codegen_cprover_gotoc::GotocCtx; use crate::kani_middle::attributes::KaniAttributes; use cbmc::goto_program::FunctionContract; diff --git a/library/kani/src/internal.rs b/library/kani/src/internal.rs index e4a8ff99dfdb..d2f2970d1c05 100644 --- a/library/kani/src/internal.rs +++ b/library/kani/src/internal.rs @@ -1,3 +1,6 @@ +// Copyright Kani Contributors +// SPDX-License-Identifier: Apache-2.0 OR MIT + /// Helper trait for code generation for `modifies` contracts. /// /// We allow the user to provide us with a pointer-like object that we convert as needed. From 96d4b127a8e9ccadf32c4fbdbb5bdb4a12ccb99e Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Mon, 29 Jan 2024 16:56:42 -0500 Subject: [PATCH 59/72] Clippy suggestions --- library/kani_macros/src/lib.rs | 2 +- library/kani_macros/src/sysroot/contracts.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/kani_macros/src/lib.rs b/library/kani_macros/src/lib.rs index 491d5c2c9df1..32bd44d2ea38 100644 --- a/library/kani_macros/src/lib.rs +++ b/library/kani_macros/src/lib.rs @@ -190,7 +190,7 @@ pub fn modifies(attr: TokenStream, item: TokenStream) -> TokenStream { /// This module implements Kani attributes in a way that only Kani's compiler can understand. /// This code should only be activated when pre-building Kani's sysroot. -//#[cfg(kani_sysroot)] +#[cfg(kani_sysroot)] mod sysroot { use proc_macro_error::{abort, abort_call_site}; diff --git a/library/kani_macros/src/sysroot/contracts.rs b/library/kani_macros/src/sysroot/contracts.rs index 73ce50d179d4..3aeb8c1788d9 100644 --- a/library/kani_macros/src/sysroot/contracts.rs +++ b/library/kani_macros/src/sysroot/contracts.rs @@ -856,9 +856,9 @@ fn is_replace_return_havoc(stmt: &syn::Stmt) -> bool { /// For each argument create an expression that passes this argument along unmodified. /// /// Reconstructs structs that may have been deconstructed with patterns. -fn exprs_for_args<'a, T>( - args: &'a syn::punctuated::Punctuated, -) -> impl Iterator + Clone + 'a { +fn exprs_for_args( + args: &syn::punctuated::Punctuated, +) -> impl Iterator + Clone + '_ { args.iter().map(|arg| match arg { FnArg::Receiver(_) => Expr::Verbatim(quote!(self)), FnArg::Typed(typed) => pat_to_expr(&typed.pat), @@ -1351,7 +1351,7 @@ fn chunks_by<'a, T, C: Default + Extend>( std::iter::from_fn(move || { let mut new = C::default(); let mut empty = true; - while let Some(tok) = iter.next() { + for tok in iter.by_ref() { empty = false; if pred(&tok) { break; From d105b87519a957a5848e7e40eae6d883e9488567 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Mon, 29 Jan 2024 17:11:35 -0500 Subject: [PATCH 60/72] Update error reporting --- kani-compiler/src/kani_middle/attributes.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kani-compiler/src/kani_middle/attributes.rs b/kani-compiler/src/kani_middle/attributes.rs index 6c5e82698f82..e9edbedfcd82 100644 --- a/kani-compiler/src/kani_middle/attributes.rs +++ b/kani-compiler/src/kani_middle/attributes.rs @@ -611,8 +611,9 @@ fn parse_modify_values<'a>( let mut iter = t.trees(); std::iter::from_fn(move || { let tree = iter.next()?; - let wrong_token_err = - || tcx.sess.span_err(tree.span(), "Unexpected token. Expected identifier."); + let wrong_token_err = || { + tcx.sess.parse_sess.dcx.span_err(tree.span(), "Unexpected token. Expected identifier.") + }; let result = match tree { TokenTree::Token(token, _) => { if let TokenKind::Ident(id, _) = &token.kind { @@ -639,7 +640,7 @@ fn parse_modify_values<'a>( match iter.next() { None | Some(comma_tok!()) => (), Some(not_comma) => { - tcx.sess.span_err( + tcx.sess.parse_sess.dcx.span_err( not_comma.span(), "Unexpected token, expected end of attribute or comma", ); From 5eab70198b4983effdc552685561bb20a8e7099b Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Tue, 30 Jan 2024 15:55:44 -0500 Subject: [PATCH 61/72] Fixing manes used by contracts metadata --- .../src/codegen_cprover_gotoc/compiler_interface.rs | 4 ++-- kani-compiler/src/kani_middle/metadata.rs | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index e34c5945fbdd..4dd8b6c95bdd 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -7,7 +7,7 @@ use crate::args::ReachabilityType; use crate::codegen_cprover_gotoc::GotocCtx; use crate::kani_middle::analysis; use crate::kani_middle::attributes::{is_test_harness_description, KaniAttributes}; -use crate::kani_middle::metadata::gen_test_metadata; +use crate::kani_middle::metadata::{canonical_mangled_name, gen_test_metadata}; use crate::kani_middle::provide; use crate::kani_middle::reachability::{ collect_reachable_items, filter_const_crate_items, filter_crate_items, @@ -255,7 +255,7 @@ impl CodegenBackend for GotocCodegenBackend { self.queries .lock() .unwrap() - .add_modifies_contract(harness.name().intern(), assigns_contract); + .add_modifies_contract(canonical_mangled_name(harness).intern(), assigns_contract); } } } diff --git a/kani-compiler/src/kani_middle/metadata.rs b/kani-compiler/src/kani_middle/metadata.rs index 925e79ba7aeb..02f5da107556 100644 --- a/kani-compiler/src/kani_middle/metadata.rs +++ b/kani-compiler/src/kani_middle/metadata.rs @@ -14,15 +14,19 @@ use stable_mir::CrateDef; use super::{attributes::KaniAttributes, SourceLocation}; +pub fn canonical_mangled_name(instance: Instance) -> String { + let pretty_name = instance.name(); + // Main function a special case in order to support `--function main` + // TODO: Get rid of this: https://github.com/model-checking/kani/issues/2129 + if pretty_name == "main" { pretty_name } else { instance.mangled_name() } +} + /// Create the harness metadata for a proof harness for a given function. pub fn gen_proof_metadata(tcx: TyCtxt, instance: Instance, base_name: &Path) -> HarnessMetadata { let def = instance.def; let attributes = KaniAttributes::for_instance(tcx, instance).harness_attributes(); let pretty_name = instance.name(); - // Main function a special case in order to support `--function main` - // TODO: Get rid of this: https://github.com/model-checking/kani/issues/2129 - let mangled_name = - if pretty_name == "main" { pretty_name.clone() } else { instance.mangled_name() }; + let mangled_name = canonical_mangled_name(instance); // We get the body span to include the entire function definition. // This is required for concrete playback to properly position the generated test. From 4bd758377edbb53d2081cc5693e475341bafc5cd Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Tue, 30 Jan 2024 15:55:53 -0500 Subject: [PATCH 62/72] Fix test case --- .../function-contract/modifies/expr_replace_fail.expected | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/expected/function-contract/modifies/expr_replace_fail.expected b/tests/expected/function-contract/modifies/expr_replace_fail.expected index 4ea7c000a8bc..f8019df2927c 100644 --- a/tests/expected/function-contract/modifies/expr_replace_fail.expected +++ b/tests/expected/function-contract/modifies/expr_replace_fail.expected @@ -1,7 +1,7 @@ main.assertion\ - Status: FAILURE\ -- Description: "Increment" +- Description: ""Increment"" -Failed Checks: Increment +Failed Checks: "Increment" VERIFICATION:- FAILED From 0a4503afd7de3263e76aa8c902d3df92699f1f53 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 31 Jan 2024 12:37:43 -0500 Subject: [PATCH 63/72] Formatting --- .../src/codegen_cprover_gotoc/compiler_interface.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 4dd8b6c95bdd..9b9bf4fa3f33 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -252,10 +252,10 @@ impl CodegenBackend for GotocCodegenBackend { ); results.extend(gcx, items, None); if let Some(assigns_contract) = contract_info { - self.queries - .lock() - .unwrap() - .add_modifies_contract(canonical_mangled_name(harness).intern(), assigns_contract); + self.queries.lock().unwrap().add_modifies_contract( + canonical_mangled_name(harness).intern(), + assigns_contract, + ); } } } From 6414e221743b0b838b189fc0009b088daae87354 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 31 Jan 2024 12:41:34 -0500 Subject: [PATCH 64/72] Clippy complained --- kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index 3e4f37ea9af2..dc888cc99660 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -41,7 +41,7 @@ impl<'tcx> GotocCtx<'tcx> { MonoItem::Fn(instance @ Instance { def, .. }) if wrapped_fn == rustc_internal::internal(def.def_id()) => { - Some(instance.clone()) + Some(instance) } _ => None, }); From 6bbbb24dcc5cc6287b42550a3729253ffe86614f Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 31 Jan 2024 12:47:51 -0500 Subject: [PATCH 65/72] Whoops --- kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs index dc888cc99660..964286984fc6 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs @@ -41,7 +41,7 @@ impl<'tcx> GotocCtx<'tcx> { MonoItem::Fn(instance @ Instance { def, .. }) if wrapped_fn == rustc_internal::internal(def.def_id()) => { - Some(instance) + Some(*instance) } _ => None, }); From 9b17d944cda9991e21a2420eb979077bd1e4583d Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 31 Jan 2024 14:09:07 -0500 Subject: [PATCH 66/72] Name the other Rc test case 'fixme' --- .../unsafe_rc.rs} | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) rename tests/expected/function-contract/{.unsafe_assigns_rc => modifies/unsafe_rc.rs} (60%) diff --git a/tests/expected/function-contract/.unsafe_assigns_rc b/tests/expected/function-contract/modifies/unsafe_rc.rs similarity index 60% rename from tests/expected/function-contract/.unsafe_assigns_rc rename to tests/expected/function-contract/modifies/unsafe_rc.rs index 53b011f36717..e3e210f73a8b 100644 --- a/tests/expected/function-contract/.unsafe_assigns_rc +++ b/tests/expected/function-contract/modifies/unsafe_rc.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Zfunction-contracts +/// Illustrates the problem from https://github.com/model-checking/kani/issues/2907 + use std::rc::Rc; use std::ops::Deref; @@ -22,12 +24,11 @@ fn main() { modify(ptr.clone()); } - -// #[kani::proof] -// #[kani::stub_verified(modify)] -// fn replace_modify() { -// let begin = kani::any_where(|i| *i < 100); -// let i = Rc::new(RefCell::new(begin)); -// modify(i.clone()); -// kani::assert(*i.borrow() == begin + 1, "end"); -// } +#[kani::proof] +#[kani::stub_verified(modify)] +fn replace_modify() { + let begin = kani::any_where(|i| *i < 100); + let i = Rc::new(RefCell::new(begin)); + modify(i.clone()); + kani::assert(*i.borrow() == begin + 1, "end"); +} From 52e703a97caef2631749a2f751549fd32c573a67 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Wed, 31 Jan 2024 22:56:50 -0500 Subject: [PATCH 67/72] Forgot that this now needs formatting --- tests/expected/function-contract/modifies/unsafe_rc.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/expected/function-contract/modifies/unsafe_rc.rs b/tests/expected/function-contract/modifies/unsafe_rc.rs index e3e210f73a8b..81cc65ce9957 100644 --- a/tests/expected/function-contract/modifies/unsafe_rc.rs +++ b/tests/expected/function-contract/modifies/unsafe_rc.rs @@ -2,10 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // kani-flags: -Zfunction-contracts +use std::ops::Deref; /// Illustrates the problem from https://github.com/model-checking/kani/issues/2907 - use std::rc::Rc; -use std::ops::Deref; #[kani::modifies({ let intref : &u32 = ptr.deref().deref(); @@ -19,7 +18,7 @@ fn modify(ptr: Rc<&mut u32>) { #[kani::proof_for_contract(modify)] fn main() { - let mut i : u32 = kani::any(); + let mut i: u32 = kani::any(); let ptr = Rc::new(&mut i); modify(ptr.clone()); } From 40e3abe5c535d969f42007e0f49e64a267b0afa4 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Thu, 1 Feb 2024 00:14:26 -0500 Subject: [PATCH 68/72] Used the wrong name --- .../modifies/{unsafe_rc.rs => unsafe_rc_fixme.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/expected/function-contract/modifies/{unsafe_rc.rs => unsafe_rc_fixme.rs} (100%) diff --git a/tests/expected/function-contract/modifies/unsafe_rc.rs b/tests/expected/function-contract/modifies/unsafe_rc_fixme.rs similarity index 100% rename from tests/expected/function-contract/modifies/unsafe_rc.rs rename to tests/expected/function-contract/modifies/unsafe_rc_fixme.rs From 75a554d2d57cbcd63bd485bb980e59d347573e4c Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 2 Feb 2024 12:50:51 -0500 Subject: [PATCH 69/72] Suggestions from code review --- .../compiler_interface.rs | 2 +- kani-compiler/src/kani_compiler.rs | 4 +- kani-compiler/src/kani_queries/mod.rs | 29 +++++++++++--- kani-driver/src/call_goto_instrument.rs | 38 +++++++++---------- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs index 9b9bf4fa3f33..1cfff307f856 100644 --- a/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs +++ b/kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs @@ -252,7 +252,7 @@ impl CodegenBackend for GotocCodegenBackend { ); results.extend(gcx, items, None); if let Some(assigns_contract) = contract_info { - self.queries.lock().unwrap().add_modifies_contract( + self.queries.lock().unwrap().register_assigns_contract( canonical_mangled_name(harness).intern(), assigns_contract, ); diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index 68db28bd4b2c..783742b13309 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -192,12 +192,12 @@ impl KaniCompiler { metadata.proof_harnesses.iter_mut().chain(metadata.test_harnesses.iter_mut()) { if let Some(modifies_contract) = - qdb.get_modifies_contracts(&harness.mangled_name) + qdb.assigns_contract_for((&harness.mangled_name).intern()) { harness.contract = modifies_contract.into(); } } - qdb.assert_modifies_contracts_received(); + qdb.assert_assigns_contracts_retrieved(); } match &self.stage { CompilationStage::Init => self.run_compilation_session(&orig_args)?, diff --git a/kani-compiler/src/kani_queries/mod.rs b/kani-compiler/src/kani_queries/mod.rs index 8aef160c3d9c..e752a4c7c4fd 100644 --- a/kani-compiler/src/kani_queries/mod.rs +++ b/kani-compiler/src/kani_queries/mod.rs @@ -45,18 +45,35 @@ impl QueryDb { self.args.as_ref().expect("Arguments have not been initialized") } - pub fn add_modifies_contract(&mut self, name: InternedString, contract: AssignsContract) { + /// Register that a CBMC-level `assigns` contract for a function that is + /// called from this harness. + pub fn register_assigns_contract( + &mut self, + harness_name: InternedString, + contract: AssignsContract, + ) { + let replaced = self.modifies_contracts.insert(harness_name, contract); assert!( - self.modifies_contracts.insert(name, contract).is_none(), - "Invariant broken, tried adding second modifies contracts to: {name}", + replaced.is_none(), + "Invariant broken, tried adding second modifies contracts to: {harness_name}", ) } - pub fn get_modifies_contracts(&mut self, key: &str) -> Option { - self.modifies_contracts.remove(&key.intern()) + /// Lookup if a CBMC-level `assigns` contract was registered for this + /// harness with [`Self::add_assigns_contract`]. + /// + /// This removes the contract from the registry and is intended to be used in + /// conjunction with [`Self::assert_assigns_contracts_retrieved`] to uphold + /// the invariant that each contract has been handled and only handled once. + pub fn assigns_contract_for( + &mut self, + harness_name: InternedString, + ) -> Option { + self.modifies_contracts.remove(&harness_name) } - pub fn assert_modifies_contracts_received(&self) { + /// Assert that the contract registry is empty. See [`Self::assigns_contract_for`] + pub fn assert_assigns_contracts_retrieved(&self) { assert!( self.modifies_contracts.is_empty(), "Invariant broken: The modifies contracts for {} have not been retrieved", diff --git a/kani-driver/src/call_goto_instrument.rs b/kani-driver/src/call_goto_instrument.rs index d219b5d228ae..83744eddabfd 100644 --- a/kani-driver/src/call_goto_instrument.rs +++ b/kani-driver/src/call_goto_instrument.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use anyhow::Result; -use std::ffi::OsString; +use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io::BufReader; use std::path::Path; @@ -164,25 +164,18 @@ impl KaniSession { /// Make CBMC enforce a function contract. pub fn instrument_contracts(&self, harness: &HarnessMetadata, file: &Path) -> Result<()> { - let check = harness.contract.as_ref(); - if check.is_none() { - return Ok(()); - } - - let mut args: Vec = - vec!["--dfcc".into(), (&harness.mangled_name).into()]; - - if let Some(assigns) = check { - args.extend([ - "--enforce-contract".into(), - assigns.contracted_function_name.as_str().into(), - "--nondet-static-exclude".into(), - assigns.recursion_tracker.as_str().into(), - ]); - } - - args.extend([file.into(), file.into()]); - + let Some(assigns) = harness.contract.as_ref() else { return Ok(()) }; + + let args: &[std::ffi::OsString] = &[ + "--dfcc".into(), + (&harness.mangled_name).into(), + "--enforce-contract".into(), + assigns.contracted_function_name.as_str().into(), + "--nondet-static-exclude".into(), + assigns.recursion_tracker.as_str().into(), + file.into(), + file.into(), + ]; self.call_goto_instrument(args) } @@ -215,7 +208,10 @@ impl KaniSession { } /// Non-public helper function to actually do the run of goto-instrument - fn call_goto_instrument(&self, args: Vec) -> Result<()> { + fn call_goto_instrument>( + &self, + args: impl IntoIterator, + ) -> Result<()> { // TODO get goto-instrument path from self let mut cmd = Command::new("goto-instrument"); cmd.args(args); From edd7bf81fe1fa690ea14538bbe72f48eebed0062 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 2 Feb 2024 12:51:13 -0500 Subject: [PATCH 70/72] Forgot to stage after renaming --- .../function-contract/modifies/refcell_fixme.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/expected/function-contract/modifies/refcell_fixme.rs diff --git a/tests/expected/function-contract/modifies/refcell_fixme.rs b/tests/expected/function-contract/modifies/refcell_fixme.rs new file mode 100644 index 000000000000..8ae9cb390eb7 --- /dev/null +++ b/tests/expected/function-contract/modifies/refcell_fixme.rs @@ -0,0 +1,13 @@ +use std::cell::RefCell; +use std::ops::Deref; + +#[kani::modifies(cell.borrow().deref())] +fn modifies_ref_cell(cell: &RefCell) { + *cell.borrow_mut() = 100; +} + +#[kani::proof_for_contract(modifies_ref_cell)] +fn check_harness() { + let rc = RefCell::new(0); + modifies_ref_cell(&rc); +} From 7d088109b68d44e6fb4dba686ccc60220e1e708f Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 2 Feb 2024 16:12:32 -0500 Subject: [PATCH 71/72] Moving all the contract communication logic into the compiler --- kani-compiler/src/kani_compiler.rs | 16 +++++++++++++--- kani-compiler/src/kani_queries/mod.rs | 24 ++++-------------------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index 783742b13309..b3936e1bd33d 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -30,6 +30,7 @@ use clap::Parser; use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_driver::{Callbacks, Compilation, RunCompiler}; +use rustc_hash::FxHashMap; use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::definitions::DefPathHash; use rustc_interface::Config; @@ -187,17 +188,26 @@ impl KaniCompiler { // Because this modifies `self.stage` we need to run this before // borrowing `&self.stage` immutably if let CompilationStage::Done { metadata: Some((metadata, _)), .. } = &mut self.stage { - let mut qdb = self.queries.lock().unwrap(); + let mut contracts = self + .queries + .lock() + .unwrap() + .assigns_contracts() + .map(|(k, v)| (*k, v.clone())) + .collect::>(); for harness in metadata.proof_harnesses.iter_mut().chain(metadata.test_harnesses.iter_mut()) { if let Some(modifies_contract) = - qdb.assigns_contract_for((&harness.mangled_name).intern()) + contracts.remove(&(&harness.mangled_name).intern()) { harness.contract = modifies_contract.into(); } } - qdb.assert_assigns_contracts_retrieved(); + assert!( + contracts.is_empty(), + "Invariant broken: not all contracts have been handled." + ) } match &self.stage { CompilationStage::Init => self.run_compilation_session(&orig_args)?, diff --git a/kani-compiler/src/kani_queries/mod.rs b/kani-compiler/src/kani_queries/mod.rs index e752a4c7c4fd..e918bf1b8a73 100644 --- a/kani-compiler/src/kani_queries/mod.rs +++ b/kani-compiler/src/kani_queries/mod.rs @@ -59,26 +59,10 @@ impl QueryDb { ) } - /// Lookup if a CBMC-level `assigns` contract was registered for this - /// harness with [`Self::add_assigns_contract`]. - /// - /// This removes the contract from the registry and is intended to be used in - /// conjunction with [`Self::assert_assigns_contracts_retrieved`] to uphold - /// the invariant that each contract has been handled and only handled once. - pub fn assigns_contract_for( - &mut self, - harness_name: InternedString, - ) -> Option { - self.modifies_contracts.remove(&harness_name) - } - - /// Assert that the contract registry is empty. See [`Self::assigns_contract_for`] - pub fn assert_assigns_contracts_retrieved(&self) { - assert!( - self.modifies_contracts.is_empty(), - "Invariant broken: The modifies contracts for {} have not been retrieved", - PrintList(self.modifies_contracts.keys()) - ) + /// Lookup all CBMC-level `assigns` contract were registered with + /// [`Self::add_assigns_contract`]. + pub fn assigns_contracts(&self) -> impl Iterator { + self.modifies_contracts.iter() } } From 1807020309a3b5d690e5524ba6cb572ccc29f210 Mon Sep 17 00:00:00 2001 From: Justus Adam Date: Fri, 2 Feb 2024 16:32:40 -0500 Subject: [PATCH 72/72] Wrong import --- kani-compiler/src/kani_compiler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kani-compiler/src/kani_compiler.rs b/kani-compiler/src/kani_compiler.rs index b3936e1bd33d..118821f5995f 100644 --- a/kani-compiler/src/kani_compiler.rs +++ b/kani-compiler/src/kani_compiler.rs @@ -29,8 +29,8 @@ use cbmc::{InternString, InternedString}; use clap::Parser; use kani_metadata::{ArtifactType, HarnessMetadata, KaniMetadata}; use rustc_codegen_ssa::traits::CodegenBackend; +use rustc_data_structures::fx::FxHashMap; use rustc_driver::{Callbacks, Compilation, RunCompiler}; -use rustc_hash::FxHashMap; use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::definitions::DefPathHash; use rustc_interface::Config;