Skip to content

Commit 852fd8f

Browse files
pi314mmMatias Scharagercelinvalfeliperodri
authored
Function Contracts: Modify Slices (rust-lang#3295)
Using the `__CPROVER_object_upto` function to pass modifies clauses to asserts clauses in goto for rust slices. Resolves rust-lang#2908 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Matias Scharager <[email protected]> Co-authored-by: Celina G. Val <[email protected]> Co-authored-by: Felipe R. Monteiro <[email protected]>
1 parent b1681e7 commit 852fd8f

File tree

14 files changed

+314
-29
lines changed

14 files changed

+314
-29
lines changed

kani-compiler/src/codegen_cprover_gotoc/codegen/contract.rs

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use crate::codegen_cprover_gotoc::GotocCtx;
44
use crate::kani_middle::attributes::KaniAttributes;
55
use cbmc::goto_program::FunctionContract;
6-
use cbmc::goto_program::{Lambda, Location};
6+
use cbmc::goto_program::{Expr, Lambda, Location, Type};
77
use kani_metadata::AssignsContract;
88
use rustc_hir::def_id::DefId as InternalDefId;
99
use rustc_smir::rustc_internal;
@@ -12,6 +12,8 @@ use stable_mir::mir::Local;
1212
use stable_mir::CrateDef;
1313
use tracing::debug;
1414

15+
use stable_mir::ty::{RigidTy, TyKind};
16+
1517
impl<'tcx> GotocCtx<'tcx> {
1618
/// Given the `proof_for_contract` target `function_under_contract` and the reachable `items`,
1719
/// find or create the `AssignsContract` that needs to be enforced and attach it to the symbol
@@ -142,11 +144,63 @@ impl<'tcx> GotocCtx<'tcx> {
142144
let assigns = modified_places
143145
.into_iter()
144146
.map(|local| {
145-
Lambda::as_contract_for(
146-
&goto_annotated_fn_typ,
147-
None,
148-
self.codegen_place_stable(&local.into(), loc).unwrap().goto_expr.dereference(),
149-
)
147+
if self.is_fat_pointer_stable(self.local_ty_stable(local)) {
148+
let unref = match self.local_ty_stable(local).kind() {
149+
TyKind::RigidTy(RigidTy::Ref(_, ty, _)) => ty,
150+
kind => unreachable!("{:?} is not a reference", kind),
151+
};
152+
let size = match unref.kind() {
153+
TyKind::RigidTy(RigidTy::Slice(elt_type)) => {
154+
elt_type.layout().unwrap().shape().size.bytes()
155+
}
156+
TyKind::RigidTy(RigidTy::Str) => 1,
157+
// For adt, see https://rust-lang.zulipchat.com/#narrow/stream/182449-t-compiler.2Fhelp
158+
TyKind::RigidTy(RigidTy::Adt(..)) => {
159+
todo!("Adt fat pointers not implemented")
160+
}
161+
kind => unreachable!("Generating a slice fat pointer to {:?}", kind),
162+
};
163+
Lambda::as_contract_for(
164+
&goto_annotated_fn_typ,
165+
None,
166+
Expr::symbol_expression(
167+
"__CPROVER_object_upto",
168+
Type::code(
169+
vec![
170+
Type::empty()
171+
.to_pointer()
172+
.as_parameter(None, Some("ptr".into())),
173+
Type::size_t().as_parameter(None, Some("size".into())),
174+
],
175+
Type::empty(),
176+
),
177+
)
178+
.call(vec![
179+
self.codegen_place_stable(&local.into(), loc)
180+
.unwrap()
181+
.goto_expr
182+
.member("data", &self.symbol_table)
183+
.cast_to(Type::empty().to_pointer()),
184+
self.codegen_place_stable(&local.into(), loc)
185+
.unwrap()
186+
.goto_expr
187+
.member("len", &self.symbol_table)
188+
.mul(Expr::size_constant(
189+
size.try_into().unwrap(),
190+
&self.symbol_table,
191+
)),
192+
]),
193+
)
194+
} else {
195+
Lambda::as_contract_for(
196+
&goto_annotated_fn_typ,
197+
None,
198+
self.codegen_place_stable(&local.into(), loc)
199+
.unwrap()
200+
.goto_expr
201+
.dereference(),
202+
)
203+
}
150204
})
151205
.chain(shadow_memory_assign)
152206
.collect();

kani-compiler/src/kani_middle/transform/contracts.rs

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,29 @@ use rustc_middle::ty::TyCtxt;
1111
use rustc_smir::rustc_internal;
1212
use stable_mir::mir::mono::Instance;
1313
use stable_mir::mir::{Body, ConstOperand, Operand, TerminatorKind};
14-
use stable_mir::ty::{FnDef, MirConst, RigidTy, TyKind};
14+
use stable_mir::ty::{FnDef, MirConst, RigidTy, TyKind, TypeAndMut};
1515
use stable_mir::{CrateDef, DefId};
1616
use std::collections::HashSet;
1717
use std::fmt::Debug;
1818
use tracing::{debug, trace};
1919

20-
/// Check if we can replace calls to any_modifies.
20+
/// Check if we can replace calls to any_modifies or write_any.
2121
///
2222
/// This pass will replace the entire body, and it should only be applied to stubs
2323
/// that have a body.
24+
///
25+
/// write_any is replaced with one of write_any_slim, write_any_slice, or write_any_str
26+
/// depending on what the type of the input it
27+
///
28+
/// any_modifies is replaced with any
2429
#[derive(Debug)]
2530
pub struct AnyModifiesPass {
2631
kani_any: Option<FnDef>,
2732
kani_any_modifies: Option<FnDef>,
33+
kani_write_any: Option<FnDef>,
34+
kani_write_any_slim: Option<FnDef>,
35+
kani_write_any_slice: Option<FnDef>,
36+
kani_write_any_str: Option<FnDef>,
2837
stubbed: HashSet<DefId>,
2938
target_fn: Option<InternedString>,
3039
}
@@ -78,6 +87,18 @@ impl AnyModifiesPass {
7887
let kani_any_modifies = tcx
7988
.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniAnyModifies"))
8089
.map(item_fn_def);
90+
let kani_write_any = tcx
91+
.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAny"))
92+
.map(item_fn_def);
93+
let kani_write_any_slim = tcx
94+
.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAnySlim"))
95+
.map(item_fn_def);
96+
let kani_write_any_slice = tcx
97+
.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAnySlice"))
98+
.map(item_fn_def);
99+
let kani_write_any_str = tcx
100+
.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniWriteAnyStr"))
101+
.map(item_fn_def);
81102
let (target_fn, stubbed) = if let Some(harness) = unit.harnesses.first() {
82103
let attributes = KaniAttributes::for_instance(tcx, *harness);
83104
let target_fn =
@@ -86,7 +107,16 @@ impl AnyModifiesPass {
86107
} else {
87108
(None, HashSet::new())
88109
};
89-
AnyModifiesPass { kani_any, kani_any_modifies, target_fn, stubbed }
110+
AnyModifiesPass {
111+
kani_any,
112+
kani_any_modifies,
113+
kani_write_any,
114+
kani_write_any_slim,
115+
kani_write_any_slice,
116+
kani_write_any_str,
117+
target_fn,
118+
stubbed,
119+
}
90120
}
91121

92122
/// If we apply `transform_any_modifies` in all contract-generated items,
@@ -105,7 +135,7 @@ impl AnyModifiesPass {
105135
let mut changed = false;
106136
let locals = body.locals().to_vec();
107137
for bb in body.blocks.iter_mut() {
108-
let TerminatorKind::Call { func, .. } = &mut bb.terminator.kind else { continue };
138+
let TerminatorKind::Call { func, args, .. } = &mut bb.terminator.kind else { continue };
109139
if let TyKind::RigidTy(RigidTy::FnDef(def, instance_args)) =
110140
func.ty(&locals).unwrap().kind()
111141
&& Some(def) == self.kani_any_modifies
@@ -117,6 +147,47 @@ impl AnyModifiesPass {
117147
*func = Operand::Constant(new_func);
118148
changed = true;
119149
}
150+
151+
// if this is a valid kani::write_any function
152+
if let TyKind::RigidTy(RigidTy::FnDef(def, instance_args)) =
153+
func.ty(&locals).unwrap().kind()
154+
&& Some(def) == self.kani_write_any
155+
&& args.len() == 1
156+
&& let Some(fn_sig) = func.ty(&locals).unwrap().kind().fn_sig()
157+
&& let Some(TypeAndMut { ty: internal_type, mutability: _ }) =
158+
fn_sig.skip_binder().inputs()[0].kind().builtin_deref(true)
159+
{
160+
// case on the type of the input
161+
if let TyKind::RigidTy(RigidTy::Slice(_)) = internal_type.kind() {
162+
//if the input is a slice, use write_any_slice
163+
let instance =
164+
Instance::resolve(self.kani_write_any_slice.unwrap(), &instance_args)
165+
.unwrap();
166+
let literal = MirConst::try_new_zero_sized(instance.ty()).unwrap();
167+
let span = bb.terminator.span;
168+
let new_func = ConstOperand { span, user_ty: None, const_: literal };
169+
*func = Operand::Constant(new_func);
170+
} else if let TyKind::RigidTy(RigidTy::Str) = internal_type.kind() {
171+
//if the input is a str, use write_any_str
172+
let instance =
173+
Instance::resolve(self.kani_write_any_str.unwrap(), &instance_args)
174+
.unwrap();
175+
let literal = MirConst::try_new_zero_sized(instance.ty()).unwrap();
176+
let span = bb.terminator.span;
177+
let new_func = ConstOperand { span, user_ty: None, const_: literal };
178+
*func = Operand::Constant(new_func);
179+
} else {
180+
//otherwise, use write_any_slim
181+
let instance =
182+
Instance::resolve(self.kani_write_any_slim.unwrap(), &instance_args)
183+
.unwrap();
184+
let literal = MirConst::try_new_zero_sized(instance.ty()).unwrap();
185+
let span = bb.terminator.span;
186+
let new_func = ConstOperand { span, user_ty: None, const_: literal };
187+
*func = Operand::Constant(new_func);
188+
}
189+
changed = true;
190+
}
120191
}
121192
(changed, body)
122193
}

library/kani/src/internal.rs

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,69 @@
11
// Copyright Kani Contributors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

4+
use crate::arbitrary::Arbitrary;
5+
use std::ptr;
6+
47
/// Helper trait for code generation for `modifies` contracts.
58
///
69
/// We allow the user to provide us with a pointer-like object that we convert as needed.
710
#[doc(hidden)]
811
pub trait Pointer<'a> {
912
/// Type of the pointed-to data
10-
type Inner;
13+
type Inner: ?Sized;
1114

1215
/// Used for checking assigns contracts where we pass immutable references to the function.
1316
///
1417
/// We're using a reference to self here, because the user can use just a plain function
1518
/// argument, for instance one of type `&mut _`, in the `modifies` clause which would move it.
1619
unsafe fn decouple_lifetime(&self) -> &'a Self::Inner;
1720

18-
/// used for havocking on replecement of a `modifies` clause.
19-
unsafe fn assignable(self) -> &'a mut Self::Inner;
21+
unsafe fn assignable(self) -> *mut Self::Inner;
2022
}
2123

22-
impl<'a, 'b, T> Pointer<'a> for &'b T {
24+
impl<'a, 'b, T: ?Sized> Pointer<'a> for &'b T {
2325
type Inner = T;
2426
unsafe fn decouple_lifetime(&self) -> &'a Self::Inner {
2527
std::mem::transmute(*self)
2628
}
2729

28-
#[allow(clippy::transmute_ptr_to_ref)]
29-
unsafe fn assignable(self) -> &'a mut Self::Inner {
30+
unsafe fn assignable(self) -> *mut Self::Inner {
3031
std::mem::transmute(self as *const T)
3132
}
3233
}
3334

34-
impl<'a, 'b, T> Pointer<'a> for &'b mut T {
35+
impl<'a, 'b, T: ?Sized> Pointer<'a> for &'b mut T {
3536
type Inner = T;
3637

3738
#[allow(clippy::transmute_ptr_to_ref)]
3839
unsafe fn decouple_lifetime(&self) -> &'a Self::Inner {
3940
std::mem::transmute::<_, &&'a T>(self)
4041
}
4142

42-
unsafe fn assignable(self) -> &'a mut Self::Inner {
43-
std::mem::transmute(self)
43+
unsafe fn assignable(self) -> *mut Self::Inner {
44+
self as *mut T
4445
}
4546
}
4647

47-
impl<'a, T> Pointer<'a> for *const T {
48+
impl<'a, T: ?Sized> Pointer<'a> for *const T {
4849
type Inner = T;
4950
unsafe fn decouple_lifetime(&self) -> &'a Self::Inner {
5051
&**self as &'a T
5152
}
5253

53-
#[allow(clippy::transmute_ptr_to_ref)]
54-
unsafe fn assignable(self) -> &'a mut Self::Inner {
54+
unsafe fn assignable(self) -> *mut Self::Inner {
5555
std::mem::transmute(self)
5656
}
5757
}
5858

59-
impl<'a, T> Pointer<'a> for *mut T {
59+
impl<'a, T: ?Sized> Pointer<'a> for *mut T {
6060
type Inner = T;
6161
unsafe fn decouple_lifetime(&self) -> &'a Self::Inner {
6262
&**self as &'a T
6363
}
6464

65-
#[allow(clippy::transmute_ptr_to_ref)]
66-
unsafe fn assignable(self) -> &'a mut Self::Inner {
67-
std::mem::transmute(self)
65+
unsafe fn assignable(self) -> *mut Self::Inner {
66+
self
6867
}
6968
}
7069

@@ -97,3 +96,49 @@ pub fn init_contracts() {}
9796
pub fn apply_closure<T, U: Fn(&T) -> bool>(f: U, x: &T) -> bool {
9897
f(x)
9998
}
99+
100+
/// Recieves a reference to a pointer-like object and assigns kani::any_modifies to that object.
101+
/// Only for use within function contracts and will not be replaced if the recursive or function stub
102+
/// replace contracts are not used.
103+
#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")]
104+
#[rustc_diagnostic_item = "KaniWriteAny"]
105+
#[inline(never)]
106+
#[doc(hidden)]
107+
pub unsafe fn write_any<T: ?Sized>(_pointer: *mut T) {
108+
// This function should not be reacheable.
109+
// Users must include `#[kani::recursion]` in any function contracts for recursive functions;
110+
// otherwise, this might not be properly instantiate. We mark this as unreachable to make
111+
// sure Kani doesn't report any false positives.
112+
unreachable!()
113+
}
114+
115+
/// Fill in a slice with kani::any.
116+
/// Intended as a post compilation replacement for write_any
117+
#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")]
118+
#[rustc_diagnostic_item = "KaniWriteAnySlice"]
119+
#[inline(always)]
120+
pub unsafe fn write_any_slice<T: Arbitrary>(slice: *mut [T]) {
121+
(*slice).fill_with(T::any)
122+
}
123+
124+
/// Fill in a pointer with kani::any.
125+
/// Intended as a post compilation replacement for write_any
126+
#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")]
127+
#[rustc_diagnostic_item = "KaniWriteAnySlim"]
128+
#[inline(always)]
129+
pub unsafe fn write_any_slim<T: Arbitrary>(pointer: *mut T) {
130+
ptr::write(pointer, T::any())
131+
}
132+
133+
/// Fill in a str with kani::any.
134+
/// Intended as a post compilation replacement for write_any.
135+
/// Not yet implemented
136+
#[crate::unstable(feature = "function-contracts", issue = "none", reason = "function-contracts")]
137+
#[rustc_diagnostic_item = "KaniWriteAnyStr"]
138+
#[inline(always)]
139+
pub unsafe fn write_any_str(_s: *mut str) {
140+
//TODO: strings introduce new UB
141+
//(*s).as_bytes_mut().fill_with(u8::any)
142+
//TODO: String validation
143+
unimplemented!("Kani does not support creating arbitrary `str`")
144+
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,19 @@ impl<'a> ContractConditionsHandler<'a> {
156156
let sig = &mut self.annotated_fn.sig;
157157
for (arg, arg_type) in wrapper_args.clone().zip(type_params) {
158158
// Add the type parameter to the function signature's generic parameters list
159+
let mut bounds: syn::punctuated::Punctuated<syn::TypeParamBound, syn::token::Plus> =
160+
syn::punctuated::Punctuated::new();
161+
bounds.push(syn::TypeParamBound::Trait(syn::TraitBound {
162+
paren_token: None,
163+
modifier: syn::TraitBoundModifier::Maybe(Token![?](Span::call_site())),
164+
lifetimes: None,
165+
path: syn::Ident::new("Sized", Span::call_site()).into(),
166+
}));
159167
sig.generics.params.push(syn::GenericParam::Type(syn::TypeParam {
160168
attrs: vec![],
161169
ident: arg_type.clone(),
162-
colon_token: None,
163-
bounds: Default::default(),
170+
colon_token: Some(Token![:](Span::call_site())),
171+
bounds: bounds,
164172
eq_token: None,
165173
default: None,
166174
}));

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ impl<'a> ContractConditionsHandler<'a> {
9494
let result = Ident::new(INTERNAL_RESULT_IDENT, Span::call_site());
9595
quote!(
9696
#(#before)*
97-
#(*unsafe { kani::internal::Pointer::assignable(kani::internal::untracked_deref(&(#attr))) } = kani::any_modifies();)*
97+
#(unsafe{kani::internal::write_any(kani::internal::Pointer::assignable(kani::internal::untracked_deref(&#attr)))};)*
9898
#(#after)*
9999
#result
100100
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
assertion\
2+
- Status: SUCCESS\
3+
- Description: "|_| x.iter().map(|v| *v == 0).fold(true,|a,b|a&b)"\
4+
5+
VERIFICATION:- SUCCESSFUL

0 commit comments

Comments
 (0)