Skip to content

Commit 62a8c0f

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

File tree

2 files changed

+341
-23
lines changed

2 files changed

+341
-23
lines changed

src/derive_util.rs

+31
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

+310-23
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,291 @@ 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+
{
647+
// TODO(https://github.com/rust-lang/rust/issues/115080): Inline this
648+
// function once #115080 is resolved.
649+
#[inline(always)]
650+
fn try_read_from_inner<T: Sized, F: FnOnce(&MaybeValid<T>) -> bool>(
651+
bytes: &[u8],
652+
is_bit_valid: F,
653+
) -> Option<&T> {
654+
let maybe_valid = Ref::<_, MaybeValid<T>>::new(bytes)?.into_ref();
655+
656+
if is_bit_valid(maybe_valid) {
657+
Some(unsafe { &*bytes.as_ptr().cast::<T>() })
658+
} else {
659+
None
660+
}
661+
}
662+
663+
try_read_from_inner(bytes, Self::is_bit_valid)
664+
}
665+
666+
/// TODO
667+
fn try_from_mut(bytes: &mut [u8]) -> Option<&mut Self>
668+
where
669+
// TODO: Remove the `Sized` bound.
670+
Self: AsBytes + Sized,
671+
{
672+
// TODO(https://github.com/rust-lang/rust/issues/115080): Inline this
673+
// function once #115080 is resolved.
674+
#[inline(always)]
675+
fn try_read_from_mut_inner<T: Sized, F: FnOnce(&MaybeValid<T>) -> bool>(
676+
bytes: &mut [u8],
677+
is_bit_valid: F,
678+
) -> Option<&mut T> {
679+
let maybe_valid = Ref::<_, MaybeValid<T>>::new(&*bytes)?.into_ref();
680+
681+
if is_bit_valid(maybe_valid) {
682+
Some(unsafe { &mut *bytes.as_mut_ptr().cast::<T>() })
683+
} else {
684+
None
685+
}
686+
}
687+
688+
try_read_from_mut_inner(bytes, Self::is_bit_valid)
689+
}
690+
691+
/// TODO
692+
fn try_read_from(bytes: &[u8]) -> Option<Self>
693+
where
694+
Self: Sized,
695+
{
696+
// TODO(https://github.com/rust-lang/rust/issues/115080): Inline this
697+
// function once #115080 is resolved.
698+
#[inline(always)]
699+
fn try_read_from_inner<T: Sized, F: FnOnce(&MaybeValid<T>) -> bool>(
700+
bytes: &[u8],
701+
is_bit_valid: F,
702+
) -> Option<T> {
703+
let maybe_valid = MaybeValid::<T>::read_from(bytes)?;
704+
705+
if is_bit_valid(&maybe_valid) {
706+
Some(unsafe { maybe_valid.assume_valid() })
707+
} else {
708+
None
709+
}
710+
}
711+
712+
try_read_from_inner(bytes, Self::is_bit_valid)
713+
}
714+
}
715+
716+
unsafe impl<T: FromBytes> TryFromBytes for T {
717+
fn is_bit_valid(_candidate: &MaybeValid<T>) -> bool {
718+
true
719+
}
720+
}
721+
722+
// TODO: This conflicts with the blanket impl for `T: TryFromBytes`.
723+
724+
// unsafe impl<const N: usize, T: TryFromBytes + Sized> TryFromBytes for [T; N]
725+
// where
726+
// // TODO: Why can't Rust infer this based on the blanket impl for `T: Sized`?
727+
// // It sets `MaybeUninit = MaybeUninit<T>`, which is `Sized`.
728+
// <T as AsMaybeUninit>::MaybeUninit: Sized,
729+
// {
730+
// fn is_bit_valid(candidate: &MaybeValid<[T; N]>) -> bool {
731+
// candidate.as_slice().as_slice_of_maybe_valids().iter().all(|c| T::is_bit_valid(c))
732+
// }
733+
// }
734+
735+
unsafe impl<T: TryFromBytes + Sized> TryFromBytes for [T]
736+
where
737+
// TODO: Why can't Rust infer this based on the blanket impl for `T: Sized`?
738+
// It sets `MaybeUninit = MaybeUninit<T>`, which is `Sized`.
739+
<T as AsMaybeUninit>::MaybeUninit: Sized,
740+
{
741+
fn is_bit_valid(candidate: &MaybeValid<[T]>) -> bool {
742+
candidate.as_slice_of_maybe_valids().iter().all(|c| T::is_bit_valid(c))
743+
}
744+
}
745+
746+
/// # Safety
747+
///
748+
/// It must be sound to transmute `&MaybeValid<$ty>` into `&$repr`.
749+
macro_rules! unsafe_impl_try_from_bytes {
750+
($ty:ty, $repr:ty, |$candidate:ident| $is_bit_valid:expr) => {
751+
unsafe impl TryFromBytes for $ty {
752+
fn is_bit_valid(candidate: &MaybeValid<$ty>) -> bool {
753+
let $candidate = unsafe { &*(candidate as *const MaybeValid<$ty> as *const $repr) };
754+
$is_bit_valid
755+
}
756+
}
757+
};
758+
}
759+
760+
safety_comment! {
761+
/// SAFETY:
762+
/// All of the `NonZeroXxx` types have the same layout as `Xxx`. Also, every
763+
/// byte of such a type is required to be initialized, so it is guaranteed
764+
/// that every byte of a `MaybeValid<NonZeroXxx>` must also be initialized.
765+
/// Thus, it is sound to transmute a `&MaybeValid<NonZeroXxx>` to a `&Xxx`.
766+
///
767+
/// TODO: Why are these impls correct (ie, ensure valid NonZeroXxx types)?
768+
unsafe_impl_try_from_bytes!(NonZeroU8, u8, |n| *n != 0);
769+
unsafe_impl_try_from_bytes!(NonZeroU16, u16, |n| *n != 0);
770+
unsafe_impl_try_from_bytes!(NonZeroU32, u32, |n| *n != 0);
771+
unsafe_impl_try_from_bytes!(NonZeroU64, u64, |n| *n != 0);
772+
unsafe_impl_try_from_bytes!(NonZeroU128, u128, |n| *n != 0);
773+
unsafe_impl_try_from_bytes!(NonZeroUsize, usize, |n| *n != 0);
774+
unsafe_impl_try_from_bytes!(NonZeroI8, i8, |n| *n != 0);
775+
unsafe_impl_try_from_bytes!(NonZeroI16, i16, |n| *n != 0);
776+
unsafe_impl_try_from_bytes!(NonZeroI32, i32, |n| *n != 0);
777+
unsafe_impl_try_from_bytes!(NonZeroI64, i64, |n| *n != 0);
778+
unsafe_impl_try_from_bytes!(NonZeroI128, i128, |n| *n != 0);
779+
unsafe_impl_try_from_bytes!(NonZeroIsize, isize, |n| *n != 0);
780+
}
781+
782+
unsafe_impl_try_from_bytes!(bool, u8, |byte| *byte < 2);
783+
784+
unsafe_impl_try_from_bytes!(char, [u8; 4], |bytes| {
785+
let c = u32::from_ne_bytes(*bytes);
786+
char::from_u32(c).is_some()
787+
});
788+
789+
unsafe_impl_try_from_bytes!(str, [u8], |bytes| core::str::from_utf8(bytes).is_ok());
790+
791+
mod try_from_bytes_derive_example {
792+
use super::*;
793+
794+
struct Foo {
795+
a: u8,
796+
b: u16,
797+
}
798+
799+
impl_try_from_bytes!(Foo { a: u8, b: u16 });
800+
801+
struct Bar(Foo);
802+
803+
impl Bar {
804+
fn is_valid(&self) -> bool {
805+
u16::from(self.0.a) < self.0.b
806+
}
807+
}
808+
809+
impl_try_from_bytes!(Bar { 0: Foo } => is_valid);
810+
}
811+
502812
/// Types which are safe to treat as an immutable byte slice.
503813
///
504814
/// WARNING: Do not implement this trait yourself! Instead, use
@@ -696,29 +1006,6 @@ pub unsafe trait Unaligned {
6961006
Self: Sized;
6971007
}
6981008

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-
7221009
/// Unsafely implements trait(s) for a type.
7231010
macro_rules! unsafe_impl {
7241011
// Implement `$trait` for `$ty` with no bounds.

0 commit comments

Comments
 (0)