Skip to content

Commit 8a2b7e5

Browse files
authored
Contracts for a few core functions (rust-lang#3107)
Adds a new regression test suite that includes safety contracts for `core` functions. For that, we've also improved the memory intrinsics, and added a few new ones. One last change was to add a new unsupported check to the invalid value workflow for a case that we were missing.
1 parent 7eb6ce7 commit 8a2b7e5

File tree

19 files changed

+667
-153
lines changed

19 files changed

+667
-153
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ exclude = [
6262
"tests/perf",
6363
"tests/cargo-ui",
6464
"tests/slow",
65+
"tests/std-checks",
6566
"tests/assess-scan-test-scaffold",
6667
"tests/script-based-pre",
6768
"tests/script-based-pre/build-cache-bin/target/new_dep",

kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,11 @@ impl GotocHook for Panic {
207207
}
208208
}
209209

210-
/// Encodes __CPROVER_r_ok
211-
struct IsReadOk;
212-
impl GotocHook for IsReadOk {
210+
/// Encodes __CPROVER_r_ok(ptr, size)
211+
struct IsAllocated;
212+
impl GotocHook for IsAllocated {
213213
fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool {
214-
matches_function(tcx, instance.def, "KaniIsReadOk")
214+
matches_function(tcx, instance.def, "KaniIsAllocated")
215215
}
216216

217217
fn handle(
@@ -398,7 +398,7 @@ pub fn fn_hooks() -> GotocHooks {
398398
Rc::new(Assert),
399399
Rc::new(Cover),
400400
Rc::new(Nondet),
401-
Rc::new(IsReadOk),
401+
Rc::new(IsAllocated),
402402
Rc::new(RustAlloc),
403403
Rc::new(MemCmp),
404404
Rc::new(UntrackedDeref),

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use stable_mir::mir::{
1212
VarDebugInfo,
1313
};
1414
use stable_mir::ty::{Const, GenericArgs, Span, Ty, UintTy};
15+
use std::fmt::Debug;
1516
use std::mem;
1617

1718
/// This structure mimics a Body that can actually be modified.
@@ -224,6 +225,20 @@ impl MutableBody {
224225
}
225226
}
226227
}
228+
229+
/// Clear all the existing logic of this body and turn it into a simple `return`.
230+
///
231+
/// This function can be used when a new implementation of the body is needed.
232+
/// For example, Kani intrinsics usually have a dummy body, which is replaced
233+
/// by the compiler. This function allow us to delete the dummy body before
234+
/// creating a new one.
235+
///
236+
/// Note: We do not prune the local variables today for simplicity.
237+
pub fn clear_body(&mut self) {
238+
self.blocks.clear();
239+
let terminator = Terminator { kind: TerminatorKind::Return, span: self.span };
240+
self.blocks.push(BasicBlock { statements: Vec::default(), terminator })
241+
}
227242
}
228243

229244
#[derive(Clone, Debug)]

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

Lines changed: 85 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use crate::kani_middle::transform::check_values::SourceOp::UnsupportedCheck;
1919
use crate::kani_middle::transform::{TransformPass, TransformationType};
2020
use crate::kani_queries::QueryDb;
2121
use rustc_middle::ty::TyCtxt;
22-
use rustc_smir::rustc_internal;
2322
use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape, WrappingRange};
2423
use stable_mir::mir::mono::{Instance, InstanceKind};
2524
use stable_mir::mir::visit::{Location, PlaceContext, PlaceRef};
@@ -31,19 +30,14 @@ use stable_mir::mir::{
3130
use stable_mir::target::{MachineInfo, MachineSize};
3231
use stable_mir::ty::{AdtKind, Const, IndexedVal, RigidTy, Ty, TyKind, UintTy};
3332
use stable_mir::CrateDef;
34-
use std::fmt::{Debug, Formatter};
33+
use std::fmt::Debug;
3534
use strum_macros::AsRefStr;
3635
use tracing::{debug, trace};
3736

3837
/// Instrument the code with checks for invalid values.
38+
#[derive(Debug)]
3939
pub struct ValidValuePass {
40-
check_type: CheckType,
41-
}
42-
43-
impl ValidValuePass {
44-
pub fn new(tcx: TyCtxt) -> Self {
45-
ValidValuePass { check_type: CheckType::new(tcx) }
46-
}
40+
pub check_type: CheckType,
4741
}
4842

4943
impl TransformPass for ValidValuePass {
@@ -81,13 +75,6 @@ impl TransformPass for ValidValuePass {
8175
}
8276
}
8377

84-
impl Debug for ValidValuePass {
85-
/// Implement manually since MachineInfo doesn't currently derive Debug.
86-
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
87-
"ValidValuePass".fmt(f)
88-
}
89-
}
90-
9178
impl ValidValuePass {
9279
fn build_check(&self, tcx: TyCtxt, body: &mut MutableBody, instruction: UnsafeInstruction) {
9380
debug!(?instruction, "build_check");
@@ -98,89 +85,30 @@ impl ValidValuePass {
9885
let value = body.new_assignment(rvalue, &mut source);
9986
let rvalue_ptr = Rvalue::AddressOf(Mutability::Not, Place::from(value));
10087
for range in ranges {
101-
let result =
102-
self.build_limits(body, &range, rvalue_ptr.clone(), &mut source);
103-
let msg = format!(
104-
"Undefined Behavior: Invalid value of type `{}`",
105-
// TODO: Fix pretty_ty
106-
rustc_internal::internal(tcx, target_ty)
107-
);
88+
let result = build_limits(body, &range, rvalue_ptr.clone(), &mut source);
89+
let msg =
90+
format!("Undefined Behavior: Invalid value of type `{target_ty}`",);
10891
body.add_check(tcx, &self.check_type, &mut source, result, &msg);
10992
}
11093
}
11194
SourceOp::DerefValidity { pointee_ty, rvalue, ranges } => {
11295
for range in ranges {
113-
let result = self.build_limits(body, &range, rvalue.clone(), &mut source);
114-
let msg = format!(
115-
"Undefined Behavior: Invalid value of type `{}`",
116-
// TODO: Fix pretty_ty
117-
rustc_internal::internal(tcx, pointee_ty)
118-
);
96+
let result = build_limits(body, &range, rvalue.clone(), &mut source);
97+
let msg =
98+
format!("Undefined Behavior: Invalid value of type `{pointee_ty}`",);
11999
body.add_check(tcx, &self.check_type, &mut source, result, &msg);
120100
}
121101
}
122102
SourceOp::UnsupportedCheck { check, ty } => {
123103
let reason = format!(
124-
"Kani currently doesn't support checking validity of `{check}` for `{}` type",
125-
rustc_internal::internal(tcx, ty)
104+
"Kani currently doesn't support checking validity of `{check}` for `{ty}`",
126105
);
127106
self.unsupported_check(tcx, body, &mut source, &reason);
128107
}
129108
}
130109
}
131110
}
132111

133-
fn build_limits(
134-
&self,
135-
body: &mut MutableBody,
136-
req: &ValidValueReq,
137-
rvalue_ptr: Rvalue,
138-
source: &mut SourceInstruction,
139-
) -> Local {
140-
let span = source.span(body.blocks());
141-
debug!(?req, ?rvalue_ptr, ?span, "build_limits");
142-
let primitive_ty = uint_ty(req.size.bytes());
143-
let start_const = body.new_const_operand(req.valid_range.start, primitive_ty, span);
144-
let end_const = body.new_const_operand(req.valid_range.end, primitive_ty, span);
145-
let orig_ptr = if req.offset != 0 {
146-
let start_ptr = move_local(body.new_assignment(rvalue_ptr, source));
147-
let byte_ptr = move_local(body.new_cast_ptr(
148-
start_ptr,
149-
Ty::unsigned_ty(UintTy::U8),
150-
Mutability::Not,
151-
source,
152-
));
153-
let offset_const = body.new_const_operand(req.offset as _, UintTy::Usize, span);
154-
let offset = move_local(body.new_assignment(Rvalue::Use(offset_const), source));
155-
move_local(body.new_binary_op(BinOp::Offset, byte_ptr, offset, source))
156-
} else {
157-
move_local(body.new_assignment(rvalue_ptr, source))
158-
};
159-
let value_ptr =
160-
body.new_cast_ptr(orig_ptr, Ty::unsigned_ty(primitive_ty), Mutability::Not, source);
161-
let value =
162-
Operand::Copy(Place { local: value_ptr, projection: vec![ProjectionElem::Deref] });
163-
let start_result = body.new_binary_op(BinOp::Ge, value.clone(), start_const, source);
164-
let end_result = body.new_binary_op(BinOp::Le, value, end_const, source);
165-
if req.valid_range.wraps_around() {
166-
// valid >= start || valid <= end
167-
body.new_binary_op(
168-
BinOp::BitOr,
169-
move_local(start_result),
170-
move_local(end_result),
171-
source,
172-
)
173-
} else {
174-
// valid >= start && valid <= end
175-
body.new_binary_op(
176-
BinOp::BitAnd,
177-
move_local(start_result),
178-
move_local(end_result),
179-
source,
180-
)
181-
}
182-
}
183-
184112
fn unsupported_check(
185113
&self,
186114
tcx: TyCtxt,
@@ -216,7 +144,7 @@ fn uint_ty(bytes: usize) -> UintTy {
216144

217145
/// Represent a requirement for the value stored in the given offset.
218146
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
219-
struct ValidValueReq {
147+
pub struct ValidValueReq {
220148
/// Offset in bytes.
221149
offset: usize,
222150
/// Size of this requirement.
@@ -384,9 +312,13 @@ impl<'a> MirVisitor for CheckValueVisitor<'a> {
384312
} else if self.target.is_none() {
385313
// Leave it as an exhaustive match to be notified when a new kind is added.
386314
match &stmt.kind {
387-
StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(_)) => {
388-
// Source and destination have the same type, so no invalid value cannot be
389-
// generated.
315+
StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(copy)) => {
316+
// Source is a *const T and it must be safe for read.
317+
// TODO: Implement value check.
318+
self.push_target(SourceOp::UnsupportedCheck {
319+
check: "copy_nonoverlapping".to_string(),
320+
ty: copy.src.ty(self.locals).unwrap(),
321+
});
390322
}
391323
StatementKind::Assign(place, rvalue) => {
392324
// First check rvalue.
@@ -573,11 +505,27 @@ impl<'a> MirVisitor for CheckValueVisitor<'a> {
573505
// We only care about *mut T as *mut U
574506
return;
575507
};
508+
if dest_pointee_ty.kind().is_unit() {
509+
// Ignore cast to *mut () since nothing can be written to it.
510+
// This is a common pattern
511+
return;
512+
}
513+
576514
let src_ty = op.ty(self.locals).unwrap();
577515
debug!(?src_ty, ?dest_ty, "visit_rvalue mutcast");
578516
let TyKind::RigidTy(RigidTy::RawPtr(src_pointee_ty, _)) = src_ty.kind() else {
579517
unreachable!()
580518
};
519+
520+
if src_pointee_ty.kind().is_unit() {
521+
// We cannot track what was the initial type. Thus, fail.
522+
self.push_target(SourceOp::UnsupportedCheck {
523+
check: "mutable cast".to_string(),
524+
ty: src_ty,
525+
});
526+
return;
527+
}
528+
581529
if let Ok(src_validity) =
582530
ty_validity_per_offset(&self.machine, src_pointee_ty, 0)
583531
{
@@ -626,6 +574,8 @@ impl<'a> MirVisitor for CheckValueVisitor<'a> {
626574
})
627575
}
628576
}
577+
// `DynStar` is not currently supported in Kani.
578+
// TODO: https://github.com/model-checking/kani/issues/1784
629579
CastKind::DynStar => self.push_target(UnsupportedCheck {
630580
check: "Dyn*".to_string(),
631581
ty: (rvalue.ty(self.locals).unwrap()),
@@ -784,10 +734,58 @@ fn expect_instance(locals: &[LocalDecl], func: &Operand) -> Instance {
784734
}
785735
}
786736

737+
/// Instrument MIR to check the value pointed by `rvalue_ptr` satisfies requirement `req`.
738+
///
739+
/// The MIR will do something equivalent to:
740+
/// ```rust
741+
/// let ptr = rvalue_ptr.byte_offset(req.offset);
742+
/// let typed_ptr = ptr as *const Unsigned<req.size>; // Some unsigned type with length req.size
743+
/// let value = unsafe { *typed_ptr };
744+
/// req.valid_range.contains(value)
745+
/// ```
746+
pub fn build_limits(
747+
body: &mut MutableBody,
748+
req: &ValidValueReq,
749+
rvalue_ptr: Rvalue,
750+
source: &mut SourceInstruction,
751+
) -> Local {
752+
let span = source.span(body.blocks());
753+
debug!(?req, ?rvalue_ptr, ?span, "build_limits");
754+
let primitive_ty = uint_ty(req.size.bytes());
755+
let start_const = body.new_const_operand(req.valid_range.start, primitive_ty, span);
756+
let end_const = body.new_const_operand(req.valid_range.end, primitive_ty, span);
757+
let orig_ptr = if req.offset != 0 {
758+
let start_ptr = move_local(body.new_assignment(rvalue_ptr, source));
759+
let byte_ptr = move_local(body.new_cast_ptr(
760+
start_ptr,
761+
Ty::unsigned_ty(UintTy::U8),
762+
Mutability::Not,
763+
source,
764+
));
765+
let offset_const = body.new_const_operand(req.offset as _, UintTy::Usize, span);
766+
let offset = move_local(body.new_assignment(Rvalue::Use(offset_const), source));
767+
move_local(body.new_binary_op(BinOp::Offset, byte_ptr, offset, source))
768+
} else {
769+
move_local(body.new_assignment(rvalue_ptr, source))
770+
};
771+
let value_ptr =
772+
body.new_cast_ptr(orig_ptr, Ty::unsigned_ty(primitive_ty), Mutability::Not, source);
773+
let value = Operand::Copy(Place { local: value_ptr, projection: vec![ProjectionElem::Deref] });
774+
let start_result = body.new_binary_op(BinOp::Ge, value.clone(), start_const, source);
775+
let end_result = body.new_binary_op(BinOp::Le, value, end_const, source);
776+
if req.valid_range.wraps_around() {
777+
// valid >= start || valid <= end
778+
body.new_binary_op(BinOp::BitOr, move_local(start_result), move_local(end_result), source)
779+
} else {
780+
// valid >= start && valid <= end
781+
body.new_binary_op(BinOp::BitAnd, move_local(start_result), move_local(end_result), source)
782+
}
783+
}
784+
787785
/// Traverse the type and find all invalid values and their location in memory.
788786
///
789787
/// Not all values are currently supported. For those not supported, we return Error.
790-
fn ty_validity_per_offset(
788+
pub fn ty_validity_per_offset(
791789
machine_info: &MachineInfo,
792790
ty: Ty,
793791
current_offset: usize,

0 commit comments

Comments
 (0)