Skip to content

Commit 25ad12b

Browse files
authored
Start the verification of slice::Iter (rust-lang#148)
This PR implements the invariant for `Iter` and `IterMut` and contains harnesses for some `Iter` methods. I commented out a bunch of harnesses that are currently failing until I can debug them further.
1 parent c738450 commit 25ad12b

File tree

3 files changed

+210
-5
lines changed

3 files changed

+210
-5
lines changed

library/core/src/slice/iter.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ use crate::hint::assert_unchecked;
88
use crate::iter::{
99
FusedIterator, TrustedLen, TrustedRandomAccess, TrustedRandomAccessNoCoerce, UncheckedIterator,
1010
};
11+
#[cfg(kani)]
12+
use crate::kani;
1113
use crate::marker::PhantomData;
1214
use crate::mem::{self, SizedTypeProperties};
1315
use crate::num::NonZero;
1416
use crate::ptr::{NonNull, without_provenance, without_provenance_mut};
1517
use crate::{cmp, fmt};
18+
use crate::ub_checks::Invariant;
1619

1720
#[stable(feature = "boxed_slice_into_iter", since = "1.80.0")]
1821
impl<T> !Iterator for [T] {}
@@ -157,6 +160,32 @@ impl<T> AsRef<[T]> for Iter<'_, T> {
157160
}
158161
}
159162

163+
#[unstable(feature = "ub_checks", issue = "none")]
164+
impl<T> Invariant for Iter<'_, T> {
165+
/// An iterator can be safely used if its pointer can be read for its current length.
166+
///
167+
/// If the type is a ZST or the encoded length is `0`, the only safety requirement is that
168+
/// its pointer is aligned (since zero-size access is always safe for aligned pointers).
169+
///
170+
/// For other cases, we need to ensure that it is safe to read the memory between
171+
/// `self.ptr` and `self.end_or_len`.
172+
fn is_safe(&self) -> bool {
173+
let ty_size = crate::mem::size_of::<T>();
174+
// Use `abs_diff` since `end_or_len` may be smaller than `ptr` if `T` is a ZST.
175+
let distance = self.ptr.addr().get().abs_diff(self.end_or_len as usize);
176+
if ty_size == 0 || distance == 0 {
177+
self.ptr.is_aligned()
178+
} else {
179+
let slice_ptr: *const [T] =
180+
crate::ptr::from_raw_parts(self.ptr.as_ptr(), distance / ty_size);
181+
crate::ub_checks::same_allocation(self.ptr.as_ptr(), self.end_or_len)
182+
&& self.ptr.addr().get() <= self.end_or_len as usize
183+
&& distance % ty_size == 0
184+
&& crate::ub_checks::can_dereference(slice_ptr)
185+
}
186+
}
187+
}
188+
160189
/// Mutable slice iterator.
161190
///
162191
/// This struct is created by the [`iter_mut`] method on [slices].
@@ -196,6 +225,29 @@ pub struct IterMut<'a, T: 'a> {
196225
_marker: PhantomData<&'a mut T>,
197226
}
198227

228+
#[unstable(feature = "ub_checks", issue = "none")]
229+
impl<T> Invariant for IterMut<'_, T> {
230+
/// This is similar to [Iter] with an extra write requirement.
231+
///
232+
/// It must be safe to write in the memory interval between `self.ptr`
233+
/// and `self.end_or_len`.
234+
fn is_safe(&self) -> bool {
235+
let ty_size = crate::mem::size_of::<T>();
236+
let distance = self.ptr.addr().get().abs_diff(self.end_or_len as usize);
237+
if ty_size == 0 || distance == 0 {
238+
self.ptr.is_aligned()
239+
} else {
240+
let slice_ptr: *mut [T] =
241+
crate::ptr::from_raw_parts_mut(self.ptr.as_ptr(), distance / ty_size);
242+
crate::ub_checks::same_allocation(self.ptr.as_ptr(), self.end_or_len)
243+
&& self.ptr.addr().get() <= self.end_or_len as usize
244+
&& distance % ty_size == 0
245+
&& crate::ub_checks::can_dereference(slice_ptr)
246+
&& crate::ub_checks::can_write(slice_ptr)
247+
}
248+
}
249+
}
250+
199251
#[stable(feature = "core_impl_debug", since = "1.9.0")]
200252
impl<T: fmt::Debug> fmt::Debug for IterMut<'_, T> {
201253
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -3464,3 +3516,137 @@ impl<'a, T: 'a + fmt::Debug, P> fmt::Debug for ChunkByMut<'a, T, P> {
34643516
f.debug_struct("ChunkByMut").field("slice", &self.slice).finish()
34653517
}
34663518
}
3519+
3520+
/// Verify the safety of the code implemented in this module (including generated code from macros).
3521+
#[cfg(kani)]
3522+
#[unstable(feature = "kani", issue = "none")]
3523+
mod verify {
3524+
use super::*;
3525+
use crate::kani;
3526+
3527+
fn any_slice<T>(orig_slice: &[T]) -> &[T] {
3528+
if kani::any() {
3529+
let last = kani::any_where(|idx: &usize| *idx <= orig_slice.len());
3530+
let first = kani::any_where(|idx: &usize| *idx <= last);
3531+
&orig_slice[first..last]
3532+
} else {
3533+
let ptr = kani::any_where::<usize, _>(|val| *val != 0) as *const T;
3534+
kani::assume(ptr.is_aligned());
3535+
unsafe { crate::slice::from_raw_parts(ptr, 0) }
3536+
}
3537+
}
3538+
3539+
fn any_iter<'a, T>(orig_slice: &'a [T]) -> Iter<'a, T> {
3540+
let slice = any_slice(orig_slice);
3541+
Iter::new(slice)
3542+
}
3543+
3544+
/// Macro that generates a harness for a given `Iter` method.
3545+
///
3546+
/// Takes the name of the harness, the element type, and an expression to check.
3547+
macro_rules! check_safe_abstraction {
3548+
($harness:ident, $elem_ty:ty, $call:expr) => {
3549+
#[kani::proof]
3550+
fn $harness() {
3551+
let array: [$elem_ty; MAX_LEN] = kani::any();
3552+
let mut iter = any_iter::<$elem_ty>(&array);
3553+
let target = $call;
3554+
target(&mut iter);
3555+
kani::assert(iter.is_safe(), "Iter is safe");
3556+
}
3557+
};
3558+
}
3559+
3560+
/// Macro that generates a harness for a given unsafe `Iter` method.
3561+
///
3562+
/// Takes:
3563+
/// 1. The name of the harness
3564+
/// 2. the element type
3565+
/// 3. the target function
3566+
/// 4. the optional arguments.
3567+
macro_rules! check_unsafe_contracts {
3568+
($harness:ident, $elem_ty:ty, $func:ident($($args:expr),*)) => {
3569+
#[kani::proof_for_contract(Iter::$func)]
3570+
fn $harness() {
3571+
let array: [$elem_ty; MAX_LEN] = kani::any();
3572+
let mut iter = any_iter::<$elem_ty>(&array);
3573+
let _ = unsafe { iter.$func($($args),*) };
3574+
}
3575+
};
3576+
}
3577+
3578+
macro_rules! check_iter_with_ty {
3579+
($module:ident, $ty:ty, $max:expr) => {
3580+
mod $module {
3581+
use super::*;
3582+
const MAX_LEN: usize = $max;
3583+
3584+
#[kani::proof]
3585+
fn check_new_iter() {
3586+
let array: [$ty; MAX_LEN] = kani::any();
3587+
let slice = any_slice::<$ty>(&array);
3588+
let mut iter = Iter::new(slice);
3589+
kani::assert(iter.is_safe(), "Iter is safe");
3590+
}
3591+
3592+
/// Count consumes the value, thus, invoke it directly.
3593+
#[kani::proof]
3594+
fn check_count() {
3595+
let array: [$ty; MAX_LEN] = kani::any();
3596+
let mut iter = any_iter::<$ty>(&array);
3597+
iter.count();
3598+
}
3599+
3600+
#[kani::proof]
3601+
fn check_default() {
3602+
let iter: Iter<'_, $ty> = Iter::default();
3603+
kani::assert(iter.is_safe(), "Iter is safe");
3604+
}
3605+
3606+
check_unsafe_contracts!(check_next_back_unchecked, $ty, next_back_unchecked());
3607+
//check_unsafe_contracts!(check_post_inc_start, $ty, post_inc_start(kani::any()));
3608+
//check_unsafe_contracts!(check_pre_dec_end, $ty, pre_dec_end(kani::any()));
3609+
3610+
// FIXME: The functions that are commented out are currently failing verification.
3611+
// Debugging the issue is currently blocked by:
3612+
// https://github.com/model-checking/kani/issues/3670
3613+
//
3614+
// Public functions that call safe abstraction `make_slice`.
3615+
// check_safe_abstraction!(check_as_slice, $ty, as_slice);
3616+
// check_safe_abstraction!(check_as_ref, $ty, as_ref);
3617+
3618+
// check_safe_abstraction!(check_advance_back_by, $ty, advance_back_by, kani::any());
3619+
3620+
check_safe_abstraction!(check_is_empty, $ty, |iter: &mut Iter<'_, $ty>| {
3621+
let _ = iter.is_empty();
3622+
});
3623+
check_safe_abstraction!(check_len, $ty, |iter: &mut Iter<'_, $ty>| {
3624+
let _ = iter.len();
3625+
});
3626+
check_safe_abstraction!(check_size_hint, $ty, |iter: &mut Iter<'_, $ty>| {
3627+
let _ = iter.size_hint();
3628+
});
3629+
//check_safe_abstraction!(check_nth, $ty, |iter: &mut Iter<'_, $ty>| { let _ = iter.nth(kani::any()); });
3630+
//check_safe_abstraction!(check_advance_by, $ty, |iter: &mut Iter<'_, $ty>| { let _ = iter.advance_by(kani::any()); });
3631+
check_safe_abstraction!(check_next_back, $ty, |iter: &mut Iter<'_, $ty>| {
3632+
let _ = iter.next_back();
3633+
});
3634+
//check_safe_abstraction!(check_nth_back, $ty, |iter: &mut Iter<'_, $ty>| { let _ = iter.nth_back(kani::any()); });
3635+
check_safe_abstraction!(check_next, $ty, |iter: &mut Iter<'_, $ty>| {
3636+
let _ = iter.next();
3637+
});
3638+
3639+
// Ensure that clone always generates a safe object.
3640+
check_safe_abstraction!(check_clone, $ty, |iter: &mut Iter<'_, $ty>| {
3641+
kani::assert(iter.clone().is_safe(), "Clone is safe");
3642+
});
3643+
}
3644+
};
3645+
}
3646+
3647+
// FIXME: Add harnesses for ZST with alignment > 1.
3648+
check_iter_with_ty!(verify_unit, (), isize::MAX as usize);
3649+
check_iter_with_ty!(verify_u8, u8, u32::MAX as usize);
3650+
check_iter_with_ty!(verify_char, char, 50);
3651+
check_iter_with_ty!(verify_tup, (char, u8), 50);
3652+
}

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

Lines changed: 11 additions & 0 deletions
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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ pub use predicates::*;
171171
/// Provide a few predicates to be used in safety contracts.
172172
///
173173
/// At runtime, they are no-op, and always return true.
174+
/// FIXME: In some cases, we could do better, for example check if not null and aligned.
174175
#[cfg(not(kani))]
175176
mod predicates {
176177
/// Checks if a pointer can be dereferenced, ensuring:
@@ -179,7 +180,7 @@ mod predicates {
179180
/// * `src` points to a properly initialized value of type `T`.
180181
///
181182
/// [`crate::ptr`]: https://doc.rust-lang.org/std/ptr/index.html
182-
pub fn can_dereference<T>(src: *const T) -> bool {
183+
pub fn can_dereference<T: ?Sized>(src: *const T) -> bool {
183184
let _ = src;
184185
true
185186
}
@@ -188,30 +189,37 @@ mod predicates {
188189
/// * `dst` must be valid for writes.
189190
/// * `dst` must be properly aligned. Use `write_unaligned` if this is not the
190191
/// case.
191-
pub fn can_write<T>(dst: *mut T) -> bool {
192+
pub fn can_write<T: ?Sized>(dst: *mut T) -> bool {
192193
let _ = dst;
193194
true
194195
}
195196

196197
/// Check if a pointer can be the target of unaligned reads.
197198
/// * `src` must be valid for reads.
198199
/// * `src` must point to a properly initialized value of type `T`.
199-
pub fn can_read_unaligned<T>(src: *const T) -> bool {
200+
pub fn can_read_unaligned<T: ?Sized>(src: *const T) -> bool {
200201
let _ = src;
201202
true
202203
}
203204

204205
/// Check if a pointer can be the target of unaligned writes.
205206
/// * `dst` must be valid for writes.
206-
pub fn can_write_unaligned<T>(dst: *mut T) -> bool {
207+
pub fn can_write_unaligned<T: ?Sized>(dst: *mut T) -> bool {
207208
let _ = dst;
208209
true
209210
}
211+
212+
/// Checks if two pointers point to the same allocation.
213+
pub fn same_allocation<T: ?Sized>(src: *const T, dst: *const T) -> bool {
214+
let _ = (src, dst);
215+
true
216+
}
210217
}
211218

212219
#[cfg(kani)]
213220
mod predicates {
214-
pub use crate::kani::mem::{can_dereference, can_write, can_read_unaligned, can_write_unaligned};
221+
pub use crate::kani::mem::{can_dereference, can_write, can_read_unaligned, can_write_unaligned,
222+
same_allocation};
215223
}
216224

217225
/// This trait should be used to specify and check type safety invariants for a

0 commit comments

Comments
 (0)