From d7c16fab8b7704318d16e6b85d3611d0f3e10a03 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Wed, 6 Sep 2023 18:57:18 +0000 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. Makes progress on #5 --- src/lib.rs | 876 ++++++++++++++++++++++++++++++++++++++++++-------- src/macros.rs | 120 +++++-- src/util.rs | 55 ++++ 3 files changed, 892 insertions(+), 159 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 558e8d931e..45f8e1d136 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,16 +185,25 @@ use core::{ NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping, }, ops::{Deref, DerefMut}, - ptr, slice, + ptr::{self, NonNull}, + slice, }; #[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "alloc")] -use { - alloc::{boxed::Box, vec::Vec}, - core::ptr::NonNull, -}; +use alloc::{boxed::Box, vec::Vec}; + +use crate::util::AsAddress as _; + +// For each polyfill, as soon as the corresponding feature is stable, the +// polyfill import will be unused because method/function resolution will prefer +// the inherent method/function over a trait method/function. Thus, we suppress +// the `unused_imports` warning. +// +// See the documentation on `util::polyfills` for more information. +#[allow(unused_imports)] +use crate::util::polyfills::{NonNullExt as _, NonNullSliceExt as _}; // This is a hack to allow zerocopy-derive derives to work in this crate. They // assume that zerocopy is linked as an extern crate, so they access items from @@ -229,8 +238,8 @@ pub struct DstLayout { } #[cfg_attr(test, derive(Copy, Clone, Debug))] -enum _CastType { - _Prefix, +enum CastType { + Prefix, _Suffix, } @@ -306,11 +315,11 @@ impl DstLayout { /// `validate_cast` will panic. The caller should not rely on /// `validate_cast` panicking in any particular condition, even if /// `debug_assertions` are enabled. - const fn _validate_cast( + const fn validate_cast( &self, addr: usize, bytes_len: usize, - cast_type: _CastType, + cast_type: CastType, ) -> Option<(usize, usize)> { // `debug_assert!`, but with `#[allow(clippy::arithmetic_side_effects)]`. macro_rules! __debug_assert { @@ -355,8 +364,8 @@ impl DstLayout { // bytes_len`) is not aligned, then no valid start address will be // aligned either. let offset = match cast_type { - _CastType::_Prefix => 0, - _CastType::_Suffix => bytes_len, + CastType::Prefix => 0, + CastType::_Suffix => bytes_len, }; // Addition is guaranteed not to overflow because `offset <= bytes_len`, @@ -447,15 +456,28 @@ impl DstLayout { __debug_assert!(self_bytes <= bytes_len); let split_at = match cast_type { - _CastType::_Prefix => self_bytes, + CastType::Prefix => self_bytes, // Guaranteed not to underflow because `self_bytes <= bytes_len` // (lemma 4). #[allow(clippy::arithmetic_side_effects)] - _CastType::_Suffix => bytes_len - self_bytes, + CastType::_Suffix => bytes_len - self_bytes, }; Some((elems, split_at)) } + + /// Calls `validate_exact_cast` and ensures that all of `bytes_len` are + /// used. + /// + /// Passes all arguments to `validate_cast`, and passes `CastType::Prefix`. + /// If the cast would leave any suffix bytes left over, + /// `validate_exact_cast` returns `None`. + const fn validate_exact_cast(&self, addr: usize, bytes_len: usize) -> Option { + match self.validate_cast(addr, bytes_len, CastType::Prefix) { + Some((elems, split_at)) if split_at == bytes_len => Some(elems), + Some(_) | None => None, + } + } } /// A trait which carries information about a type's layout that is used by the @@ -474,12 +496,27 @@ impl DstLayout { pub unsafe trait KnownLayout: sealed::KnownLayoutSealed { #[doc(hidden)] const LAYOUT: DstLayout; + + /// SAFETY: The returned pointer has the same address and provenance as + /// `bytes`. If `Self` is a DST, the returned pointer's referent has `elems` + /// elements in its trailing slice. If `Self` is sized, `elems` is ignored. + #[doc(hidden)] + fn raw_from_ptr_len(bytes: NonNull, elems: usize) -> NonNull; } impl sealed::KnownLayoutSealed for [T] {} // SAFETY: Delegates safety to `DstLayout::for_slice`. unsafe impl KnownLayout for [T] { const LAYOUT: DstLayout = DstLayout::for_slice::(); + + // SAFETY: `.cast` preserves address and provenance. The returned pointer + // refers to an object with `elems` elements by construction. + #[inline(always)] + fn raw_from_ptr_len(data: NonNull, elems: usize) -> NonNull { + // TODO(#67): Remove this allow. See NonNullExt for more details. + #[allow(unstable_name_collisions)] + NonNull::slice_from_raw_parts(data.cast::(), elems) + } } #[rustfmt::skip] @@ -884,6 +921,215 @@ 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 +/// +/// On its own, `T: TryFromBytes` does not make any guarantees about the layout +/// or representation of `T`. It merely provides the ability to perform a +/// validity check at runtime via methods like [`try_from_ref`]. +/// +/// TODO(#352): UnsafeCell? We need to specify that it's unsound to implement +/// `TryFromBytes` for types which contain them, but we also don't want to allow +/// code to use `TryFromBytes` to mean "has no `UnsafeCell`" in case we relax +/// that requirement in the future. +/// +/// Currently, it is not possible to stably implement `TryFromBytes` other than +/// by using `#[derive(TryFromBytes)]`. While there are `#[doc(hidden)]` items +/// on this trait that provide well-defined safety invariants, no stability +/// guarantees are made with respect to these items. In particular, future +/// releases of zerocopy may make backwards-breaking changes to these items, +/// including changes that only affect soundness, which may cause code which +/// uses those items to silently become unsound. +/// +/// [`try_from_ref`]: TryFromBytes::try_from_ref +pub unsafe trait TryFromBytes: KnownLayout { + /// Does a given memory range contain a valid instance of `Self`? + /// + /// # Safety + /// + /// ## Preconditions + /// + /// `candidate` must be a validly-aligned pointer, and it must be [valid] + /// for reads. The total length encoded by the pointer must not overflow an + /// `isize`. The memory addressed by `c` must fall within a single + /// allocation. + /// + /// `candidate`'s referent is not required to contain a valid `Self`. + /// However, it must satisfy the requirement that uninitialized bytes may + /// only be present where it is possible for them to be present in `Self`. + /// This is a dynamic property: if, at a particular byte offset, a valid + /// enum discriminant is set, the subsequent bytes may only have + /// uninitialized bytes as specificed by the corresponding enum. + /// + /// Formally, given `len = size_of_val_raw(candidate)`, at every byte + /// offset, `b`, in the range `[0, len)`: + /// - If, in all instances `s: Self` of length `len`, the byte at offset `b` + /// in `s` is initialized, then the byte at offset `b` within `*candidate` + /// must be initialized. + /// - Let `c` be the contents of the byte range `[0, b)` in `*candidate`. + /// Let `S` be the subset of valid instances of `Self` of length `len` + /// which contain `c` in the offset range `[0, b)`. If, for all instances + /// of `s: Self` in `S`, the byte at offset `b` in `s` is initialized, + /// then the byte at offset `b` in `*candidate` must be initialized. + /// + /// Pragmatically, this means that if `*candidate` is guaranteed to + /// contain an enum type at a particular offset, and the enum discriminant + /// stored in `*candidate` corresponds to a valid variant of that enum + /// type, then it is guaranteed that the appropriate bytes of `*candidate` + /// are initialized as defined by that variant's bit validity (although + /// note that the variant may contain another enum type, in which case the + /// same rules apply depending on the state of its discriminant, and so on + /// recursively). + /// + /// ## Postconditions + /// + /// Unsafe code may assume that, if `is_bit_valid(candidate)` returns true, + /// `*candidate` contains a valid `Self`. + /// + /// [valid]: https://doc.rust-lang.org/std/ptr/index.html#safety + #[doc(hidden)] + unsafe fn is_bit_valid(candidate: NonNull) -> 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> { + // PANICS: `bytes` is a `&[u8]`, and so the sum of its address and + // length cannot overflow `usize`. + let elems = Self::LAYOUT.validate_exact_cast(bytes.addr(), bytes.len())?; + let slf = Self::raw_from_ptr_len(NonNull::from(bytes).cast::(), elems); + + // SAFETY: + // - `bytes` is a `&[u8]`, which guarantees that its length doesn't + // overflow `isize` and that it comes from a single allocation + // - `validate_exact_cast` checked alignment + // - all bytes of `bytes` are initialized + if unsafe { !Self::is_bit_valid(slf) } { + return None; + } + + // SAFETY: + // - `is_bit_valid` guaranteed that `*slf` contains a valid `Self` + // - Since `Self` is not allowed to contain any `UnsafeCell`s: + // - The caller cannot use the `&Self` to perform interior mutation on + // a byte range that `bytes` views as not containing `UnsafeCell`s + // - The caller cannot use the `&Self` to write invalid values to + // `bytes` (namely, uninitialized bytes, as `[u8]` has no other bit + // validity constraints) + // - Since `[u8]` does not contain any `UnsafeCell`s, we are guaranteed + // that, having verified that `slf` currently contains a valid `Self`, + // code with access to `bytes` cannot cause it to no longer contain a + // valid `Self` in the future + Some(unsafe { &*slf.as_ptr() }) + } + + /// 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 + Self: AsBytes, + { + // PANICS: `bytes` is a `&[u8]`, and so the sum of its address and + // length cannot overflow `usize`. + let elems = Self::LAYOUT.validate_exact_cast(bytes.addr(), bytes.len())?; + let slf = Self::raw_from_ptr_len(NonNull::from(bytes).cast::(), elems); + + // SAFETY: + // - `bytes` is a `&[u8]`, which guarantees that its length doesn't + // overflow `isize` and that it comes from a single allocation + // - `validate_exact_cast` checked alignment + // - all bytes of `bytes` are initialized + if unsafe { !Self::is_bit_valid(slf) } { + return None; + } + + // SAFETY: + // - `is_bit_valid` guaranteed that `*slf` contains a valid `Self` + // - Since `Self: AsBytes`, any values written to the returned `&mut + // Self` will be valid for `[u8]` once it is accessible again + // - Since the returned `&mut Self` has the same lifetime as the input + // `&mut [u8]`, that input cannot be directly mutated so long as the + // returned reference exists. Thus, having verified that `slf` + // currently contains a valid `Self`, code with access to `bytes` + // cannot cause it to no longer contain a valid `Self` in the future. + Some(unsafe { &mut *slf.as_ptr() }) + } + + /// 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, + { + // A note on performance: We unconditionally read `size_of::()` + // bytes into the local stack frame before validation. This has + // advantages and disadvantages: + // - It allows `MaybeUninit` 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_uninit = MaybeUninit::::read_from(bytes)?; + let candidate = NonNull::from(&maybe_uninit).cast::(); + + // SAFETY: + // - `MaybeUninit` has the same alignment as `Self`, so this is + // aligned + // - `maybe_uninit` was initialized from `bytes`, so all of its bytes + // are initialized + if unsafe { !Self::is_bit_valid(candidate) } { + return None; + } + + // SAFETY: `is_bit_valid` promises that it only returns true if its + // argument contains a valid `Self`. This is exactly the safety + // precondition of `assume_init`. + Some(unsafe { maybe_uninit.assume_init() }) + } +} + /// Types which are safe to treat as an immutable byte slice. /// /// WARNING: Do not implement this trait yourself! Instead, use @@ -1000,7 +1246,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`. @@ -1114,19 +1361,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: @@ -1138,30 +1386,31 @@ 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] - /// - `AsBytes`: the `{f32,f64}::to_bits` methods' documentation [4,5] + /// - `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] /// - /// TODO: Make these arguments more precisely in terms of the documentation. + /// TODO(#61): Make these arguments more precisely in terms of the + /// documentation. /// /// [1] https://doc.rust-lang.org/nightly/std/primitive.f32.html#method.from_bits /// [2] https://doc.rust-lang.org/nightly/std/primitive.f64.html#method.from_bits @@ -1169,8 +1418,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! { @@ -1186,6 +1435,18 @@ 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, the `is_bit_valid` + /// caller is required to ensure that the referent of the `NonNull` + /// argument is initialized. Since `u8` has no alignment requirement, this + /// means that, after converting from `NonNull` to `&u8`, the + /// resulting reference is properly aligned and points to a + /// properly-initialized `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: @@ -1197,6 +1458,33 @@ safety_comment! { /// /// [1] https://doc.rust-lang.org/reference/types/textual.html unsafe_impl!(char: FromZeroes, AsBytes); + /// SAFETY: + /// - `MaybeUninit` has no bit validity requirements, and has the same + /// alignment as `char`. Since the `is_bit_valid` caller must promise that + /// the provided `NonNull` is properly-aligned and points a region + /// which is valid for reads, that's all we need to ensure that converting + /// it to a `&MaybeUninit` is sound. + /// - Since we transmute `c` from the bytes passed to `is_bit_valid` without + /// modifying them, and since `char::from_u32` guarantees that it returns + /// `None` if its input is not a valid `char` [1], this function only + /// returns `true` if its argument is a valid `char`, and so this is a + /// sound implementation of `TryFromBytes::is_bit_valid`. + /// + /// Note that it might be slightly simpler to treat `candidate` as a `[u8; + /// 4]` - it would make the code and the safety argument a bit simpler. + /// However, using `&MaybeUninit` ensures that the compiler preserves + /// alignment information, which it may be able to use to produce smaller or + /// better-performing assembly. + /// + /// [1] https://doc.rust-lang.org/std/primitive.char.html#method.from_u32 + unsafe_impl!(char: TryFromBytes; |candidate: &MaybeUninit| { + // SAFETY: `MaybeUninit` has no bit validity constraints. + let c: MaybeUninit = unsafe { mem::transmute(*candidate) }; + // SAFETY: Since all bytes of a `char` must be initialized, the bytes + // passed to this function must all have been initialized. + let c = unsafe { c.assume_init() }; + char::from_u32(c).is_some() + }); } safety_comment! { /// SAFETY: @@ -1209,6 +1497,23 @@ 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, `is_bit_valid`'s + /// caller must ensure that the bytes they pass are all initialized. Since + /// `&[u8]` has no alignment requirement and no bit validity requirement + /// beyond that its bytes be initialized, it is sound to convert the + /// caller's `NonNull` (which they promise is valid for reads) to a + /// `&[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! { @@ -1246,12 +1551,40 @@ safety_comment! { unsafe_impl!(NonZeroI128: AsBytes); unsafe_impl!(NonZeroUsize: AsBytes); unsafe_impl!(NonZeroIsize: AsBytes); + + /// SAFETY: + /// - The caller is required to provide a `NonNull` which is + /// properly aligned and is valid for reads. Since very byte of a + /// `NonZeroXxx` must be initialized, the pointer's referent must have all + /// its bytes initialized. + /// + /// Since `Xxx` has the same size and alignment as `NonZeroXxx`, the + /// provided `NonNull` is also aligned to `Xxx` and is valid + /// for reads of size `Xxx`. Since `Xxx` has no bit validity requirements + /// other than that its bytes are initialized, this means that the + /// provided `NonNull` may soundly be converted 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 @@ -1264,32 +1597,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); @@ -1305,6 +1640,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); @@ -1316,12 +1652,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`. /// @@ -1332,8 +1669,11 @@ 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: FromZeroes => FromZeroes for MaybeUninit); - unsafe_impl!(T: FromBytes => FromBytes for MaybeUninit); + /// + /// TODO(#310): Do we really want `TryFromBytes` bounds for everything here? + unsafe_impl!(T: TryFromBytes => TryFromBytes for MaybeUninit); + unsafe_impl!(T: TryFromBytes => FromZeroes for MaybeUninit); + unsafe_impl!(T: TryFromBytes => FromBytes for MaybeUninit); unsafe_impl!(T: Unaligned => Unaligned for MaybeUninit); assert_unaligned!(MaybeUninit<()>, MaybeUninit); } @@ -1341,7 +1681,20 @@ 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 if the provided `NonNull>` satisfies the + /// preconditions for `TryFromBytes::>::is_bit_valid`, + /// then it is sound to convert it to a `NonNull`, and that pointer + /// satisfies the preconditions for `TryFromBytes::::is_bit_valid`. + /// For DSTs, `ManuallyDrop` and `T` have the same pointer metadata, + /// so pointer casting preserves length. + /// + /// Since their bit validity requirements are the same, + /// `TryFromBytes::::is_bit_valid` is a sound implementation of + /// `TryFromBytes::>::is_bit_valid`. /// - `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 @@ -1354,6 +1707,15 @@ 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(https://github.com/rust-lang/rust/pull/115522): Update these docs + /// once ManuallyDrop bit validity is guaranteed. + unsafe_impl!( + T: ?Sized TryFromBytes => TryFromBytes for ManuallyDrop; + |c: NonNull| unsafe { T::is_bit_valid(c) } + ); unsafe_impl!(T: ?Sized + FromZeroes => FromZeroes for ManuallyDrop); unsafe_impl!(T: ?Sized + FromBytes => FromBytes for ManuallyDrop); unsafe_impl!(T: ?Sized + AsBytes => AsBytes for ManuallyDrop); @@ -1379,15 +1741,67 @@ 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!(const N: usize, T: TryFromBytes => TryFromBytes for [T; N]; |c: NonNull<[T; N]>| { + #[allow(unstable_name_collisions)] + let slice = NonNull::slice_from_raw_parts(c.cast::(), N); + // SAFETY: The preconditions of `is_bit_valid` are identical for `[T; + // N]` and for `[T]` if the passed pointer encodes a slice of `N` + // elements. Thus, if the caller has upheld their preconditions, then we + // uphold the preconditions for this call. + unsafe { <[T] as TryFromBytes>::is_bit_valid(slice) } + }); unsafe_impl!(const N: usize, T: FromZeroes => FromZeroes for [T; N]); unsafe_impl!(const N: usize, T: FromBytes => FromBytes for [T; N]); 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: NonNull<[T]>| { + let base = c.cast::().as_ptr(); + // TODO(#67): Remove this allow. See NonNulSlicelExt for more details. + #[allow(unstable_name_collisions)] + (0..c.len()).all(|i| { + // TODO(https://github.com/rust-lang/rust/issues/74265): Use + // `NonNull::get_unchecked_mut`. + + // SAFETY: + // - `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`. + // - It is a precondition of `is_bit_valid` that the total length + // encoded by `c` doesn't overflow `isize`. + // - Since `c` must point to a valid allocation, and valid + // allocations cannot wrap around the address space, we know that + // this addition will not wrap around either. + let elem = unsafe { base.add(i) }; + // SAFETY: `base` is constructed from a `NonNull` pointer, and the + // addition that produces `elem` is guaranteed not to wrap + // around/overflow, so `elem >= base > 0`. + let elem = unsafe { NonNull::new_unchecked(elem) }; + + // 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. + 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]); @@ -1439,8 +1853,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`. // @@ -1451,21 +1865,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. @@ -1479,7 +1895,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); )* } } }; @@ -3006,7 +3422,7 @@ mod tests { test!(@generate_cast_type $cast_type) ).for_each(|(base_size, align, trailing_size, addr, bytes_len, cast_type)| { let actual = std::panic::catch_unwind(|| { - layout(base_size, align, trailing_size)._validate_cast(addr, bytes_len, cast_type) + layout(base_size, align, trailing_size).validate_cast(addr, bytes_len, cast_type) }).map_err(|d| { *d.downcast::<&'static str>().expect("expected string panic message").as_ref() }); @@ -3019,8 +3435,8 @@ mod tests { (@generate_usize _) => { 0..8 }; (@generate_align _) => { [1, 2, 4, 8, 16] }; (@generate_opt_usize _) => { [None].into_iter().chain((0..8).map(Some).into_iter()) }; - (@generate_cast_type _) => { [_CastType::_Prefix, _CastType::_Suffix] }; - (@generate_cast_type $variant:ident) => { [_CastType::$variant] }; + (@generate_cast_type _) => { [CastType::Prefix, CastType::_Suffix] }; + (@generate_cast_type $variant:ident) => { [CastType::$variant] }; // Some expressions need to be wrapped in parentheses in order to be // valid `tt`s (required by the top match pattern). See the comment // below for more details. This arm removes these parentheses to @@ -3038,8 +3454,8 @@ mod tests { test!(layout((2..8), _, ((1..8).map(Some))).validate_cast(_, [1], _), Ok(None)); // addr is unaligned for prefix cast - test!(layout(_, [2], [None]).validate_cast(ODDS, _, _Prefix), Ok(None)); - test!(layout(_, [2], (NZ_EVENS.map(Some))).validate_cast(ODDS, _, _Prefix), Ok(None)); + test!(layout(_, [2], [None]).validate_cast(ODDS, _, Prefix), Ok(None)); + test!(layout(_, [2], (NZ_EVENS.map(Some))).validate_cast(ODDS, _, Prefix), Ok(None)); // addr is aligned, but end of buffer is unaligned for suffix cast test!(layout(_, [2], [None]).validate_cast(EVENS, ODDS, _Suffix), Ok(None)); @@ -3886,9 +4302,180 @@ 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); + fn with_failing_test_cases(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(_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(_f: F) { + $($( + // `unused_qualifications` is spuriously triggered on + // `Option::::None`. + #[allow(unused_qualifications)] + let case = $failure_case.as_bytes(); + _f(case.as_bytes()); + )*)? + } + }; + } + + // 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<[u8]>` and + // `ManuallyDrop<[bool]>` once we have an easy way of converting + // `[ManuallyDrop] -> ManuallyDrop<[T]>`. Here are some suggested + // test cases: + // + // @success [ManuallyDrop::new(true), ManuallyDrop::new(false)][..], + // [ManuallyDrop::new(false), ManuallyDrop::new(true)][..], + ManuallyDrop<[u8]> => @success; + ManuallyDrop<[bool]> + => @success, + @failure [2u8], [3u8], [0xFFu8], [0u8, 1u8, 2u8]; + [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 c = NonNull::from(val); + // SAFETY: + // - Since `val` is a normal reference, `c` is guranteed to + // be aligned, to point to a single allocation, and to + // have a size which doesn't overflow `isize`. + // - Since `val` is a valid `$ty`, `c`'s referent satisfies + // the bit validity constraints of `is_bit_valid`, which + // are a superset of the bit validity constraints of + // `$ty`. + let res = unsafe { <$ty as TryFromBytes>::is_bit_valid(c) }; + assert!(res, "{}::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); + }); + #[allow(clippy::as_conversions)] + <$ty as TryFromBytesTestable>::with_failing_test_cases(|c| { + let res = <$ty as TryFromBytes>::try_from_ref(c); + assert!(res.is_none(), "{}::is_bit_valid({:?}): got true, expected false", stringify!($ty), c); + }); + + #[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); }; @@ -3908,77 +4495,86 @@ 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]>: TryFromBytes, FromZeroes, FromBytes, AsBytes, Unaligned); + assert_impls!(ManuallyDrop: TryFromBytes, FromZeroes, AsBytes, Unaligned, !FromBytes); + assert_impls!(ManuallyDrop<[bool]>: TryFromBytes, FromZeroes, AsBytes, Unaligned, !FromBytes); + assert_impls!(ManuallyDrop: !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); + assert_impls!(ManuallyDrop<[NotZerocopy]>: !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); - assert_impls!(MaybeUninit: FromZeroes, FromBytes, Unaligned, !AsBytes); - assert_impls!(MaybeUninit: !FromZeroes, !FromBytes, !AsBytes, !Unaligned); + assert_impls!(MaybeUninit: TryFromBytes, FromZeroes, FromBytes, Unaligned, !AsBytes); + assert_impls!(MaybeUninit: TryFromBytes, FromZeroes, FromBytes, Unaligned, !AsBytes); + assert_impls!(MaybeUninit>: TryFromBytes, FromZeroes, FromBytes, Unaligned, !AsBytes); + assert_impls!(MaybeUninit>: TryFromBytes, FromZeroes, FromBytes, Unaligned, !AsBytes); + assert_impls!(MaybeUninit: !TryFromBytes, !FromZeroes, !FromBytes, !AsBytes, !Unaligned); + assert_impls!(MaybeUninit>: !TryFromBytes, !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 aebc8d69cc..1c462bf689 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -29,13 +29,40 @@ macro_rules! safety_comment { } /// Unsafely implements trait(s) for a type. +/// +/// # Safety +/// +/// The trait impl must be sound. +/// +/// TODO: Update these for `as_bit_valid` taking raw pointer. +/// +/// When implementing `TryFromBytes`: +/// - If no `is_bit_valid` impl is provided, then it must be valid for +/// `is_bit_valid` to unconditionally return `true`. In other words, it must +/// be the case that any initialized sequence of bytes constitutes a valid +/// instance of `$ty`. +/// - If an `is_bit_valid` impl is provided, then: +/// - If the provided closure takes a `NonNull<$repr>` argument, then given a +/// `NonNull<$ty>` which satisfies the preconditions of +/// `TryFromBytes::<$ty>::is_bit_valid`, it must be guaranteed that a +/// `NonNull<$repr>` with the same address, provenance, and pointer metadata +/// satisfies the preconditions of `TryFromBytes::<$repr>::is_bit_valid`. +/// - If the provided closure takes a `&$repr` argument, then given a +/// `NonNull<$ty>` which satisfies the preconditions of +/// `TryFromBytes::<$ty>::is_bit_valid`, it must be sound to convert it to a +/// `$repr` pointer with the same address, provenance, and pointer metadata, +/// and to subsequently dereference that pointer as a `&$repr`. +/// - The impl of `is_bit_valid` must only return `true` for its argument +/// `NonNull<$repr>` if the original `NonNull<$ty>` refers to a valid `$ty`. macro_rules! unsafe_impl { // Implement `$trait` for `$ty` with no bounds. - ($ty:ty: $trait:ty) => { - unsafe impl $trait for $ty { #[allow(clippy::missing_inline_in_public_items)] 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); )* }; // This arm is identical to the following one, except it contains a @@ -66,36 +93,66 @@ macro_rules! unsafe_impl { ( const $constname:ident : $constty:ident $(,)? $($tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?),* - => $trait:ident for $ty:ty + => $trait:ident for $ty:ty $(; |$candidate:ident $(: &$ref_repr:ty)? $(: NonNull<$ptr_repr:ty>)?| $is_bit_valid:expr)? ) => { unsafe_impl!( @inner @const $constname: $constty, $($tyvar $(: $(? $optbound +)* + $($bound +)*)?,)* - => $trait for $ty + => $trait for $ty $(; |$candidate $(: &$ref_repr)? $(: NonNull<$ptr_repr>)?| $is_bit_valid)? ); }; ( $($tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?),* - => $trait:ident for $ty:ty + => $trait:ident for $ty:ty $(; |$candidate:ident $(: &$ref_repr:ty)? $(: NonNull<$ptr_repr:ty>)?| $is_bit_valid:expr)? ) => { unsafe_impl!( @inner $($tyvar $(: $(? $optbound +)* + $($bound +)*)?,)* - => $trait for $ty + => $trait for $ty $(; |$candidate $(: &$ref_repr)? $(: NonNull<$ptr_repr>)?| $is_bit_valid)? ); }; ( @inner $(@const $constname:ident : $constty:ident,)* $($tyvar:ident $(: $(? $optbound:ident +)* + $($bound:ident +)* )?,)* - => $trait:ident for $ty:ty + => $trait:ident for $ty:ty $(; |$candidate:ident $(: &$ref_repr:ty)? $(: NonNull<$ptr_repr:ty>)?| $is_bit_valid:expr)? ) => { unsafe impl<$(const $constname: $constty,)* $($tyvar $(: $(? $optbound +)* $($bound +)*)?),*> $trait for $ty { - #[allow(clippy::missing_inline_in_public_items)] - fn only_derive_is_allowed_to_implement_this_trait() {} + unsafe_impl!(@method $trait $(; |$candidate: $(&$ref_repr)? $(NonNull<$ptr_repr>)?| $is_bit_valid)?); + } + }; + + (@method TryFromBytes ; |$candidate:ident: &$repr:ty| $is_bit_valid:expr) => { + #[inline] + unsafe fn is_bit_valid(candidate: NonNull) -> bool { + // SAFETY: The caller has promised that it is sound to perform this + // pointer cast and dereference. + #[allow(clippy::as_conversions)] + let $candidate = unsafe { &*(candidate.as_ptr() as *const $repr) }; + $is_bit_valid } }; + (@method TryFromBytes ; |$candidate:ident: NonNull<$repr:ty>| $is_bit_valid:expr) => { + #[inline] + unsafe fn is_bit_valid(candidate: NonNull) -> bool { + // SAFETY: `candidate` is a non-null pointer. The caller has + // promised that it is sound to invoke their `$is_bit_valid` with + // this pointer. + #[allow(clippy::as_conversions)] + let $candidate = unsafe { NonNull::new_unchecked(candidate.as_ptr() as *mut $repr) }; + $is_bit_valid + } + }; + (@method TryFromBytes) => { #[inline(always)] unsafe fn is_bit_valid(_: NonNull) -> bool { true } }; + (@method $trait:ident) => { + #[allow(clippy::missing_inline_in_public_items)] + fn only_derive_is_allowed_to_implement_this_trait() {} + }; + // TODO: Test this with trybuild. + (@method $trait:ident; |$_candidate:ident $(: &$_ref_repr:ty)? $(: NonNull<$_ptr_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 @@ -204,11 +261,21 @@ macro_rules! impl_known_layout { }; ($($ty:ty),*) => { $(impl_known_layout!(@inner , => $ty);)* }; (@inner $(const $constvar:ident : $constty:ty)? , $($tyvar:ident $(: ?$optbound:ident)?)? => $ty:ty) => { - impl<$(const $constvar : $constty,)? $($tyvar $(: ?$optbound)?)?> sealed::KnownLayoutSealed for $ty {} - // SAFETY: Delegates safety to `DstLayout::for_type`. - unsafe impl<$(const $constvar : $constty,)? $($tyvar $(: ?$optbound)?)?> KnownLayout for $ty { - const LAYOUT: DstLayout = DstLayout::for_type::<$ty>(); - } + const _: () = { + use core::ptr::NonNull; + + impl<$(const $constvar : $constty,)? $($tyvar $(: ?$optbound)?)?> sealed::KnownLayoutSealed for $ty {} + // SAFETY: Delegates safety to `DstLayout::for_type`. + unsafe impl<$(const $constvar : $constty,)? $($tyvar $(: ?$optbound)?)?> KnownLayout for $ty { + const LAYOUT: DstLayout = DstLayout::for_type::<$ty>(); + + // SAFETY: `.cast` preserves address and provenance. + #[inline(always)] + fn raw_from_ptr_len(bytes: NonNull, _elems: usize) -> NonNull { + bytes.cast::() + } + } + }; }; } @@ -225,10 +292,25 @@ macro_rules! impl_known_layout { /// and this operation must preserve referent size (ie, `size_of_val_raw`). macro_rules! unsafe_impl_known_layout { ($($tyvar:ident: ?Sized + KnownLayout =>)? #[repr($repr:ty)] $ty:ty) => { - impl<$($tyvar: ?Sized + KnownLayout)?> sealed::KnownLayoutSealed for $ty {} - unsafe impl<$($tyvar: ?Sized + KnownLayout)?> KnownLayout for $ty { - const LAYOUT: DstLayout = <$repr as KnownLayout>::LAYOUT; - } + const _: () = { + use core::ptr::NonNull; + + impl<$($tyvar: ?Sized + KnownLayout)?> sealed::KnownLayoutSealed for $ty {} + unsafe impl<$($tyvar: ?Sized + KnownLayout)?> KnownLayout for $ty { + const LAYOUT: DstLayout = <$repr as KnownLayout>::LAYOUT; + + // SAFETY: All operations preserve address and provenance. Caller + // has promised that the `as` cast preserves size. + #[inline(always)] + #[allow(unused_qualifications)] // for `core::ptr::NonNull` + fn raw_from_ptr_len(bytes: NonNull, elems: usize) -> NonNull { + #[allow(clippy::as_conversions)] + let ptr = <$repr>::raw_from_ptr_len(bytes, elems).as_ptr() as *mut Self; + // SAFETY: `ptr` was converted from `bytes`, which is non-null. + unsafe { NonNull::new_unchecked(ptr) } + } + } + }; }; } diff --git a/src/util.rs b/src/util.rs index ed810dcb1c..ccc2e27655 100644 --- a/src/util.rs +++ b/src/util.rs @@ -53,6 +53,61 @@ pub(crate) fn aligned_to(t: T) -> bool { remainder == 0 } +/// Since we support multiple versions of Rust, there are often features which +/// have been stabilized in the most recent stable release which do not yet +/// exist (stably) on our MSRV. This module provides polyfills for those +/// features so that we can write more "modern" code, and just remove the +/// polyfill once our MSRV supports the corresponding feature. Without this, +/// we'd have to write worse/more verbose code and leave TODO comments sprinkled +/// throughout the codebase to update to the new pattern once it's stabilized. +/// +/// Each trait is imported as `_` at the crate root; each polyfill should "just +/// work" at usage sites. +pub(crate) mod polyfills { + use core::ptr::{self, NonNull}; + + // A polyfill for `NonNull::slice_from_raw_parts` that we can use before our + // MSRV is 1.70, when that function was stabilized. + // + // TODO(#67): Once our MSRV is 1.70, remove this. + pub(crate) trait NonNullExt { + fn slice_from_raw_parts(data: Self, len: usize) -> NonNull<[T]>; + } + + impl NonNullExt for NonNull { + #[inline(always)] + fn slice_from_raw_parts(data: Self, len: usize) -> NonNull<[T]> { + let ptr = ptr::slice_from_raw_parts_mut(data.as_ptr(), len); + // SAFETY: `ptr` is converted from `data`, which is non-null. + unsafe { NonNull::new_unchecked(ptr) } + } + } + + // A polyfill for `NonNull::len` that we can use before our MSRV is 1.63, + // when that function was stabilized. + // + // TODO(#67): Once our MSRV is 1.63, remove this. + pub(crate) trait NonNullSliceExt { + fn len(&self) -> usize; + } + + impl NonNullSliceExt for NonNull<[T]> { + #[inline(always)] + fn len(&self) -> usize { + #[allow(clippy::as_conversions)] + let slc = self.as_ptr() as *const [()]; + // SAFETY: + // - `()` has alignment 1, so `slc` is trivially aligned + // - `slc` was derived from a non-null pointer + // - the size is 0 regardless of the length, so it is sound to + // materialize a reference regardless of location + // - pointer provenance may be an issue, but we never dereference + let slc = unsafe { &*slc }; + slc.len() + } + } +} + #[cfg(test)] pub(crate) mod testutil { use core::fmt::{self, Display, Formatter};