Skip to content

Commit 34b35d8

Browse files
authored
Fix contract of constant fn with effect feature (#3259)
We now check if the host effect index is present. If so, remove it before performing stub operation. Resolves #3258
1 parent ca110f7 commit 34b35d8

File tree

6 files changed

+78
-9
lines changed

6 files changed

+78
-9
lines changed

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

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc_smir::rustc_internal;
1717
use stable_mir::mir::mono::Instance;
1818
use stable_mir::mir::visit::{Location, MirVisitor};
1919
use stable_mir::mir::Constant;
20-
use stable_mir::ty::FnDef;
20+
use stable_mir::ty::{FnDef, RigidTy, TyKind};
2121
use stable_mir::{CrateDef, CrateItem};
2222

2323
use self::annotations::update_stub_mapping;
@@ -37,6 +37,24 @@ pub fn harness_stub_map(
3737
stub_pairs
3838
}
3939

40+
/// Retrieve the index of the host parameter if old definition has one, but not the new definition.
41+
///
42+
/// This is to allow constant functions to be stubbed by non-constant functions when the
43+
/// `effect` feature is on.
44+
///
45+
/// Note that the opposite is not supported today, but users should be able to change their stubs.
46+
///
47+
/// Note that this has no effect at runtime.
48+
pub fn contract_host_param(tcx: TyCtxt, old_def: FnDef, new_def: FnDef) -> Option<usize> {
49+
let old_generics = tcx.generics_of(rustc_internal::internal(tcx, old_def.def_id()));
50+
let new_generics = tcx.generics_of(rustc_internal::internal(tcx, new_def.def_id()));
51+
if old_generics.host_effect_index.is_some() && new_generics.host_effect_index.is_none() {
52+
old_generics.host_effect_index
53+
} else {
54+
None
55+
}
56+
}
57+
4058
/// Checks whether the stub is compatible with the original function/method: do
4159
/// the arities and types (of the parameters and return values) match up? This
4260
/// does **NOT** check whether the type variables are constrained to implement
@@ -61,15 +79,26 @@ pub fn check_compatibility(tcx: TyCtxt, old_def: FnDef, new_def: FnDef) -> Resul
6179
// Check whether the numbers of generic parameters match.
6280
let old_def_id = rustc_internal::internal(tcx, old_def.def_id());
6381
let new_def_id = rustc_internal::internal(tcx, new_def.def_id());
64-
let old_num_generics = tcx.generics_of(old_def_id).count();
65-
let stub_num_generics = tcx.generics_of(new_def_id).count();
66-
if old_num_generics != stub_num_generics {
82+
let old_ty = rustc_internal::stable(tcx.type_of(old_def_id)).value;
83+
let new_ty = rustc_internal::stable(tcx.type_of(new_def_id)).value;
84+
let TyKind::RigidTy(RigidTy::FnDef(_, mut old_args)) = old_ty.kind() else {
85+
unreachable!("Expected function, but found {old_ty}")
86+
};
87+
let TyKind::RigidTy(RigidTy::FnDef(_, new_args)) = new_ty.kind() else {
88+
unreachable!("Expected function, but found {new_ty}")
89+
};
90+
if let Some(idx) = contract_host_param(tcx, old_def, new_def) {
91+
old_args.0.remove(idx);
92+
}
93+
94+
// TODO: We should check for the parameter type too or replacement will fail.
95+
if old_args.0.len() != new_args.0.len() {
6796
let msg = format!(
6897
"mismatch in the number of generic parameters: original function/method `{}` takes {} generic parameters(s), stub `{}` takes {}",
6998
old_def.name(),
70-
old_num_generics,
99+
old_args.0.len(),
71100
new_def.name(),
72-
stub_num_generics
101+
new_args.0.len(),
73102
);
74103
return Err(msg);
75104
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! This module contains code related to the MIR-to-MIR pass that performs the
44
//! stubbing of functions and methods.
55
use crate::kani_middle::codegen_units::Stubs;
6-
use crate::kani_middle::stubbing::validate_stub_const;
6+
use crate::kani_middle::stubbing::{contract_host_param, validate_stub_const};
77
use crate::kani_middle::transform::body::{MutMirVisitor, MutableBody};
88
use crate::kani_middle::transform::{TransformPass, TransformationType};
99
use crate::kani_queries::QueryDb;
@@ -46,8 +46,12 @@ impl TransformPass for FnStubPass {
4646
fn transform(&self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) {
4747
trace!(function=?instance.name(), "transform");
4848
let ty = instance.ty();
49-
if let TyKind::RigidTy(RigidTy::FnDef(fn_def, args)) = ty.kind() {
49+
if let TyKind::RigidTy(RigidTy::FnDef(fn_def, mut args)) = ty.kind() {
5050
if let Some(replace) = self.stubs.get(&fn_def) {
51+
if let Some(idx) = contract_host_param(tcx, fn_def, *replace) {
52+
debug!(?idx, "FnStubPass::transform remove_host_param");
53+
args.0.remove(idx);
54+
}
5155
let new_instance = Instance::resolve(*replace, &args).unwrap();
5256
debug!(from=?instance.name(), to=?new_instance.name(), "FnStubPass::transform");
5357
if let Some(body) = FnStubValidator::validate(tcx, (fn_def, *replace), new_instance)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Checking harness check...
2+
VERIFICATION:- SUCCESSFUL
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Zfunction-contracts -Zmem-predicates
4+
5+
//! Check that Kani contract can be applied to a constant function.
6+
//! <https://github.com/model-checking/kani/issues/3258>
7+
8+
#![feature(effects)]
9+
10+
#[kani::requires(kani::mem::can_dereference(arg))]
11+
const unsafe fn dummy<T>(arg: *const T) -> T {
12+
std::ptr::read(arg)
13+
}
14+
15+
#[kani::proof_for_contract(dummy)]
16+
fn check() {
17+
unsafe { dummy(&kani::any::<u8>()) };
18+
}

tests/script-based-pre/verify_std_cmd/verify_std.expected

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ VERIFICATION:- SUCCESSFUL
66
Checking harness verify::harness...
77
VERIFICATION:- SUCCESSFUL
88

9+
Checking harness verify::check_dummy_read...
10+
VERIFICATION:- SUCCESSFUL
11+
912
Checking harness num::verify::check_non_zero...
1013
VERIFICATION:- SUCCESSFUL
1114

12-
Complete - 3 successfully verified harnesses, 0 failures, 3 total.
15+
Complete - 4 successfully verified harnesses, 0 failures, 4 total.

tests/script-based-pre/verify_std_cmd/verify_std.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ pub mod verify {
4747
fn fake_function(x: bool) -> bool {
4848
x
4949
}
50+
51+
#[kani::proof_for_contract(dummy_read)]
52+
fn check_dummy_read() {
53+
let val: char = kani::any();
54+
assert_eq!(unsafe { dummy_read(&val) }, val);
55+
}
56+
57+
/// Ensure we can verify constant functions.
58+
#[kani::requires(kani::mem::can_dereference(ptr))]
59+
#[rustc_diagnostic_item = "dummy_read"]
60+
const unsafe fn dummy_read<T: Copy>(ptr: *const T) -> T {
61+
*ptr
62+
}
5063
}
5164
'
5265

0 commit comments

Comments
 (0)