Skip to content

Commit 041beda

Browse files
authored
Implement proper function pointer handling for validity checks (#3606)
We previously had a `todo!()` for the validity check handling of function pointers. However, the validity check only cares about intrinsics calls, which can only be done directly. Thus, remove the `todo!()` and refactor the code so that it's clear why we don't care about that case. I added a test that I was working on when I bumped into this issue. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent b0f856c commit 041beda

File tree

3 files changed

+71
-50
lines changed

3 files changed

+71
-50
lines changed

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

Lines changed: 44 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use rustc_middle::ty::{Const, TyCtxt};
2424
use rustc_smir::rustc_internal;
2525
use stable_mir::CrateDef;
2626
use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape, WrappingRange};
27-
use stable_mir::mir::mono::{Instance, InstanceKind};
27+
use stable_mir::mir::mono::Instance;
2828
use stable_mir::mir::visit::{Location, PlaceContext, PlaceRef};
2929
use stable_mir::mir::{
3030
AggregateKind, BasicBlockIdx, BinOp, Body, CastKind, ConstOperand, FieldIdx, Local, LocalDecl,
@@ -385,50 +385,47 @@ impl MirVisitor for CheckValueVisitor<'_, '_> {
385385
// Note: For transmute, both Src and Dst must be valid type.
386386
// In this case, we need to save the Dst, and invoke super_terminator.
387387
self.super_terminator(term, location);
388-
let instance = expect_instance(self.locals, func);
389-
if instance.kind == InstanceKind::Intrinsic {
390-
match instance.intrinsic_name().unwrap().as_str() {
391-
"write_bytes" => {
392-
// The write bytes intrinsic may trigger UB in safe code.
393-
// pub unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize)
394-
// <https://doc.rust-lang.org/stable/core/intrinsics/fn.write_bytes.html>
395-
// This is an over-approximation since writing an invalid value is
396-
// not UB, only reading it will be.
397-
assert_eq!(
398-
args.len(),
399-
3,
400-
"Unexpected number of arguments for `write_bytes`"
401-
);
402-
let TyKind::RigidTy(RigidTy::RawPtr(target_ty, Mutability::Mut)) =
403-
args[0].ty(self.locals).unwrap().kind()
404-
else {
405-
unreachable!()
406-
};
407-
let validity = ty_validity_per_offset(&self.machine, target_ty, 0);
408-
match validity {
409-
Ok(ranges) if ranges.is_empty() => {}
410-
Ok(ranges) => {
411-
let sz = rustc_internal::stable(Const::from_target_usize(
412-
self.tcx,
413-
target_ty.layout().unwrap().shape().size.bytes() as u64,
414-
));
415-
self.push_target(SourceOp::BytesValidity {
416-
target_ty,
417-
rvalue: Rvalue::Repeat(args[1].clone(), sz),
418-
ranges,
419-
})
420-
}
421-
_ => self.push_target(SourceOp::UnsupportedCheck {
422-
check: "write_bytes".to_string(),
423-
ty: target_ty,
424-
}),
388+
match intrinsic_name(self.locals, func).as_deref() {
389+
Some("write_bytes") => {
390+
// The write bytes intrinsic may trigger UB in safe code.
391+
// pub unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize)
392+
// <https://doc.rust-lang.org/stable/core/intrinsics/fn.write_bytes.html>
393+
// This is an over-approximation since writing an invalid value is
394+
// not UB, only reading it will be.
395+
assert_eq!(
396+
args.len(),
397+
3,
398+
"Unexpected number of arguments for `write_bytes`"
399+
);
400+
let TyKind::RigidTy(RigidTy::RawPtr(target_ty, Mutability::Mut)) =
401+
args[0].ty(self.locals).unwrap().kind()
402+
else {
403+
unreachable!()
404+
};
405+
let validity = ty_validity_per_offset(&self.machine, target_ty, 0);
406+
match validity {
407+
Ok(ranges) if ranges.is_empty() => {}
408+
Ok(ranges) => {
409+
let sz = rustc_internal::stable(Const::from_target_usize(
410+
self.tcx,
411+
target_ty.layout().unwrap().shape().size.bytes() as u64,
412+
));
413+
self.push_target(SourceOp::BytesValidity {
414+
target_ty,
415+
rvalue: Rvalue::Repeat(args[1].clone(), sz),
416+
ranges,
417+
})
425418
}
419+
_ => self.push_target(SourceOp::UnsupportedCheck {
420+
check: "write_bytes".to_string(),
421+
ty: target_ty,
422+
}),
426423
}
427-
"transmute" | "transmute_copy" => {
428-
unreachable!("Should've been lowered")
429-
}
430-
_ => {}
431424
}
425+
Some("transmute") | Some("transmute_copy") => {
426+
unreachable!("Should've been lowered")
427+
}
428+
_ => {}
432429
}
433430
}
434431
TerminatorKind::Goto { .. }
@@ -741,16 +738,13 @@ fn assignment_check_points(
741738
invalid_ranges
742739
}
743740

744-
/// Retrieve instance for the given function operand.
741+
/// Retrieve the name of the intrinsic if this operand is an intrinsic.
745742
///
746-
/// This will panic if the operand is not a function or if it cannot be resolved.
747-
fn expect_instance(locals: &[LocalDecl], func: &Operand) -> Instance {
743+
/// Intrinsics can only be invoked directly, so we can safely ignore other operand types.
744+
fn intrinsic_name(locals: &[LocalDecl], func: &Operand) -> Option<String> {
748745
let ty = func.ty(locals).unwrap();
749-
match ty.kind() {
750-
TyKind::RigidTy(RigidTy::FnDef(def, args)) => Instance::resolve(def, &args).unwrap(),
751-
TyKind::RigidTy(RigidTy::FnPtr(sig)) => todo!("Add support to FnPtr: {sig:?}"),
752-
_ => unreachable!("Found: {func:?}"),
753-
}
746+
let TyKind::RigidTy(RigidTy::FnDef(def, args)) = ty.kind() else { return None };
747+
Instance::resolve(def, &args).unwrap().intrinsic_name()
754748
}
755749

756750
/// Instrument MIR to check the value pointed by `rvalue_ptr` satisfies requirement `req`.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2 of 2 cover properties satisfied
2+
3+
Complete - 3 successfully verified harnesses, 0 failures, 3 total.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Z valid-value-checks -Z mem-predicates
4+
//! Check that Kani can identify invalid value when using `can_dereference` API.
5+
6+
#[kani::proof]
7+
fn check_can_dereference_char() {
8+
let val: [u32; 2] = kani::any();
9+
kani::cover!(kani::mem::can_dereference(&val as *const _ as *const [char; 2]));
10+
kani::cover!(!kani::mem::can_dereference(&val as *const _ as *const [char; 2]));
11+
}
12+
13+
#[kani::proof]
14+
fn check_can_dereference_always_valid() {
15+
let val: [char; 2] = [kani::any(), kani::any()];
16+
assert!(kani::mem::can_dereference(&val as *const _ as *const [u32; 2]));
17+
}
18+
19+
#[kani::proof]
20+
fn check_can_dereference_always_invalid() {
21+
let val: [u8; 2] = kani::any();
22+
kani::assume(val[0] > 1 || val[1] > 1);
23+
assert!(!kani::mem::can_dereference(&val as *const _ as *const [bool; 2]));
24+
}

0 commit comments

Comments
 (0)