Skip to content

Commit 2b20207

Browse files
authored
Fix size and alignment computation for intrinsics (#3734)
Implement overflow checks for `size_of_val`, and panic for foreign types. Resolves #3616 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 5b338c9 commit 2b20207

File tree

10 files changed

+316
-13
lines changed

10 files changed

+316
-13
lines changed

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

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -215,17 +215,6 @@ impl GotocCtx<'_> {
215215
}};
216216
}
217217

218-
macro_rules! codegen_size_align {
219-
($which: ident) => {{
220-
let args = instance_args(&instance);
221-
// The type `T` that we'll compute the size or alignment.
222-
let target_ty = args.0[0].expect_ty();
223-
let arg = fargs.remove(0);
224-
let size_align = self.size_and_align_of_dst(*target_ty, arg);
225-
self.codegen_expr_to_place_stable(place, size_align.$which, loc)
226-
}};
227-
}
228-
229218
// Most atomic intrinsics do:
230219
// 1. Perform an operation on a primary argument (e.g., addition)
231220
// 2. Return the previous value of the primary argument
@@ -422,7 +411,6 @@ impl GotocCtx<'_> {
422411
Intrinsic::MaxNumF32 => codegen_simple_intrinsic!(Fmaxf),
423412
Intrinsic::MaxNumF64 => codegen_simple_intrinsic!(Fmax),
424413
Intrinsic::MinAlignOf => codegen_intrinsic_const!(),
425-
Intrinsic::MinAlignOfVal => codegen_size_align!(align),
426414
Intrinsic::MinNumF32 => codegen_simple_intrinsic!(Fminf),
427415
Intrinsic::MinNumF64 => codegen_simple_intrinsic!(Fmin),
428416
Intrinsic::MulWithOverflow => {
@@ -516,7 +504,6 @@ impl GotocCtx<'_> {
516504
loc,
517505
),
518506
Intrinsic::SimdXor => codegen_intrinsic_binop!(bitxor),
519-
Intrinsic::SizeOfVal => codegen_size_align!(size),
520507
Intrinsic::SqrtF32 => codegen_simple_intrinsic!(Sqrtf),
521508
Intrinsic::SqrtF64 => codegen_simple_intrinsic!(Sqrt),
522509
Intrinsic::SubWithOverflow => self.codegen_op_with_overflow(
@@ -559,6 +546,9 @@ impl GotocCtx<'_> {
559546
assert!(self.place_ty_stable(place).kind().is_unit());
560547
self.codegen_write_bytes(fargs, farg_types, loc)
561548
}
549+
Intrinsic::SizeOfVal | Intrinsic::MinAlignOfVal => {
550+
unreachable!("Intrinsic `{}` is handled before codegen", intrinsic_str)
551+
}
562552
// Unimplemented
563553
Intrinsic::Unimplemented { name, issue_link } => {
564554
self.codegen_unimplemented_stmt(&name, loc, &issue_link)

kani-compiler/src/kani_middle/kani_functions.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ pub enum KaniIntrinsic {
6161
pub enum KaniModel {
6262
#[strum(serialize = "AlignOfDynObjectModel")]
6363
AlignOfDynObject,
64+
#[strum(serialize = "AlignOfValRawModel")]
65+
AlignOfVal,
6466
#[strum(serialize = "AnyModel")]
6567
Any,
6668
#[strum(serialize = "CopyInitStateModel")]
@@ -95,6 +97,8 @@ pub enum KaniModel {
9597
SizeOfDynObject,
9698
#[strum(serialize = "SizeOfSliceObjectModel")]
9799
SizeOfSliceObject,
100+
#[strum(serialize = "SizeOfValRawModel")]
101+
SizeOfVal,
98102
#[strum(serialize = "StoreArgumentModel")]
99103
StoreArgument,
100104
#[strum(serialize = "WriteAnySliceModel")]

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use stable_mir::mir::mono::{Instance, MonoItem};
3333
use std::collections::HashMap;
3434
use std::fmt::Debug;
3535

36+
use crate::kani_middle::transform::rustc_intrinsics::RustcIntrinsicsPass;
3637
pub use internal_mir::RustcInternalMir;
3738

3839
pub(crate) mod body;
@@ -43,6 +44,7 @@ mod dump_mir_pass;
4344
mod internal_mir;
4445
mod kani_intrinsics;
4546
mod loop_contracts;
47+
mod rustc_intrinsics;
4648
mod stubs;
4749

4850
/// Object used to retrieve a transformed instance body.
@@ -88,6 +90,7 @@ impl BodyTransformation {
8890
});
8991
transformer.add_pass(queries, IntrinsicGeneratorPass::new(check_type, &queries));
9092
transformer.add_pass(queries, LoopContractPass::new(tcx, queries, &unit));
93+
transformer.add_pass(queries, RustcIntrinsicsPass::new(&queries));
9194
transformer
9295
}
9396

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
//! Module responsible for implementing a few Rust compiler intrinsics.
5+
//!
6+
//! Note that some rustc intrinsics are lowered to MIR instructions. Those can also be handled
7+
//! here.
8+
9+
use crate::intrinsics::Intrinsic;
10+
use crate::kani_middle::kani_functions::{KaniFunction, KaniModel};
11+
use crate::kani_middle::transform::body::{MutMirVisitor, MutableBody};
12+
use crate::kani_middle::transform::{TransformPass, TransformationType};
13+
use crate::kani_queries::QueryDb;
14+
use rustc_middle::ty::TyCtxt;
15+
use stable_mir::mir::mono::Instance;
16+
use stable_mir::mir::{Body, ConstOperand, LocalDecl, Operand, Terminator, TerminatorKind};
17+
use stable_mir::ty::{FnDef, MirConst, RigidTy, TyKind};
18+
use std::collections::HashMap;
19+
use tracing::debug;
20+
21+
/// Generate the body for a few Kani intrinsics.
22+
#[derive(Debug)]
23+
pub struct RustcIntrinsicsPass {
24+
/// Used to cache FnDef lookups for intrinsics models.
25+
models: HashMap<KaniModel, FnDef>,
26+
}
27+
28+
impl TransformPass for RustcIntrinsicsPass {
29+
fn transformation_type() -> TransformationType
30+
where
31+
Self: Sized,
32+
{
33+
TransformationType::Stubbing
34+
}
35+
36+
fn is_enabled(&self, _query_db: &QueryDb) -> bool
37+
where
38+
Self: Sized,
39+
{
40+
true
41+
}
42+
43+
/// Transform the function body by inserting checks one-by-one.
44+
/// For every unsafe dereference or a transmute operation, we check all values are valid.
45+
fn transform(&mut self, _tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) {
46+
debug!(function=?instance.name(), "transform");
47+
let mut new_body = MutableBody::from(body);
48+
let mut visitor = ReplaceIntrinsicVisitor::new(&self.models, new_body.locals().to_vec());
49+
visitor.visit_body(&mut new_body);
50+
(visitor.changed, new_body.into())
51+
}
52+
}
53+
54+
impl RustcIntrinsicsPass {
55+
pub fn new(queries: &QueryDb) -> Self {
56+
let models = queries
57+
.kani_functions()
58+
.iter()
59+
.filter_map(|(func, def)| {
60+
if let KaniFunction::Model(model) = func { Some((*model, *def)) } else { None }
61+
})
62+
.collect();
63+
debug!(?models, "RustcIntrinsicsPass::new");
64+
RustcIntrinsicsPass { models }
65+
}
66+
}
67+
68+
struct ReplaceIntrinsicVisitor<'a> {
69+
models: &'a HashMap<KaniModel, FnDef>,
70+
locals: Vec<LocalDecl>,
71+
changed: bool,
72+
}
73+
74+
impl<'a> ReplaceIntrinsicVisitor<'a> {
75+
fn new(models: &'a HashMap<KaniModel, FnDef>, locals: Vec<LocalDecl>) -> Self {
76+
ReplaceIntrinsicVisitor { models, locals, changed: false }
77+
}
78+
}
79+
80+
impl MutMirVisitor for ReplaceIntrinsicVisitor<'_> {
81+
/// Replace the terminator for some rustc's intrinsics.
82+
///
83+
/// In some cases, we replace a function call to a rustc intrinsic by a call to the
84+
/// corresponding Kani intrinsic.
85+
///
86+
/// Our models are usually augmented by some trait bounds, or they leverage Kani intrinsics to
87+
/// implement the given semantics.
88+
///
89+
/// Note that we only need to replace function calls since intrinsics must always be called
90+
/// directly. I.e., no need to handle function pointers.
91+
fn visit_terminator(&mut self, term: &mut Terminator) {
92+
if let TerminatorKind::Call { func, .. } = &mut term.kind {
93+
if let TyKind::RigidTy(RigidTy::FnDef(def, args)) =
94+
func.ty(&self.locals).unwrap().kind()
95+
{
96+
if def.is_intrinsic() {
97+
let instance = Instance::resolve(def, &args).unwrap();
98+
let intrinsic = Intrinsic::from_instance(&instance);
99+
debug!(?intrinsic, "handle_terminator");
100+
let model = match intrinsic {
101+
Intrinsic::SizeOfVal => self.models[&KaniModel::SizeOfVal],
102+
Intrinsic::MinAlignOfVal => self.models[&KaniModel::AlignOfVal],
103+
// The rest is handled in codegen.
104+
_ => {
105+
return self.super_terminator(term);
106+
}
107+
};
108+
let new_instance = Instance::resolve(model, &args).unwrap();
109+
let literal = MirConst::try_new_zero_sized(new_instance.ty()).unwrap();
110+
let span = term.span;
111+
let new_func = ConstOperand { span, user_ty: None, const_: literal };
112+
*func = Operand::Constant(new_func);
113+
self.changed = true;
114+
}
115+
}
116+
}
117+
self.super_terminator(term);
118+
}
119+
}

library/kani_core/src/models.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,39 @@
1111
#[allow(clippy::crate_in_macro_def)]
1212
macro_rules! generate_models {
1313
() => {
14+
/// Model rustc intrinsics. These definitions are not visible to the crate user.
15+
/// They are used by Kani's compiler.
16+
#[allow(dead_code)]
17+
mod rustc_intrinsics {
18+
use crate::kani;
19+
use core::ptr::Pointee;
20+
#[kanitool::fn_marker = "SizeOfValRawModel"]
21+
pub fn size_of_val_raw<T: ?Sized>(ptr: *const T) -> usize {
22+
if let Some(size) = kani::mem::checked_size_of_raw(ptr) {
23+
size
24+
} else if core::mem::size_of::<<T as Pointee>::Metadata>() == 0 {
25+
kani::panic("cannot compute `size_of_val` for extern types")
26+
} else {
27+
kani::safety_check(false, "failed to compute `size_of_val`");
28+
// Unreachable without panic.
29+
kani::kani_intrinsic()
30+
}
31+
}
32+
33+
#[kanitool::fn_marker = "AlignOfValRawModel"]
34+
pub fn align_of_val_raw<T: ?Sized>(ptr: *const T) -> usize {
35+
if let Some(size) = kani::mem::checked_align_of_raw(ptr) {
36+
size
37+
} else if core::mem::size_of::<<T as Pointee>::Metadata>() == 0 {
38+
kani::panic("cannot compute `align_of_val` for extern types")
39+
} else {
40+
kani::safety_check(false, "failed to compute `align_of_val`");
41+
// Unreachable without panic.
42+
kani::kani_intrinsic()
43+
}
44+
}
45+
}
46+
1447
#[allow(dead_code)]
1548
mod mem_models {
1649
use core::ptr::{self, DynMetadata, Pointee};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Checking harness check_zst1024...
2+
Checking harness check_large...
3+
Checking harness check_1char_tup...
4+
Checking harness check_1zst_usize...
5+
6+
Complete - 4 successfully verified harnesses, 0 failures, 4 total
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! This test case checks the behavior of `align_of_val_raw` for dynamically sized types.
5+
6+
#![feature(layout_for_ptr)]
7+
#![feature(ptr_metadata)]
8+
9+
use std::any::Any;
10+
use std::mem::align_of;
11+
12+
#[allow(unused)]
13+
#[derive(Clone, Copy, kani::Arbitrary)]
14+
struct Wrapper<T, U: ?Sized> {
15+
head: T,
16+
dst: U,
17+
}
18+
19+
/// Create a ZST with an alignment of 1024.
20+
#[allow(unused)]
21+
#[repr(align(1024))]
22+
#[derive(kani::Arbitrary)]
23+
struct Zst1024;
24+
25+
/// Create a structure with large alignment (2^29).
26+
///
27+
/// This seems to be the maximum supported today:
28+
/// <https://github.com/rust-lang/rust/blob/7db7489f9bc2/tests/ui/repr/repr-align.rs#L11>
29+
#[repr(align(536870912))]
30+
#[derive(kani::Arbitrary)]
31+
struct LargeAlign {
32+
data: [usize; 100],
33+
}
34+
35+
/// Generates a harness with different type combinations and check the alignment is correct.
36+
/// We use a constant for the original type, so it is pre-populated by the compiler.
37+
///
38+
/// ## Parameters
39+
/// - `name`: Name of the harness.
40+
/// - `t1` / `t2`: Types used for different tail and head combinations.
41+
/// - `expected`: The expected alignment.
42+
macro_rules! check_alignment {
43+
($name:ident, $t1:ty, $t2:ty, $expected:expr) => {
44+
#[kani::proof]
45+
fn $name() {
46+
const EXPECTED: usize = align_of::<Wrapper<$t1, $t2>>();
47+
assert_eq!(EXPECTED, $expected);
48+
49+
let var: Wrapper<$t1, $t2> = kani::any();
50+
let wide_ptr: &Wrapper<$t1, dyn Any> = &var as &_;
51+
let dyn_t2_align = align_of_val(wide_ptr);
52+
assert_eq!(dyn_t2_align, EXPECTED, "Expected same alignment as before coercion");
53+
54+
let var: Wrapper<$t2, $t1> = kani::any();
55+
let wide_ptr: &Wrapper<$t2, dyn Any> = &var as &_;
56+
let dyn_t1_align = align_of_val(wide_ptr);
57+
assert_eq!(dyn_t1_align, EXPECTED, "Expected same alignment as before coercion");
58+
59+
let var: Wrapper<$t1, [$t2; 0]> = kani::any();
60+
let wide_ptr: &Wrapper<$t1, [$t2]> = &var as &_;
61+
let slice_t2_align = align_of_val(wide_ptr);
62+
assert_eq!(slice_t2_align, EXPECTED, "Expected same alignment as before coercion");
63+
64+
let var: Wrapper<$t2, [$t1; 0]> = kani::any();
65+
let wide_ptr: &Wrapper<$t2, [$t1]> = &var as &_;
66+
let slice_t1_align = align_of_val(wide_ptr);
67+
assert_eq!(slice_t1_align, EXPECTED, "Expected same alignment as before coercion");
68+
}
69+
};
70+
}
71+
72+
check_alignment!(check_1zst_usize, usize, (), align_of::<usize>());
73+
check_alignment!(check_1char_tup, (char, usize, u128), char, align_of::<u128>());
74+
check_alignment!(check_zst1024, (char, usize, u128), Zst1024, align_of::<Zst1024>());
75+
check_alignment!(check_large, u128, LargeAlign, align_of::<LargeAlign>());
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Checking harness check_size_of_overflows...
2+
3+
safety_check\
4+
Status: FAILURE\
5+
Description: "failed to compute `size_of_val`"
6+
7+
Status: SUCCESS\
8+
Description: "assertion failed: unsafe { size_of_val_raw(new_ptr) } == expected_size"
9+
10+
Failed Checks: failed to compute `size_of_val`
11+
VERIFICATION:- FAILED
12+
13+
Checking harness check_size_of_adt_overflows...
14+
15+
0 of 2 cover properties satisfied (2 unreachable)
16+
Failed Checks: failed to compute `size_of_val`
17+
VERIFICATION:- FAILED
18+
19+
Verification failed for - check_size_of_overflows
20+
Verification failed for - check_size_of_adt_overflows

0 commit comments

Comments
 (0)