Skip to content

Commit

Permalink
Contracts & Harnesses for byte_add, byte_offset, and `byte_offset…
Browse files Browse the repository at this point in the history
…_from` (#103)

# Description:

This PR introduces function contracts and proof harness for the NonNull
pointer in the Rust core library. Specifically, it verifies three new
APIs—`byte_offset`, `byte_add`, and `byte_offset_from` with Kani. These
changes enhance the functionality of pointer arithmetic and verification
for NonNull pointers.

# Changes Overview:

**Covered APIs:**
1. `NonNull::byte_add`: Adds a specified byte offset to a pointer.
2. `NonNull::byte_offset`: Allows calculating an offset from a pointer
in bytes.
4. `NonNull::byte_offset_from`: Calculates the distance between two
pointers in bytes.

**Proof harness:**
1. `non_null_byte_add_proof`
2. `non_null_byte_offset_proof`
3. `non_null_byte_offset_from_proof`

Towards #53
  • Loading branch information
danielhumanmod authored Dec 3, 2024
1 parent ca85f7f commit 892ee59
Showing 1 changed file with 86 additions and 0 deletions.
86 changes: 86 additions & 0 deletions library/core/src/ptr/non_null.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,11 @@ impl<T: ?Sized> NonNull<T> {
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
#[stable(feature = "non_null_convenience", since = "1.80.0")]
#[rustc_const_stable(feature = "non_null_convenience", since = "1.80.0")]
#[requires(
(self.as_ptr().addr() as isize).checked_add(count).is_some() &&
kani::mem::same_allocation(self.as_ptr(), self.as_ptr().wrapping_byte_offset(count))
)]
#[ensures(|result: &Self| result.as_ptr() == self.as_ptr().wrapping_byte_offset(count))]
pub const unsafe fn byte_offset(self, count: isize) -> Self {
// SAFETY: the caller must uphold the safety contract for `offset` and `byte_offset` has
// the same safety contract.
Expand Down Expand Up @@ -607,6 +612,10 @@ impl<T: ?Sized> NonNull<T> {
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
#[stable(feature = "non_null_convenience", since = "1.80.0")]
#[rustc_const_stable(feature = "non_null_convenience", since = "1.80.0")]
#[requires(
count <= (isize::MAX as usize) - self.as_ptr().addr() && // Ensure the offset doesn't overflow
(count == 0 || kani::mem::same_allocation(self.as_ptr(), self.as_ptr().wrapping_byte_add(count))) // Ensure the offset is within the same allocation
)]
pub const unsafe fn byte_add(self, count: usize) -> Self {
// SAFETY: the caller must uphold the safety contract for `add` and `byte_add` has the same
// safety contract.
Expand Down Expand Up @@ -820,6 +829,14 @@ impl<T: ?Sized> NonNull<T> {
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
#[stable(feature = "non_null_convenience", since = "1.80.0")]
#[rustc_const_stable(feature = "non_null_convenience", since = "1.80.0")]
#[requires(
self.as_ptr().addr() == origin.as_ptr().addr() ||
kani::mem::same_allocation(self.as_ptr() as *const(), origin.as_ptr() as *const())
)]
#[ensures(
|result: &isize|
*result == (self.as_ptr() as *const u8).offset_from(origin.as_ptr() as *const u8)
)]
pub const unsafe fn byte_offset_from<U: ?Sized>(self, origin: NonNull<U>) -> isize {
// SAFETY: the caller must uphold the safety contract for `byte_offset_from`.
unsafe { self.pointer.byte_offset_from(origin.pointer) }
Expand Down Expand Up @@ -1736,6 +1753,7 @@ impl<T: ?Sized> From<&T> for NonNull<T> {
mod verify {
use super::*;
use crate::ptr::null_mut;
use crate::mem;
use kani::PointerGenerator;

trait SampleTrait {
Expand Down Expand Up @@ -2345,4 +2363,72 @@ mod verify {
generate_write_volatile_harness!(usize, {core::mem::size_of::<usize>()}, non_null_check_write_volatile_usize);
generate_write_volatile_harness!((), 1, non_null_check_write_volatile_unit);

#[kani::proof_for_contract(NonNull::byte_add)]
pub fn non_null_byte_add_proof() {
// Make size as 1000 to ensure the array is large enough to cover various senarios
// while maintaining a reasonable proof runtime
const ARR_SIZE: usize = mem::size_of::<i32>() * 1000;
let mut generator = PointerGenerator::<ARR_SIZE>::new();

let count: usize = kani::any();
let raw_ptr: *mut i32 = generator.any_in_bounds().ptr as *mut i32;

unsafe {
let ptr = NonNull::new(raw_ptr).unwrap();
let result = ptr.byte_add(count);
}
}

#[kani::proof_for_contract(NonNull::byte_add)]
pub fn non_null_byte_add_dangling_proof() {
let ptr = NonNull::<i32>::dangling();
unsafe {
let _ = ptr.byte_add(0);
}
}

#[kani::proof_for_contract(NonNull::byte_offset)]
pub fn non_null_byte_offset_proof() {
const ARR_SIZE: usize = mem::size_of::<i32>() * 1000;
let mut generator = PointerGenerator::<ARR_SIZE>::new();

let count: isize = kani::any();
let raw_ptr: *mut i32 = generator.any_in_bounds().ptr as *mut i32;

unsafe {
let ptr = NonNull::new(raw_ptr).unwrap();
let result = ptr.byte_offset(count);
}
}

#[kani::proof_for_contract(NonNull::byte_offset_from)]
pub fn non_null_byte_offset_from_proof() {
const SIZE: usize = mem::size_of::<i32>() * 1000;
let mut generator1 = PointerGenerator::<SIZE>::new();
let mut generator2 = PointerGenerator::<SIZE>::new();

let ptr: *mut i32 = generator1.any_in_bounds().ptr as *mut i32;

let origin: *mut i32 = if kani::any() {
generator1.any_in_bounds().ptr as *mut i32
} else {
generator2.any_in_bounds().ptr as *mut i32
};

let ptr_nonnull = unsafe { NonNull::new(ptr).unwrap() };
let origin_nonnull = unsafe { NonNull::new(origin).unwrap() };

unsafe {
let result = ptr_nonnull.byte_offset_from(origin_nonnull);
}
}

#[kani::proof_for_contract(NonNull::byte_offset_from)]
pub fn non_null_byte_offset_from_dangling_proof() {
let origin = NonNull::<i32>::dangling();
let ptr = origin;
unsafe {
let _ = ptr.byte_offset_from(origin);
}
}
}

0 comments on commit 892ee59

Please sign in to comment.