Skip to content

Commit 2a4cf85

Browse files
committed
[WIP] TryFromBytes
Makes progress on #5
1 parent 514cc58 commit 2a4cf85

File tree

2 files changed

+325
-23
lines changed

2 files changed

+325
-23
lines changed

src/derive_util.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,34 @@ macro_rules! union_has_padding {
6262
false $(|| core::mem::size_of::<$t>() != core::mem::size_of::<$ts>())*
6363
};
6464
}
65+
66+
#[doc(hidden)]
67+
pub use project::project as __project;
68+
69+
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
70+
#[macro_export]
71+
macro_rules! impl_try_from_bytes {
72+
($ty:ty { $($f:tt: $f_ty:ty),* } $(=> $validation_method:ident)?) => {
73+
#[allow(unused_qualifications)]
74+
unsafe impl zerocopy::TryFromBytes for $ty {
75+
fn is_bit_valid(bytes: &zerocopy::MaybeValid<Self>) -> bool {
76+
true $(&& {
77+
let f: &zerocopy::MaybeValid<$f_ty>
78+
= zerocopy::derive_util::__project!(&bytes.$f);
79+
zerocopy::TryFromBytes::is_bit_valid(f)
80+
})*
81+
$(&& {
82+
let bytes_ptr: *const zerocopy::MaybeValid<Self> = bytes;
83+
let slf = unsafe { &*bytes_ptr.cast::<Self>() };
84+
// TODO: What about interior mutability? One approach would
85+
// be to have the validation method operate on a
86+
// `#[repr(transparent)]` `Freeze` container that implements
87+
// `Projectable`. If we eventually get a `Freeze` or
88+
// `NoCell` trait, that container could implement `Deref`
89+
// for types which don't contain any cells.
90+
slf.$validation_method()
91+
})?
92+
}
93+
}
94+
}
95+
}

src/lib.rs

Lines changed: 294 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ use core::{
157157
ptr, slice,
158158
};
159159

160+
use project::Projectable;
161+
160162
#[cfg(feature = "alloc")]
161163
extern crate alloc;
162164
#[cfg(feature = "alloc")]
@@ -174,6 +176,29 @@ mod zerocopy {
174176
pub(crate) use crate::*;
175177
}
176178

179+
/// Documents multiple unsafe blocks with a single safety comment.
180+
///
181+
/// Invoked as:
182+
///
183+
/// ```rust,ignore
184+
/// safety_comment! {
185+
/// // Non-doc comments come first.
186+
/// /// SAFETY:
187+
/// /// Safety comment starts on its own line.
188+
/// macro_1!(args);
189+
/// macro_2! { args };
190+
/// }
191+
/// ```
192+
///
193+
/// The macro invocations are emitted, each decorated with the following
194+
/// attribute: `#[allow(clippy::undocumented_unsafe_blocks)]`.
195+
macro_rules! safety_comment {
196+
(#[doc = r" SAFETY:"] $(#[doc = $_doc:literal])* $($macro:ident!$args:tt;)*) => {
197+
#[allow(clippy::undocumented_unsafe_blocks)]
198+
const _: () = { $($macro!$args;)* };
199+
}
200+
}
201+
177202
/// Types for which a sequence of bytes all set to zero represents a valid
178203
/// instance of the type.
179204
///
@@ -499,6 +524,275 @@ pub unsafe trait FromBytes: FromZeroes {
499524
}
500525
}
501526

527+
/// TODO
528+
///
529+
/// # Safety
530+
///
531+
/// `AsMaybeUninit` must only be implemented for types which are `Sized` or
532+
/// whose last field is a slice whose element type is `Sized` (this includes
533+
/// slice types themselves; in a slice type, the "last field" simply refers to
534+
/// the slice itself).
535+
pub unsafe trait AsMaybeUninit {
536+
/// TODO
537+
///
538+
/// # Safety
539+
///
540+
/// For `T: AsMaybeUninit`, the following must hold:
541+
/// - Given `m: T::MaybeUninit`, it is sound to write uninitialized bytes at
542+
/// every byte offset in `m` (this description avoids the "what lengths
543+
/// are valid" problem)
544+
/// - `T` and `T::MaybeUninit` have the same alignment requirement (can't
545+
/// use `align_of` to describe this because it requires that its argument
546+
/// is sized)
547+
/// - `T` and `T::MaybeUninit` are either both `Sized` or both `!Sized`
548+
/// - If they are `Sized`, `size_of::<T>() == size_of::<T::MaybeUninit>()`
549+
/// - If they are `!Sized`, then they are both DSTs with a trailing slice.
550+
/// Given `t: &T` and `m: &T::MaybeUninit` with the same number of
551+
/// elements in their trailing slices, `size_of_val(t) == size_of_val(m)`.
552+
type MaybeUninit: ?Sized + FromBytes;
553+
}
554+
555+
unsafe impl<T: Sized> AsMaybeUninit for T {
556+
type MaybeUninit = MaybeUninit<T>;
557+
}
558+
559+
unsafe impl<T: Sized> AsMaybeUninit for [T] {
560+
type MaybeUninit = [MaybeUninit<T>];
561+
}
562+
563+
unsafe impl AsMaybeUninit for str {
564+
type MaybeUninit = <[u8] as AsMaybeUninit>::MaybeUninit;
565+
}
566+
567+
/// A value which might or might not constitute a valid instance of `T`.
568+
///
569+
/// `MaybeValid<T>` has the same layout (size and alignment) and field offsets
570+
/// as `T`. However, it may contain any bit pattern with a few restrictions:
571+
/// Given `m: MaybeValid<T>` and a byte offset, `b` in the range `[0,
572+
/// size_of::<MaybeValid<T>>())`:
573+
/// - If, in all valid instances `t: T`, the byte at offset `b` in `t` is
574+
/// initialized, then the byte at offset `b` within `m` is guaranteed to be
575+
/// initialized.
576+
/// - Let `s` be the sequence of bytes of length `b` in the offset range `[0,
577+
/// b)` in `m`. Let `TT` be the subset of valid instances of `T` which contain
578+
/// this sequence in the offset range `[0, b)`. If, for all instances of `t:
579+
/// T` in `TT`, the byte at offset `b` in `t` is initialized, then the byte at
580+
/// offset `b` in `m` is guaranteed to be initialized.
581+
///
582+
/// Pragmatically, this means that if `m` is guaranteed to contain an enum
583+
/// type at a particular offset, and the enum discriminant stored in `m`
584+
/// corresponds to a valid variant of that enum type, then it is guaranteed
585+
/// that the appropriate bytes of `m` are initialized as defined by that
586+
/// variant's layout (although note that the variant's layout may contain
587+
/// another enum type, in which case the same rules apply depending on the
588+
/// state of its discriminant, and so on recursively).
589+
///
590+
/// # Safety
591+
///
592+
/// TODO (make sure to mention enum layout)
593+
#[derive(FromZeroes, FromBytes, AsBytes, Unaligned)]
594+
#[repr(transparent)]
595+
pub struct MaybeValid<T: AsMaybeUninit + ?Sized> {
596+
inner: T::MaybeUninit,
597+
}
598+
599+
impl<T> MaybeValid<T> {
600+
/// TODO
601+
pub const unsafe fn assume_valid(self) -> T {
602+
unsafe { self.inner.assume_init() }
603+
}
604+
}
605+
606+
impl<T> MaybeValid<[T]> {
607+
/// TODO
608+
pub const fn as_slice_of_maybe_valids(&self) -> &[MaybeValid<T>] {
609+
let inner: &[<T as AsMaybeUninit>::MaybeUninit] = &self.inner;
610+
// SAFETY: Since `inner` is a `&[T::MaybeUninit]`, and `MaybeValid<T>`
611+
// is a `repr(transparent)` struct around `T::MaybeUninit`, `inner` has
612+
// the same layout as `&[MaybeValid<T>]`.
613+
unsafe { mem::transmute(inner) }
614+
}
615+
}
616+
617+
impl<const N: usize, T> MaybeValid<[T; N]> {
618+
/// TODO
619+
pub const fn as_slice(&self) -> &MaybeValid<[T]> {
620+
todo!()
621+
}
622+
}
623+
624+
unsafe impl<T, F> Projectable<F, MaybeValid<F>> for MaybeValid<T> {
625+
type Inner = T;
626+
}
627+
628+
impl<T> Debug for MaybeValid<T> {
629+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
630+
f.pad(core::any::type_name::<Self>())
631+
}
632+
}
633+
634+
/// TODO
635+
pub unsafe trait TryFromBytes: AsMaybeUninit {
636+
/// TODO
637+
fn is_bit_valid(candidate: &MaybeValid<Self>) -> bool;
638+
639+
/// TODO
640+
// Note that, in a future in which we distinguish between `FromBytes` and `RefFromBytes`,
641+
// this requires `where Self: RefFromBytes` to disallow interior mutability.
642+
fn try_from_ref(bytes: &[u8]) -> Option<&Self>
643+
where
644+
// TODO: Remove this bound.
645+
Self: Sized,
646+
// TODO: Why can't Rust infer this based on the blanket impl for `T:
647+
// Sized`? It sets `MaybeUninit = MaybeUninit<T>`, which is `Sized`.
648+
<Self as AsMaybeUninit>::MaybeUninit: Sized,
649+
{
650+
// let byte_array: &ByteArray<Self> = TryFrom::try_from(bytes).ok()?;
651+
// let aligned = Align::try_from_ref(byte_array)?;
652+
let maybe_valid = Ref::<_, MaybeValid<Self>>::new(bytes)?.into_ref();
653+
654+
if Self::is_bit_valid(maybe_valid) {
655+
Some(unsafe { &*bytes.as_ptr().cast::<Self>() })
656+
} else {
657+
None
658+
}
659+
}
660+
661+
/// TODO
662+
fn try_from_mut(bytes: &mut [u8]) -> Option<&mut Self>
663+
where
664+
// TODO: Remove the `Sized` bound.
665+
Self: AsBytes + Sized,
666+
// TODO: Why can't Rust infer this based on the blanket impl for `T:
667+
// Sized`? It sets `MaybeUninit = MaybeUninit<T>`, which is `Sized`.
668+
<Self as AsMaybeUninit>::MaybeUninit: Sized,
669+
{
670+
// let byte_array: &ByteArray<Self> = TryFrom::try_from(&*bytes).ok()?;
671+
// let aligned = Align::try_from_ref(byte_array)?;
672+
let maybe_valid = Ref::<_, MaybeValid<Self>>::new(&*bytes)?.into_ref();
673+
674+
if Self::is_bit_valid(maybe_valid) {
675+
Some(unsafe { &mut *bytes.as_mut_ptr().cast::<Self>() })
676+
} else {
677+
None
678+
}
679+
}
680+
681+
/// TODO
682+
fn try_read_from(bytes: &[u8]) -> Option<Self>
683+
where
684+
Self: Sized,
685+
// TODO: Why can't Rust infer this based on the blanket impl for `T:
686+
// Sized`? It sets `MaybeUninit = MaybeUninit<T>`, which is `Sized`.
687+
<Self as AsMaybeUninit>::MaybeUninit: Sized,
688+
{
689+
// let byte_array: &ByteArray<Self> = TryFrom::try_from(bytes).ok()?;
690+
let maybe_valid = MaybeValid::<Self>::read_from(bytes)?;
691+
692+
if Self::is_bit_valid(&maybe_valid) {
693+
Some(unsafe { maybe_valid.assume_valid() })
694+
} else {
695+
None
696+
}
697+
}
698+
}
699+
700+
unsafe impl<T: FromBytes> TryFromBytes for T {
701+
fn is_bit_valid(_candidate: &MaybeValid<T>) -> bool {
702+
true
703+
}
704+
}
705+
706+
// TODO: This conflicts with the blanket impl for `T: TryFromBytes`.
707+
708+
// unsafe impl<const N: usize, T: TryFromBytes + Sized> TryFromBytes for [T; N]
709+
// where
710+
// // TODO: Why can't Rust infer this based on the blanket impl for `T: Sized`?
711+
// // It sets `MaybeUninit = MaybeUninit<T>`, which is `Sized`.
712+
// <T as AsMaybeUninit>::MaybeUninit: Sized,
713+
// {
714+
// fn is_bit_valid(candidate: &MaybeValid<[T; N]>) -> bool {
715+
// candidate.as_slice().as_slice_of_maybe_valids().iter().all(|c| T::is_bit_valid(c))
716+
// }
717+
// }
718+
719+
unsafe impl<T: TryFromBytes + Sized> TryFromBytes for [T]
720+
where
721+
// TODO: Why can't Rust infer this based on the blanket impl for `T: Sized`?
722+
// It sets `MaybeUninit = MaybeUninit<T>`, which is `Sized`.
723+
<T as AsMaybeUninit>::MaybeUninit: Sized,
724+
{
725+
fn is_bit_valid(candidate: &MaybeValid<[T]>) -> bool {
726+
candidate.as_slice_of_maybe_valids().iter().all(|c| T::is_bit_valid(c))
727+
}
728+
}
729+
730+
/// # Safety
731+
///
732+
/// It must be sound to transmute `&MaybeValid<$ty>` into `&$repr`.
733+
macro_rules! unsafe_impl_try_from_bytes {
734+
($ty:ty, $repr:ty, |$candidate:ident| $is_bit_valid:expr) => {
735+
unsafe impl TryFromBytes for $ty {
736+
fn is_bit_valid(candidate: &MaybeValid<$ty>) -> bool {
737+
let $candidate = unsafe { &*(candidate as *const MaybeValid<$ty> as *const $repr) };
738+
$is_bit_valid
739+
}
740+
}
741+
};
742+
}
743+
744+
safety_comment! {
745+
/// SAFETY:
746+
/// All of the `NonZeroXxx` types have the same layout as `Xxx`. Also, every
747+
/// byte of such a type is required to be initialized, so it is guaranteed
748+
/// that every byte of a `MaybeValid<NonZeroXxx>` must also be initialized.
749+
/// Thus, it is sound to transmute a `&MaybeValid<NonZeroXxx>` to a `&Xxx`.
750+
///
751+
/// TODO: Why are these impls correct (ie, ensure valid NonZeroXxx types)?
752+
unsafe_impl_try_from_bytes!(NonZeroU8, u8, |n| *n != 0);
753+
unsafe_impl_try_from_bytes!(NonZeroU16, u16, |n| *n != 0);
754+
unsafe_impl_try_from_bytes!(NonZeroU32, u32, |n| *n != 0);
755+
unsafe_impl_try_from_bytes!(NonZeroU64, u64, |n| *n != 0);
756+
unsafe_impl_try_from_bytes!(NonZeroU128, u128, |n| *n != 0);
757+
unsafe_impl_try_from_bytes!(NonZeroUsize, usize, |n| *n != 0);
758+
unsafe_impl_try_from_bytes!(NonZeroI8, i8, |n| *n != 0);
759+
unsafe_impl_try_from_bytes!(NonZeroI16, i16, |n| *n != 0);
760+
unsafe_impl_try_from_bytes!(NonZeroI32, i32, |n| *n != 0);
761+
unsafe_impl_try_from_bytes!(NonZeroI64, i64, |n| *n != 0);
762+
unsafe_impl_try_from_bytes!(NonZeroI128, i128, |n| *n != 0);
763+
unsafe_impl_try_from_bytes!(NonZeroIsize, isize, |n| *n != 0);
764+
}
765+
766+
unsafe_impl_try_from_bytes!(bool, u8, |byte| *byte < 2);
767+
768+
unsafe_impl_try_from_bytes!(char, [u8; 4], |bytes| {
769+
let c = u32::from_ne_bytes(*bytes);
770+
char::from_u32(c).is_some()
771+
});
772+
773+
unsafe_impl_try_from_bytes!(str, [u8], |bytes| core::str::from_utf8(bytes).is_ok());
774+
775+
mod try_from_bytes_derive_example {
776+
use super::*;
777+
778+
struct Foo {
779+
a: u8,
780+
b: u16,
781+
}
782+
783+
impl_try_from_bytes!(Foo { a: u8, b: u16 });
784+
785+
struct Bar(Foo);
786+
787+
impl Bar {
788+
fn is_valid(&self) -> bool {
789+
u16::from(self.0.a) < self.0.b
790+
}
791+
}
792+
793+
impl_try_from_bytes!(Bar { 0: Foo } => is_valid);
794+
}
795+
502796
/// Types which are safe to treat as an immutable byte slice.
503797
///
504798
/// WARNING: Do not implement this trait yourself! Instead, use
@@ -696,29 +990,6 @@ pub unsafe trait Unaligned {
696990
Self: Sized;
697991
}
698992

699-
/// Documents multiple unsafe blocks with a single safety comment.
700-
///
701-
/// Invoked as:
702-
///
703-
/// ```rust,ignore
704-
/// safety_comment! {
705-
/// // Non-doc comments come first.
706-
/// /// SAFETY:
707-
/// /// Safety comment starts on its own line.
708-
/// macro_1!(args);
709-
/// macro_2! { args };
710-
/// }
711-
/// ```
712-
///
713-
/// The macro invocations are emitted, each decorated with the following
714-
/// attribute: `#[allow(clippy::undocumented_unsafe_blocks)]`.
715-
macro_rules! safety_comment {
716-
(#[doc = r" SAFETY:"] $(#[doc = $_doc:literal])* $($macro:ident!$args:tt;)*) => {
717-
#[allow(clippy::undocumented_unsafe_blocks)]
718-
const _: () = { $($macro!$args;)* };
719-
}
720-
}
721-
722993
/// Unsafely implements trait(s) for a type.
723994
macro_rules! unsafe_impl {
724995
// Implement `$trait` for `$ty` with no bounds.

0 commit comments

Comments
 (0)