Skip to content

Commit

Permalink
[wip] 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 4f19b8c
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 12 deletions.
35 changes: 32 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,30 @@ 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: TODO
//
// Here's the old safety comment:
//
// SAFETY: The caller promises that `c` is aligned and is valid for
// reads. They also promise that any byte which is always initialized in
// a valid `[T]` is initialized in `c`'s referent. While the exact,
// formal property is slightly more complicated (see the safety docs on
// `is_bit_valid`), what's important is that, for types which are merely
// the concatenation of other types (structs, tuples, arrays, slices),
// the property is also compositional - if it holds for the larger type,
// then by definition it holds for each element of the type. Thus, since
// the caller has promised that it holds of the entire `[T]`, it must
// also hold for each individual `T`.
//
// Thus, the preconditions for this call are satisfied.
//
// Note that this call 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| 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 +7690,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 +7700,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 +7868,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
89 changes: 80 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,73 @@ pub(crate) mod ptr {
// Nightly docs.
slc.len()
}

pub(crate) fn iter(&self) -> impl Iterator<Item = Ptr<'a, T>> {
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
// lengths fits in an `isize`. Since `elem` is contained by
// `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 by `self`, the computed offset of `elem` must
// wrap around the address space.
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`.
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 is
// within the allocation of `self`
// 3. `elem` is entirely contained by 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 by
// `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 4f19b8c

Please sign in to comment.