Skip to content

Commit

Permalink
[WIP] TryFromBytes
Browse files Browse the repository at this point in the history
TODO: How to support `project!` when `Projectable` is implemented
multiple times for `ByteArray`, `Align`, and `AlignedByteArray`?
If we only emit an impl for `AlignedByteArray`, everything is fine,
but once we emit the other two, type inference fails.

Makes progress on #5
  • Loading branch information
joshlf committed Aug 17, 2023
1 parent 865a9b5 commit f373439
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 27 deletions.
26 changes: 26 additions & 0 deletions src/derive_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,29 @@ 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<Self>) -> 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<Self> = bytes;
let slf = unsafe { &*bytes_ptr.cast::<Self>() };
// TODO: What about interior mutability?
slf.$validation_method()
})?
}
}
}
}
202 changes: 175 additions & 27 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,160 @@ pub unsafe trait FromBytes: FromZeroes {
}
}

/// TODO
pub type AlignedByteArray<T> = Align<ByteArray<T>, T>;

/// A value which might or might not constitute a valid instance of `T`.
///
/// `MaybeValid<T>` 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<T>` and a byte offset, `b` in the range `[0,
/// size_of::<MaybeValid<T>>())`:
/// - 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<T> {
inner: MaybeUninit<T>,
}

impl<T> MaybeValid<T> {
/// TODO
pub const unsafe fn assume_valid(self) -> T {
unsafe { self.inner.assume_init() }
}
}

unsafe impl<T, F> Projectable<F, MaybeValid<F>> for MaybeValid<T> {
type Inner = T;
}

impl<T> From<ByteArray<T>> for MaybeValid<T> {
fn from(bytes: ByteArray<T>) -> MaybeValid<T> {
todo!()
}
}

impl<'a, T> From<&'a Align<ByteArray<T>, T>> for &'a MaybeValid<T> {
fn from(bytes: &'a Align<ByteArray<T>, T>) -> &'a MaybeValid<T> {
todo!()
}
}

impl<'a, T> From<&'a mut Align<ByteArray<T>, T>> for &'a mut MaybeValid<T> {
fn from(bytes: &'a mut Align<ByteArray<T>, T>) -> &'a mut MaybeValid<T> {
todo!()
}
}

impl<T> Debug for MaybeValid<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad(core::any::type_name::<Self>())
}
}

/// TODO
pub unsafe trait TryFromBytes {
/// TODO
fn is_bit_valid(candidate: &MaybeValid<Self>) -> bool
where
Self: Sized;

/// 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
Self: Sized,
{
let byte_array: &ByteArray<Self> = 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::<Self>() })
} else {
None
}
}

/// TODO
fn try_from_mut(bytes: &mut [u8]) -> Option<&mut Self>
where
Self: AsBytes + Sized,
{
let byte_array: &ByteArray<Self> = 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::<Self>() })
} else {
None
}
}

/// TODO
fn try_read_from(bytes: &[u8]) -> Option<Self>
where
Self: Sized,
{
let byte_array: &ByteArray<Self> = 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<T: FromBytes> TryFromBytes for T {
fn is_bit_valid(_candidate: &MaybeValid<Self>) -> bool
where
Self: Sized,
{
true
}
}

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
Expand Down Expand Up @@ -1543,21 +1697,9 @@ impl<T: Display + ?Sized, A> Display for Align<T, A> {
}
}

unsafe impl<T: ?Sized, A> Projectable for Align<T, A> {
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<T, A>` is already
// aligned to `max(align_of::<U>(), align_of::<A>())`.
type Wrapped<U> = Align<U, A>;

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<T: ?Sized, F: ?Sized, A> Projectable<F, Align<F, A>> for Align<T, A> {
// type Inner = T;
// }

/// A type with no alignment requirement.
///
Expand Down Expand Up @@ -2171,18 +2313,9 @@ impl<T> Ord for ByteArray<T> {
}
}

unsafe impl<T> Projectable for ByteArray<T> {
type Inner = T;
type Wrapped<U> = ByteArray<U>;

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<T, F> Projectable<F, ByteArray<F>> for ByteArray<T> {
// type Inner = T;
// }

// Used in `transmute!` below.
#[doc(hidden)]
Expand Down Expand Up @@ -3668,6 +3801,21 @@ mod alloc_support {
#[doc(inline)]
pub use alloc_support::*;

/// Transmutes a `T` into a `U`.
///
/// # Safety
///
/// Safety requirements are the same as for `core::mem::transmute`, except that
/// the caller must also ensure that `size_of::<T>() == size_of::<U>()`.
unsafe fn transmute_size_unchecked<T, U>(t: T) -> U {
union Transmute<T, U> {
t: ManuallyDrop<T>,
u: ManuallyDrop<U>,
}

unsafe { ManuallyDrop::into_inner(Transmute { t: ManuallyDrop::new(t) }.u) }
}

#[cfg(test)]
mod tests {
#![allow(clippy::unreadable_literal)]
Expand Down

0 comments on commit f373439

Please sign in to comment.