Skip to content

Commit

Permalink
implement TryFromBytes for [T]
Browse files Browse the repository at this point in the history
  • Loading branch information
jswrenn committed Dec 4, 2023
1 parent 490bd8f commit f4b02ad
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 12 deletions.
46 changes: 43 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3511,8 +3511,8 @@ safety_comment! {
///
/// In other words, the layout of a `[T]` or `[T; N]` is a sequence of `T`s
/// laid out back-to-back with no bytes in between. Therefore, `[T]` or `[T;
/// N]` are `FromZeroes`, `FromBytes`, and `AsBytes` if `T` is
/// (respectively). Furthermore, since an array/slice has "the same
/// N]` are `TryFromBytes`, `FromZeroes`, `FromBytes`, and `AsBytes` if `T`
/// is (respectively). Furthermore, since an array/slice has "the same
/// alignment of `T`", `[T]` and `[T; N]` are `Unaligned` if `T` is.
///
/// Note that we don't `assert_unaligned!` for slice types because
Expand All @@ -3524,6 +3524,41 @@ safety_comment! {
unsafe_impl!(const N: usize, T: AsBytes => AsBytes for [T; N]);
unsafe_impl!(const N: usize, T: Unaligned => Unaligned for [T; N]);
assert_unaligned!([(); 0], [(); 1], [u8; 0], [u8; 1]);
unsafe_impl!(T: TryFromBytes => TryFromBytes for [T]; |c: Ptr<[T]>| {
// SAFETY: Assuming the preconditions of `is_bit_valid` are satisfied,
// so too will the postcondition: that, if `is_bit_valid(candidate)`
// returns true, `*candidate` contains a valid `Self`. Per the reference
// [1]:
//
// An array of `[T; N]` has a size of `size_of::<T>() * N` and the
// same alignment of `T`. Arrays are laid out so that the zero-based
// `nth` element of the array is offset from the start of the array by
// `n * size_of::<T>()` bytes.
//
// ...
//
// Slices have the same layout as the section of the array they slice.
//
// In other words, the layout of a `[T] is a sequence of `T`s laid out
// back-to-back with no bytes in between. If all elements in `candidate`
// are `is_bit_valid`, so too is `candidate`.
//
// Note that any of the below calls may panic, but it would still be
// sound even if it did. `is_bit_valid` does not promise that it will
// not panic (in fact, it explicitly warns that it's a possibility), and
// we have not violated any safety invariants that we must fix before
// returning.
c.iter().all(|elem|
// SAFETY: We uphold the safety contract of `is_bit_valid(elem)`, by
// precondition on the surrounding call to `is_bit_valid`. The
// memory referenced by `elem` is contained entirely within `c`, and
// satisfies the preconditions satisfied by `c`. By axiom, we assume
// that `Iterator:all` does not invalidate these preconditions
// (e.g., by writing to `elem`.) Since `elem` is derived from `c`,
// it is only possible for uninitialized bytes to occur in `elem` at
// the same bytes they occur within `c`.
unsafe { <T as TryFromBytes>::is_bit_valid(elem) })
});
unsafe_impl!(T: FromZeroes => FromZeroes for [T]);
unsafe_impl!(T: FromBytes => FromBytes for [T]);
unsafe_impl!(T: AsBytes => AsBytes for [T]);
Expand Down Expand Up @@ -7666,6 +7701,7 @@ mod tests {
@failure 0xD800u32, 0xDFFFu32, 0x110000u32;
str => @success "", "hello", "❤️🧡💛💚💙💜",
@failure [0, 159, 146, 150];
[u8] => @success [], [0, 1, 2];
NonZeroU8, NonZeroI8, NonZeroU16, NonZeroI16, NonZeroU32,
NonZeroI32, NonZeroU64, NonZeroI64, NonZeroU128, NonZeroI128,
NonZeroUsize, NonZeroIsize
Expand All @@ -7675,6 +7711,9 @@ mod tests {
// `0` may be any integer type with a different size or
// alignment than some `NonZeroXxx` types).
@failure Option::<Self>::None;
[bool]
=> @success [true, false], [false, true],
@failure [2u8], [3u8], [0xFFu8], [0u8, 1u8, 2u8];
);

// Asserts that `$ty` implements any `$trait` and doesn't implement any
Expand Down Expand Up @@ -7840,7 +7879,8 @@ mod tests {
assert_impls!(Unalign<u8>: KnownLayout, FromZeroes, FromBytes, AsBytes, Unaligned, !TryFromBytes);
assert_impls!(Unalign<NotZerocopy>: Unaligned, !KnownLayout, !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes);

assert_impls!([u8]: KnownLayout, FromZeroes, FromBytes, AsBytes, Unaligned, !TryFromBytes);
assert_impls!([u8]: KnownLayout, TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned);
assert_impls!([bool]: KnownLayout, TryFromBytes, FromZeroes, AsBytes, Unaligned, !FromBytes);
assert_impls!([NotZerocopy]: !KnownLayout, !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
assert_impls!([u8; 0]: KnownLayout, FromZeroes, FromBytes, AsBytes, Unaligned, !TryFromBytes);
assert_impls!([NotZerocopy; 0]: KnownLayout, !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
Expand Down
99 changes: 90 additions & 9 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ pub(crate) mod ptr {
/// [covariant]: https://doc.rust-lang.org/reference/subtyping.html
pub struct Ptr<'a, T: 'a + ?Sized> {
// INVARIANTS:
// - `ptr` is derived from some valid Rust allocation, `A`
// - `ptr` has the same provenance as `A`
// - `ptr` addresses a byte range which is entirely contained in `A`
// - `ptr` addresses a byte range whose length fits in an `isize`
// - `ptr` addresses a byte range which does not wrap around the address
// space
// - `ptr` is validly-aligned for `T`
// - `A` is guaranteed to live for at least `'a`
// - `T: 'a`
// 1. `ptr` is derived from some valid Rust allocation, `A`
// 2. `ptr` has the same provenance as `A`
// 3. `ptr` addresses a byte range which is entirely contained in `A`
// 4. `ptr` addresses a byte range whose length fits in an `isize`
// 5. `ptr` addresses a byte range which does not wrap around the address
// space
// 6. `ptr` is validly-aligned for `T`
// 7. `A` is guaranteed to live for at least `'a`
// 8. `T: 'a`
ptr: NonNull<T>,
_lifetime: PhantomData<&'a ()>,
}
Expand Down Expand Up @@ -255,6 +255,10 @@ pub(crate) mod ptr {

impl<'a, T> Ptr<'a, [T]> {
/// The number of slice elements referenced by `self`.
///
/// # Safety
///
/// Unsafe code my rely on `len` satisfying the above contract.
fn len(&self) -> usize {
#[allow(clippy::as_conversions)]
let slc = self.ptr.as_ptr() as *const [()];
Expand Down Expand Up @@ -286,6 +290,83 @@ pub(crate) mod ptr {
// Nightly docs.
slc.len()
}

pub(crate) fn iter(&self) -> impl Iterator<Item = Ptr<'a, T>> {
// TODO(#429): Once `NonNull::cast` documents that it preserves
// provenance, cite those docs.
let base = self.ptr.cast::<T>().as_ptr();
(0..self.len()).map(move |i| {
// TODO(https://github.com/rust-lang/rust/issues/74265): Use
// `NonNull::get_unchecked_mut`.

// SAFETY: If the following conditions are not satisfied
// `pointer::cast` may induce Undefined Behavior [1]:
// > 1. Both the starting and resulting pointer must be either
// > in bounds or one byte past the end of the same allocated
// > object.
// > 2. The computed offset, in bytes, cannot overflow an
// > `isize`.
// > 3. The offset being in bounds cannot rely on “wrapping
// > around” the address space. That is, the
// > infinite-precision sum must fit in a `usize`.
//
// [1] https://doc.rust-lang.org/std/primitive.pointer.html#method.add
//
// We satisfy all three of these conditions here:
// 1. `base` (by invariant on `self`) points to an allocated
// object. By contract, `self.len()` accurately reflects the
// number of elements in the slice. `i` is in bounds of
// `c.len()` by construction, and so the result of this
// addition cannot overflow past the end of the allocation
// referred to by `c`.
// 2. By invariant on `Ptr`, `self` addresses a byte range whose
// length fits in an `isize`. Since `elem` is contained in
// `self`, the computed offset of `elem` must fit within
// `isize.`
// 3. By invariant on `Ptr`, `self` addresses a byte range which
// does not wrap around the address space. Since `elem` is
// contained in `self`, the computed offset of `elem` must
// wrap around the address space.
//
// TODO(#429): Once `pointer::add` documents that it preserves
// provenance, cite those docs.
let elem = unsafe { base.add(i) };

// SAFETY:
// - `elem` must not be null. `base` is constructed from a
// `NonNull` pointer, and the addition that produces `elem`
// must not overflow or wrap around, so `elem >= base > 0`.
//
// TODO(#429): Once `NonNull::new_unchecked` documents that it
// preserves provenance, cite those docs.
let elem = unsafe { NonNull::new_unchecked(elem) };

// SAFETY: The safety invariants of `Ptr` (see definition) are
// satisfied:
// 1. `elem` is derived from a valid Rust allocation, because
// `self` is derived from a valid Rust allocation, by
// invariant on `Ptr`
// 2. `elem` has the same provenance as `self`, because it
// derived from `self` using a series of
// provenance-preserving operations
// 3. `elem` is entirely contained in the allocation of `self`
// (see above)
// 4. `elem` addresses a byte range whose length fits in an
// `isize` (see above)
// 5. `elem` addresses a byte range which does not wrap around
// the address space (see above)
// 6. `elem` is validly-aligned for `T`. `self`, which
// represents a `[T]` is validly aligned for `T`, and `elem`
// is an element within that `[T]`
// 7. The allocation of `elem` is guaranteed to live for at
// least `'a`, because `elem` is entirely contained in
// `self`, which lives for at least `'a` by invariant on
// `Ptr`.
// 8. `T: 'a`, because `elem` is an element within `[T]`, and
// `[T]: 'a` by invariant on `Ptr`
Ptr { ptr: elem, _lifetime: PhantomData }
})
}
}

impl<'a, T: 'a + ?Sized> From<&'a T> for Ptr<'a, T> {
Expand Down

0 comments on commit f4b02ad

Please sign in to comment.