Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function Contracts: Modify Slices #3295

Merged
merged 39 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
33be618
initial working for bytesize
Jun 25, 2024
3437710
add tests
Jun 25, 2024
90a5d3f
arbitrary size slices
Jun 26, 2024
13ebd4e
new test
Jun 26, 2024
971b4c3
Merge branch 'main' into modifies_fat
pi314mm Jun 26, 2024
c1163ff
any instead of any_modifies
Jun 26, 2024
2e497f1
Merge branch 'modifies_fat' of github.com:pi314mm/kani into modifies_fat
Jun 26, 2024
11f8a5b
Merge branch 'main' into modifies_fat
Jul 1, 2024
0248c77
new compilation phase
Jul 1, 2024
1fd0849
hack
Jul 8, 2024
e5bf457
Merge branch 'main' into modifies_fat
pi314mm Jul 8, 2024
d3706a7
strings
Jul 8, 2024
b0da8b0
Merge branch 'modifies_fat' of github.com:pi314mm/kani into modifies_fat
Jul 8, 2024
f28023d
fmt
Jul 8, 2024
1e336d3
unnecessary closure
Jul 8, 2024
3883281
Merge branch 'main' into modifies_fat
pi314mm Jul 8, 2024
96ca603
move functions
Jul 9, 2024
89f789c
Merge branch 'modifies_fat' of github.com:pi314mm/kani into modifies_fat
Jul 9, 2024
f04cb09
Merge branch 'main' into modifies_fat
pi314mm Jul 9, 2024
6cef19b
fmt
Jul 9, 2024
34c1996
Merge branch 'main' into modifies_fat
pi314mm Jul 9, 2024
c28bad7
raw pointer
Jul 10, 2024
52d3839
clippy
Jul 10, 2024
0bd765e
change any_modifies back
Jul 10, 2024
e038b0f
Update replace.rs
pi314mm Jul 10, 2024
9c2b7e8
strings todo
Jul 11, 2024
55ca498
strings todo
Jul 11, 2024
310c6b4
Update library/kani/src/internal.rs
pi314mm Jul 12, 2024
202ca3a
Merge branch 'main' into modifies_fat
pi314mm Jul 12, 2024
56f0d67
Merge branch 'main' into modifies_fat
pi314mm Jul 12, 2024
7be0551
Merge branch 'main' into modifies_fat
pi314mm Jul 15, 2024
e214aba
Merge branch 'main' into modifies_fat
pi314mm Jul 15, 2024
565873c
Merge branch 'main' into modifies_fat
pi314mm Jul 16, 2024
dff4d7c
string test fails
Jul 16, 2024
ff56fc1
new test
Jul 16, 2024
9478594
comments
Jul 16, 2024
7bd5634
new test
Jul 16, 2024
02d4909
Merge branch 'main' into modifies_fat
pi314mm Jul 17, 2024
3115a3e
Merge branch 'main' into modifies_fat
feliperodri Jul 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 60 additions & 6 deletions kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::codegen_cprover_gotoc::GotocCtx;
use crate::kani_middle::attributes::KaniAttributes;
use cbmc::goto_program::FunctionContract;
use cbmc::goto_program::{Lambda, Location};
use cbmc::goto_program::{Expr, Lambda, Location, Type};
use kani_metadata::AssignsContract;
use rustc_hir::def_id::DefId as InternalDefId;
use rustc_smir::rustc_internal;
Expand All @@ -12,6 +12,8 @@ use stable_mir::mir::Local;
use stable_mir::CrateDef;
use tracing::debug;

use stable_mir::ty::{RigidTy, TyKind};

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
Expand Down Expand Up @@ -142,11 +144,63 @@ impl<'tcx> GotocCtx<'tcx> {
let assigns = modified_places
.into_iter()
.map(|local| {
Lambda::as_contract_for(
&goto_annotated_fn_typ,
None,
self.codegen_place_stable(&local.into(), loc).unwrap().goto_expr.dereference(),
)
if self.is_fat_pointer_stable(self.local_ty_stable(local)) {
let unref = match self.local_ty_stable(local).kind() {
TyKind::RigidTy(RigidTy::Ref(_, ty, _)) => ty,
kind => unreachable!("{:?} is not a reference", kind),
};
let size = match unref.kind() {
TyKind::RigidTy(RigidTy::Slice(elt_type)) => {
elt_type.layout().unwrap().shape().size.bytes()
}
TyKind::RigidTy(RigidTy::Str) => 1,
// For adt, see https://rust-lang.zulipchat.com/#narrow/stream/182449-t-compiler.2Fhelp
TyKind::RigidTy(RigidTy::Adt(..)) => {
todo!("Adt fat pointers not implemented")
}
kind => unreachable!("Generating a slice fat pointer to {:?}", kind),
};
Lambda::as_contract_for(
&goto_annotated_fn_typ,
None,
Expr::symbol_expression(
"__CPROVER_object_upto",
Type::code(
vec![
Type::empty()
.to_pointer()
.as_parameter(None, Some("ptr".into())),
Type::size_t().as_parameter(None, Some("size".into())),
],
Type::empty(),
),
)
.call(vec![
self.codegen_place_stable(&local.into(), loc)
.unwrap()
.goto_expr
.member("data", &self.symbol_table)
.cast_to(Type::empty().to_pointer()),
self.codegen_place_stable(&local.into(), loc)
.unwrap()
.goto_expr
.member("len", &self.symbol_table)
.mul(Expr::size_constant(
size.try_into().unwrap(),
&self.symbol_table,
)),
]),
)
} else {
Lambda::as_contract_for(
&goto_annotated_fn_typ,
None,
self.codegen_place_stable(&local.into(), loc)
.unwrap()
.goto_expr
.dereference(),
)
}
})
.chain(shadow_memory_assign)
.collect();
Expand Down
79 changes: 75 additions & 4 deletions kani-compiler/src/kani_middle/transform/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,29 @@ use rustc_middle::ty::TyCtxt;
use rustc_smir::rustc_internal;
use stable_mir::mir::mono::Instance;
use stable_mir::mir::{Body, ConstOperand, Operand, TerminatorKind};
use stable_mir::ty::{FnDef, MirConst, RigidTy, TyKind};
use stable_mir::ty::{FnDef, MirConst, RigidTy, TyKind, TypeAndMut};
use stable_mir::{CrateDef, DefId};
use std::collections::HashSet;
use std::fmt::Debug;
use tracing::{debug, trace};

/// Check if we can replace calls to any_modifies.
/// Check if we can replace calls to any_modifies or write_any.
///
/// This pass will replace the entire body, and it should only be applied to stubs
/// that have a body.
///
/// write_any is replaced with one of write_any_slim, write_any_slice, or write_any_str
/// depending on what the type of the input it
///
/// any_modifies is replaced with any
#[derive(Debug)]
pub struct AnyModifiesPass {
kani_any: Option<FnDef>,
kani_any_modifies: Option<FnDef>,
kani_write_any: Option<FnDef>,
kani_write_any_slim: Option<FnDef>,
kani_write_any_slice: Option<FnDef>,
kani_write_any_str: Option<FnDef>,
pi314mm marked this conversation as resolved.
Show resolved Hide resolved
stubbed: HashSet<DefId>,
target_fn: Option<InternedString>,
}
Expand Down Expand Up @@ -78,6 +87,18 @@ impl AnyModifiesPass {
let kani_any_modifies = tcx
.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniAnyModifies"))
.map(item_fn_def);
let kani_write_any = tcx
.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAny"))
.map(item_fn_def);
let kani_write_any_slim = tcx
.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAnySlim"))
.map(item_fn_def);
let kani_write_any_slice = tcx
.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAnySlice"))
.map(item_fn_def);
let kani_write_any_str = tcx
.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAnyStr"))
.map(item_fn_def);
let (target_fn, stubbed) = if let Some(harness) = unit.harnesses.first() {
let attributes = KaniAttributes::for_instance(tcx, *harness);
let target_fn =
Expand All @@ -86,7 +107,16 @@ impl AnyModifiesPass {
} else {
(None, HashSet::new())
};
AnyModifiesPass { kani_any, kani_any_modifies, target_fn, stubbed }
AnyModifiesPass {
kani_any,
kani_any_modifies,
kani_write_any,
kani_write_any_slim,
kani_write_any_slice,
kani_write_any_str,
target_fn,
stubbed,
}
}

/// If we apply `transform_any_modifies` in all contract-generated items,
Expand All @@ -105,7 +135,7 @@ impl AnyModifiesPass {
let mut changed = false;
let locals = body.locals().to_vec();
for bb in body.blocks.iter_mut() {
let TerminatorKind::Call { func, .. } = &mut bb.terminator.kind else { continue };
let TerminatorKind::Call { func, args, .. } = &mut bb.terminator.kind else { continue };
if let TyKind::RigidTy(RigidTy::FnDef(def, instance_args)) =
func.ty(&locals).unwrap().kind()
&& Some(def) == self.kani_any_modifies
Expand All @@ -117,6 +147,47 @@ impl AnyModifiesPass {
*func = Operand::Constant(new_func);
changed = true;
}

// if this is a valid kani::write_any function
if let TyKind::RigidTy(RigidTy::FnDef(def, instance_args)) =
pi314mm marked this conversation as resolved.
Show resolved Hide resolved
func.ty(&locals).unwrap().kind()
&& Some(def) == self.kani_write_any
&& args.len() == 1
&& let Some(fn_sig) = func.ty(&locals).unwrap().kind().fn_sig()
&& let Some(TypeAndMut { ty: internal_type, mutability: _ }) =
fn_sig.skip_binder().inputs()[0].kind().builtin_deref(true)
{
// case on the type of the input
if let TyKind::RigidTy(RigidTy::Slice(_)) = internal_type.kind() {
//if the input is a slice, use write_any_slice
let instance =
Instance::resolve(self.kani_write_any_slice.unwrap(), &instance_args)
.unwrap();
let literal = MirConst::try_new_zero_sized(instance.ty()).unwrap();
let span = bb.terminator.span;
let new_func = ConstOperand { span, user_ty: None, const_: literal };
*func = Operand::Constant(new_func);
} else if let TyKind::RigidTy(RigidTy::Str) = internal_type.kind() {
//if the input is a str, use write_any_str
let instance =
Instance::resolve(self.kani_write_any_str.unwrap(), &instance_args)
.unwrap();
let literal = MirConst::try_new_zero_sized(instance.ty()).unwrap();
let span = bb.terminator.span;
let new_func = ConstOperand { span, user_ty: None, const_: literal };
*func = Operand::Constant(new_func);
} else {
//otherwise, use write_any_slim
let instance =
Instance::resolve(self.kani_write_any_slim.unwrap(), &instance_args)
.unwrap();
let literal = MirConst::try_new_zero_sized(instance.ty()).unwrap();
let span = bb.terminator.span;
let new_func = ConstOperand { span, user_ty: None, const_: literal };
*func = Operand::Constant(new_func);
}
changed = true;
}
}
(changed, body)
}
Expand Down
77 changes: 61 additions & 16 deletions library/kani/src/internal.rs
Original file line number Diff line number Diff line change
@@ -1,70 +1,69 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::arbitrary::Arbitrary;
use std::ptr;

/// 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;
type Inner: ?Sized;

/// 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;
unsafe fn assignable(self) -> *mut Self::Inner;
}

impl<'a, 'b, T> Pointer<'a> for &'b T {
impl<'a, 'b, T: ?Sized> 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 {
unsafe fn assignable(self) -> *mut Self::Inner {
std::mem::transmute(self as *const T)
}
}

impl<'a, 'b, T> Pointer<'a> for &'b mut T {
impl<'a, 'b, T: ?Sized> 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)
unsafe fn assignable(self) -> *mut Self::Inner {
self as *mut T
}
}

impl<'a, T> Pointer<'a> for *const T {
impl<'a, T: ?Sized> 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 {
unsafe fn assignable(self) -> *mut Self::Inner {
std::mem::transmute(self)
}
}

impl<'a, T> Pointer<'a> for *mut T {
impl<'a, T: ?Sized> 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)
unsafe fn assignable(self) -> *mut Self::Inner {
self
}
}

Expand Down Expand Up @@ -97,3 +96,49 @@ pub fn init_contracts() {}
pub fn apply_closure<T, U: Fn(&T) -> bool>(f: U, x: &T) -> bool {
f(x)
}

/// Recieves a reference to a pointer-like object and assigns kani::any_modifies to that object.
/// Only for use within function contracts and will not be replaced if the recursive or function stub
/// replace contracts are not used.
#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")]
#[rustc_diagnostic_item = "KaniWriteAny"]
#[inline(never)]
#[doc(hidden)]
pub unsafe fn write_any<T: ?Sized>(_pointer: *mut T) {
// This function should not be reacheable.
// Users must include `#[kani::recursion]` in any function contracts for recursive functions;
// otherwise, this might not be properly instantiate. We mark this as unreachable to make
// sure Kani doesn't report any false positives.
unreachable!()
}

/// Fill in a slice with kani::any.
/// Intended as a post compilation replacement for write_any
#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")]
#[rustc_diagnostic_item = "KaniWriteAnySlice"]
#[inline(always)]
pub unsafe fn write_any_slice<T: Arbitrary>(slice: *mut [T]) {
(*slice).fill_with(T::any)
}

/// Fill in a pointer with kani::any.
/// Intended as a post compilation replacement for write_any
#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")]
#[rustc_diagnostic_item = "KaniWriteAnySlim"]
#[inline(always)]
pub unsafe fn write_any_slim<T: Arbitrary>(pointer: *mut T) {
ptr::write(pointer, T::any())
}

/// Fill in a str with kani::any.
/// Intended as a post compilation replacement for write_any.
/// Not yet implemented
#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")]
#[rustc_diagnostic_item = "KaniWriteAnyStr"]
#[inline(always)]
pub unsafe fn write_any_str(_s: *mut str) {
//TODO: strings introduce new UB
//(*s).as_bytes_mut().fill_with(u8::any)
//TODO: String validation
unimplemented!("Kani does not support creating arbitrary `str`")
}
pi314mm marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 10 additions & 2 deletions library/kani_macros/src/sysroot/contracts/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,19 @@ impl<'a> ContractConditionsHandler<'a> {
let sig = &mut self.annotated_fn.sig;
for (arg, arg_type) in wrapper_args.clone().zip(type_params) {
// Add the type parameter to the function signature's generic parameters list
let mut bounds: syn::punctuated::Punctuated<syn::TypeParamBound, syn::token::Plus> =
syn::punctuated::Punctuated::new();
bounds.push(syn::TypeParamBound::Trait(syn::TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::Maybe(Token![?](Span::call_site())),
lifetimes: None,
path: syn::Ident::new("Sized", Span::call_site()).into(),
}));
sig.generics.params.push(syn::GenericParam::Type(syn::TypeParam {
attrs: vec![],
ident: arg_type.clone(),
colon_token: None,
bounds: Default::default(),
colon_token: Some(Token![:](Span::call_site())),
bounds: bounds,
eq_token: None,
default: None,
}));
Expand Down
2 changes: 1 addition & 1 deletion library/kani_macros/src/sysroot/contracts/replace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ impl<'a> ContractConditionsHandler<'a> {
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
quote!(
#(#before)*
#(*unsafe { kani::internal::Pointer::assignable(kani::internal::untracked_deref(&(#attr))) } = kani::any_modifies();)*
#(unsafe{kani::internal::write_any(kani::internal::Pointer::assignable(kani::internal::untracked_deref(&#attr)))};)*
#(#after)*
#result
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
assertion\
- Status: SUCCESS\
- Description: "|_| x.iter().map(|v| *v == 0).fold(true,|a,b|a&b)"\

VERIFICATION:- SUCCESSFUL
Loading
Loading