Skip to content

Commit b163ae0

Browse files
committed
Merge branch 'transmute_unchecked' of https://github.com/AlexLB99/verify-rust-std into transmute_unchecked
2 parents 036b504 + c3c1498 commit b163ae0

File tree

4 files changed

+506
-1
lines changed

4 files changed

+506
-1
lines changed

Diff for: .github/pull_requests.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ members = [
1616
"robdockins",
1717
"HuStmpHrrr",
1818
"Eh2406",
19-
"jswrenn"
19+
"jswrenn",
20+
"havelund",
21+
"jorajeev"
2022
]

Diff for: library/core/src/ffi/c_str.rs

+109
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ use crate::ptr::NonNull;
99
use crate::slice::memchr;
1010
use crate::{fmt, intrinsics, ops, slice, str};
1111

12+
use crate::ub_checks::Invariant;
13+
14+
#[cfg(kani)]
15+
use crate::kani;
16+
1217
// FIXME: because this is doc(inline)d, we *have* to use intra-doc links because the actual link
1318
// depends on where the item is being documented. however, since this is libcore, we can't
1419
// actually reference libstd or liballoc in intra-doc links. so, the best we can do is remove the
@@ -207,6 +212,22 @@ impl fmt::Display for FromBytesWithNulError {
207212
}
208213
}
209214

215+
#[unstable(feature = "ub_checks", issue = "none")]
216+
impl Invariant for &CStr {
217+
/**
218+
* Safety invariant of a valid CStr:
219+
* 1. An empty CStr should have a null byte.
220+
* 2. A valid CStr should end with a null-terminator and contains
221+
* no intermediate null bytes.
222+
*/
223+
fn is_safe(&self) -> bool {
224+
let bytes: &[c_char] = &self.inner;
225+
let len = bytes.len();
226+
227+
!bytes.is_empty() && bytes[len - 1] == 0 && !bytes[..len-1].contains(&0)
228+
}
229+
}
230+
210231
impl CStr {
211232
/// Wraps a raw C string with a safe C string wrapper.
212233
///
@@ -833,3 +854,91 @@ impl Iterator for Bytes<'_> {
833854

834855
#[unstable(feature = "cstr_bytes", issue = "112115")]
835856
impl FusedIterator for Bytes<'_> {}
857+
858+
#[cfg(kani)]
859+
#[unstable(feature = "kani", issue = "none")]
860+
mod verify {
861+
use super::*;
862+
863+
// Helper function
864+
fn arbitrary_cstr(slice: &[u8]) -> &CStr {
865+
let result = CStr::from_bytes_until_nul(&slice);
866+
kani::assume(result.is_ok());
867+
let c_str = result.unwrap();
868+
assert!(c_str.is_safe());
869+
c_str
870+
}
871+
872+
// pub const fn from_bytes_until_nul(bytes: &[u8]) -> Result<&CStr, FromBytesUntilNulError>
873+
#[kani::proof]
874+
#[kani::unwind(32)] // 7.3 seconds when 16; 33.1 seconds when 32
875+
fn check_from_bytes_until_nul() {
876+
const MAX_SIZE: usize = 32;
877+
let string: [u8; MAX_SIZE] = kani::any();
878+
// Covers the case of a single null byte at the end, no null bytes, as
879+
// well as intermediate null bytes
880+
let slice = kani::slice::any_slice_of_array(&string);
881+
882+
let result = CStr::from_bytes_until_nul(slice);
883+
if let Ok(c_str) = result {
884+
assert!(c_str.is_safe());
885+
}
886+
}
887+
888+
// pub const fn count_bytes(&self) -> usize
889+
#[kani::proof]
890+
#[kani::unwind(32)]
891+
fn check_count_bytes() {
892+
const MAX_SIZE: usize = 32;
893+
let mut bytes: [u8; MAX_SIZE] = kani::any();
894+
895+
// Non-deterministically generate a length within the valid range [0, MAX_SIZE]
896+
let mut len: usize = kani::any_where(|&x| x < MAX_SIZE);
897+
898+
// If a null byte exists before the generated length
899+
// adjust len to its position
900+
if let Some(pos) = bytes[..len].iter().position(|&x| x == 0) {
901+
len = pos;
902+
} else {
903+
// If no null byte, insert one at the chosen length
904+
bytes[len] = 0;
905+
}
906+
907+
let c_str = CStr::from_bytes_until_nul(&bytes).unwrap();
908+
// Verify that count_bytes matches the adjusted length
909+
assert_eq!(c_str.count_bytes(), len);
910+
assert!(c_str.is_safe());
911+
}
912+
913+
// pub const fn to_bytes(&self) -> &[u8]
914+
#[kani::proof]
915+
#[kani::unwind(32)]
916+
fn check_to_bytes() {
917+
const MAX_SIZE: usize = 32;
918+
let string: [u8; MAX_SIZE] = kani::any();
919+
let slice = kani::slice::any_slice_of_array(&string);
920+
let c_str = arbitrary_cstr(slice);
921+
922+
let bytes = c_str.to_bytes();
923+
let end_idx = bytes.len();
924+
// Comparison does not include the null byte
925+
assert_eq!(bytes, &slice[..end_idx]);
926+
assert!(c_str.is_safe());
927+
}
928+
929+
// pub const fn to_bytes_with_nul(&self) -> &[u8]
930+
#[kani::proof]
931+
#[kani::unwind(33)]
932+
fn check_to_bytes_with_nul() {
933+
const MAX_SIZE: usize = 32;
934+
let string: [u8; MAX_SIZE] = kani::any();
935+
let slice = kani::slice::any_slice_of_array(&string);
936+
let c_str = arbitrary_cstr(slice);
937+
938+
let bytes = c_str.to_bytes_with_nul();
939+
let end_idx = bytes.len();
940+
// Comparison includes the null byte
941+
assert_eq!(bytes, &slice[..end_idx]);
942+
assert!(c_str.is_safe());
943+
}
944+
}

Diff for: library/core/src/ptr/mut_ptr.rs

+177
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ use crate::cmp::Ordering::{Equal, Greater, Less};
33
use crate::intrinsics::const_eval_select;
44
use crate::mem::SizedTypeProperties;
55
use crate::slice::{self, SliceIndex};
6+
use safety::{ensures, requires};
7+
8+
#[cfg(kani)]
9+
use crate::kani;
610

711
impl<T: ?Sized> *mut T {
812
/// Returns `true` if the pointer is null.
@@ -400,6 +404,22 @@ impl<T: ?Sized> *mut T {
400404
#[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")]
401405
#[inline(always)]
402406
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
407+
// Note: It is the caller's responsibility to ensure that `self` is non-null and properly aligned.
408+
// These conditions are not verified as part of the preconditions.
409+
#[requires(
410+
// Precondition 1: the computed offset `count * size_of::<T>()` does not overflow `isize`
411+
count.checked_mul(core::mem::size_of::<T>() as isize).is_some() &&
412+
// Precondition 2: adding the computed offset to `self` does not cause overflow
413+
(self as isize).checked_add((count * core::mem::size_of::<T>() as isize)).is_some() &&
414+
// Precondition 3: If `T` is a unit type (`size_of::<T>() == 0`), this check is unnecessary as it has no allocated memory.
415+
// Otherwise, for non-unit types, `self` and `self.wrapping_offset(count)` should point to the same allocated object,
416+
// restricting `count` to prevent crossing allocation boundaries.
417+
((core::mem::size_of::<T>() == 0) || (kani::mem::same_allocation(self, self.wrapping_offset(count))))
418+
)]
419+
// Postcondition: If `T` is a unit type (`size_of::<T>() == 0`), no allocation check is needed.
420+
// Otherwise, for non-unit types, ensure that `self` and `result` point to the same allocated object,
421+
// verifying that the result remains within the same allocation as `self`.
422+
#[ensures(|result| (core::mem::size_of::<T>() == 0) || kani::mem::same_allocation(self as *const T, *result as *const T))]
403423
pub const unsafe fn offset(self, count: isize) -> *mut T
404424
where
405425
T: Sized,
@@ -998,6 +1018,23 @@ impl<T: ?Sized> *mut T {
9981018
#[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")]
9991019
#[inline(always)]
10001020
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
1021+
// Note: It is the caller's responsibility to ensure that `self` is non-null and properly aligned.
1022+
// These conditions are not verified as part of the preconditions.
1023+
#[requires(
1024+
// Precondition 1: the computed offset `count * size_of::<T>()` does not overflow `isize`
1025+
count.checked_mul(core::mem::size_of::<T>()).is_some() &&
1026+
count * core::mem::size_of::<T>() <= isize::MAX as usize &&
1027+
// Precondition 2: adding the computed offset to `self` does not cause overflow
1028+
(self as isize).checked_add((count * core::mem::size_of::<T>()) as isize).is_some() &&
1029+
// Precondition 3: If `T` is a unit type (`size_of::<T>() == 0`), this check is unnecessary as it has no allocated memory.
1030+
// Otherwise, for non-unit types, `self` and `self.wrapping_add(count)` should point to the same allocated object,
1031+
// restricting `count` to prevent crossing allocation boundaries.
1032+
((core::mem::size_of::<T>() == 0) || (kani::mem::same_allocation(self, self.wrapping_add(count))))
1033+
)]
1034+
// Postcondition: If `T` is a unit type (`size_of::<T>() == 0`), no allocation check is needed.
1035+
// Otherwise, for non-unit types, ensure that `self` and `result` point to the same allocated object,
1036+
// verifying that the result remains within the same allocation as `self`.
1037+
#[ensures(|result| (core::mem::size_of::<T>() == 0) || kani::mem::same_allocation(self as *const T, *result as *const T))]
10011038
pub const unsafe fn add(self, count: usize) -> Self
10021039
where
10031040
T: Sized,
@@ -1107,6 +1144,23 @@ impl<T: ?Sized> *mut T {
11071144
#[cfg_attr(bootstrap, rustc_allow_const_fn_unstable(unchecked_neg))]
11081145
#[inline(always)]
11091146
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
1147+
// Note: It is the caller's responsibility to ensure that `self` is non-null and properly aligned.
1148+
// These conditions are not verified as part of the preconditions.
1149+
#[requires(
1150+
// Precondition 1: the computed offset `count * size_of::<T>()` does not overflow `isize`
1151+
count.checked_mul(core::mem::size_of::<T>()).is_some() &&
1152+
count * core::mem::size_of::<T>() <= isize::MAX as usize &&
1153+
// Precondition 2: subtracting the computed offset from `self` does not cause overflow
1154+
(self as isize).checked_sub((count * core::mem::size_of::<T>()) as isize).is_some() &&
1155+
// Precondition 3: If `T` is a unit type (`size_of::<T>() == 0`), this check is unnecessary as it has no allocated memory.
1156+
// Otherwise, for non-unit types, `self` and `self.wrapping_sub(count)` should point to the same allocated object,
1157+
// restricting `count` to prevent crossing allocation boundaries.
1158+
((core::mem::size_of::<T>() == 0) || (kani::mem::same_allocation(self, self.wrapping_sub(count))))
1159+
)]
1160+
// Postcondition: If `T` is a unit type (`size_of::<T>() == 0`), no allocation check is needed.
1161+
// Otherwise, for non-unit types, ensure that `self` and `result` point to the same allocated object,
1162+
// verifying that the result remains within the same allocation as `self`.
1163+
#[ensures(|result| (core::mem::size_of::<T>() == 0) || kani::mem::same_allocation(self as *const T, *result as *const T))]
11101164
pub const unsafe fn sub(self, count: usize) -> Self
11111165
where
11121166
T: Sized,
@@ -2302,3 +2356,126 @@ impl<T: ?Sized> PartialOrd for *mut T {
23022356
*self >= *other
23032357
}
23042358
}
2359+
2360+
#[cfg(kani)]
2361+
#[unstable(feature = "kani", issue = "none")]
2362+
mod verify {
2363+
use crate::kani;
2364+
2365+
/// This macro generates proofs for contracts on `add`, `sub`, and `offset`
2366+
/// operations for pointers to integer, composite, and unit types.
2367+
/// - `$type`: Specifies the pointee type.
2368+
/// - `$proof_name`: Specifies the name of the generated proof for contract.
2369+
macro_rules! generate_mut_arithmetic_harness {
2370+
($type:ty, $proof_name:ident, add) => {
2371+
#[kani::proof_for_contract(<*mut $type>::add)]
2372+
pub fn $proof_name() {
2373+
// 200 bytes are large enough to cover all pointee types used for testing
2374+
const BUF_SIZE: usize = 200;
2375+
let mut generator = kani::PointerGenerator::<BUF_SIZE>::new();
2376+
let test_ptr: *mut $type = generator.any_in_bounds().ptr;
2377+
let count: usize = kani::any();
2378+
unsafe {
2379+
test_ptr.add(count);
2380+
}
2381+
}
2382+
};
2383+
($type:ty, $proof_name:ident, sub) => {
2384+
#[kani::proof_for_contract(<*mut $type>::sub)]
2385+
pub fn $proof_name() {
2386+
// 200 bytes are large enough to cover all pointee types used for testing
2387+
const BUF_SIZE: usize = 200;
2388+
let mut generator = kani::PointerGenerator::<BUF_SIZE>::new();
2389+
let test_ptr: *mut $type = generator.any_in_bounds().ptr;
2390+
let count: usize = kani::any();
2391+
unsafe {
2392+
test_ptr.sub(count);
2393+
}
2394+
}
2395+
};
2396+
($type:ty, $proof_name:ident, offset) => {
2397+
#[kani::proof_for_contract(<*mut $type>::offset)]
2398+
pub fn $proof_name() {
2399+
// 200 bytes are large enough to cover all pointee types used for testing
2400+
const BUF_SIZE: usize = 200;
2401+
let mut generator = kani::PointerGenerator::<BUF_SIZE>::new();
2402+
let test_ptr: *mut $type = generator.any_in_bounds().ptr;
2403+
let count: isize = kani::any();
2404+
unsafe {
2405+
test_ptr.offset(count);
2406+
}
2407+
}
2408+
};
2409+
}
2410+
2411+
// <*mut T>:: add() integer types verification
2412+
generate_mut_arithmetic_harness!(i8, check_mut_add_i8, add);
2413+
generate_mut_arithmetic_harness!(i16, check_mut_add_i16, add);
2414+
generate_mut_arithmetic_harness!(i32, check_mut_add_i32, add);
2415+
generate_mut_arithmetic_harness!(i64, check_mut_add_i64, add);
2416+
generate_mut_arithmetic_harness!(i128, check_mut_add_i128, add);
2417+
generate_mut_arithmetic_harness!(isize, check_mut_add_isize, add);
2418+
// Due to a bug of kani this test case is malfunctioning for now.
2419+
// Tracking issue: https://github.com/model-checking/kani/issues/3743
2420+
// generate_mut_arithmetic_harness!(u8, check_mut_add_u8, add);
2421+
generate_mut_arithmetic_harness!(u16, check_mut_add_u16, add);
2422+
generate_mut_arithmetic_harness!(u32, check_mut_add_u32, add);
2423+
generate_mut_arithmetic_harness!(u64, check_mut_add_u64, add);
2424+
generate_mut_arithmetic_harness!(u128, check_mut_add_u128, add);
2425+
generate_mut_arithmetic_harness!(usize, check_mut_add_usize, add);
2426+
2427+
// <*mut T>:: add() unit type verification
2428+
generate_mut_arithmetic_harness!((), check_mut_add_unit, add);
2429+
2430+
// <*mut T>:: add() composite types verification
2431+
generate_mut_arithmetic_harness!((i8, i8), check_mut_add_tuple_1, add);
2432+
generate_mut_arithmetic_harness!((f64, bool), check_mut_add_tuple_2, add);
2433+
generate_mut_arithmetic_harness!((i32, f64, bool), check_mut_add_tuple_3, add);
2434+
generate_mut_arithmetic_harness!((i8, u16, i32, u64, isize), check_mut_add_tuple_4, add);
2435+
2436+
// <*mut T>:: sub() integer types verification
2437+
generate_mut_arithmetic_harness!(i8, check_mut_sub_i8, sub);
2438+
generate_mut_arithmetic_harness!(i16, check_mut_sub_i16, sub);
2439+
generate_mut_arithmetic_harness!(i32, check_mut_sub_i32, sub);
2440+
generate_mut_arithmetic_harness!(i64, check_mut_sub_i64, sub);
2441+
generate_mut_arithmetic_harness!(i128, check_mut_sub_i128, sub);
2442+
generate_mut_arithmetic_harness!(isize, check_mut_sub_isize, sub);
2443+
generate_mut_arithmetic_harness!(u8, check_mut_sub_u8, sub);
2444+
generate_mut_arithmetic_harness!(u16, check_mut_sub_u16, sub);
2445+
generate_mut_arithmetic_harness!(u32, check_mut_sub_u32, sub);
2446+
generate_mut_arithmetic_harness!(u64, check_mut_sub_u64, sub);
2447+
generate_mut_arithmetic_harness!(u128, check_mut_sub_u128, sub);
2448+
generate_mut_arithmetic_harness!(usize, check_mut_sub_usize, sub);
2449+
2450+
// <*mut T>:: sub() unit type verification
2451+
generate_mut_arithmetic_harness!((), check_mut_sub_unit, sub);
2452+
2453+
// <*mut T>:: sub() composite types verification
2454+
generate_mut_arithmetic_harness!((i8, i8), check_mut_sub_tuple_1, sub);
2455+
generate_mut_arithmetic_harness!((f64, bool), check_mut_sub_tuple_2, sub);
2456+
generate_mut_arithmetic_harness!((i32, f64, bool), check_mut_sub_tuple_3, sub);
2457+
generate_mut_arithmetic_harness!((i8, u16, i32, u64, isize), check_mut_sub_tuple_4, sub);
2458+
2459+
// fn <*mut T>::offset() integer types verification
2460+
generate_mut_arithmetic_harness!(i8, check_mut_offset_i8, offset);
2461+
generate_mut_arithmetic_harness!(i16, check_mut_offset_i16, offset);
2462+
generate_mut_arithmetic_harness!(i32, check_mut_offset_i32, offset);
2463+
generate_mut_arithmetic_harness!(i64, check_mut_offset_i64, offset);
2464+
generate_mut_arithmetic_harness!(i128, check_mut_offset_i128, offset);
2465+
generate_mut_arithmetic_harness!(isize, check_mut_offset_isize, offset);
2466+
generate_mut_arithmetic_harness!(u8, check_mut_offset_u8, offset);
2467+
generate_mut_arithmetic_harness!(u16, check_mut_offset_u16, offset);
2468+
generate_mut_arithmetic_harness!(u32, check_mut_offset_u32, offset);
2469+
generate_mut_arithmetic_harness!(u64, check_mut_offset_u64, offset);
2470+
generate_mut_arithmetic_harness!(u128, check_mut_offset_u128, offset);
2471+
generate_mut_arithmetic_harness!(usize, check_mut_offset_usize, offset);
2472+
2473+
// fn <*mut T>::offset() unit type verification
2474+
generate_mut_arithmetic_harness!((), check_mut_offset_unit, offset);
2475+
2476+
// fn <*mut T>::offset() composite type verification
2477+
generate_mut_arithmetic_harness!((i8, i8), check_mut_offset_tuple_1, offset);
2478+
generate_mut_arithmetic_harness!((f64, bool), check_mut_offset_tuple_2, offset);
2479+
generate_mut_arithmetic_harness!((i32, f64, bool), check_mut_offset_tuple_3, offset);
2480+
generate_mut_arithmetic_harness!((i8, u16, i32, u64, isize), check_mut_offset_tuple_4, offset);
2481+
}

0 commit comments

Comments
 (0)