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

Implement TryFromBytes for [T] #666

Merged
merged 1 commit into from Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 44 additions & 3 deletions src/lib.rs
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,42 @@ 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`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// (e.g., by writing to `elem`.) Since `elem` is derived from `c`,
// (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 +7702,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 +7712,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 +7880,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
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