From 041ef79fc6d938f515f6676d9f18067c99e689b3 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Fri, 11 Aug 2023 21:04:04 -0700 Subject: [PATCH] Add TryFromBytes trait `TryFromBytes` can be implemented for types which are not `FromZeroes` or `FromBytes`; it supports performing a runtime check to determine whether a given byte sequence contains a valid instance of `Self`. This is the first step of #5. Future commits will add support for a custom derive and for implementing `TryFromBytes` on unsized types. TODO: Add tests Makes progress on #5 --- src/lib.rs | 799 +++++++++++++++++++++++++++++++++++++++++--------- src/macros.rs | 67 ++++- 2 files changed, 721 insertions(+), 145 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2e33e9ae776..a9a2f1e0e81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -821,6 +821,173 @@ pub unsafe trait FromBytes: FromZeroes { } } +/// Types whose validity can be checked at runtime, allowing them to be +/// conditionally converted from byte slices. +/// +/// WARNING: Do not implement this trait yourself! Instead, use +/// `#[derive(TryFromBytes)]`. +/// +/// `TryFromBytes` types can safely be deserialized from an untrusted sequence +/// of bytes by performing a runtime check that the byte sequence contains a +/// valid instance of `Self`. +/// +/// `TryFromBytes` is ignorant of byte order. For byte order-aware types, see +/// the [`byteorder`] module. +/// +/// # Safety +/// +/// Unsafe code may assume that [`is_bit_valid`] is correct: that, if +/// `is_bit_valid(candidate)` returns true, `candidate` contains a valid `Self`, +/// and it is sound to treat `candidate` as a `&Self`. +/// +/// Implementations of `TryFromBytes` must ensure that [`is_bit_valid`] +/// satisfies its documented safety invariants. +/// +/// It is unsound to implement `TryFromBytes` for a type which contains any +/// `UnsafeCell`s. +/// +/// [`is_bit_valid`]: TryFromBytes::is_bit_valid +// TODO(#5): Describe in the documentation that `TryFromBytes` is compositional? +// Describe anything about the `is_bit_valid` impls that the custom derive +// emits? +pub unsafe trait TryFromBytes: AsMaybeUninit { + /// Does this [`MaybeValid`] contain a valid instance of `Self`? + /// + /// # Safety + /// + /// Unsafe code may assume that, if `is_bit_valid(candidate)` returns true, + /// `candidate` contains a valid `Self`, and that it is sound to treat + /// `candidate` as a `&Self`. + fn is_bit_valid(candidate: &MaybeValid) -> bool; + + /// Attempts to interpret a byte slice as a `Self`. + /// + /// `try_from_ref` validates that `bytes` contains a valid `Self` as defined + /// by [`is_bit_valid`]. If it does, then `bytes` is reinterpreted as a + /// `Self`. + /// + /// [`is_bit_valid`]: TryFromBytes::is_bit_valid + // TODO(#251): In a future in which we distinguish between `FromBytes` and + // `RefFromBytes`, this requires `where Self: RefFromBytes` to disallow + // interior mutability. + #[inline] + fn try_from_ref(bytes: &[u8]) -> Option<&Self> + where + // TODO(#5): Support unsized types. + Self: Sized, + { + // TODO(https://github.com/rust-lang/rust/issues/115080): Inline this + // function once #115080 is resolved. + #[inline(always)] + fn try_read_from_inner(bytes: &[u8], is_bit_valid: F) -> Option<&T> + where + F: FnOnce(&MaybeValid) -> bool, + MaybeValid: FromBytes, + { + let maybe_valid = Ref::<_, MaybeValid>::new(bytes)?.into_ref(); + if is_bit_valid(maybe_valid) { + // SAFETY: `is_bit_valid` promises that it only returns true if + // its argument contains a valid `T`. This is exactly the safety + // precondition of `MaybeValid::assume_valid_ref`. + Some(unsafe { maybe_valid.assume_valid_ref() }) + } else { + None + } + } + + try_read_from_inner(bytes, Self::is_bit_valid) + } + + /// Attempts to interpret a mutable byte slice as a `Self`. + /// + /// `try_from_mut` validates that `bytes` contains a valid `Self` as defined + /// by [`is_bit_valid`]. If it does, then `bytes` is reinterpreted as a + /// `Self`. + /// + /// [`is_bit_valid`]: TryFromBytes::is_bit_valid + // TODO(#251): In a future in which we distinguish between `FromBytes` and + // `RefFromBytes`, this requires `where Self: RefFromBytes` to disallow + // interior mutability. + #[inline] + fn try_from_mut(bytes: &mut [u8]) -> Option<&mut Self> + where + // TODO(#5): Support unsized types. + Self: AsBytes + Sized, + { + // TODO(https://github.com/rust-lang/rust/issues/115080): Inline this + // function once #115080 is resolved. + #[inline(always)] + fn try_read_from_mut_inner(bytes: &mut [u8], is_bit_valid: F) -> Option<&mut T> + where + T: AsBytes, + F: FnOnce(&MaybeValid) -> bool, + MaybeValid: FromBytes, + { + let maybe_valid = Ref::<_, MaybeValid>::new(bytes)?.into_mut(); + if is_bit_valid(maybe_valid) { + // SAFETY: `is_bit_valid` promises that it only returns true if + // its argument contains a valid `T`. This is exactly the safety + // precondition of `MaybeValid::assume_valid_mut`. + Some(unsafe { maybe_valid.assume_valid_mut() }) + } else { + None + } + } + + try_read_from_mut_inner(bytes, Self::is_bit_valid) + } + + /// Attempts to read a `Self` from a byte slice. + /// + /// `try_read_from` validates that `bytes` contains a valid `Self` as + /// defined by [`is_bit_valid`]. If it does, then that `Self` is copied and + /// returned by-value. + /// + /// [`is_bit_valid`]: TryFromBytes::is_bit_valid + // TODO(#251): In a future in which we distinguish between `FromBytes` and + // `RefFromBytes`, this requires `where Self: RefFromBytes` to disallow + // interior mutability. + #[inline] + fn try_read_from(bytes: &[u8]) -> Option + where + Self: Sized, + { + // TODO(https://github.com/rust-lang/rust/issues/115080): Inline this + // function once #115080 is resolved. + #[inline(always)] + fn try_read_from_inner(bytes: &[u8], is_bit_valid: F) -> Option + where + F: FnOnce(&MaybeValid) -> bool, + MaybeValid: FromBytes, + { + // A note on performance: We unconditionally read `size_of::()` + // bytes into the local stack frame before validation. This has + // advantages and disadvantages: + // - It allows `MaybeValid` to be aligned to `T`, and thus allows + // `is_bit_valid` to operate on an aligned value. + // - It requires us to perform the copy even if validation fails. + // + // The authors believe that this is a worthwhile tradeoff. Allowing + // `is_bit_valid` to operate on an aligned value can make the + // generated machine code significantly smaller and faster. On the + // other hand, we expect the vast majority of calls to + // `try_read_from` to succeed, and in these cases, the copy will not + // be wasted. + let maybe_valid = MaybeValid::::read_from(bytes)?; + if is_bit_valid(&maybe_valid) { + // SAFETY: `is_bit_valid` promises that it only returns true if + // its argument contains a valid `T`. This is exactly the safety + // precondition of `MaybeValid::assume_valid`. + Some(unsafe { maybe_valid.assume_valid() }) + } else { + None + } + } + + try_read_from_inner(bytes, Self::is_bit_valid) + } +} + /// Types which are safe to treat as an immutable byte slice. /// /// WARNING: Do not implement this trait yourself! Instead, use @@ -936,7 +1103,8 @@ pub unsafe trait AsBytes { // reference, the only other references to this memory region that // could exist are other immutable references, and those don't allow // mutation. `AsBytes` prohibits types which contain `UnsafeCell`s, - // which are the only types for which this rule wouldn't be sufficient. + // which are the only types for which this rule wouldn't be + // sufficient. // - The total size of the resulting slice is no larger than // `isize::MAX` because no allocation produced by safe code can be // larger than `isize::MAX`. @@ -1046,19 +1214,20 @@ safety_comment! { /// SAFETY: /// Per the reference [1], "the unit tuple (`()`) ... is guaranteed as a /// zero-sized type to have a size of 0 and an alignment of 1." - /// - `FromZeroes`, `FromBytes`: There is only one possible sequence of 0 - /// bytes, and `()` is inhabited. + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`: There + /// is only one possible sequence of 0 bytes, and `()` is inhabited. /// - `AsBytes`: Since `()` has size 0, it contains no padding bytes. /// - `Unaligned`: `()` has alignment 1. /// /// [1] https://doc.rust-lang.org/reference/type-layout.html#tuple-layout - unsafe_impl!((): FromZeroes, FromBytes, AsBytes, Unaligned); + unsafe_impl!((): TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); assert_unaligned!(()); } safety_comment! { /// SAFETY: - /// - `FromZeroes`, `FromBytes`: all bit patterns are valid for integers [1] + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`: all bit + /// patterns are valid for integers [1] /// - `AsBytes`: integers have no padding bytes [1] /// - `Unaligned` (`u8` and `i8` only): The reference [2] specifies the size /// of `u8` and `i8` as 1 byte. We also know that: @@ -1070,26 +1239,26 @@ safety_comment! { /// [1] TODO(https://github.com/rust-lang/reference/issues/1291): Once the /// reference explicitly guarantees these properties, cite it. /// [2] https://doc.rust-lang.org/reference/type-layout.html#primitive-data-layout - unsafe_impl!(u8: FromZeroes, FromBytes, AsBytes, Unaligned); - unsafe_impl!(i8: FromZeroes, FromBytes, AsBytes, Unaligned); + unsafe_impl!(u8: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + unsafe_impl!(i8: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); assert_unaligned!(u8, i8); - unsafe_impl!(u16: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(i16: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(u32: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(i32: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(u64: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(i64: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(u128: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(i128: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(usize: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(isize: FromZeroes, FromBytes, AsBytes); + unsafe_impl!(u16: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(i16: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(u32: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(i32: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(u64: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(i64: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(u128: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(i128: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(usize: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(isize: TryFromBytes, FromZeroes, FromBytes, AsBytes); } safety_comment! { /// SAFETY: - /// - `FromZeroes`, `FromBytes`: the `{f32,f64}::from_bits` constructors' - /// documentation [1,2] states that they are currently equivalent to - /// `transmute`. [3] + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`: the + /// `{f32,f64}::from_bits` constructors' documentation [1, 2] states that + /// they are currently equivalent to `transmute`. [3] /// - `AsBytes`: the `{f32,f64}::to_bits` methods' documentation [4,5] /// states that they are currently equivalent to `transmute`. [3] /// @@ -1101,8 +1270,8 @@ safety_comment! { /// reference explicitly guarantees these properties, cite it. /// [4] https://doc.rust-lang.org/nightly/std/primitive.f32.html#method.to_bits /// [5] https://doc.rust-lang.org/nightly/std/primitive.f64.html#method.to_bits - unsafe_impl!(f32: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(f64: FromZeroes, FromBytes, AsBytes); + unsafe_impl!(f32: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(f64: TryFromBytes, FromZeroes, FromBytes, AsBytes); } safety_comment! { @@ -1118,6 +1287,17 @@ safety_comment! { /// [1] https://doc.rust-lang.org/reference/types/boolean.html unsafe_impl!(bool: FromZeroes, AsBytes, Unaligned); assert_unaligned!(bool); + /// SAFETY: + /// - Since `bool`'s single byte is always initialized, `MaybeValid`'s + /// single byte must also always be initialized. Thus, it is sound to + /// transmute a `MaybeValid` to a `u8`. Since `u8` has alignment 1, + /// there can never be any alignment issues, and so it is sound to + /// transmute a `&MaybeValid` to a `&u8`. + /// - All values less than 2 are valid instances of `bool` [1], and so this + /// is a sound implementation of `TryFromBytes::is_bit_valid`. + /// + /// [1] https://doc.rust-lang.org/reference/types/boolean.html + unsafe_impl!(bool: TryFromBytes; |byte: &u8| *byte < 2); } safety_comment! { /// SAFETY: @@ -1129,6 +1309,22 @@ safety_comment! { /// /// [1] https://doc.rust-lang.org/reference/types/textual.html unsafe_impl!(char: FromZeroes, AsBytes); + /// SAFETY: + /// - Since `char`'s 4 bytes are always initialized, `MaybeValid`'s + /// bytes must also always be initialized. Thus, it is sound to transmute + /// a `MaybeValid` to a `[u8; 4]`. Since `[u8; 4]` has alignment 1, + /// there can never be any alignment issues, and so it is sound to + /// transmute a `&MaybeValid` to a `&[u8; 4]`. + /// - Since we use `from_ne_bytes`, `c` has the same bits as the argument to + /// `is_bit_valid`. `char::from_u32` guarantees that it returns `None` if + /// its input is not a valid `char` [1], and so this is a sound + /// implementation of `TryFromBytes::is_bit_valid`. + /// + /// [1] https://doc.rust-lang.org/std/primitive.char.html#method.from_u32 + unsafe_impl!(char: TryFromBytes; |bytes: &[u8; 4]| { + let c = u32::from_ne_bytes(*bytes); + char::from_u32(c).is_some() + }); } safety_comment! { /// SAFETY: @@ -1141,6 +1337,22 @@ safety_comment! { /// /// [1] https://doc.rust-lang.org/reference/type-layout.html#str-layout unsafe_impl!(str: FromZeroes, AsBytes, Unaligned); + /// SAFETY: + /// - Since `str`'s bytes are all always initialized, `MaybeValid`'s + /// bytes must also always be initialized. Thus, it is sound to transmute + /// `MaybeValid` to `[u8]`. Since `str` and `[u8]` have the same + /// layout, they have the same alignment, and so it is sound to transmute + /// `&MaybeValid` to `&u8`. + /// - `str`'s bit validity requirement is that it is valid UTF-8. [1] Thus, + /// if `from_utf8` can successfully convert `bytes` to a `str`, then the + /// `str` is valid [2], and so this is a sound implementation of + /// `TryFromBytes::is_bit_valid`. + /// + /// [1] https://doc.rust-lang.org/reference/types/textual.html + /// [2] https://doc.rust-lang.org/core/str/fn.from_utf8.html + unsafe_impl!(str: TryFromBytes; |bytes: &[u8]| { + core::str::from_utf8(bytes).is_ok() + }); } safety_comment! { @@ -1178,12 +1390,34 @@ safety_comment! { unsafe_impl!(NonZeroI128: AsBytes); unsafe_impl!(NonZeroUsize: AsBytes); unsafe_impl!(NonZeroIsize: AsBytes); + + /// SAFETY: + /// - `NonZeroXxx` has the same layout as `Xxx`. Also, every byte of + /// `NonZeroXxx` is required to be initialized, so it is guaranteed that + /// every byte of `MaybeValid` must also be initialized. Thus, + /// it is sound to transmute a `&MaybeValid` to a `&Xxx`. + /// - `NonZeroXxx`'s only validity constraint is that it is non-zero, which + /// all of these closures ensure. Thus, these closures are sound + /// implementations of `TryFromBytes::is_bit_valid`. + unsafe_impl!(NonZeroU8: TryFromBytes; |n: &u8| *n != 0); + unsafe_impl!(NonZeroI8: TryFromBytes; |n: &i8| *n != 0); + unsafe_impl!(NonZeroU16: TryFromBytes; |n: &u16| *n != 0); + unsafe_impl!(NonZeroI16: TryFromBytes; |n: &i16| *n != 0); + unsafe_impl!(NonZeroU32: TryFromBytes; |n: &u32| *n != 0); + unsafe_impl!(NonZeroI32: TryFromBytes; |n: &i32| *n != 0); + unsafe_impl!(NonZeroU64: TryFromBytes; |n: &u64| *n != 0); + unsafe_impl!(NonZeroI64: TryFromBytes; |n: &i64| *n != 0); + unsafe_impl!(NonZeroU128: TryFromBytes; |n: &u128| *n != 0); + unsafe_impl!(NonZeroI128: TryFromBytes; |n: &i128| *n != 0); + unsafe_impl!(NonZeroUsize: TryFromBytes; |n: &usize| *n != 0); + unsafe_impl!(NonZeroIsize: TryFromBytes; |n: &isize| *n != 0); } safety_comment! { /// SAFETY: - /// - `FromZeroes`, `FromBytes`, `AsBytes`: The Rust compiler reuses `0` - /// value to represent `None`, so `size_of::>() == - /// size_of::()`; see `NonZeroXxx` documentation. + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`, + /// `AsBytes`: The Rust compiler reuses `0` value to represent `None`, so + /// `size_of::>() == size_of::()`; see + /// `NonZeroXxx` documentation. /// - `Unaligned`: `NonZeroU8` and `NonZeroI8` document that /// `Option` and `Option` both have size 1. [1] [2] /// This is worded in a way that makes it unclear whether it's meant as a @@ -1196,32 +1430,34 @@ safety_comment! { /// /// TODO(https://github.com/rust-lang/rust/pull/104082): Cite documentation /// for layout guarantees. - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes, Unaligned); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes, Unaligned); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); assert_unaligned!(Option, Option); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); - unsafe_impl!(Option: FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); + unsafe_impl!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes); } safety_comment! { /// SAFETY: /// For all `T`, `PhantomData` has size 0 and alignment 1. [1] - /// - `FromZeroes`, `FromBytes`: There is only one possible sequence of 0 - /// bytes, and `PhantomData` is inhabited. + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`: There + /// is only one possible sequence of 0 bytes, and `PhantomData` is + /// inhabited. /// - `AsBytes`: Since `PhantomData` has size 0, it contains no padding /// bytes. /// - `Unaligned`: Per the preceding reference, `PhantomData` has alignment /// 1. /// /// [1] https://doc.rust-lang.org/std/marker/struct.PhantomData.html#layout-1 + unsafe_impl!(T: ?Sized => TryFromBytes for PhantomData); unsafe_impl!(T: ?Sized => FromZeroes for PhantomData); unsafe_impl!(T: ?Sized => FromBytes for PhantomData); unsafe_impl!(T: ?Sized => AsBytes for PhantomData); @@ -1237,6 +1473,7 @@ safety_comment! { /// /// [1] https://doc.rust-lang.org/nightly/core/num/struct.Wrapping.html#layout-1 /// [2] https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent + // TODO(#5): Implement `TryFromBytes` for `Wrapping`. unsafe_impl!(T: FromZeroes => FromZeroes for Wrapping); unsafe_impl!(T: FromBytes => FromBytes for Wrapping); unsafe_impl!(T: AsBytes => AsBytes for Wrapping); @@ -1248,12 +1485,13 @@ safety_comment! { // since it may contain uninitialized bytes. // /// SAFETY: - /// - `FromZeroes`, `FromBytes`: `MaybeUninit` has no restrictions on its - /// contents. Unfortunately, in addition to bit validity, `FromZeroes` and - /// `FromBytes` also require that implementers contain no `UnsafeCell`s. - /// Thus, we require `T: FromZeroes` and `T: FromBytes` in order to ensure - /// that `T` - and thus `MaybeUninit` - contains to `UnsafeCell`s. - /// Thus, requiring that `T` implement each of these traits is sufficient + /// - `TryFromBytes` (with no validator), `FromZeroes`, `FromBytes`: + /// `MaybeUninit` has no restrictions on its contents. Unfortunately, + /// in addition to bit validity, these traits also require that + /// implementers contain no `UnsafeCell`s. Thus, we require a trait bound + /// for `T` in order to ensure that `T` - and thus `MaybeUninit` - + /// contains to `UnsafeCell`s. Thus, requiring that `T` implement each of + /// these traits is sufficient. /// - `Unaligned`: `MaybeUninit` is guaranteed by its documentation [1] /// to have the same alignment as `T`. /// @@ -1263,6 +1501,7 @@ safety_comment! { /// `FromBytes` and `RefFromBytes`, or if we introduce a separate /// `NoCell`/`Freeze` trait, we can relax the trait bounds for `FromZeroes` /// and `FromBytes`. + unsafe_impl!(T: TryFromBytes => TryFromBytes for mem::MaybeUninit); unsafe_impl!(T: FromZeroes => FromZeroes for mem::MaybeUninit); unsafe_impl!(T: FromBytes => FromBytes for mem::MaybeUninit); unsafe_impl!(T: Unaligned => Unaligned for mem::MaybeUninit); @@ -1272,7 +1511,13 @@ safety_comment! { /// SAFETY: /// `ManuallyDrop` has the same layout as `T`, and accessing the inner value /// is safe (meaning that it's unsound to leave the inner value - /// uninitialized while exposing the `ManuallyDrop` to safe code). + /// uninitialized while exposing the `ManuallyDrop` to safe code). It's nearly + /// certain that `ManuallyDrop` has the same bit validity as `T`, but this + /// is technically not yet documented. [1] + /// - `TryFromBytes`: `ManuallyDrop` has the same layout and bit validity + /// as `T`, so it is sound to convert `&MaybeValid>` to + /// `&MaybeValid`, and if `T::is_bit_valid` accepts a `T` as valid, + /// then that `T` also constitutes a valid `ManuallyDrop`. /// - `FromZeroes`, `FromBytes`: Since it has the same layout as `T`, any /// valid `T` is a valid `ManuallyDrop`. If `T: FromZeroes`, a sequence /// of zero bytes is a valid `T`, and thus a valid `ManuallyDrop`. If @@ -1285,6 +1530,36 @@ safety_comment! { /// code can only ever access a `ManuallyDrop` with all initialized bytes. /// - `Unaligned`: `ManuallyDrop` has the same layout (and thus alignment) /// as `T`, and `T: Unaligned` guarantees that that alignment is 1. + /// + /// [1] https://github.com/rust-lang/rust/pull/115522 + /// + /// TODO(#5): Implement `TryFromBytes` for `ManuallyDrop` for `T: ?Sized`. + /// + /// TODO(https://github.com/rust-lang/rust/pull/115522): Update these docs + /// once ManuallyDrop bit validity is guaranteed. + unsafe_impl!(T: TryFromBytes => TryFromBytes for ManuallyDrop; |c: &MaybeValid>| { + // TODO(https://github.com/rust-lang/rust/issues/115080): Inline this + // function once #115080 is resolved. That should also let us change the + // type of `c` in the outer closure to `&MaybeValid` and let the + // `unsafe_impl!` macro handle the conversion for us, simplifying this + // body to just `::is_bit_valid(c)`. + #[inline(always)] + fn is_bit_valid) -> bool>( + c: &MaybeValid>, + is_bit_valid: F, + ) -> bool { + // SAFETY: `ManuallyDrop` has the same layout and bit validity as + // `T`, so it is sound to convert a `&MaybeValid>` + // to a `&MaybeValid`. + // + // Note: this Clippy warning is only emitted on our MSRV (1.61), but + // not on later versions of Clippy. Thus, we consider it spurious. + #[allow(clippy::as_conversions)] + let candidate = unsafe { &*(c as *const MaybeValid> as *const MaybeValid) }; + is_bit_valid(candidate) + } + is_bit_valid(c, T::is_bit_valid) + }); unsafe_impl!(T: ?Sized + FromZeroes => FromZeroes for ManuallyDrop); unsafe_impl!(T: ?Sized + FromBytes => FromBytes for ManuallyDrop); unsafe_impl!(T: ?Sized + AsBytes => AsBytes for ManuallyDrop); @@ -1310,15 +1585,34 @@ safety_comment! { /// (respectively). Furthermore, since an array/slice has "the same /// alignment of `T`", `[T]` and `[T; N]` are `Unaligned` if `T` is. /// + /// Finally, because of this layout equivalence, an instance of `[T]` or + /// `[T; N]` is valid if each `T` is valid. Thus, it is sound to implement + /// `TryFromBytes::is_bit_valid` by calling `is_bit_valid` on each element. + /// /// Note that we don't `assert_unaligned!` for slice types because /// `assert_unaligned!` uses `align_of`, which only works for `Sized` types. /// /// [1] https://doc.rust-lang.org/reference/type-layout.html#array-layout + unsafe_impl!(T: TryFromBytes, const N: usize => TryFromBytes for [T; N]; |c: &MaybeValid<[T; N]>| { + <[T] as TryFromBytes>::is_bit_valid(c.as_slice()) + }); unsafe_impl!(T: FromZeroes, const N: usize => FromZeroes for [T; N]); unsafe_impl!(T: FromBytes, const N: usize => FromBytes for [T; N]); unsafe_impl!(T: AsBytes, const N: usize => AsBytes for [T; N]); unsafe_impl!(T: Unaligned, const N: usize => Unaligned for [T; N]); assert_unaligned!([(); 0], [(); 1], [u8; 0], [u8; 1]); + unsafe_impl!(T: TryFromBytes => TryFromBytes for [T]; |c: &MaybeValid<[T]>| { + // TODO(https://github.com/rust-lang/rust/issues/115080): Inline this + // function once #115080 is resolved. + #[inline(always)] + fn is_bit_valid) -> bool>( + c: &MaybeValid<[T]>, + is_bit_valid: F, + ) -> bool { + c.as_slice_of_maybe_valids().iter().all(is_bit_valid) + } + is_bit_valid(c, T::is_bit_valid) + }); unsafe_impl!(T: FromZeroes => FromZeroes for [T]); unsafe_impl!(T: FromBytes => FromBytes for [T]); unsafe_impl!(T: AsBytes => AsBytes for [T]); @@ -1370,8 +1664,8 @@ safety_comment! { // Given this background, we can observe that: // - The size and bit pattern requirements of a SIMD type are equivalent to the // equivalent array type. Thus, for any SIMD type whose primitive `T` is -// `FromZeroes`, `FromBytes`, or `AsBytes`, that SIMD type is also -// `FromZeroes`, `FromBytes`, or `AsBytes` respectively. +// `FromZeroes`, `FromBytes`, `TryFromBytes`, or `AsBytes`, that SIMD type is +// also `FromZeroes`, `FromBytes`, `TryFromBytes`, or `AsBytes` respectively. // - Since no upper bound is placed on the alignment, no SIMD type can be // guaranteed to be `Unaligned`. // @@ -1382,21 +1676,23 @@ safety_comment! { // // See issue #38 [2]. While this behavior is not technically guaranteed, the // likelihood that the behavior will change such that SIMD types are no longer -// `FromZeroes`, `FromBytes`, or `AsBytes` is next to zero, as that would defeat -// the entire purpose of SIMD types. Nonetheless, we put this behavior behind -// the `simd` Cargo feature, which requires consumers to opt into this stability -// hazard. +// `FromZeroes`, `FromBytes`, `TryFromBytes`, or `AsBytes` is next to zero, as +// that would defeat the entire purpose of SIMD types. Nonetheless, we put this +// behavior behind the `simd` Cargo feature, which requires consumers to opt +// into this stability hazard. // // [1] https://rust-lang.github.io/unsafe-code-guidelines/layout/packed-simd-vectors.html // [2] https://github.com/rust-lang/unsafe-code-guidelines/issues/38 #[cfg(feature = "simd")] mod simd { - /// Defines a module which implements `FromZeroes`, `FromBytes`, and - /// `AsBytes` for a set of types from a module in `core::arch`. + /// Defines a module which implements `FromZeroes`, `FromBytes`, + /// `TryFromBytes`, and `AsBytes` for a set of types from a module in + /// `core::arch`. /// /// `$arch` is both the name of the defined module and the name of the /// module in `core::arch`, and `$typ` is the list of items from that module - /// to implement `FromZeroes`, `FromBytes`, and `AsBytes` for. + /// for which to implement `FromZeroes`, `FromBytes`, `TryFromBytes`, and + /// `AsBytes`. #[allow(unused_macros)] // `allow(unused_macros)` is needed because some // target/feature combinations don't emit any impls // and thus don't use this macro. @@ -1409,7 +1705,7 @@ mod simd { safety_comment! { /// SAFETY: /// See comment on module definition for justification. - $( unsafe_impl!($typ: FromZeroes, FromBytes, AsBytes); )* + $( unsafe_impl!($typ: TryFromBytes, FromZeroes, FromBytes, AsBytes); )* } } }; @@ -1806,11 +2102,11 @@ safety_comment! { // /// SAFETY: /// - `FromZeroes`, `FromBytes`: `MaybeUninit` has no restrictions on its - /// contents. Unfortunately, in addition to bit validity, `FromZeroes` and - /// `FromBytes` also require that implementers contain no `UnsafeCell`s. - /// Thus, we require `T: FromZeroes` and `T: FromBytes` in order to ensure - /// that `T` - and thus `MaybeUninit` - contains to `UnsafeCell`s. - /// Thus, requiring that `T` implement each of these traits is sufficient + /// contents. Unfortunately, in addition to bit validity, these traits + /// also require that implementers contain no `UnsafeCell`s. Thus, we + /// require a trait bound for `T` in order to ensure that `T` - and thus + /// `MaybeUninit` - contains no `UnsafeCell`s. Thus, requiring that `T` + /// implement each of these traits is sufficient. /// - `Unaligned`: `MaybeUninit` is guaranteed by its documentation [1] /// to have the same alignment as `T`. /// @@ -1820,6 +2116,17 @@ safety_comment! { /// `FromBytes` and `RefFromBytes`, or if we introduce a separate /// `NoCell`/`Freeze` trait, we can relax the trait bounds for `FromZeroes` /// and `FromBytes`. + /// + /// TODO(https://github.com/rust-lang/rust/issues/115080#issuecomment-1704524775): + /// Support implementing `TryFromBytes` for `MaybeUninit`. We would like + /// to use a `T: TryFromBytes` bound, but can't as explained in that issue + /// comment. It's possible that future improvements made as part of #5 may + /// allow us to work around this rustc limitation. In particular, if we + /// allow deriving `AsMaybeUninit` and at the same time remove our blanket + /// impl of `AsMaybeUninit` for `T: Sized`, there's a chance that that may + /// fix things, although I'm not 100% sure and I haven't tested it. Another + /// fix might be to introduce a `NoCell`/`Freeze` trait and use that as the + /// bound instead of `TryFromBytes`. unsafe_impl!(T: ?Sized + AsMaybeUninit + FromZeroes => FromZeroes for MaybeUninit); unsafe_impl!(T: ?Sized + AsMaybeUninit + FromBytes => FromBytes for MaybeUninit); unsafe_impl!(T: ?Sized + AsMaybeUninit + Unaligned => Unaligned for MaybeUninit); @@ -1868,6 +2175,16 @@ pub struct MaybeValid { safety_comment! { /// SAFETY: + /// - `FromZeroes`, `FromBytes`: `MaybeValid` doesn't impose any bit + /// validity constraints beyond requiring that certain bytes are + /// initialized. `T: TryFromBytes` ensures that `T` (or `[T]`) don't + /// contain any `UnsafeCell`s, which in turn ensures that `T::MaybeUninit` + /// (and thus `MaybeValid`) doesn't contain any `UnsafeCell`s. Note + /// that we cannot require `T: FromZeroes` or `T: FromBytes` in the + /// `FromZeroes` and `FromBytes` impls. The reason is that we rely on + /// `MaybeValid` to be `FromBytes` in our default method impls on + /// `TryFromBytes`, where we don't have any guarantee that `T: FromZeroes` + /// or `T: FromBytes`. /// - `AsBytes`: `MaybeValid` requires that, if a byte in `T` is always /// initialized, the equivalent byte in `MaybeValid` must be /// initialized. `T: AsBytes` implies that all bytes in `T` must always be @@ -1878,8 +2195,35 @@ safety_comment! { /// - `Unaligned`: `MaybeValid` and `MaybeValid<[T]>` have the same /// alignment as `T`. /// - /// TODO(#5): Implement `FromZeroes` and `FromBytes` for `MaybeValid` and - /// `MaybeValid<[T]>`. + /// TODO(#5): In a future in which we derive `AsMaybeUninit` rather than + /// using the blanket impl for all `T: Sized`, we can be more precise about + /// `UnsafeCell`s. In particular, instead of guaranteeing that + /// `T::MaybeUninit` contains `UnsafeCell`s where `T` does, but we can + /// guarantee that `T::MaybeUninit` never contains any `UnsafeCell`s + /// regardless of whether `T` does. We can do this by doing the following: + /// + /// ``` + /// unsafe impl AsMaybeUninnit for UnsafeCell { + /// type MaybeUninit = MaybeUninit; + /// } + /// ``` + /// + /// Then, in code emitted by custom derive, we can set `MaybeUninit` to an + /// anonymous struct type where each field type, `F`, is replaced by + /// `F::MaybeUninit`. Once we've done this, we can implement `TryFromBytes`, + /// `FromZeroes`, and `FromBytes` for `MaybeValid` with no bounds on `T`. + /// + /// TODO(#5): Implement `TryFromBytes` for `MaybeValid` once we support + /// unsized types or once we support implementing traits for `MaybeValid` + /// without bounds on `T` (see previous TODO). Currently, `T: TryFromBytes + /// => TryFromBytes for MaybeValid` doesn't work because the impl emitted + /// by `unsafe_impl!` isn't smart enough to realize that `MaybeValid` is + /// `Sized` (I suspect this is another instance of + /// https://github.com/rust-lang/rust/issues/115080, but I'm not sure). + unsafe_impl!(T: TryFromBytes => FromZeroes for MaybeValid); + unsafe_impl!(T: TryFromBytes => FromZeroes for MaybeValid<[T]>); + unsafe_impl!(T: TryFromBytes => FromBytes for MaybeValid); + unsafe_impl!(T: TryFromBytes => FromBytes for MaybeValid<[T]>); unsafe_impl!(T: AsBytes => AsBytes for MaybeValid); unsafe_impl!(T: AsBytes => AsBytes for MaybeValid<[T]>); unsafe_impl!(T: Unaligned => Unaligned for MaybeValid); @@ -4946,9 +5290,192 @@ mod tests { #[test] fn test_impls() { + use core::borrow::Borrow; + + // A type that can supply test cases for testing + // `TryFromBytes::is_bit_valid`. All types passed to `assert_impls!` + // must implement this trait; that macro uses it to generate runtime + // tests for `TryFromBytes` impls. + // + // All `T: FromBytes` types are provided with a blanket impl. Other + // types must implement `TryFromBytesTestable` directly (ie using + // `impl_try_from_bytes_testable!`). + trait TryFromBytesTestable { + fn with_passing_test_cases(f: F); + // Both arguments to `f` have the same contents. The `&[u8]` + // argument is provided so that the contents may be printed on error + // for debugging (`MaybeValid: !AsBytes`). + fn with_failing_test_cases, &[u8])>(f: F); + } + + impl TryFromBytesTestable for T { + fn with_passing_test_cases(f: F) { + // Test with a zeroed value. + f(&Self::new_zeroed()); + + let ffs = { + let mut t = Self::new_zeroed(); + let ptr: *mut T = &mut t; + // SAFETY: `T: FromBytes` + unsafe { ptr::write_bytes(ptr.cast::(), 0xFF, mem::size_of::()) }; + t + }; + + // Test with a value initialized with 0xFF. + f(&ffs); + } + + fn with_failing_test_cases, &[u8])>(_f: F) {} + } + + // Implements `TryFromBytesTestable`. + // + // # Safety + // + // All failure cases must have a valid size and alignment for `Self`. + // For unsized types, the failure case's type's trailing slice element + // must have the same size as `Self`'s trailing slice element so that + // `as` casting a raw pointer preserves length. + macro_rules! impl_try_from_bytes_testable { + // Implements for a type with no type parameters. + (=> @success $($success_case:expr),* $(, @failure $($failure_case:expr),*)?) => {}; + ($ty:ty $(,$tys:ty)* => @success $($success_case:expr),* $(, @failure $($failure_case:expr),*)?) => { + impl TryFromBytesTestable for $ty { + impl_try_from_bytes_testable!( + @methods @success $($success_case),* + $(, @failure $($failure_case),*)? + ); + } + impl_try_from_bytes_testable!($($tys),* => @success $($success_case),* $(, @failure $($failure_case),*)?); + }; + // Implements for multiple types with no type parameters. + ($($($ty:ty),* => @success $($success_case:expr), * $(, @failure $($failure_case:expr),*)?;)*) => { + $( + impl_try_from_bytes_testable!($($ty),* => @success $($success_case),* $(, @failure $($failure_case),*)*); + )* + }; + // Implements only the methods; caller must invoke this from inside + // an impl block. + (@methods @success $($success_case:expr),* $(, @failure $($failure_case:expr),*)?) => { + fn with_passing_test_cases(_f: F) { + $( + _f($success_case.borrow()); + )* + } + + fn with_failing_test_cases, &[u8])>(_f: F) { + $($( + // `unused_qualifications` is spuriously triggered on + // `Option::::None`. + #[allow(unused_qualifications)] + let case = $failure_case.as_bytes(); + // SAFETY: Caller promises that this conversion is + // valid, and that `m` will be properly sized and + // aligned. + #[allow(clippy::undocumented_unsafe_blocks)] // spuriously triggered in 1.61 + let m = unsafe { + #[allow(clippy::as_conversions)] + let m = &*(case as *const _ as *const MaybeValid); + m + }; + _f(&m, case); + )*)? + } + }; + } + + // SAFETY: All failure cases use a type with: + // - the same size as `Self` + // - the same alignment as `Self` + // - for unsized types, the same slice element size as `Self` + impl_try_from_bytes_testable!( + bool => @success true, false, + @failure 2u8, 3u8, 0xFFu8; + char => @success '\u{0}', '\u{D7FF}', '\u{E000}', '\u{10FFFF}', + @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 + => @success Self::new(1).unwrap(), + // Doing this instead of `0` ensures that we always satisfy + // the size and alignment requirements of `Self` (whereas + // `0` may be any integer type with a different size or + // alignment than some `NonZeroXxx` types). + @failure Option::::None; + ManuallyDrop + => @success ManuallyDrop::new(true), ManuallyDrop::new(false), + @failure 3u8, 4u8, 0xFFu8; + // TODO(#321): Add success test cases for `ManuallyDrop<[bool]>` + // once we have an easy way of converting `[ManuallyDrop] -> + // ManuallyDrop<[bool]>`. + // + // TODO(#5): Add failure test cases for `ManuallyDrop<[bool]>` + // once we support generic unsized `MaybeValid`. + // + // Here are some suggested test cases: + // + // @success [ManuallyDrop::new(true), ManuallyDrop::new(false)][..], + // [ManuallyDrop::new(false), ManuallyDrop::new(true)][..], + // @failure [3u8], [4u8], [0xFFu8], [0u8, 1, 0xFF]; + ManuallyDrop<[bool]> => @success; + mem::MaybeUninit + => @success mem::MaybeUninit::new(false), + mem::MaybeUninit::new(true), + mem::MaybeUninit::uninit(), + // SAFETY: `mem::MaybeUninit` is `FromBytes` + // when `T: FromBytes` only because that's a way of + // ensuring that it contains no `UnsafeCell`s. Any + // bit patterns are still sound. + unsafe { core::mem::transmute::<_, mem::MaybeUninit>(0xFFu8) }; + [bool; 0] => @success []; + [bool; 1] + => @success [true], [false], + @failure [2u8], [3u8], [0xFFu8]; + [bool] + => @success [true, false], [false, true], + @failure [2u8], [3u8], [0xFFu8], [0u8, 1u8, 2u8]; + + + ); + // Asserts that `$ty` implements any `$trait` and doesn't implement any // `!$trait`. Note that all `$trait`s must come before any `!$trait`s. + // + // For `T: TryFromBytes`, uses `TryFromBytesTestable` to test success + // and failure cases for `TryFromBytes::is_bit_valid`. macro_rules! assert_impls { + ($ty:ty: TryFromBytes) => { + <$ty as TryFromBytesTestable>::with_passing_test_cases(|val| { + let m = MaybeValid::from_ref(val); + assert!(<$ty as TryFromBytes>::is_bit_valid(m), "{}::is_bit_valid({:?}): got false, expected true", stringify!($ty), val); + + // TODO(#5): In addition to testing `is_bit_valid`, test the + // methods built on top of it. This would both allow us to + // test their implementations and actually convert the bytes + // to `$ty`, giving Miri a chance to catch if this is + // unsound (ie, if our `is_bit_valid` impl is buggy). + // + // The following code was tried, but it doesn't work because + // a) some types are not `AsBytes` and, b) some types are + // not `Sized`. + // + // let r = <$ty as TryFromBytes>::try_from_ref(val.as_bytes()).unwrap(); + // assert_eq!(r, &val); + // let r = <$ty as TryFromBytes>::try_from_mut(val.as_bytes_mut()).unwrap(); + // assert_eq!(r, &mut val); + // let v = <$ty as TryFromBytes>::try_read_from(val.as_bytes()).unwrap(); + // assert_eq!(v, val); + }); + <$ty as TryFromBytesTestable>::with_failing_test_cases(|m, bytes| { + assert!(!<$ty as TryFromBytes>::is_bit_valid(m), "{}::is_bit_valid({:?}): got true, expected false", stringify!($ty), bytes); + }); + + #[allow(dead_code)] + const _: () = { static_assertions::assert_impl_all!($ty: TryFromBytes); }; + }; ($ty:ty: $trait:ident) => { #[allow(dead_code)] const _: () = { static_assertions::assert_impl_all!($ty: $trait); }; @@ -4968,90 +5495,96 @@ mod tests { }; } - assert_impls!((): FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!(u8: FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!(i8: FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!(u16: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(i16: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(u32: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(i32: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(u64: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(i64: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(u128: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(i128: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(usize: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(isize: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(f32: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(f64: FromZeroes, FromBytes, AsBytes, !Unaligned); - - assert_impls!(bool: FromZeroes, AsBytes, Unaligned, !FromBytes); - assert_impls!(char: FromZeroes, AsBytes, !FromBytes, !Unaligned); - assert_impls!(str: FromZeroes, AsBytes, Unaligned, !FromBytes); - - assert_impls!(NonZeroU8: AsBytes, Unaligned, !FromZeroes, !FromBytes); - assert_impls!(NonZeroI8: AsBytes, Unaligned, !FromZeroes, !FromBytes); - assert_impls!(NonZeroU16: AsBytes, !FromZeroes, !FromBytes, !Unaligned); - assert_impls!(NonZeroI16: AsBytes, !FromZeroes, !FromBytes, !Unaligned); - assert_impls!(NonZeroU32: AsBytes, !FromZeroes, !FromBytes, !Unaligned); - assert_impls!(NonZeroI32: AsBytes, !FromZeroes, !FromBytes, !Unaligned); - assert_impls!(NonZeroU64: AsBytes, !FromZeroes, !FromBytes, !Unaligned); - assert_impls!(NonZeroI64: AsBytes, !FromZeroes, !FromBytes, !Unaligned); - assert_impls!(NonZeroU128: AsBytes, !FromZeroes, !FromBytes, !Unaligned); - assert_impls!(NonZeroI128: AsBytes, !FromZeroes, !FromBytes, !Unaligned); - assert_impls!(NonZeroUsize: AsBytes, !FromZeroes, !FromBytes, !Unaligned); - assert_impls!(NonZeroIsize: AsBytes, !FromZeroes, !FromBytes, !Unaligned); - - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, !Unaligned); - assert_impls!(Option: FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!((): TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!(u8: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!(i8: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!(u16: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(i16: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(u32: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(i32: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(u64: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(i64: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(u128: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(i128: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(usize: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(isize: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(f32: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(f64: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + + assert_impls!(bool: TryFromBytes, FromZeroes, AsBytes, Unaligned, !FromBytes); + assert_impls!(char: TryFromBytes, FromZeroes, AsBytes, !FromBytes, !Unaligned); + assert_impls!(str: TryFromBytes, FromZeroes, AsBytes, Unaligned, !FromBytes); + + assert_impls!(NonZeroU8: TryFromBytes, AsBytes, Unaligned, !FromZeroes, !FromBytes); + assert_impls!(NonZeroI8: TryFromBytes, AsBytes, Unaligned, !FromZeroes, !FromBytes); + assert_impls!(NonZeroU16: TryFromBytes, AsBytes, !FromZeroes, !FromBytes, !Unaligned); + assert_impls!(NonZeroI16: TryFromBytes, AsBytes, !FromZeroes, !FromBytes, !Unaligned); + assert_impls!(NonZeroU32: TryFromBytes, AsBytes, !FromZeroes, !FromBytes, !Unaligned); + assert_impls!(NonZeroI32: TryFromBytes, AsBytes, !FromZeroes, !FromBytes, !Unaligned); + assert_impls!(NonZeroU64: TryFromBytes, AsBytes, !FromZeroes, !FromBytes, !Unaligned); + assert_impls!(NonZeroI64: TryFromBytes, AsBytes, !FromZeroes, !FromBytes, !Unaligned); + assert_impls!(NonZeroU128: TryFromBytes, AsBytes, !FromZeroes, !FromBytes, !Unaligned); + assert_impls!(NonZeroI128: TryFromBytes, AsBytes, !FromZeroes, !FromBytes, !Unaligned); + assert_impls!(NonZeroUsize: TryFromBytes, AsBytes, !FromZeroes, !FromBytes, !Unaligned); + assert_impls!(NonZeroIsize: TryFromBytes, AsBytes, !FromZeroes, !FromBytes, !Unaligned); + + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); + assert_impls!(Option: TryFromBytes, FromZeroes, FromBytes, AsBytes, !Unaligned); // Implements none of the ZC traits. struct NotZerocopy; - assert_impls!(PhantomData: FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!(PhantomData<[u8]>: FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!(PhantomData: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!(PhantomData<[u8]>: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!(ManuallyDrop: FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!(ManuallyDrop<[u8]>: FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!(ManuallyDrop: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); - assert_impls!(ManuallyDrop<[NotZerocopy]>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); + assert_impls!(ManuallyDrop: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!(ManuallyDrop<[u8]>: FromZeroes, FromBytes, AsBytes, Unaligned, !TryFromBytes); + assert_impls!(ManuallyDrop: TryFromBytes, FromZeroes, AsBytes, Unaligned, !FromBytes); + assert_impls!(ManuallyDrop<[bool]>: FromZeroes, AsBytes, Unaligned, !FromBytes, !TryFromBytes); + assert_impls!(ManuallyDrop: !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); + assert_impls!(ManuallyDrop<[NotZerocopy]>: !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); - assert_impls!(mem::MaybeUninit: FromZeroes, FromBytes, Unaligned, !AsBytes); - assert_impls!(mem::MaybeUninit: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); + assert_impls!(mem::MaybeUninit: TryFromBytes, FromZeroes, FromBytes, Unaligned, !AsBytes); + assert_impls!(mem::MaybeUninit: TryFromBytes, FromZeroes, Unaligned, !AsBytes, !FromBytes); + assert_impls!(mem::MaybeUninit: !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); - assert_impls!(MaybeUninit: FromZeroes, FromBytes, Unaligned, !AsBytes); - assert_impls!(MaybeUninit>: FromZeroes, FromBytes, Unaligned, !AsBytes); - assert_impls!(MaybeUninit<[u8]>: FromZeroes, FromBytes, Unaligned, !AsBytes); - assert_impls!(MaybeUninit>: FromZeroes, FromBytes, Unaligned, !AsBytes); - assert_impls!(MaybeUninit: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); - assert_impls!(MaybeUninit>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); + assert_impls!(MaybeUninit: FromZeroes, FromBytes, Unaligned, !TryFromBytes, !AsBytes); + assert_impls!(MaybeUninit>: FromZeroes, FromBytes, Unaligned, !TryFromBytes, !AsBytes); + assert_impls!(MaybeUninit<[u8]>: FromZeroes, FromBytes, Unaligned, !TryFromBytes, !AsBytes); + assert_impls!(MaybeUninit>: FromZeroes, FromBytes, Unaligned, !TryFromBytes, !AsBytes); + assert_impls!(MaybeUninit: !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); + assert_impls!(MaybeUninit>: !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); - assert_impls!(MaybeValid: Unaligned, AsBytes, !FromZeroes, !FromBytes); + assert_impls!(MaybeValid: FromZeroes, FromBytes, Unaligned, AsBytes); assert_impls!(MaybeValid>: Unaligned, AsBytes, !FromZeroes, !FromBytes); - assert_impls!(MaybeValid<[u8]>: Unaligned, AsBytes, !FromZeroes, !FromBytes); + assert_impls!(MaybeValid<[u8]>: FromZeroes, FromBytes, Unaligned, AsBytes); assert_impls!(MaybeValid: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); assert_impls!(MaybeValid>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); assert_impls!(Wrapping: FromZeroes, FromBytes, AsBytes, Unaligned); assert_impls!(Wrapping: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); - assert_impls!(Unalign: FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!(Unalign: Unaligned, !FromZeroes, !FromBytes, !AsBytes); - - assert_impls!([u8]: FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!([NotZerocopy]: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); - assert_impls!([u8; 0]: FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!([NotZerocopy; 0]: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); - assert_impls!([u8; 1]: FromZeroes, FromBytes, AsBytes, Unaligned); - assert_impls!([NotZerocopy; 1]: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); + assert_impls!(Unalign: FromZeroes, FromBytes, AsBytes, Unaligned, !TryFromBytes); + assert_impls!(Unalign: Unaligned, !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes); + + assert_impls!([u8]: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!([bool]: TryFromBytes, FromZeroes, AsBytes, Unaligned, !FromBytes); + assert_impls!([NotZerocopy]: !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); + assert_impls!([u8; 0]: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!([bool; 0]: TryFromBytes, FromZeroes, AsBytes, Unaligned, !FromBytes); + assert_impls!([NotZerocopy; 0]: !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); + assert_impls!([u8; 1]: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!([bool; 1]: TryFromBytes, FromZeroes, AsBytes, Unaligned, !FromBytes); + assert_impls!([NotZerocopy; 1]: !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); } } diff --git a/src/macros.rs b/src/macros.rs index 5ffdbd16e8a..8381918073c 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -13,47 +13,90 @@ /// /// Safety comment starts on its own line. /// macro_1!(args); /// macro_2! { args }; +/// /// SAFETY: +/// /// Subsequent safety comments are allowed but not required. +/// macro_3! { args }; /// } /// ``` /// /// The macro invocations are emitted, each decorated with the following /// attribute: `#[allow(clippy::undocumented_unsafe_blocks)]`. macro_rules! safety_comment { - (#[doc = r" SAFETY:"] $(#[doc = $_doc:literal])* $($macro:ident!$args:tt;)*) => { + (#[doc = r" SAFETY:"] $(#[doc = $_doc:literal])* $($macro:ident!$args:tt; $(#[doc = r" SAFETY:"] $(#[doc = $__doc:literal])*)?)*) => { #[allow(clippy::undocumented_unsafe_blocks)] const _: () = { $($macro!$args;)* }; } } /// Unsafely implements trait(s) for a type. +/// +/// # Safety +/// +/// The trait impl must be sound. +/// +/// When implementing `TryFromBytes`: +/// - If no `is_bit_valid` impl is provided, then it must be valid for +/// `is_bit_valid` to unconditionally return `true`. +/// - If an `is_bit_valid` impl is provided, then: +/// - It must be sound to transmute `&MaybeValid<$ty>` into `&$repr`. +/// - The impl of `is_bit_valid` must satisfy `TryFromByte`'s safety +/// requirements. macro_rules! unsafe_impl { // Implement `$trait` for `$ty` with no bounds. - ($ty:ty: $trait:ty) => { - unsafe impl $trait for $ty { fn only_derive_is_allowed_to_implement_this_trait() {} } + ($ty:ty: $trait:ident $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => { + unsafe impl $trait for $ty { + unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?); + } }; // Implement all `$traits` for `$ty` with no bounds. - ($ty:ty: $($traits:ty),*) => { + ($ty:ty: $($traits:ident),*) => { $( unsafe_impl!($ty: $traits); )* }; // For all `$tyvar` with no bounds, implement `$trait` for `$ty`. - ($tyvar:ident => $trait:ident for $ty:ty) => { - unsafe impl<$tyvar> $trait for $ty { fn only_derive_is_allowed_to_implement_this_trait() {} } + ($tyvar:ident => $trait:ident for $ty:ty $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => { + unsafe impl<$tyvar> $trait for $ty { + unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?); + } }; // For all `$tyvar: $bound`, implement `$trait` for `$ty`. - ($tyvar:ident: $bound:path => $trait:ident for $ty:ty) => { - unsafe impl<$tyvar: $bound> $trait for $ty { fn only_derive_is_allowed_to_implement_this_trait() {} } + ($tyvar:ident: $bound:path => $trait:ident for $ty:ty $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => { + unsafe impl<$tyvar: $bound> $trait for $ty { + unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?); + } }; // For all `$tyvar: $bound + ?Sized`, implement `$trait` for `$ty`. - ($tyvar:ident: ?Sized $(+ $bounds:path)* => $trait:ident for $ty:ty) => { - unsafe impl<$tyvar: ?Sized $(+ $bounds)*> $trait for $ty { fn only_derive_is_allowed_to_implement_this_trait() {} } + ($tyvar:ident: ?Sized $(+ $bounds:path)* => $trait:ident for $ty:ty $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => { + unsafe impl<$tyvar: ?Sized $(+ $bounds)*> $trait for $ty { + unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?); + } }; // For all `$tyvar: $bound` and for all `const $constvar: $constty`, // implement `$trait` for `$ty`. - ($tyvar:ident: $bound:path, const $constvar:ident: $constty:ty => $trait:ident for $ty:ty) => { + ($tyvar:ident: $bound:path, const $constvar:ident: $constty:ty => $trait:ident for $ty:ty $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => { unsafe impl<$tyvar: $bound, const $constvar: $constty> $trait for $ty { - fn only_derive_is_allowed_to_implement_this_trait() {} + unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?); } }; + + (@method TryFromBytes ; |$candidate:ident: &$repr:ty| $is_bit_valid:expr) => { + fn is_bit_valid(candidate: &MaybeValid) -> bool { + // SAFETY: The macro caller has promised that it is sound to + // transmute `&MaybeValid` to `&$repr`. + // + // Note: this Clippy warning is only emitted on our MSRV (1.61), but + // not on later versions of Clippy. Thus, we consider it spurious. + #[allow(clippy::as_conversions)] + let $candidate = unsafe { &*(candidate as *const MaybeValid as *const $repr) }; + $is_bit_valid + } + }; + (@method TryFromBytes) => { fn is_bit_valid(_: &MaybeValid) -> bool { true } }; + (@method $trait:ident) => { + fn only_derive_is_allowed_to_implement_this_trait() {} + }; + (@method $trait:ident; |$_candidate:ident: &$_repr:ty| $_is_bit_valid:expr) => { + compile_error!("Can't provide `is_bit_valid` impl for trait other than `TryFromBytes`"); + }; } /// Implements trait(s) for a type or verifies the given implementation by