Skip to content

Commit d29f29e

Browse files
Contracts & Harnesses for add, addr, and align_offset (#105)
Towards #53 ## Changes Three function contracts & four harnesses: - added contract and harness for `non_null::add` - added contract and harness for `non_null::addr` - added contract and harnesses for `non_null::align_offset`, including both positive and negative harness that triggers panic. The ensures clause for `align_offset` is referenced from [`align_offset`](https://github.com/model-checking/verify-rust-std/pull/69/files) in `library/core/src/ptr/mod.rs`. ## Revalidation To revalidate the verification results, run `kani verify-std -Z unstable-options "path/to/library" -Z function-contracts -Z mem-predicates --harness ptr::non_null::verify`. This will run all six harnesses in the module. All default checks should pass: ``` SUMMARY: ** 0 of 1556 failed VERIFICATION:- SUCCESSFUL Verification Time: 0.28004378s Complete - 6 successfully verified harnesses, 0 failures, 6 total. ``` ### :exclamation: Warning Running the above command with the default installed cargo kani will result in compilation error due to the latest merged from [PR#91](#91). Detailed errors are commented under that PR. This issue is waiting to be resolved. ## TODO: - Use `Layout` to create dynamically sized arrays in place of fixed size array in harnesses. This approach currently has errors and is documented in [discussion](#104). - Verify multiple data types: these will be added in future PR. - Add `requires` clause in contract to constrain `count` to be within object memory size: there is a current [issue](#99) with using `ub_checks::can_write` to get the object size. A workaround is implemented in the harness. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Carolyn Zech <[email protected]>
1 parent 83e04a0 commit d29f29e

File tree

1 file changed

+90
-1
lines changed

1 file changed

+90
-1
lines changed

library/core/src/ptr/non_null.rs

+90-1
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ impl<T: ?Sized> NonNull<T> {
294294
#[must_use]
295295
#[inline]
296296
#[stable(feature = "strict_provenance", since = "CURRENT_RUSTC_VERSION")]
297+
#[ensures(|result| result.get() == self.as_ptr() as *const() as usize)]
297298
pub fn addr(self) -> NonZero<usize> {
298299
// SAFETY: The pointer is guaranteed by the type to be non-null,
299300
// meaning that the address will be non-zero.
@@ -567,6 +568,11 @@ impl<T: ?Sized> NonNull<T> {
567568
#[must_use = "returns a new pointer rather than modifying its argument"]
568569
#[stable(feature = "non_null_convenience", since = "1.80.0")]
569570
#[rustc_const_stable(feature = "non_null_convenience", since = "1.80.0")]
571+
#[requires(count.checked_mul(core::mem::size_of::<T>()).is_some()
572+
&& count * core::mem::size_of::<T>() <= isize::MAX as usize
573+
&& (self.pointer as isize).checked_add(count as isize * core::mem::size_of::<T>() as isize).is_some() // check wrapping add
574+
&& kani::mem::same_allocation(self.pointer, self.pointer.wrapping_offset(count as isize)))]
575+
#[ensures(|result: &NonNull<T>| result.as_ptr() == self.as_ptr().offset(count as isize))]
570576
pub const unsafe fn add(self, count: usize) -> Self
571577
where
572578
T: Sized,
@@ -1208,6 +1214,36 @@ impl<T: ?Sized> NonNull<T> {
12081214
#[must_use]
12091215
#[stable(feature = "non_null_convenience", since = "1.80.0")]
12101216
#[rustc_const_unstable(feature = "const_align_offset", issue = "90962")]
1217+
#[ensures(|result| {
1218+
// Post-condition reference: https://github.com/model-checking/verify-rust-std/pull/69/files
1219+
let stride = crate::mem::size_of::<T>();
1220+
// ZSTs
1221+
if stride == 0 {
1222+
if self.pointer.addr() % align == 0 {
1223+
return *result == 0;
1224+
} else {
1225+
return *result == usize::MAX;
1226+
}
1227+
}
1228+
// In this case, the pointer cannot be aligned
1229+
if (align % stride == 0) && (self.pointer.addr() % stride != 0) {
1230+
return *result == usize::MAX;
1231+
}
1232+
// Checking if the answer should indeed be usize::MAX when align % stride != 0
1233+
// requires computing gcd(a, stride), which is too expensive without
1234+
// quantifiers (https://model-checking.github.io/kani/rfc/rfcs/0010-quantifiers.html).
1235+
// This should be updated once quantifiers are available.
1236+
if (align % stride != 0 && *result == usize::MAX) {
1237+
return true;
1238+
}
1239+
// If we reach this case, either:
1240+
// - align % stride == 0 and self.pointer.addr() % stride == 0, so it is definitely possible to align the pointer
1241+
// - align % stride != 0 and result != usize::MAX, so align_offset is claiming that it's possible to align the pointer
1242+
// Check that applying the returned result does indeed produce an aligned address
1243+
let product = usize::wrapping_mul(*result, stride);
1244+
let new_addr = usize::wrapping_add(product, self.pointer.addr());
1245+
*result != usize::MAX && new_addr % align == 0
1246+
})]
12111247
pub const fn align_offset(self, align: usize) -> usize
12121248
where
12131249
T: Sized,
@@ -1811,6 +1847,7 @@ impl<T: ?Sized> From<&T> for NonNull<T> {
18111847
mod verify {
18121848
use super::*;
18131849
use crate::ptr::null_mut;
1850+
use kani::PointerGenerator;
18141851

18151852
// pub const unsafe fn new_unchecked(ptr: *mut T) -> Self
18161853
#[kani::proof_for_contract(NonNull::new_unchecked)]
@@ -1821,12 +1858,64 @@ mod verify {
18211858
}
18221859
}
18231860

1824-
// pub const unsafe fn new(ptr: *mut T) -> Option<Self>
1861+
// pub const fn new(ptr: *mut T) -> Option<Self>
18251862
#[kani::proof_for_contract(NonNull::new)]
18261863
pub fn non_null_check_new() {
18271864
let mut x: i32 = kani::any();
18281865
let xptr = &mut x;
18291866
let maybe_null_ptr = if kani::any() { xptr as *mut i32 } else { null_mut() };
18301867
let _ = NonNull::new(maybe_null_ptr);
18311868
}
1869+
1870+
// pub const unsafe fn add(self, count: usize) -> Self
1871+
#[kani::proof_for_contract(NonNull::add)]
1872+
pub fn non_null_check_add() {
1873+
const SIZE: usize = 100000;
1874+
let mut generator = PointerGenerator::<100000>::new();
1875+
let raw_ptr: *mut i8 = generator.any_in_bounds().ptr;
1876+
let ptr = unsafe { NonNull::new(raw_ptr).unwrap()};
1877+
// Create a non-deterministic count value
1878+
let count: usize = kani::any();
1879+
1880+
unsafe {
1881+
let result = ptr.add(count);
1882+
}
1883+
}
1884+
1885+
// pub fn addr(self) -> NonZero<usize>
1886+
#[kani::proof_for_contract(NonNull::addr)]
1887+
pub fn non_null_check_addr() {
1888+
// Create NonNull pointer & get pointer address
1889+
let x = kani::any::<usize>() as *mut i32;
1890+
let Some(nonnull_xptr) = NonNull::new(x) else { return; };
1891+
let address = nonnull_xptr.addr();
1892+
}
1893+
1894+
// pub fn align_offset(self, align: usize) -> usize
1895+
#[kani::proof_for_contract(NonNull::align_offset)]
1896+
pub fn non_null_check_align_offset() {
1897+
// Create NonNull pointer
1898+
let x = kani::any::<usize>() as *mut i32;
1899+
let Some(nonnull_xptr) = NonNull::new(x) else { return; };
1900+
1901+
// Call align_offset with valid align value
1902+
let align: usize = kani::any();
1903+
kani::assume(align.is_power_of_two());
1904+
nonnull_xptr.align_offset(align);
1905+
}
1906+
1907+
// pub fn align_offset(self, align: usize) -> usize
1908+
#[kani::should_panic]
1909+
#[kani::proof_for_contract(NonNull::align_offset)]
1910+
pub fn non_null_check_align_offset_negative() {
1911+
// Create NonNull pointer
1912+
let x = kani::any::<usize>() as *mut i8;
1913+
let Some(nonnull_xptr) = NonNull::new(x) else { return; };
1914+
1915+
// Generate align value that is not necessarily a power of two
1916+
let invalid_align: usize = kani::any();
1917+
1918+
// Trigger panic
1919+
let offset = nonnull_xptr.align_offset(invalid_align);
1920+
}
18321921
}

0 commit comments

Comments
 (0)