Skip to content

Commit

Permalink
Implement try_transmute!
Browse files Browse the repository at this point in the history
Closes #1013.

Makes progress towards #5.
  • Loading branch information
jswrenn committed Mar 5, 2024
1 parent 5c9afdf commit e6dc8ee
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 0 deletions.
102 changes: 102 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,47 @@ pub unsafe trait TryFromBytes {
#[doc(hidden)]
fn is_bit_valid<A: invariant::at_least::Shared>(candidate: Maybe<'_, Self, A>) -> bool;

/// Is a given source value a valid instance of `Self`?
///
/// # Safety
///
/// Unsafe code may assume that, if `is_src_valid(src)` returns true, `*src`
/// is a bit-valid instance of `Self`.
///
/// # Panics
///
/// `is_src_valid` panics under exactly the same circumstances as
/// [`is_bit_valid`].
///
/// [`is_bit_valid`]: TryFromBytes::is_bit_valid
#[doc(hidden)]
#[inline]
fn is_src_valid<Src>(src: &mut Src) -> bool
where
Src: IntoBytes,
Self: Sized,
{
let c_ptr = crate::Ptr::from_mut(src);

if mem::size_of::<Src>() > mem::size_of::<Self>() {
return false;
}

// SAFETY:
// - We just validated that `size_of::<Src>()` <= `size_of::<Self>()`
// - `c_ptr` is exclusively aliased, so we do not need to reason about
// `UnsafeCell`
#[allow(clippy::as_conversions)]
let c_ptr = unsafe { c_ptr.cast_unsized(|p| p as *mut Self) };

// SAFETY: `c_ptr` is derived from `src` which is `IntoBytes`. By
// invariant on `IntoByte`s, `c_ptr`'s referent consists entirely of
// initialized bytes.
let c_ptr = unsafe { c_ptr.assume_validity::<crate::invariant::Initialized>() };

Self::is_bit_valid(c_ptr)
}

/// Attempts to interpret a byte slice as a `Self`.
///
/// `try_from_ref` validates that `bytes` contains a valid `Self`, and that
Expand Down Expand Up @@ -4732,6 +4773,67 @@ macro_rules! transmute_mut {
}}
}

/// Conditionally transmutes a value of one type to a value of another type of
/// the same size.
///
/// This macro consumes an expression of type `Src` and produces a `Result<Dst,
/// Src>`. It produces `Ok(dst)` if `src` is a bit-valid instance of `Dst`;
/// otherwise it produces `Err(src)`.
///
/// The expression `$e` must have a concrete type, `T`, which implements
/// [`IntoBytes`]. The `try_transmute!` expression must also have a concrete
/// type, `U` (`U` is inferred from the calling context), and `U` must implement
/// [`TryFromBytes`].
///
/// Note that the `T` produced by the expression `$e` will *not* be dropped.
/// Semantically, its bits will be copied into a new value of type `U`, the
/// original `T` will be forgotten, and the value of type `U` will be returned.
///
/// # Examples
///
/// ```
/// # use zerocopy::try_transmute;
/// assert_eq!(try_transmute!(0u8), Ok(false));
/// assert_eq!(try_transmute!(1u8), Ok(true));
/// assert_eq!(try_transmute!(255u8), Result::<bool, _>::Err(255));
/// ```
#[macro_export]
macro_rules! try_transmute {
($e:expr) => {{
// NOTE: This must be a macro (rather than a function with trait bounds)
// because there's no way, in a generic context, to enforce that two
// types have the same size. `core::mem::transmute` uses compiler magic
// to enforce this so long as the types are concrete.

let e = $e;
if false {
// This branch, though never taken, ensures that the type of `e` is
// `IntoBytes` and that the type of this macro invocation expression
// is `TryFromBytes`.

struct AssertIsIntoBytes<T: $crate::IntoBytes>(T);
let _ = AssertIsIntoBytes(e);

struct AssertIsTryFromBytes<U: $crate::TryFromBytes>(U);
#[allow(unused, unreachable_code)]
let u = AssertIsTryFromBytes(loop {});
Ok(u.0)
} else if false {
// Check that the sizes of the source and destination types are
// equal.

// SAFETY: This code is never executed.
Ok(unsafe {
// Clippy: It's okay to transmute a type to itself.
#[allow(clippy::useless_transmute)]
$crate::macro_util::core_reexport::mem::transmute(e)
})
} else {
$crate::macro_util::try_transmute::<_, _>(e)
}
}}
}

/// Includes a file and safely transmutes it to a value of an arbitrary type.
///
/// The file will be included as a byte array, `[u8; N]`, which will be
Expand Down
19 changes: 19 additions & 0 deletions src/macro_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,25 @@ pub unsafe fn transmute_mut<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>(
unsafe { &mut *dst }
}

/// A helper for `try_transmute!`.
#[inline(always)]
pub fn try_transmute<Src, Dst>(mut src: Src) -> Result<Dst, Src>
where
Src: crate::IntoBytes,
Dst: crate::TryFromBytes,
{
if !Dst::is_src_valid(&mut src) {
return Err(src);
}

let src = ManuallyDrop::new(src);

// SAFETY:
// - `is_src_valid` validates that `src` is a bit-valid instance of `Dst`
// - `src` is `ManuallyDrop`
Ok(unsafe { core::mem::transmute_copy(&*src) })
}

// NOTE: We can't change this to a `pub use core as core_reexport` until [1] is
// fixed or we update to a semver-breaking version (as of this writing, 0.8.0)
// on the `main` branch.
Expand Down

0 comments on commit e6dc8ee

Please sign in to comment.