Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add harnesses for all public functions of Layout #43

Merged
merged 23 commits into from
Oct 2, 2024
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
96f1603
Add harnesses for all public functions of `Layout`
tautschnig Jul 23, 2024
617ba54
Fix syntax errors
tautschnig Jul 23, 2024
6a6bd64
Add some ensures clauses
tautschnig Jul 26, 2024
08d6f58
Fix syntax errors, add ensures
tautschnig Jul 30, 2024
ac03385
Merge remote-tracking branch 'origin/main' into layout-harnesses
tautschnig Aug 8, 2024
8c21272
Add more ensures clauses
tautschnig Aug 8, 2024
0b62529
Wrong clause was caught
tautschnig Aug 9, 2024
e331419
Avoid multiplication
tautschnig Aug 9, 2024
45505ed
Add assumptions
tautschnig Aug 9, 2024
4156263
Fixed contracts
tautschnig Aug 9, 2024
5231a68
Try disabling one more clause
tautschnig Aug 9, 2024
0428277
Merge remote-tracking branch 'origin/main' into layout-harnesses
tautschnig Aug 20, 2024
f3bf6aa
Merge remote-tracking branch 'origin/main' into layout-harnesses
tautschnig Aug 20, 2024
8e0c75a
Use Arbitrary, add contracts
tautschnig Aug 20, 2024
41635f0
More on for_value, for_value_raw
tautschnig Aug 20, 2024
10493b5
add size constraints
tautschnig Aug 20, 2024
c534529
Update library/core/src/alloc/layout.rs
tautschnig Aug 21, 2024
70a0fcc
Merge remote-tracking branch 'origin/main' into layout-harnesses
tautschnig Aug 21, 2024
e1c598c
Cleanup contracts
tautschnig Aug 21, 2024
3cab463
Merge remote-tracking branch 'origin/main' into layout-harnesses
tautschnig Sep 16, 2024
8013417
Try to use Invariant
tautschnig Sep 16, 2024
2e44a57
Revert "Try to use Invariant"
tautschnig Oct 2, 2024
8acb587
Merge remote-tracking branch 'origin/main' into layout-harnesses
tautschnig Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 191 additions & 2 deletions library/core/src/alloc/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// collections, resulting in having to optimize down excess IR multiple times.
// Your performance intuition is useless. Run perf.

use safety::requires;
use safety::{ensures, requires};
use crate::error::Error;
use crate::ptr::{Alignment, NonNull};
use crate::{assert_unsafe_precondition, cmp, fmt, mem};
Expand Down Expand Up @@ -69,6 +69,10 @@ impl Layout {
#[rustc_const_stable(feature = "const_alloc_layout_size_align", since = "1.50.0")]
#[inline]
#[rustc_allow_const_fn_unstable(ptr_alignment_type)]
#[ensures(|result| result.is_err() || align.is_power_of_two())]
#[ensures(|result| result.is_err() || size <= isize::MAX as usize - (align - 1))]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().size() == size)]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().align() == align)]
pub const fn from_size_align(size: usize, align: usize) -> Result<Self, LayoutError> {
if Layout::is_size_align_valid(size, align) {
// SAFETY: Layout::is_size_align_valid checks the preconditions for this call.
Expand Down Expand Up @@ -128,6 +132,8 @@ impl Layout {
#[inline]
#[rustc_allow_const_fn_unstable(ptr_alignment_type)]
#[requires(Layout::from_size_align(size, align).is_ok())]
#[ensures(|result| result.size() == size)]
#[ensures(|result| result.align() == align)]
pub const unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Self {
assert_unsafe_precondition!(
check_library_ub,
Expand Down Expand Up @@ -169,6 +175,8 @@ impl Layout {
#[rustc_const_stable(feature = "alloc_layout_const_new", since = "1.42.0")]
#[must_use]
#[inline]
#[ensures(|result| result.size() == mem::size_of::<T>())]
#[ensures(|result| result.align() == mem::align_of::<T>())]
pub const fn new<T>() -> Self {
let (size, align) = size_align::<T>();
// SAFETY: if the type is instantiated, rustc already ensures that its
Expand All @@ -184,6 +192,8 @@ impl Layout {
#[rustc_const_unstable(feature = "const_alloc_layout", issue = "67521")]
#[must_use]
#[inline]
#[requires(mem::align_of_val(t).is_power_of_two())]
tautschnig marked this conversation as resolved.
Show resolved Hide resolved
#[ensures(|result| result.align() == mem::align_of_val(t))]
pub const fn for_value<T: ?Sized>(t: &T) -> Self {
let (size, align) = (mem::size_of_val(t), mem::align_of_val(t));
// SAFETY: see rationale in `new` for why this is using the unsafe variant
Expand Down Expand Up @@ -220,6 +230,10 @@ impl Layout {
#[unstable(feature = "layout_for_ptr", issue = "69835")]
#[rustc_const_unstable(feature = "const_alloc_layout", issue = "67521")]
#[must_use]
// TODO: we should try to capture the above constraints on T in a `requires` clause, and the
// metadata helpers from https://github.com/model-checking/verify-rust-std/pull/37 may be able
// to accomplish this.
#[ensures(|result| result.align().is_power_of_two())]
celinval marked this conversation as resolved.
Show resolved Hide resolved
pub const unsafe fn for_value_raw<T: ?Sized>(t: *const T) -> Self {
// SAFETY: we pass along the prerequisites of these functions to the caller
let (size, align) = unsafe { (mem::size_of_val_raw(t), mem::align_of_val_raw(t)) };
Expand All @@ -237,6 +251,7 @@ impl Layout {
#[rustc_const_unstable(feature = "alloc_layout_extra", issue = "55724")]
#[must_use]
#[inline]
#[ensures(|result| result.is_aligned())]
pub const fn dangling(&self) -> NonNull<u8> {
// SAFETY: align is guaranteed to be non-zero
unsafe { NonNull::new_unchecked(crate::ptr::without_provenance_mut::<u8>(self.align())) }
Expand All @@ -258,6 +273,8 @@ impl Layout {
/// `align` violates the conditions listed in [`Layout::from_size_align`].
#[stable(feature = "alloc_layout_manipulation", since = "1.44.0")]
#[inline]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().align() >= align)]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().align().is_power_of_two())]
pub fn align_to(&self, align: usize) -> Result<Self, LayoutError> {
Layout::from_size_align(self.size(), cmp::max(self.align(), align))
}
Expand All @@ -283,6 +300,7 @@ impl Layout {
#[must_use = "this returns the padding needed, \
without modifying the `Layout`"]
#[inline]
#[ensures(|result| *result <= align)]
pub const fn padding_needed_for(&self, align: usize) -> usize {
let len = self.size();

Expand Down Expand Up @@ -319,6 +337,10 @@ impl Layout {
#[must_use = "this returns a new `Layout`, \
without modifying the original"]
#[inline]
#[ensures(|result| result.size() >= self.size())]
#[ensures(|result| result.align() == self.align())]
#[ensures(|result| result.size() % result.align() == 0)]
#[ensures(|result| self.size() + self.padding_needed_for(self.align()) == result.size())]
pub const fn pad_to_align(&self) -> Layout {
let pad = self.padding_needed_for(self.align());
// This cannot overflow. Quoting from the invariant of Layout:
Expand All @@ -341,6 +363,20 @@ impl Layout {
/// On arithmetic overflow, returns `LayoutError`.
#[unstable(feature = "alloc_layout_extra", issue = "55724")]
#[inline]
// for Kani (v0.54.0), the below modulo operation is too costly to prove (running into the
// 6-hours timeout on GitHub); we use a weaker postcondition instead
#[cfg_attr(not(kani),
ensures(|result| result.is_err() || n == 0 || result.as_ref().unwrap().0.size() % n == 0))]
#[cfg_attr(kani,
ensures(|result| result.is_err() || n == 0 || result.as_ref().unwrap().0.size() >= self.size()))]
// for Kani (v0.54.0), the below multiplication is too costly to prove (running into the
// 6-hours timeout on GitHub); we use a weaker postcondition instead
#[cfg_attr(not(kani),
ensures(|result| result.is_err() ||
result.as_ref().unwrap().0.size() == n * result.as_ref().unwrap().1))]
#[cfg_attr(kani,
ensures(|result| result.is_err() || n == 0 ||
result.as_ref().unwrap().0.size() >= result.as_ref().unwrap().1))]
pub fn repeat(&self, n: usize) -> Result<(Self, usize), LayoutError> {
// This cannot overflow. Quoting from the invariant of Layout:
// > `size`, when rounded up to the nearest multiple of `align`,
Expand Down Expand Up @@ -401,6 +437,10 @@ impl Layout {
/// ```
#[stable(feature = "alloc_layout_manipulation", since = "1.44.0")]
#[inline]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().0.align() == cmp::max(self.align(), next.align()))]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().0.size() >= self.size() + next.size())]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().1 >= self.size())]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().1 <= result.as_ref().unwrap().0.size())]
pub fn extend(&self, next: Self) -> Result<(Self, usize), LayoutError> {
let new_align = cmp::max(self.align, next.align);
let pad = self.padding_needed_for(next.align());
Expand All @@ -427,6 +467,13 @@ impl Layout {
/// On arithmetic overflow, returns `LayoutError`.
#[unstable(feature = "alloc_layout_extra", issue = "55724")]
#[inline]
// for Kani (v0.54.0), the below multiplication is too costly to prove (running into the
// 6-hours timeout on GitHub); we use a weaker postcondition instead
#[cfg_attr(not(kani),
ensures(|result| result.is_err() || result.as_ref().unwrap().size() == n * self.size()))]
#[cfg_attr(kani,
ensures(|result| result.is_err() || n == 0 || result.as_ref().unwrap().size() >= self.size()))]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().align() == self.align())]
pub fn repeat_packed(&self, n: usize) -> Result<Self, LayoutError> {
let size = self.size().checked_mul(n).ok_or(LayoutError)?;
// The safe constructor is called here to enforce the isize size limit.
Expand All @@ -441,6 +488,8 @@ impl Layout {
/// On arithmetic overflow, returns `LayoutError`.
#[unstable(feature = "alloc_layout_extra", issue = "55724")]
#[inline]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().size() == self.size() + next.size())]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().align() == self.align())]
pub fn extend_packed(&self, next: Self) -> Result<Self, LayoutError> {
let new_size = self.size().checked_add(next.size()).ok_or(LayoutError)?;
// The safe constructor is called here to enforce the isize size limit.
Expand All @@ -454,6 +503,8 @@ impl Layout {
#[stable(feature = "alloc_layout_manipulation", since = "1.44.0")]
#[rustc_const_unstable(feature = "const_alloc_layout", issue = "67521")]
#[inline]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().size() == n * mem::size_of::<T>())]
#[ensures(|result| result.is_err() || result.as_ref().unwrap().align() == mem::align_of::<T>())]
pub const fn array<T>(n: usize) -> Result<Self, LayoutError> {
// Reduce the amount of code we need to monomorphize per `T`.
return inner(mem::size_of::<T>(), Alignment::of::<T>(), n);
Expand Down Expand Up @@ -520,15 +571,153 @@ impl fmt::Display for LayoutError {
mod verify {
use super::*;

impl kani::Arbitrary for Layout {
fn any() -> Self {
let align = kani::any::<Alignment>();
let size = kani::any_where(|s: &usize| *s <= isize::MAX as usize - (align.as_usize() - 1));
unsafe { Layout { size, align } }
}
}

// pub const fn from_size_align(size: usize, align: usize) -> Result<Self, LayoutError>
#[kani::proof_for_contract(Layout::from_size_align)]
pub fn check_from_size_align() {
let s = kani::any::<usize>();
let a = kani::any::<usize>();
let _ = Layout::from_size_align(s, a);
}

// pub const unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Self
#[kani::proof_for_contract(Layout::from_size_align_unchecked)]
pub fn check_from_size_align_unchecked() {
let s = kani::any::<usize>();
let a = kani::any::<usize>();
unsafe {
let _ = Layout::from_size_align_unchecked(s, a);
}
}

// pub const fn size(&self) -> usize
#[kani::proof]
celinval marked this conversation as resolved.
Show resolved Hide resolved
pub fn check_size() {
let s = kani::any::<usize>();
let a = kani::any::<usize>();
kani::assume(Layout::from_size_align(s, a).is_ok());
unsafe {
let layout = Layout::from_size_align_unchecked(s, a);
assert_eq!(layout.size(), s);
assert_eq!(layout.align(), a);
}
}

// pub const fn align(&self) -> usize
#[kani::proof]
pub fn check_align() {
let layout = kani::any::<Layout>();
assert!(layout.align().is_power_of_two());
}

// pub const fn new<T>() -> Self
#[kani::proof_for_contract(Layout::new)]
pub fn check_new_i32() {
let _ = Layout::new::<i32>();
}

// pub const fn for_value<T: ?Sized>(t: &T) -> Self
#[kani::proof_for_contract(Layout::for_value)]
pub fn check_for_value_i32() {
let x = kani::any::<i32>();
let _ = Layout::for_value::<i32>(&x);
let array : [i32; 2] = [1, 2];
let _ = Layout::for_value::<[i32]>(&array[1 .. 1]);
let trait_ref: &dyn core::fmt::Debug = &x;
let _ = Layout::for_value::<dyn core::fmt::Debug>(trait_ref);
// TODO: the case of an extern type as unsized tail is not yet covered
}

// pub const unsafe fn for_value_raw<T: ?Sized>(t: *const T) -> Self
#[kani::proof_for_contract(Layout::for_value_raw)]
pub fn check_for_value_raw_i32() {
unsafe {
let x = kani::any::<i32>();
let _ = Layout::for_value_raw::<i32>(&x as *const i32);
let _ = Layout::for_value_raw::<[i32]>(&[] as *const [i32]);
let trait_ref: &dyn core::fmt::Debug = &x;
let _ = Layout::for_value_raw::<dyn core::fmt::Debug>(trait_ref);
// TODO: the case of an extern type as unsized tail is not yet covered
}
}

// pub const fn dangling(&self) -> NonNull<u8>
#[kani::proof_for_contract(Layout::dangling)]
pub fn check_dangling() {
let layout = kani::any::<Layout>();
let _ = layout.dangling();
}

// pub fn align_to(&self, align: usize) -> Result<Self, LayoutError>
#[kani::proof_for_contract(Layout::align_to)]
pub fn check_align_to() {
let layout = kani::any::<Layout>();
let a2 = kani::any::<usize>();
let _ = layout.align_to(a2);
}

// pub const fn padding_needed_for(&self, align: usize) -> usize
#[kani::proof_for_contract(Layout::padding_needed_for)]
pub fn check_padding_needed_for() {
let layout = kani::any::<Layout>();
let a2 = kani::any::<usize>();
if(a2.is_power_of_two() && a2 <= layout.align()) {
let _ = layout.padding_needed_for(a2);
}
}

// pub const fn pad_to_align(&self) -> Layout
#[kani::proof_for_contract(Layout::pad_to_align)]
pub fn check_pad_to_align() {
let layout = kani::any::<Layout>();
let _ = layout.pad_to_align();
}

// pub fn repeat(&self, n: usize) -> Result<(Self, usize), LayoutError>
#[kani::proof_for_contract(Layout::repeat)]
pub fn check_repeat() {
let layout = kani::any::<Layout>();
let n = kani::any::<usize>();
let _ = layout.repeat(n);
}

// pub fn extend(&self, next: Self) -> Result<(Self, usize), LayoutError>
#[kani::proof_for_contract(Layout::extend)]
pub fn check_extend() {
let layout = kani::any::<Layout>();
let layout2 = kani::any::<Layout>();
let _ = layout.extend(layout2);
}

// pub fn repeat_packed(&self, n: usize) -> Result<Self, LayoutError>
#[kani::proof_for_contract(Layout::repeat_packed)]
pub fn check_repeat_packed() {
let layout = kani::any::<Layout>();
let n = kani::any::<usize>();
let _ = layout.repeat_packed(n);
}

// pub fn extend_packed(&self, next: Self) -> Result<Self, LayoutError>
#[kani::proof_for_contract(Layout::extend_packed)]
pub fn check_extend_packed() {
let layout = kani::any::<Layout>();
let layout2 = kani::any::<Layout>();
let _ = layout.extend_packed(layout2);
}

// pub const fn array<T>(n: usize) -> Result<Self, LayoutError>
#[kani::proof_for_contract(Layout::array)]
pub fn check_array_i32() {
let n = kani::any::<usize>();
if let Ok(layout) = Layout::array::<i32>(n) {
assert!(layout.size() >= n * 4);
assert!(layout.align().is_power_of_two());
}
}
}
Loading