Skip to content

Commit 71869b2

Browse files
tautschnigzpzigi754
authored andcommitted
Do not assume that ZST-typed symbols refer to unique objects (model-checking#3134)
The Rust specification does not guarantee that ZST-typed symbols are backed by unique objects, and `rustc` appears to make use of this as can be demonstrated for both locals and statics. For parameters no such example has been found, but as there remains a lack of a guarantee we err on the safe side. Resolves: model-checking#3129
1 parent efdae5b commit 71869b2

File tree

8 files changed

+92
-42
lines changed

8 files changed

+92
-42
lines changed

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

+22-10
Original file line numberDiff line numberDiff line change
@@ -1221,7 +1221,9 @@ impl<'tcx> GotocCtx<'tcx> {
12211221
// `raw_eq` determines whether the raw bytes of two values are equal.
12221222
// https://doc.rust-lang.org/core/intrinsics/fn.raw_eq.html
12231223
//
1224-
// The implementation below calls `memcmp` and returns equal if the result is zero.
1224+
// The implementation below calls `memcmp` and returns equal if the result is zero, and
1225+
// immediately returns zero when ZSTs are compared to mimic what compare_bytes and our memcmp
1226+
// hook do.
12251227
//
12261228
// TODO: It's UB to call `raw_eq` if any of the bytes in the first or second
12271229
// arguments are uninitialized. At present, we cannot detect if there is
@@ -1240,13 +1242,17 @@ impl<'tcx> GotocCtx<'tcx> {
12401242
let dst = fargs.remove(0).cast_to(Type::void_pointer());
12411243
let val = fargs.remove(0).cast_to(Type::void_pointer());
12421244
let layout = self.layout_of_stable(ty);
1243-
let sz = Expr::int_constant(layout.size.bytes(), Type::size_t())
1244-
.with_size_of_annotation(self.codegen_ty_stable(ty));
1245-
let e = BuiltinFn::Memcmp
1246-
.call(vec![dst, val, sz], loc)
1247-
.eq(Type::c_int().zero())
1248-
.cast_to(Type::c_bool());
1249-
self.codegen_expr_to_place_stable(p, e)
1245+
if layout.size.bytes() == 0 {
1246+
self.codegen_expr_to_place_stable(p, Expr::int_constant(1, Type::c_bool()))
1247+
} else {
1248+
let sz = Expr::int_constant(layout.size.bytes(), Type::size_t())
1249+
.with_size_of_annotation(self.codegen_ty_stable(ty));
1250+
let e = BuiltinFn::Memcmp
1251+
.call(vec![dst, val, sz], loc)
1252+
.eq(Type::c_int().zero())
1253+
.cast_to(Type::c_bool());
1254+
self.codegen_expr_to_place_stable(p, e)
1255+
}
12501256
}
12511257

12521258
// This is an operation that is primarily relevant for stacked borrow
@@ -1856,8 +1862,14 @@ impl<'tcx> GotocCtx<'tcx> {
18561862
"`dst` must be properly aligned",
18571863
loc,
18581864
);
1859-
let expr = dst.dereference().assign(src, loc);
1860-
Stmt::block(vec![align_check, expr], loc)
1865+
let deref = dst.dereference();
1866+
if deref.typ().sizeof(&self.symbol_table) == 0 {
1867+
// do not attempt to dereference (and assign) a ZST
1868+
align_check
1869+
} else {
1870+
let expr = deref.assign(src, loc);
1871+
Stmt::block(vec![align_check, expr], loc)
1872+
}
18611873
}
18621874

18631875
/// Sets `count * size_of::<T>()` bytes of memory starting at `dst` to `val`

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

+36-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::codegen_cprover_gotoc::codegen::typ::std_pointee_type;
1111
use crate::codegen_cprover_gotoc::utils::{dynamic_fat_ptr, slice_fat_ptr};
1212
use crate::codegen_cprover_gotoc::GotocCtx;
1313
use crate::unwrap_or_return_codegen_unimplemented;
14-
use cbmc::goto_program::{Expr, Location, Type};
14+
use cbmc::goto_program::{Expr, ExprValue, Location, Stmt, Type};
1515
use rustc_middle::ty::layout::LayoutOf;
1616
use rustc_smir::rustc_internal;
1717
use rustc_target::abi::{TagEncoding, Variants};
@@ -636,6 +636,14 @@ impl<'tcx> GotocCtx<'tcx> {
636636
}
637637
}
638638

639+
fn is_zst_object(&self, expr: &Expr) -> bool {
640+
match expr.value() {
641+
ExprValue::Symbol { .. } => expr.typ().sizeof(&self.symbol_table) == 0,
642+
ExprValue::Member { lhs, .. } => self.is_zst_object(lhs),
643+
_ => false,
644+
}
645+
}
646+
639647
/// Codegen the reference to a given place.
640648
/// We currently have a somewhat weird way of handling ZST.
641649
/// - For `*(&T)` where `T: Unsized`, the projection's `goto_expr` is a thin pointer, so we
@@ -647,8 +655,33 @@ impl<'tcx> GotocCtx<'tcx> {
647655
let projection =
648656
unwrap_or_return_codegen_unimplemented!(self, self.codegen_place_stable(place));
649657
if self.use_thin_pointer_stable(place_ty) {
650-
// Just return the address of the place dereferenced.
651-
projection.goto_expr.address_of()
658+
// For ZST objects rustc does not necessarily generate any actual objects.
659+
let need_not_be_an_object = self.is_zst_object(&projection.goto_expr);
660+
let address_of = projection.goto_expr.clone().address_of();
661+
if need_not_be_an_object {
662+
// Create a non-deterministic numeric value, assume it is non-zero and (when
663+
// interpreted as an address) of proper alignment for the type, and cast that
664+
// numeric value to a pointer type.
665+
let loc = projection.goto_expr.location();
666+
let (var, decl) =
667+
self.decl_temp_variable(Type::size_t(), Some(Type::size_t().nondet()), *loc);
668+
let assume_non_zero =
669+
Stmt::assume(var.clone().neq(Expr::int_constant(0, var.typ().clone())), *loc);
670+
let layout = self.layout_of_stable(place_ty);
671+
let alignment = Expr::int_constant(layout.align.abi.bytes(), var.typ().clone());
672+
let assume_aligned = Stmt::assume(
673+
var.clone().rem(alignment).eq(Expr::int_constant(0, var.typ().clone())),
674+
*loc,
675+
);
676+
let cast_to_pointer_type = var.cast_to(address_of.typ().clone()).as_stmt(*loc);
677+
Expr::statement_expression(
678+
vec![decl, assume_non_zero, assume_aligned, cast_to_pointer_type],
679+
address_of.typ().clone(),
680+
)
681+
} else {
682+
// Just return the address of the place dereferenced.
683+
address_of
684+
}
652685
} else if place_ty == pointee_type(self.local_ty_stable(place.local)).unwrap() {
653686
// Just return the fat pointer if this is a simple &(*local).
654687
projection.fat_ptr_goto_expr.unwrap()

library/kani/src/mem.rs

+7-26
Original file line numberDiff line numberDiff line change
@@ -61,25 +61,18 @@ where
6161
crate::assert(!ptr.is_null(), "Expected valid pointer, but found `null`");
6262

6363
let (thin_ptr, metadata) = ptr.to_raw_parts();
64-
can_read(&metadata, thin_ptr)
65-
}
66-
67-
fn can_read<M, T>(metadata: &M, data_ptr: *const ()) -> bool
68-
where
69-
M: PtrProperties<T>,
70-
T: ?Sized,
71-
{
72-
let marker = Internal;
73-
let sz = metadata.pointee_size(marker);
74-
if metadata.dangling(marker) as *const _ == data_ptr {
75-
crate::assert(sz == 0, "Dangling pointer is only valid for zero-sized access")
64+
let sz = metadata.pointee_size(Internal);
65+
if sz == 0 {
66+
true // ZST pointers are always valid
7667
} else {
68+
// Note that this branch can't be tested in concrete execution as `is_read_ok` needs to be
69+
// stubbed.
7770
crate::assert(
78-
is_read_ok(data_ptr, sz),
71+
is_read_ok(thin_ptr, sz),
7972
"Expected valid pointer, but found dangling pointer",
8073
);
74+
true
8175
}
82-
true
8376
}
8477

8578
mod private {
@@ -256,18 +249,6 @@ mod tests {
256249
assert_valid_ptr(vec_ptr);
257250
}
258251

259-
#[test]
260-
#[should_panic(expected = "Dangling pointer is only valid for zero-sized access")]
261-
fn test_dangling_char() {
262-
test_dangling_of_t::<char>();
263-
}
264-
265-
#[test]
266-
#[should_panic(expected = "Dangling pointer is only valid for zero-sized access")]
267-
fn test_dangling_slice() {
268-
test_dangling_of_t::<&str>();
269-
}
270-
271252
#[test]
272253
#[should_panic(expected = "Expected valid pointer, but found `null`")]
273254
fn test_null_fat_ptr() {

tests/expected/function-contract/valid_ptr.expected

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Checking harness pre_condition::harness_invalid_ptr...
2-
Failed Checks: Dangling pointer is only valid for zero-sized access
2+
Failed Checks: Expected valid pointer, but found dangling pointer
33

44
Checking harness pre_condition::harness_stack_ptr...
55
VERIFICATION:- SUCCESSFUL

tests/expected/zst/expected

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Status: FAILURE\
2+
Description: "dereference failure: pointer NULL"
3+
4+
VERIFICATION:- FAILED

tests/expected/zst/main.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// This example demonstrates that rustc may choose not to allocate unique locations to ZST objects.
5+
#[repr(C)]
6+
#[derive(Copy, Clone)]
7+
struct Z(i8, i64);
8+
9+
struct Y;
10+
11+
#[kani::proof]
12+
fn test_z() -> Z {
13+
let m = Y;
14+
let n = Y;
15+
let zz = Z(1, -1);
16+
17+
let ptr: *const Z = if &n as *const _ == &m as *const _ { std::ptr::null() } else { &zz };
18+
unsafe { *ptr }
19+
}

tests/kani/Closure/zst_param.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ fn check_zst_param() {
1717
let input = kani::any();
1818
let closure = |a: Void, out: usize, b: Void| {
1919
kani::cover!();
20-
assert!(&a as *const Void != &b as *const Void, "Should succeed");
20+
assert!(&a as *const Void != std::ptr::null(), "Should succeed");
21+
assert!(&b as *const Void != std::ptr::null(), "Should succeed");
2122
out
2223
};
2324
let output = invoke(input, closure);

tests/kani/MemPredicates/thin_ptr_validity.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ mod invalid_access {
4646
}
4747

4848
#[kani::proof]
49-
#[kani::should_panic]
5049
pub fn check_invalid_zst() {
5150
let raw_ptr: *const [char; 0] =
5251
unsafe { new_dead_ptr::<[char; 2]>(['a', 'b']) } as *const _;
52+
// ZST pointer are always valid
5353
assert_valid_ptr(raw_ptr);
5454
}
5555

0 commit comments

Comments
 (0)