Skip to content

Commit 946ea80

Browse files
authored
Allow modifies clause for verification only (rust-lang#3098)
Allow contracts to be used for verification, even if it is not suitable for stubbing. For that, we remove the requirement that modifies and return types of a function annotated with contracts must implement `kani::Arbitrary`, since that is only needed for recursion and stubbing with contract. This is done via a new intrinsic `any_modifies` to Kani that should only be used by contract instrumentation. The `T: Arbitrary` requirement is only checked when users try to use the contract as stub or to check recursive functions. For now, we also require users to annotate their contracts with `kani::recursion` if they want to use inductive reasoning to verify a recursive function. Resolves model-checking/kani#2997. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Signed-off-by: Felipe R. Monteiro <[email protected]>
1 parent 8401fbb commit 946ea80

30 files changed

+314
-205
lines changed

kani-compiler/src/kani_middle/attributes.rs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ enum KaniAttributeKind {
7070
/// expanded with additional pointer arguments that are not used in the function
7171
/// but referenced by the `modifies` annotation.
7272
InnerCheck,
73+
/// Attribute used to mark contracts for functions with recursion.
74+
/// We use this attribute to properly instantiate `kani::any_modifies` in
75+
/// cases when recursion is present given our contracts instrumentation.
76+
Recursion,
7377
}
7478

7579
impl KaniAttributeKind {
@@ -84,6 +88,7 @@ impl KaniAttributeKind {
8488
| KaniAttributeKind::StubVerified
8589
| KaniAttributeKind::Unwind => true,
8690
KaniAttributeKind::Unstable
91+
| KaniAttributeKind::Recursion
8792
| KaniAttributeKind::ReplacedWith
8893
| KaniAttributeKind::CheckedWith
8994
| KaniAttributeKind::Modifies
@@ -102,13 +107,6 @@ impl KaniAttributeKind {
102107
pub fn demands_function_contract_use(self) -> bool {
103108
matches!(self, KaniAttributeKind::ProofForContract)
104109
}
105-
106-
/// Would this attribute be placed on a function as part of a function
107-
/// contract. E.g. created by `requires`, `ensures`.
108-
pub fn is_function_contract(self) -> bool {
109-
use KaniAttributeKind::*;
110-
matches!(self, CheckedWith | IsContractGenerated)
111-
}
112110
}
113111

114112
/// Bundles together common data used when evaluating the attributes of a given
@@ -200,6 +198,14 @@ impl<'tcx> KaniAttributes<'tcx> {
200198
.collect()
201199
}
202200

201+
pub(crate) fn is_contract_generated(&self) -> bool {
202+
self.map.contains_key(&KaniAttributeKind::IsContractGenerated)
203+
}
204+
205+
pub(crate) fn has_recursion(&self) -> bool {
206+
self.map.contains_key(&KaniAttributeKind::Recursion)
207+
}
208+
203209
/// Parse and extract the `proof_for_contract(TARGET)` attribute. The
204210
/// returned symbol and DefId are respectively the name and id of `TARGET`,
205211
/// the span in the span for the attribute (contents).
@@ -316,6 +322,12 @@ impl<'tcx> KaniAttributes<'tcx> {
316322
expect_no_args(self.tcx, kind, attr);
317323
})
318324
}
325+
KaniAttributeKind::Recursion => {
326+
expect_single(self.tcx, kind, &attrs);
327+
attrs.iter().for_each(|attr| {
328+
expect_no_args(self.tcx, kind, attr);
329+
})
330+
}
319331
KaniAttributeKind::Solver => {
320332
expect_single(self.tcx, kind, &attrs);
321333
attrs.iter().for_each(|attr| {
@@ -452,6 +464,9 @@ impl<'tcx> KaniAttributes<'tcx> {
452464
self.map.iter().fold(HarnessAttributes::default(), |mut harness, (kind, attributes)| {
453465
match kind {
454466
KaniAttributeKind::ShouldPanic => harness.should_panic = true,
467+
KaniAttributeKind::Recursion => {
468+
self.tcx.dcx().span_err(self.tcx.def_span(self.item), "The attribute `kani::recursion` should only be used in combination with function contracts.");
469+
},
455470
KaniAttributeKind::Solver => {
456471
harness.solver = parse_solver(self.tcx, attributes[0]);
457472
}
@@ -661,12 +676,6 @@ fn has_kani_attribute<F: Fn(KaniAttributeKind) -> bool>(
661676
tcx.get_attrs_unchecked(def_id).iter().filter_map(|a| attr_kind(tcx, a)).any(predicate)
662677
}
663678

664-
/// Test if this function was generated by expanding a contract attribute like
665-
/// `requires` and `ensures`.
666-
pub fn is_function_contract_generated(tcx: TyCtxt, def_id: DefId) -> bool {
667-
has_kani_attribute(tcx, def_id, KaniAttributeKind::is_function_contract)
668-
}
669-
670679
/// Same as [`KaniAttributes::is_harness`] but more efficient because less
671680
/// attribute parsing is performed.
672681
pub fn is_proof_harness(tcx: TyCtxt, instance: InstanceStable) -> bool {

kani-compiler/src/kani_middle/mod.rs

Lines changed: 2 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ use rustc_span::source_map::respan;
2121
use rustc_span::Span;
2222
use rustc_target::abi::call::FnAbi;
2323
use rustc_target::abi::{HasDataLayout, TargetDataLayout};
24-
use stable_mir::mir::mono::{Instance, InstanceKind, MonoItem};
25-
use stable_mir::mir::pretty::pretty_ty;
26-
use stable_mir::ty::{BoundVariableKind, FnDef, RigidTy, Span as SpanStable, Ty, TyKind};
27-
use stable_mir::visitor::{Visitable, Visitor as TypeVisitor};
24+
use stable_mir::mir::mono::{InstanceKind, MonoItem};
25+
use stable_mir::ty::{FnDef, RigidTy, Span as SpanStable, TyKind};
2826
use stable_mir::{CrateDef, DefId};
2927
use std::fs::File;
3028
use std::io::BufWriter;
@@ -89,108 +87,10 @@ pub fn check_reachable_items(tcx: TyCtxt, queries: &QueryDb, items: &[MonoItem])
8987
.check_unstable_features(&queries.args().unstable_features);
9088
def_ids.insert(def_id);
9189
}
92-
93-
// We don't short circuit here since this is a type check and can shake
94-
// out differently depending on generic parameters.
95-
if let MonoItem::Fn(instance) = item {
96-
if attributes::is_function_contract_generated(
97-
tcx,
98-
rustc_internal::internal(tcx, def_id),
99-
) {
100-
check_is_contract_safe(tcx, *instance);
101-
}
102-
}
10390
}
10491
tcx.dcx().abort_if_errors();
10592
}
10693

107-
/// A basic check that ensures a function with a contract does not receive
108-
/// mutable pointers in its input and does not return raw pointers of any kind.
109-
///
110-
/// This is a temporary safety measure because contracts cannot yet reason
111-
/// about the heap.
112-
fn check_is_contract_safe(tcx: TyCtxt, instance: Instance) {
113-
struct NoMutPtr<'tcx> {
114-
tcx: TyCtxt<'tcx>,
115-
is_prohibited: fn(Ty) -> bool,
116-
/// Where (top level) did the type we're analyzing come from. Used for
117-
/// composing error messages.
118-
r#where: &'static str,
119-
/// Adjective to describe the kind of pointer we're prohibiting.
120-
/// Essentially `is_prohibited` but in English.
121-
what: &'static str,
122-
}
123-
124-
impl<'tcx> TypeVisitor for NoMutPtr<'tcx> {
125-
type Break = ();
126-
fn visit_ty(&mut self, ty: &Ty) -> std::ops::ControlFlow<Self::Break> {
127-
if (self.is_prohibited)(*ty) {
128-
// TODO make this more user friendly
129-
self.tcx.dcx().err(format!(
130-
"{} contains a {}pointer ({}). This is prohibited for functions with contracts, \
131-
as they cannot yet reason about the pointer behavior.", self.r#where, self.what,
132-
pretty_ty(ty.kind())));
133-
}
134-
135-
// Rust's type visitor only recurses into type arguments, (e.g.
136-
// `generics` in this match). This is enough for many types, but it
137-
// won't look at the field types of structs or enums. So we override
138-
// it here and do that ourselves.
139-
//
140-
// Since the field types also must contain in some form all the type
141-
// arguments the visitor will see them as it inspects the fields and
142-
// we don't need to call back to `super`.
143-
if let TyKind::RigidTy(RigidTy::Adt(adt_def, generics)) = ty.kind() {
144-
for variant in adt_def.variants() {
145-
for field in &variant.fields() {
146-
self.visit_ty(&field.ty_with_args(&generics))?;
147-
}
148-
}
149-
std::ops::ControlFlow::Continue(())
150-
} else {
151-
// For every other type.
152-
ty.super_visit(self)
153-
}
154-
}
155-
}
156-
157-
fn is_raw_mutable_ptr(ty: Ty) -> bool {
158-
let kind = ty.kind();
159-
kind.is_raw_ptr() && kind.is_mutable_ptr()
160-
}
161-
162-
fn is_raw_ptr(ty: Ty) -> bool {
163-
let kind = ty.kind();
164-
kind.is_raw_ptr()
165-
}
166-
167-
// TODO: Replace this with fn_abi.
168-
// https://github.com/model-checking/kani/issues/1365
169-
let bound_fn_sig = instance.ty().kind().fn_sig().unwrap();
170-
171-
for var in &bound_fn_sig.bound_vars {
172-
if let BoundVariableKind::Ty(t) = var {
173-
tcx.dcx().span_err(
174-
rustc_internal::internal(tcx, instance.def.span()),
175-
format!("Found a bound type variable {t:?} after monomorphization"),
176-
);
177-
}
178-
}
179-
180-
let fn_typ = bound_fn_sig.skip_binder();
181-
182-
for (input_ty, (is_prohibited, r#where, what)) in fn_typ
183-
.inputs()
184-
.iter()
185-
.copied()
186-
.zip(std::iter::repeat((is_raw_mutable_ptr as fn(_) -> _, "This argument", "mutable ")))
187-
.chain([(fn_typ.output(), (is_raw_ptr as fn(_) -> _, "The return", ""))])
188-
{
189-
let mut v = NoMutPtr { tcx, is_prohibited, r#where, what };
190-
v.visit_ty(&input_ty);
191-
}
192-
}
193-
19494
/// Print MIR for the reachable items if the `--emit mir` option was provided to rustc.
19595
pub fn dump_mir_items(tcx: TyCtxt, items: &[MonoItem], output: &Path) {
19696
/// Convert MonoItem into a DefId.

kani-compiler/src/kani_middle/provide.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use rustc_middle::util::Providers;
1515
use rustc_middle::{mir::Body, query::queries, ty::TyCtxt};
1616
use stable_mir::mir::mono::MonoItem;
1717

18+
use crate::kani_middle::KaniAttributes;
19+
1820
/// Sets up rustc's query mechanism to apply Kani's custom queries to code from
1921
/// a crate.
2022
pub fn provide(providers: &mut Providers, queries: &QueryDb) {
@@ -61,6 +63,17 @@ fn run_kani_mir_passes<'tcx>(
6163
tracing::debug!(?def_id, "Run Kani transformation passes");
6264
let mut transformed_body = stubbing::transform(tcx, def_id, body);
6365
stubbing::transform_foreign_functions(tcx, &mut transformed_body);
66+
let item_attributes = KaniAttributes::for_item(tcx, def_id);
67+
// If we apply `transform_any_modifies` in all contract-generated items,
68+
// we will ended up instantiating `kani::any_modifies` for the replace function
69+
// every time, even if we are only checking the contract, because the function
70+
// is always included during contract instrumentation. Thus, we must only apply
71+
// the transformation if we are using a verified stub or in the presence of recursion.
72+
if item_attributes.is_contract_generated()
73+
&& (stubbing::get_stub_key(tcx, def_id).is_some() || item_attributes.has_recursion())
74+
{
75+
stubbing::transform_any_modifies(tcx, &mut transformed_body);
76+
}
6477
// This should be applied after stubbing so user stubs take precedence.
6578
ModelIntrinsics::run_pass(tcx, &mut transformed_body);
6679
tcx.arena.alloc(transformed_body)

kani-compiler/src/kani_middle/reachability.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use stable_mir::ty::{Allocation, ClosureKind, ConstantKind, RigidTy, Ty, TyKind}
3434
use stable_mir::CrateItem;
3535
use stable_mir::{CrateDef, ItemKind};
3636

37+
use crate::kani_middle::attributes::matches_diagnostic as matches_function;
3738
use crate::kani_middle::coercion;
3839
use crate::kani_middle::coercion::CoercionBase;
3940
use crate::kani_middle::stubbing::{get_stub, validate_instance};
@@ -440,6 +441,24 @@ impl<'a, 'tcx> MirVisitor for MonoItemsFnCollector<'a, 'tcx> {
440441
callee,
441442
),
442443
);
444+
} else if matches_function(self.tcx, self.instance.def, "KaniAny") {
445+
let receiver_ty = args.0[0].expect_ty();
446+
let sep = callee.rfind("::").unwrap();
447+
let trait_ = &callee[..sep];
448+
self.tcx.dcx().span_err(
449+
rustc_internal::internal(self.tcx, terminator.span),
450+
format!(
451+
"`{}` doesn't implement \
452+
`{}`. Callee: `{}`\nPlease, check whether the type of all \
453+
objects in the modifies clause (including return types) \
454+
implement `{}`.\nThis is a strict condition to use \
455+
function contracts as verified stubs.",
456+
pretty_ty(receiver_ty.kind()),
457+
trait_,
458+
callee,
459+
trait_,
460+
),
461+
);
443462
} else {
444463
panic!("unable to resolve call to `{callee}` in `{caller}`")
445464
}

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

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ use tracing::debug;
2323
/// Returns the `DefId` of the stub for the function/method identified by the
2424
/// parameter `def_id`, and `None` if the function/method is not stubbed.
2525
pub fn get_stub(tcx: TyCtxt, def_id: DefId) -> Option<DefId> {
26-
let mapping = get_stub_mapping(tcx)?;
27-
mapping.get(&def_id).copied()
26+
let stub_map = get_stub_mapping(tcx)?;
27+
stub_map.get(&def_id).copied()
28+
}
29+
30+
pub fn get_stub_key(tcx: TyCtxt, def_id: DefId) -> Option<DefId> {
31+
let stub_map = get_stub_mapping(tcx)?;
32+
stub_map.iter().find_map(|(&key, &val)| if val == def_id { Some(key) } else { None })
2833
}
2934

3035
/// Returns the new body of a function/method if it has been stubbed out;
@@ -56,6 +61,48 @@ pub fn transform_foreign_functions<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx
5661
}
5762
}
5863

64+
/// Traverse `body` searching for calls to `kani::any_modifies` and replace these calls
65+
/// with calls to `kani::any`. This happens as a separate step as it is only necessary
66+
/// for contract-generated functions.
67+
pub fn transform_any_modifies<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
68+
let mut visitor = AnyModifiesTransformer { tcx, local_decls: body.clone().local_decls };
69+
visitor.visit_body(body);
70+
}
71+
72+
struct AnyModifiesTransformer<'tcx> {
73+
/// The compiler context.
74+
tcx: TyCtxt<'tcx>,
75+
/// Local declarations of the callee function. Kani searches here for foreign functions.
76+
local_decls: IndexVec<Local, LocalDecl<'tcx>>,
77+
}
78+
79+
impl<'tcx> MutVisitor<'tcx> for AnyModifiesTransformer<'tcx> {
80+
fn tcx(&self) -> TyCtxt<'tcx> {
81+
self.tcx
82+
}
83+
84+
fn visit_operand(&mut self, operand: &mut Operand<'tcx>, _location: Location) {
85+
let func_ty = operand.ty(&self.local_decls, self.tcx);
86+
if let ty::FnDef(reachable_function, arguments) = *func_ty.kind() {
87+
if let Some(any_modifies) = self.tcx.get_diagnostic_name(reachable_function)
88+
&& any_modifies.as_str() == "KaniAnyModifies"
89+
{
90+
let Operand::Constant(function_definition) = operand else {
91+
return;
92+
};
93+
let kani_any_symbol = self
94+
.tcx
95+
.get_diagnostic_item(rustc_span::symbol::Symbol::intern("KaniAny"))
96+
.expect("We should have a `kani::any()` definition at this point.");
97+
function_definition.const_ = Const::from_value(
98+
ConstValue::ZeroSized,
99+
self.tcx.type_of(kani_any_symbol).instantiate(self.tcx, arguments),
100+
);
101+
}
102+
}
103+
}
104+
}
105+
59106
struct ForeignFunctionTransformer<'tcx> {
60107
/// The compiler context.
61108
tcx: TyCtxt<'tcx>,

library/kani/src/lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,27 @@ pub const fn cover(_cond: bool, _msg: &'static str) {}
156156
/// Note: This is a safe construct and can only be used with types that implement the `Arbitrary`
157157
/// trait. The Arbitrary trait is used to build a symbolic value that represents all possible
158158
/// valid values for type `T`.
159+
#[rustc_diagnostic_item = "KaniAny"]
159160
#[inline(always)]
160161
pub fn any<T: Arbitrary>() -> T {
161162
T::any()
162163
}
163164

165+
/// This function is only used for function contract instrumentation.
166+
/// It behaves exaclty like `kani::any<T>()`, except it will check for the trait bounds
167+
/// at compilation time. It allows us to avoid type checking errors while using function
168+
/// contracts only for verification.
169+
#[rustc_diagnostic_item = "KaniAnyModifies"]
170+
#[inline(never)]
171+
#[doc(hidden)]
172+
pub fn any_modifies<T>() -> T {
173+
// This function should not be reacheable.
174+
// Users must include `#[kani::recursion]` in any function contracts for recursive functions;
175+
// otherwise, this might not be properly instantiate. We mark this as unreachable to make
176+
// sure Kani doesn't report any false positives.
177+
unreachable!()
178+
}
179+
164180
/// This creates a symbolic *valid* value of type `T`.
165181
/// The value is constrained to be a value accepted by the predicate passed to the filter.
166182
/// You can assign the return value of this function to a variable that you want to make symbolic.

library/kani_macros/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ proc-macro2 = "1.0"
1616
proc-macro-error = "1.0.4"
1717
quote = "1.0.20"
1818
syn = { version = "2.0.18", features = ["full", "visit-mut", "visit"] }
19+
20+
[package.metadata.rust-analyzer]
21+
# This package uses rustc crates.
22+
rustc_private=true

library/kani_macros/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ pub fn should_panic(attr: TokenStream, item: TokenStream) -> TokenStream {
5252
attr_impl::should_panic(attr, item)
5353
}
5454

55+
/// Specifies that a function contains recursion for contract instrumentation.**
56+
///
57+
/// This attribute is only used for function-contract instrumentation. Kani uses
58+
/// this annotation to identify recursive functions and properly instantiate
59+
/// `kani::any_modifies` to check such functions using induction.
60+
#[proc_macro_attribute]
61+
pub fn recursion(attr: TokenStream, item: TokenStream) -> TokenStream {
62+
attr_impl::recursion(attr, item)
63+
}
64+
5565
/// Set Loop unwind limit for proof harnesses
5666
/// The attribute `#[kani::unwind(arg)]` can only be called alongside `#[kani::proof]`.
5767
/// arg - Takes in a integer value (u32) that represents the unwind value for the harness.
@@ -331,6 +341,7 @@ mod sysroot {
331341
}
332342

333343
kani_attribute!(should_panic, no_args);
344+
kani_attribute!(recursion, no_args);
334345
kani_attribute!(solver);
335346
kani_attribute!(stub);
336347
kani_attribute!(unstable);
@@ -363,6 +374,7 @@ mod regular {
363374
}
364375

365376
no_op!(should_panic);
377+
no_op!(recursion);
366378
no_op!(solver);
367379
no_op!(stub);
368380
no_op!(unstable);

0 commit comments

Comments
 (0)