Skip to content

Commit 484d420

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

File tree

2 files changed

+343
-23
lines changed

2 files changed

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

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

0 commit comments

Comments
 (0)