Skip to content

Commit fa9511d

Browse files
committed
Start the verification of slice::Iter
There are a few harnesses failing that needs further debugging. I commented them out for now.
1 parent 5738b90 commit fa9511d

File tree

3 files changed

+185
-1
lines changed

3 files changed

+185
-1
lines changed

library/core/src/slice/iter.rs

+160
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ use crate::num::NonZero;
1414
use crate::ptr::{self, without_provenance, without_provenance_mut, NonNull};
1515
use crate::{cmp, fmt};
1616

17+
#[cfg(kani)]
18+
use crate::kani;
19+
20+
use crate::ub_checks::Invariant;
21+
1722
#[stable(feature = "boxed_slice_into_iter", since = "1.80.0")]
1823
impl<T> !Iterator for [T] {}
1924

@@ -157,6 +162,23 @@ impl<T> AsRef<[T]> for Iter<'_, T> {
157162
}
158163
}
159164

165+
#[unstable(feature = "ub_checks", issue = "none")]
166+
impl<T> crate::ub_checks::Invariant for Iter<'_, T> {
167+
fn is_safe(&self) -> bool {
168+
let ty_size = crate::mem::size_of::<T>();
169+
let distance = self.ptr.addr().get().abs_diff(self.end_or_len as usize);
170+
if ty_size == 0 || distance == 0 {
171+
self.ptr.is_aligned()
172+
} else {
173+
let slice_ptr: *const [T] = crate::ptr::from_raw_parts(self.ptr.as_ptr(), distance / ty_size);
174+
crate::ub_checks::same_allocation(self.ptr.as_ptr(), self.end_or_len)
175+
&& self.ptr.addr().get() <= self.end_or_len as usize
176+
&& distance % ty_size == 0
177+
&& crate::ub_checks::can_dereference(slice_ptr)
178+
}
179+
}
180+
}
181+
160182
/// Mutable slice iterator.
161183
///
162184
/// This struct is created by the [`iter_mut`] method on [slices].
@@ -196,6 +218,24 @@ pub struct IterMut<'a, T: 'a> {
196218
_marker: PhantomData<&'a mut T>,
197219
}
198220

221+
#[unstable(feature = "ub_checks", issue = "none")]
222+
impl<T> crate::ub_checks::Invariant for IterMut<'_, T> {
223+
fn is_safe(&self) -> bool {
224+
let size = crate::mem::size_of::<T>();
225+
if size == 0 {
226+
self.ptr.is_aligned()
227+
} else {
228+
let distance = self.ptr.addr().get().abs_diff(self.end_or_len as usize);
229+
let slice_ptr: *mut [T] = crate::ptr::from_raw_parts_mut(self.ptr.as_ptr(), distance / size);
230+
crate::ub_checks::same_allocation(self.ptr.as_ptr(), self.end_or_len)
231+
&& self.ptr.addr().get() <= self.end_or_len as usize
232+
&& distance % size == 0
233+
&& crate::ub_checks::can_dereference(slice_ptr)
234+
&& crate::ub_checks::can_write(slice_ptr)
235+
}
236+
}
237+
}
238+
199239
#[stable(feature = "core_impl_debug", since = "1.9.0")]
200240
impl<T: fmt::Debug> fmt::Debug for IterMut<'_, T> {
201241
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -3464,3 +3504,123 @@ impl<'a, T: 'a + fmt::Debug, P> fmt::Debug for ChunkByMut<'a, T, P> {
34643504
f.debug_struct("ChunkByMut").field("slice", &self.slice).finish()
34653505
}
34663506
}
3507+
3508+
/// Verify the safety of the code implemented in this module (including generated code from macros).
3509+
#[unstable(feature = "kani", issue="none")]
3510+
mod verify {
3511+
use super::*;
3512+
use crate::kani;
3513+
3514+
fn any_slice<T>(orig_slice: &[T]) -> &[T] {
3515+
if kani::any() {
3516+
let first = kani::any_where(|idx: &usize| *idx <= orig_slice.len());
3517+
let last = kani::any_where(|idx: &usize| *idx >= first && *idx <= orig_slice.len());
3518+
&orig_slice[first..last]
3519+
} else {
3520+
let ptr = kani::any_where::<usize, _>(|val| *val != 0) as *const T;
3521+
kani::assume(ptr.is_aligned());
3522+
unsafe { crate::slice::from_raw_parts(ptr, 0) }
3523+
}
3524+
}
3525+
3526+
fn any_iter<'a, T>(orig_slice: &'a [T]) -> Iter<'a, T> {
3527+
let slice = any_slice(orig_slice);
3528+
Iter::new(slice)
3529+
}
3530+
3531+
/// Macro that generates a harness for a given `Iter` method.
3532+
///
3533+
/// Takes the name of the harness, the element type, and an expression to check.
3534+
macro_rules! check_safe_abstraction {
3535+
($harness:ident, $elem_ty:ty, $call:expr) => {
3536+
#[kani::proof]
3537+
fn $harness() {
3538+
let array: [$elem_ty; MAX_LEN] = kani::any();
3539+
let mut iter = any_iter::<$elem_ty>(&array);
3540+
let target = $call;
3541+
target(&mut iter);
3542+
kani::assert(iter.is_safe(), "Iter is safe");
3543+
}
3544+
};
3545+
}
3546+
3547+
/// Macro that generates a harness for a given unsafe `Iter` method.
3548+
///
3549+
/// Takes:
3550+
/// 1. The name of the harness
3551+
/// 2. the element type
3552+
/// 3. the target function
3553+
/// 4. the optional arguments.
3554+
macro_rules! check_unsafe_contracts {
3555+
($harness:ident, $elem_ty:ty, $func:ident($($args:expr),*)) => {
3556+
#[kani::proof_for_contract(Iter::$func)]
3557+
fn $harness() {
3558+
let array: [$elem_ty; MAX_LEN] = kani::any();
3559+
let mut iter = any_iter::<$elem_ty>(&array);
3560+
let _ = unsafe { iter.$func($($args),*) };
3561+
}
3562+
};
3563+
}
3564+
3565+
macro_rules! check_iter_with_ty {
3566+
($module:ident, $ty:ty, $max:expr) => {
3567+
mod $module {
3568+
use super::*;
3569+
const MAX_LEN: usize = $max;
3570+
3571+
#[kani::proof]
3572+
fn check_new_iter() {
3573+
let array: [$ty; MAX_LEN] = kani::any();
3574+
let slice = any_slice::<$ty>(&array);
3575+
let mut iter = Iter::new(slice);
3576+
kani::assert(iter.is_safe(), "Iter is safe");
3577+
}
3578+
3579+
/// Count consumes the value, thus, invoke it directly.
3580+
#[kani::proof]
3581+
fn check_count() {
3582+
let array: [$ty; MAX_LEN] = kani::any();
3583+
let mut iter = any_iter::<$ty>(&array);
3584+
iter.count();
3585+
}
3586+
3587+
#[kani::proof]
3588+
fn check_default() {
3589+
let iter: Iter<'_, $ty> = Iter::default();
3590+
kani::assert(iter.is_safe(), "Iter is safe");
3591+
}
3592+
3593+
check_unsafe_contracts!(check_next_back_unchecked, $ty, next_back_unchecked());
3594+
//check_unsafe_contracts!(check_post_inc_start, $ty, post_inc_start(kani::any()));
3595+
//check_unsafe_contracts!(check_pre_dec_end, $ty, pre_dec_end(kani::any()));
3596+
3597+
// FIXME: The functions that are commented out are currently failing verification.
3598+
// Debugging the issue is currently blocked by:
3599+
// https://github.com/model-checking/kani/issues/3670
3600+
//
3601+
// Public functions that call safe abstraction `make_slice`.
3602+
// check_safe_abstraction!(check_as_slice, $ty, as_slice);
3603+
// check_safe_abstraction!(check_as_ref, $ty, as_ref);
3604+
3605+
// check_safe_abstraction!(check_advance_back_by, $ty, advance_back_by, kani::any());
3606+
3607+
check_safe_abstraction!(check_is_empty, $ty, |iter: &mut Iter<'_, $ty>| { let _ = iter.is_empty(); });
3608+
check_safe_abstraction!(check_len, $ty, |iter: &mut Iter<'_, $ty>| { let _ = iter.len(); });
3609+
check_safe_abstraction!(check_size_hint, $ty, |iter: &mut Iter<'_, $ty>| { let _ = iter.size_hint(); });
3610+
//check_safe_abstraction!(check_nth, $ty, |iter: &mut Iter<'_, $ty>| { let _ = iter.nth(kani::any()); });
3611+
//check_safe_abstraction!(check_advance_by, $ty, |iter: &mut Iter<'_, $ty>| { let _ = iter.advance_by(kani::any()); });
3612+
//check_safe_abstraction!(check_next_back, $ty, |iter: &mut Iter<'_, $ty>| { let _ = iter.next_back(); });
3613+
//check_safe_abstraction!(check_nth_back, $ty, |iter: &mut Iter<'_, $ty>| { let _ = iter.nth_back(kani::any()); });
3614+
check_safe_abstraction!(check_next, $ty, |iter: &mut Iter<'_, $ty>| { let _ = iter.next(); });
3615+
3616+
// Ensure that clone always generates a safe object.
3617+
check_safe_abstraction!(check_clone, $ty, |iter: &mut Iter<'_, $ty>| { kani::assert(iter.clone().is_safe(), "Clone is safe"); });
3618+
}
3619+
};
3620+
}
3621+
3622+
check_iter_with_ty!(verify_unit, (), isize::MAX as usize);
3623+
check_iter_with_ty!(verify_u8, u8, u32::MAX as usize);
3624+
check_iter_with_ty!(verify_char, char, 50);
3625+
check_iter_with_ty!(verify_tup, (char, u8), 50);
3626+
}

library/core/src/slice/iter/macros.rs

+11
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ macro_rules! iterator {
7777
///
7878
/// The iterator must not be empty
7979
#[inline]
80+
#[cfg_attr(kani, kani::modifies(self))]
81+
#[safety::requires(!is_empty!(self))]
82+
#[safety::ensures(|_| self.is_safe())]
8083
unsafe fn next_back_unchecked(&mut self) -> $elem {
8184
// SAFETY: the caller promised it's not empty, so
8285
// the offsetting is in-bounds and there's an element to return.
@@ -96,6 +99,9 @@ macro_rules! iterator {
9699
// returning the old start.
97100
// Unsafe because the offset must not exceed `self.len()`.
98101
#[inline(always)]
102+
#[cfg_attr(kani, kani::modifies(self))]
103+
#[safety::requires(offset <= len!(self))]
104+
#[safety::ensures(|_| self.is_safe())]
99105
unsafe fn post_inc_start(&mut self, offset: usize) -> NonNull<T> {
100106
let old = self.ptr;
101107

@@ -115,6 +121,9 @@ macro_rules! iterator {
115121
// returning the new end.
116122
// Unsafe because the offset must not exceed `self.len()`.
117123
#[inline(always)]
124+
#[cfg_attr(kani, kani::modifies(self))]
125+
#[safety::requires(offset <= len!(self))]
126+
#[safety::ensures(|_| self.is_safe())]
118127
unsafe fn pre_dec_end(&mut self, offset: usize) -> NonNull<T> {
119128
if_zst!(mut self,
120129
// SAFETY: By our precondition, `offset` can be at most the
@@ -369,6 +378,7 @@ macro_rules! iterator {
369378
}
370379

371380
#[inline]
381+
#[safety::requires(idx < len!(self))]
372382
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item {
373383
// SAFETY: the caller must guarantee that `i` is in bounds of
374384
// the underlying slice, so `i` cannot overflow an `isize`, and
@@ -437,6 +447,7 @@ macro_rules! iterator {
437447

438448
impl<'a, T> UncheckedIterator for $name<'a, T> {
439449
#[inline]
450+
#[safety::requires(!is_empty!(self))]
440451
unsafe fn next_unchecked(&mut self) -> $elem {
441452
// SAFETY: The caller promised there's at least one more item.
442453
unsafe {

library/core/src/ub_checks.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,17 @@ pub(crate) const fn is_nonoverlapping(
162162
const_eval_select((src, dst, size, count), comptime, runtime)
163163
}
164164

165+
pub trait Invariant {
166+
/// Specify the type's safety invariants
167+
fn is_safe(&self) -> bool;
168+
}
169+
165170
pub use predicates::*;
166171

167172
/// Provide a few predicates to be used in safety contracts.
168173
///
169174
/// At runtime, they are no-op, and always return true.
175+
/// FIXME: In some cases, we could do better, for example check if not null and aligned.
170176
#[cfg(not(kani))]
171177
mod predicates {
172178
/// Checks if a pointer can be dereferenced, ensuring:
@@ -203,9 +209,16 @@ mod predicates {
203209
let _ = dst;
204210
true
205211
}
212+
213+
/// Checks if two pointers point to the same allocation.
214+
pub fn same_allocation<T>(src: *const T, dst: *const T) -> bool {
215+
let _ = (src, dst);
216+
true
217+
}
206218
}
207219

208220
#[cfg(kani)]
209221
mod predicates {
210-
pub use crate::kani::mem::{can_dereference, can_write, can_read_unaligned, can_write_unaligned};
222+
pub use crate::kani::mem::{can_dereference, can_write, can_read_unaligned, can_write_unaligned,
223+
same_allocation};
211224
}

0 commit comments

Comments
 (0)