diff --git a/src/derive_util.rs b/src/derive_util.rs index 8f72ef09c6..b32964466d 100644 --- a/src/derive_util.rs +++ b/src/derive_util.rs @@ -62,3 +62,34 @@ macro_rules! union_has_padding { false $(|| core::mem::size_of::<$t>() != core::mem::size_of::<$ts>())* }; } + +#[doc(hidden)] +pub use project::project as __project; + +#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`. +#[macro_export] +macro_rules! impl_try_from_bytes { + ($ty:ty { $($f:tt: $f_ty:ty),* } $(=> $validation_method:ident)?) => { + #[allow(unused_qualifications)] + unsafe impl zerocopy::TryFromBytes for $ty { + fn is_bit_valid(bytes: &zerocopy::MaybeValid) -> bool { + true $(&& { + let f: &zerocopy::MaybeValid<$f_ty> + = zerocopy::derive_util::__project!(&bytes.$f); + zerocopy::TryFromBytes::is_bit_valid(f) + })* + $(&& { + let bytes_ptr: *const zerocopy::MaybeValid = bytes; + let slf = unsafe { &*bytes_ptr.cast::() }; + // TODO: What about interior mutability? One approach would + // be to have the validation method operate on a + // `#[repr(transparent)]` `Freeze` container that implements + // `Projectable`. If we eventually get a `Freeze` or + // `NoCell` trait, that container could implement `Deref` + // for types which don't contain any cells. + slf.$validation_method() + })? + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 904e313d41..03ea663c64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -177,6 +177,29 @@ mod zerocopy { pub(crate) use crate::*; } +/// Documents multiple unsafe blocks with a single safety comment. +/// +/// Invoked as: +/// +/// ```rust,ignore +/// safety_comment! { +/// // Non-doc comments come first. +/// /// SAFETY: +/// /// Safety comment starts on its own line. +/// macro_1!(args); +/// macro_2! { 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;)*) => { + #[allow(clippy::undocumented_unsafe_blocks)] + const _: () = { $($macro!$args;)* }; + } +} + /// Types for which a sequence of bytes all set to zero represents a valid /// instance of the type. /// @@ -502,6 +525,278 @@ pub unsafe trait FromBytes: FromZeroes { } } +/// TODO +/// +/// # Safety +/// +/// `AsMaybeUninit` must only be implemented for types which are `Sized` or +/// whose last field is a slice whose element type is `Sized` (this includes +/// slice types themselves; in a slice type, the "last field" simply refers to +/// the slice itself). +pub unsafe trait AsMaybeUninit { + /// TODO + /// + /// # Safety + /// + /// For `T: AsMaybeUninit`, the following must hold: + /// - Given `m: T::MaybeUninit`, it is sound to write uninitialized bytes at + /// every byte offset in `m` (this description avoids the "what lengths + /// are valid" problem) + /// - `T` and `T::MaybeUninit` have the same alignment requirement (can't + /// use `align_of` to describe this because it requires that its argument + /// is sized) + /// - `T` and `T::MaybeUninit` are either both `Sized` or both `!Sized` + /// - If they are `Sized`, `size_of::() == size_of::()` + /// - If they are `!Sized`, then they are both DSTs with a trailing slice. + /// Given `t: &T` and `m: &T::MaybeUninit` with the same number of + /// elements in their trailing slices, `size_of_val(t) == size_of_val(m)`. + type MaybeUninit: ?Sized; +} + +unsafe impl AsMaybeUninit for T { + type MaybeUninit = MaybeUninit; +} + +unsafe impl AsMaybeUninit for [T] { + type MaybeUninit = [MaybeUninit]; +} + +/// A value which might or might not constitute a valid instance of `T`. +/// +/// `MaybeValid` has the same layout (size and alignment) and field offsets +/// as `T`. However, it may contain any bit pattern with a few restrictions: +/// Given `m: MaybeValid` and a byte offset, `b` in the range `[0, +/// size_of::>())`: +/// - If, in all valid instances `t: T`, the byte at offset `b` in `t` is +/// initialized, then the byte at offset `b` within `m` is guaranteed to be +/// initialized. +/// - Let `s` be the sequence of bytes of length `b` in the offset range `[0, +/// b)` in `m`. Let `TT` be the subset of valid instances of `T` which contain +/// this sequence in the offset range `[0, b)`. If, for all instances of `t: +/// T` in `TT`, the byte at offset `b` in `t` is initialized, then the byte at +/// offset `b` in `m` is guaranteed to be initialized. +/// +/// Pragmatically, this means that if `m` is guaranteed to contain an enum +/// type at a particular offset, and the enum discriminant stored in `m` +/// corresponds to a valid variant of that enum type, then it is guaranteed +/// that the appropriate bytes of `m` are initialized as defined by that +/// variant's layout (although note that the variant's layout may contain +/// another enum type, in which case the same rules apply depending on the +/// state of its discriminant, and so on recursively). +/// +/// # Safety +/// +/// TODO (make sure to mention enum layout) +#[repr(transparent)] +pub struct MaybeValid { + inner: T::MaybeUninit, +} + +impl MaybeValid { + /// TODO + pub const unsafe fn assume_valid(self) -> T { + unsafe { self.inner.assume_init() } + } +} + +impl MaybeValid<[T]> { + /// TODO + pub const fn as_slice_of_maybe_valids(&self) -> &[MaybeValid] { + let inner: &[::MaybeUninit] = &self.inner; + // SAFETY: Since `inner` is a `&[T::MaybeUninit]`, and `MaybeValid` + // is a `repr(transparent)` struct around `T::MaybeUninit`, `inner` has + // the same layout as `&[MaybeValid]`. + unsafe { mem::transmute(inner) } + } +} + +impl MaybeValid<[T; N]> { + /// TODO + pub const fn as_slice(&self) -> &MaybeValid<[T]> { + todo!() + } +} + +unsafe impl Projectable> for MaybeValid { + type Inner = T; +} + +impl From> for MaybeValid { + fn from(bytes: ByteArray) -> MaybeValid { + todo!() + } +} + +impl<'a, T> From<&'a Align, T>> for &'a MaybeValid { + fn from(bytes: &'a Align, T>) -> &'a MaybeValid { + todo!() + } +} + +impl<'a, T> From<&'a mut Align, T>> for &'a mut MaybeValid { + fn from(bytes: &'a mut Align, T>) -> &'a mut MaybeValid { + todo!() + } +} + +impl Debug for MaybeValid { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.pad(core::any::type_name::()) + } +} + +/// TODO +pub unsafe trait TryFromBytes: AsMaybeUninit { + /// TODO + fn is_bit_valid(candidate: &MaybeValid) -> bool; + + /// TODO + // Note that, in a future in which we distinguish between `FromBytes` and `RefFromBytes`, + // this requires `where Self: RefFromBytes` to disallow interior mutability. + fn try_from_ref(bytes: &[u8]) -> Option<&Self> + where + // TODO: Remove this bound. + Self: Sized, + { + let byte_array: &ByteArray = TryFrom::try_from(bytes).ok()?; + let aligned = Align::try_from_ref(byte_array)?; + + if Self::is_bit_valid(aligned.into()) { + Some(unsafe { &*bytes.as_ptr().cast::() }) + } else { + None + } + } + + /// TODO + fn try_from_mut(bytes: &mut [u8]) -> Option<&mut Self> + where + // TODO: Remove the `Sized` bound. + Self: AsBytes + Sized, + { + let byte_array: &ByteArray = TryFrom::try_from(&*bytes).ok()?; + let aligned = Align::try_from_ref(byte_array)?; + + if Self::is_bit_valid(aligned.into()) { + Some(unsafe { &mut *bytes.as_mut_ptr().cast::() }) + } else { + None + } + } + + /// TODO + fn try_read_from(bytes: &[u8]) -> Option + where + Self: Sized, + // TODO: Why can't Rust infer this based on the blanket impl for `T: + // Sized`? It sets `MaybeUninit = MaybeUninit`, which is `Sized`. + ::MaybeUninit: Sized, + { + let byte_array: &ByteArray = TryFrom::try_from(bytes).ok()?; + let maybe_valid = MaybeValid::from(byte_array.clone()); + + if Self::is_bit_valid(&maybe_valid) { + Some(unsafe { maybe_valid.assume_valid() }) + } else { + None + } + } +} + +unsafe impl TryFromBytes for T { + fn is_bit_valid(_candidate: &MaybeValid) -> bool { + true + } +} + +// TODO: This conflicts with the blanket impl for `T: TryFromBytes`. + +// unsafe impl TryFromBytes for [T; N] +// where +// // TODO: Why can't Rust infer this based on the blanket impl for `T: Sized`? +// // It sets `MaybeUninit = MaybeUninit`, which is `Sized`. +// ::MaybeUninit: Sized, +// { +// fn is_bit_valid(candidate: &MaybeValid<[T; N]>) -> bool { +// candidate.as_slice().as_slice_of_maybe_valids().iter().all(|c| T::is_bit_valid(c)) +// } +// } + +unsafe impl TryFromBytes for [T] +where + // TODO: Why can't Rust infer this based on the blanket impl for `T: Sized`? + // It sets `MaybeUninit = MaybeUninit`, which is `Sized`. + ::MaybeUninit: Sized, +{ + fn is_bit_valid(candidate: &MaybeValid<[T]>) -> bool { + candidate.as_slice_of_maybe_valids().iter().all(|c| T::is_bit_valid(c)) + } +} + +/// # Safety +/// +/// It must be sound to transmute `&MaybeValid<$ty>` into `&$repr`. +macro_rules! unsafe_impl_try_from_bytes { + ($ty:ty, $repr:ty, |$candidate:ident| $is_bit_valid:expr) => { + unsafe impl TryFromBytes for $ty { + fn is_bit_valid(candidate: &MaybeValid<$ty>) -> bool { + let $candidate = unsafe { &*(candidate as *const MaybeValid<$ty> as *const $repr) }; + $is_bit_valid + } + } + }; +} + +safety_comment! { + /// SAFETY: + /// All of the `NonZeroXxx` types have the same layout as `Xxx`. Also, every + /// byte of such a type is required to be initialized, so it is guaranteed + /// that every byte of a `MaybeValid` must also be initialized. + /// Thus, it is sound to transmute a `&MaybeValid` to a `&Xxx`. + /// + /// TODO: Why are these impls correct (ie, ensure valid NonZeroXxx types)? + unsafe_impl_try_from_bytes!(NonZeroU8, u8, |n| *n != 0); + unsafe_impl_try_from_bytes!(NonZeroU16, u16, |n| *n != 0); + unsafe_impl_try_from_bytes!(NonZeroU32, u32, |n| *n != 0); + unsafe_impl_try_from_bytes!(NonZeroU64, u64, |n| *n != 0); + unsafe_impl_try_from_bytes!(NonZeroU128, u128, |n| *n != 0); + unsafe_impl_try_from_bytes!(NonZeroUsize, usize, |n| *n != 0); + unsafe_impl_try_from_bytes!(NonZeroI8, i8, |n| *n != 0); + unsafe_impl_try_from_bytes!(NonZeroI16, i16, |n| *n != 0); + unsafe_impl_try_from_bytes!(NonZeroI32, i32, |n| *n != 0); + unsafe_impl_try_from_bytes!(NonZeroI64, i64, |n| *n != 0); + unsafe_impl_try_from_bytes!(NonZeroI128, i128, |n| *n != 0); + unsafe_impl_try_from_bytes!(NonZeroIsize, isize, |n| *n != 0); +} + +unsafe_impl_try_from_bytes!(bool, u8, |byte| *byte < 2); + +unsafe_impl_try_from_bytes!(char, [u8; 4], |bytes| { + let c = u32::from_ne_bytes(*bytes); + char::from_u32(c).is_some() +}); + +mod try_from_bytes_derive_example { + use super::*; + + struct Foo { + a: u8, + b: u16, + } + + impl_try_from_bytes!(Foo { a: u8, b: u16 }); + + struct Bar(Foo); + + impl Bar { + fn is_valid(&self) -> bool { + u16::from(self.0.a) < self.0.b + } + } + + impl_try_from_bytes!(Bar { 0: Foo } => is_valid); +} + /// Types which are safe to treat as an immutable byte slice. /// /// WARNING: Do not implement this trait yourself! Instead, use @@ -699,29 +994,6 @@ pub unsafe trait Unaligned { Self: Sized; } -/// Documents multiple unsafe blocks with a single safety comment. -/// -/// Invoked as: -/// -/// ```rust,ignore -/// safety_comment! { -/// // Non-doc comments come first. -/// /// SAFETY: -/// /// Safety comment starts on its own line. -/// macro_1!(args); -/// macro_2! { 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;)*) => { - #[allow(clippy::undocumented_unsafe_blocks)] - const _: () = { $($macro!$args;)* }; - } -} - /// Unsafely implements trait(s) for a type. macro_rules! unsafe_impl { // Implement `$trait` for `$ty` with no bounds. @@ -1543,21 +1815,9 @@ impl Display for Align { } } -unsafe impl Projectable for Align { - type Inner = T; - // SAFETY: We know that `U` can't be more aligned than `T` or else it - // couldn't be a field in `T`. Thus, any `U` within `Align` is already - // aligned to `max(align_of::(), align_of::())`. - type Wrapped = Align; - - fn foo(&self) -> *const T { - self as *const Self as *const T - } - - fn foo_mut(&mut self) -> *mut T { - self as *mut Self as *mut T - } -} +// unsafe impl Projectable> for Align { +// type Inner = T; +// } /// A type with no alignment requirement. /// @@ -2171,18 +2431,9 @@ impl Ord for ByteArray { } } -unsafe impl Projectable for ByteArray { - type Inner = T; - type Wrapped = ByteArray; - - fn foo(&self) -> *const T { - self as *const Self as *const T - } - - fn foo_mut(&mut self) -> *mut T { - self as *mut Self as *mut T - } -} +// unsafe impl Projectable> for ByteArray { +// type Inner = T; +// } // Used in `transmute!` below. #[doc(hidden)]