Skip to content

Commit 25dfce5

Browse files
celinvaladpaco-awszhassan-aws
authored
Enable fat pointer comparison for slices and add check for vtable ones (rust-lang#1195)
* Implement pointer comparison for fat pointers 1. This only works for pointers with the same provenance. 2. We should model vtable comparison a bit differently. Follow up commit * Use integer comparison for vtable. * Add check for unstable vtable pointer comparison * Address the following PR comments - Rename variables. - Add location to the assertion and other statements. - Added a link to the compiler PR that implemented fat pointer comparison. Co-authored-by: Adrian Palacios <[email protected]> Co-authored-by: Zyad Hassan <[email protected]>
1 parent deaa485 commit 25dfce5

File tree

7 files changed

+222
-118
lines changed

7 files changed

+222
-118
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub enum PropertyClass {
1818
ExactDiv,
1919
ExpectFail,
2020
FiniteCheck,
21+
/// Checks added by Kani compiler to detect UB or unstable behavior.
22+
KaniCheck,
2123
PointerOffset,
2224
SanityCheck,
2325
Unimplemented,
@@ -38,6 +40,7 @@ impl PropertyClass {
3840
PropertyClass::ExactDiv => "exact_div",
3941
PropertyClass::ExpectFail => "expect_fail",
4042
PropertyClass::FiniteCheck => "finite_check",
43+
PropertyClass::KaniCheck => "kani_check",
4144
PropertyClass::PointerOffset => "pointer_offset",
4245
PropertyClass::SanityCheck => "sanity_check",
4346
PropertyClass::Unimplemented => "unimplemented",
@@ -56,6 +59,7 @@ impl PropertyClass {
5659
"exact_div" => PropertyClass::ExactDiv,
5760
"expect_fail" => PropertyClass::ExpectFail,
5861
"finite_check" => PropertyClass::FiniteCheck,
62+
"kani_check" => PropertyClass::KaniCheck,
5963
"pointer_offset" => PropertyClass::PointerOffset,
6064
"sanity_check" => PropertyClass::SanityCheck,
6165
"unimplemented" => PropertyClass::Unimplemented,

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

Lines changed: 106 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright Kani Contributors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33
use super::typ::{is_pointer, pointee_type, TypeExt};
4+
use crate::codegen_cprover_gotoc::codegen::PropertyClass;
45
use crate::codegen_cprover_gotoc::utils::{dynamic_fat_ptr, slice_fat_ptr};
56
use crate::codegen_cprover_gotoc::{GotocCtx, VtableCtx};
67
use crate::unwrap_or_return_codegen_unimplemented;
@@ -19,44 +20,79 @@ use tracing::{debug, warn};
1920

2021
impl<'tcx> GotocCtx<'tcx> {
2122
fn codegen_comparison(&mut self, op: &BinOp, e1: &Operand<'tcx>, e2: &Operand<'tcx>) -> Expr {
22-
match op {
23-
BinOp::Eq => {
24-
if self.operand_ty(e1).is_floating_point() {
25-
self.codegen_operand(e1).feq(self.codegen_operand(e2))
26-
} else {
27-
self.codegen_operand(e1).eq(self.codegen_operand(e2))
28-
}
29-
}
30-
BinOp::Lt => self.codegen_operand(e1).lt(self.codegen_operand(e2)),
31-
BinOp::Le => self.codegen_operand(e1).le(self.codegen_operand(e2)),
32-
BinOp::Ne => {
33-
if self.operand_ty(e1).is_floating_point() {
34-
self.codegen_operand(e1).fneq(self.codegen_operand(e2))
35-
} else {
36-
self.codegen_operand(e1).neq(self.codegen_operand(e2))
37-
}
38-
}
39-
BinOp::Ge => self.codegen_operand(e1).ge(self.codegen_operand(e2)),
40-
BinOp::Gt => self.codegen_operand(e1).gt(self.codegen_operand(e2)),
41-
_ => unreachable!(),
42-
}
23+
let left_op = self.codegen_operand(e1);
24+
let right_op = self.codegen_operand(e2);
25+
let is_float = self.operand_ty(e1).is_floating_point();
26+
comparison_expr(op, left_op, right_op, is_float)
4327
}
4428

4529
/// This function codegen comparison for fat pointers.
46-
/// We currently don't support this. See issue #327 for more information.
30+
/// Fat pointer comparison must compare the raw data pointer as well as its metadata portion.
31+
///
32+
/// Since vtable pointer comparison is not well defined and it has many nuances, we decided to
33+
/// fail if the user code performs such comparison.
34+
///
35+
/// See https://github.com/model-checking/kani/issues/327 for more details.
4736
fn codegen_comparison_fat_ptr(
4837
&mut self,
4938
op: &BinOp,
50-
left: &Operand<'tcx>,
51-
right: &Operand<'tcx>,
39+
left_op: &Operand<'tcx>,
40+
right_op: &Operand<'tcx>,
41+
loc: Location,
5242
) -> Expr {
53-
debug!(?op, ?left, ?right, "codegen_comparison_fat_ptr");
54-
self.codegen_unimplemented(
55-
&format!("Fat pointer comparison '{:?}'", op),
56-
Type::Bool,
57-
Location::None,
58-
"https://github.com/model-checking/kani/issues/327",
59-
)
43+
debug!(?op, ?left_op, ?right_op, "codegen_comparison_fat_ptr");
44+
let left_typ = self.operand_ty(left_op);
45+
let right_typ = self.operand_ty(left_op);
46+
assert_eq!(left_typ, right_typ, "Cannot compare pointers of different types");
47+
assert!(self.is_ref_of_unsized(left_typ));
48+
49+
if self.is_vtable_fat_pointer(left_typ) {
50+
// Codegen an assertion failure since vtable comparison is not stable.
51+
let ret_type = Type::Bool;
52+
let body = vec![
53+
self.codegen_assert_false(
54+
PropertyClass::KaniCheck,
55+
format!("Reached unstable vtable comparison '{:?}'", op).as_str(),
56+
loc,
57+
),
58+
// Assume false to block any further exploration of this path.
59+
Stmt::assume(Expr::bool_false(), loc),
60+
ret_type.nondet().as_stmt(loc).with_location(loc),
61+
];
62+
63+
Expr::statement_expression(body, ret_type).with_location(loc)
64+
} else {
65+
// Compare data pointer.
66+
let left_ptr = self.codegen_operand(left_op);
67+
let left_data = left_ptr.clone().member("data", &self.symbol_table);
68+
let right_ptr = self.codegen_operand(right_op);
69+
let right_data = right_ptr.clone().member("data", &self.symbol_table);
70+
let data_cmp = comparison_expr(op, left_data.clone(), right_data.clone(), false);
71+
72+
// Compare the slice metadata (this logic could be adapted to compare vtable if needed).
73+
let left_len = left_ptr.member("len", &self.symbol_table);
74+
let right_len = right_ptr.member("len", &self.symbol_table);
75+
let metadata_cmp = comparison_expr(op, left_len, right_len, false);
76+
77+
// Join the results.
78+
// https://github.com/rust-lang/rust/pull/29781
79+
match op {
80+
// Only equal if both parts are equal.
81+
BinOp::Eq => data_cmp.and(metadata_cmp),
82+
// It is different if any is different.
83+
BinOp::Ne => data_cmp.or(metadata_cmp),
84+
// If data is different, only compare data.
85+
// If data is equal, apply operator to metadata.
86+
BinOp::Lt | BinOp::Le | BinOp::Ge | BinOp::Gt => {
87+
let data_eq =
88+
comparison_expr(&BinOp::Eq, left_data.clone(), right_data.clone(), false);
89+
let data_strict_comp =
90+
comparison_expr(&get_strict_operator(op), left_data, right_data, false);
91+
data_strict_comp.or(data_eq.and(metadata_cmp))
92+
}
93+
_ => unreachable!("Unexpected operator {:?}", op),
94+
}
95+
}
6096
}
6197

6298
fn codegen_unchecked_scalar_binop(
@@ -314,6 +350,7 @@ impl<'tcx> GotocCtx<'tcx> {
314350
op: &BinOp,
315351
e1: &Operand<'tcx>,
316352
e2: &Operand<'tcx>,
353+
loc: Location,
317354
) -> Expr {
318355
match op {
319356
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Shl | BinOp::Shr => {
@@ -324,7 +361,7 @@ impl<'tcx> GotocCtx<'tcx> {
324361
}
325362
BinOp::Eq | BinOp::Lt | BinOp::Le | BinOp::Ne | BinOp::Ge | BinOp::Gt => {
326363
if self.is_ref_of_unsized(self.operand_ty(e1)) {
327-
self.codegen_comparison_fat_ptr(op, e1, e2)
364+
self.codegen_comparison_fat_ptr(op, e1, e2, loc)
328365
} else {
329366
self.codegen_comparison(op, e1, e2)
330367
}
@@ -380,7 +417,7 @@ impl<'tcx> GotocCtx<'tcx> {
380417
}
381418
}
382419

383-
pub fn codegen_rvalue(&mut self, rv: &Rvalue<'tcx>) -> Expr {
420+
pub fn codegen_rvalue(&mut self, rv: &Rvalue<'tcx>, loc: Location) -> Expr {
384421
let res_ty = self.rvalue_ty(rv);
385422
match rv {
386423
Rvalue::Use(p) => self.codegen_operand(p),
@@ -395,7 +432,9 @@ impl<'tcx> GotocCtx<'tcx> {
395432
let t = self.monomorphize(*t);
396433
self.codegen_pointer_cast(k, e, t)
397434
}
398-
Rvalue::BinaryOp(op, box (ref e1, ref e2)) => self.codegen_rvalue_binary_op(op, e1, e2),
435+
Rvalue::BinaryOp(op, box (ref e1, ref e2)) => {
436+
self.codegen_rvalue_binary_op(op, e1, e2, loc)
437+
}
399438
Rvalue::CheckedBinaryOp(op, box (ref e1, ref e2)) => {
400439
self.codegen_rvalue_checked_binary_op(op, e1, e2, res_ty)
401440
}
@@ -1278,3 +1317,36 @@ fn wrapping_sub(expr: &Expr, constant: u64) -> Expr {
12781317
expr.clone().cast_to(unsigned).sub(constant)
12791318
}
12801319
}
1320+
1321+
fn comparison_expr(op: &BinOp, left: Expr, right: Expr, is_float: bool) -> Expr {
1322+
match op {
1323+
BinOp::Eq => {
1324+
if is_float {
1325+
left.feq(right)
1326+
} else {
1327+
left.eq(right)
1328+
}
1329+
}
1330+
BinOp::Lt => left.lt(right),
1331+
BinOp::Le => left.le(right),
1332+
BinOp::Ne => {
1333+
if is_float {
1334+
left.fneq(right)
1335+
} else {
1336+
left.neq(right)
1337+
}
1338+
}
1339+
BinOp::Ge => left.ge(right),
1340+
BinOp::Gt => left.gt(right),
1341+
_ => unreachable!(),
1342+
}
1343+
}
1344+
1345+
/// Remove the equality from an operator. Translates `<=` to `<` and `>=` to `>`
1346+
fn get_strict_operator(op: &BinOp) -> BinOp {
1347+
match op {
1348+
BinOp::Le => BinOp::Lt,
1349+
BinOp::Ge => BinOp::Gt,
1350+
_ => *op,
1351+
}
1352+
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -618,15 +618,15 @@ impl<'tcx> GotocCtx<'tcx> {
618618
// where the reference is implicit.
619619
unwrap_or_return_codegen_unimplemented_stmt!(self, self.codegen_place(l))
620620
.goto_expr
621-
.assign(self.codegen_rvalue(r).address_of(), location)
621+
.assign(self.codegen_rvalue(r, location).address_of(), location)
622622
} else if rty.is_bool() {
623623
unwrap_or_return_codegen_unimplemented_stmt!(self, self.codegen_place(l))
624624
.goto_expr
625-
.assign(self.codegen_rvalue(r).cast_to(Type::c_bool()), location)
625+
.assign(self.codegen_rvalue(r, location).cast_to(Type::c_bool()), location)
626626
} else {
627627
unwrap_or_return_codegen_unimplemented_stmt!(self, self.codegen_place(l))
628628
.goto_expr
629-
.assign(self.codegen_rvalue(r), location)
629+
.assign(self.codegen_rvalue(r, location), location)
630630
}
631631
}
632632
StatementKind::Deinit(place) => {
Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
1-
Checking harness check_slice_ptr...
2-
Status: FAILURE\
3-
Description: "Fat pointer comparison 'Lt' is not currently supported by Kani. Please post your example at https://github.com/model-checking/kani/issues/327"
1+
Checking harness check_lt...
2+
Failed Checks: Reached unstable vtable comparison 'Lt'
43

5-
Status: UNDETERMINED\
6-
Description: "Fat pointer comparison 'Le' is not currently supported by Kani. Please post your example at https://github.com/model-checking/kani/issues/327"
4+
Checking harness check_le...
5+
Failed Checks: Reached unstable vtable comparison 'Le'
76

8-
Status: UNDETERMINED\
9-
Description: "Fat pointer comparison 'Gt' is not currently supported by Kani. Please post your example at https://github.com/model-checking/kani/issues/327"
7+
Checking harness check_gt...
8+
Failed Checks: Reached unstable vtable comparison 'Gt'
109

11-
Status: UNDETERMINED\
12-
Description: "Fat pointer comparison 'Ge' is not currently supported by Kani. Please post your example at https://github.com/model-checking/kani/issues/327"
10+
Checking harness check_ge...
11+
Failed Checks: Reached unstable vtable comparison 'Ge'
1312

14-
Status: UNDETERMINED\
15-
Description: "Fat pointer comparison 'Ne' is not currently supported by Kani. Please post your example at https://github.com/model-checking/kani/issues/327"
13+
Checking harness check_ne...
14+
Failed Checks: Reached unstable vtable comparison 'Ne'
1615

17-
Status: UNDETERMINED\
18-
Description: "Fat pointer comparison 'Eq' is not currently supported by Kani. Please post your example at https://github.com/model-checking/kani/issues/327"
19-
20-
Failed Checks: Fat pointer comparison 'Lt' is not currently supported by Kani. Please post your example at https://github.com/model-checking/kani/issues/327
21-
22-
23-
Checking harness check_dyn_ptr...
24-
25-
Failed Checks: Fat pointer comparison 'Lt' is not currently supported by Kani. Please post your example at https://github.com/model-checking/kani/issues/327
16+
Checking harness check_eq...
17+
Failed Checks: Reached unstable vtable comparison 'Eq'

tests/expected/unsupported-fatptr-comparison/ptr_comparison.rs

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Test relation comparison for vtable fat pointers fail due to unstable behavior.
5+
use std::rc::Rc;
6+
7+
trait Dummy {}
8+
impl Dummy for u8 {}
9+
10+
struct TestData {
11+
#[allow(dead_code)]
12+
array: Rc<[u8; 10]>,
13+
smaller_ptr: *const dyn Dummy,
14+
bigger_ptr: *const dyn Dummy,
15+
}
16+
17+
fn setup() -> TestData {
18+
let array = Rc::new([0u8; 10]);
19+
TestData { array: array.clone(), smaller_ptr: &array[0], bigger_ptr: &array[5] }
20+
}
21+
22+
#[kani::proof]
23+
fn check_lt() {
24+
let data = setup();
25+
assert!(data.smaller_ptr < data.bigger_ptr);
26+
}
27+
28+
#[kani::proof]
29+
fn check_le() {
30+
let data = setup();
31+
assert!(data.smaller_ptr <= data.bigger_ptr);
32+
}
33+
34+
#[kani::proof]
35+
fn check_gt() {
36+
let data = setup();
37+
assert!(data.bigger_ptr > data.smaller_ptr);
38+
}
39+
40+
#[kani::proof]
41+
fn check_ge() {
42+
let data = setup();
43+
assert!(data.bigger_ptr >= data.smaller_ptr);
44+
}
45+
46+
#[kani::proof]
47+
fn check_ne() {
48+
let data = setup();
49+
assert!(data.bigger_ptr != data.smaller_ptr);
50+
}
51+
52+
#[kani::proof]
53+
fn check_eq() {
54+
let data = setup();
55+
assert!(!(data.bigger_ptr == data.smaller_ptr));
56+
}

0 commit comments

Comments
 (0)