From 4f19b8c5f1a5f90ce42c2e31c62701756fb3cd44 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 1 Dec 2023 21:19:55 +0000 Subject: [PATCH] [wip] implement `TryFromBytes` for `[T]` --- src/lib.rs | 35 +++++++++++++++++++-- src/util.rs | 89 +++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 112 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2c7aad6dea..e37dad2198 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 @@ -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 { ::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]); @@ -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 @@ -7675,6 +7700,9 @@ mod tests { // `0` may be any integer type with a different size or // alignment than some `NonZeroXxx` types). @failure Option::::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 @@ -7840,7 +7868,8 @@ mod tests { assert_impls!(Unalign: KnownLayout, FromZeroes, FromBytes, AsBytes, Unaligned, !TryFromBytes); assert_impls!(Unalign: 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); diff --git a/src/util.rs b/src/util.rs index f018e876d7..e1699fdc08 100644 --- a/src/util.rs +++ b/src/util.rs @@ -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, _lifetime: PhantomData<&'a ()>, } @@ -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 [()]; @@ -286,6 +290,73 @@ pub(crate) mod ptr { // Nightly docs. slc.len() } + + pub(crate) fn iter(&self) -> impl Iterator> { + let base = self.ptr.cast::().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> {