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 May 13, 2024
1 parent fdf6305 commit 80bdd61
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ impl<Src, Dst, A, V> From<SizeError<Src, Dst>> for ConvertError<A, SizeError<Src
#[derive(PartialEq, Eq)]
pub struct ValidityError<Src, Dst: ?Sized + TryFromBytes> {
/// The source value involved in the conversion.
src: Src,
pub(crate) src: Src,
/// The inner destination type inolved in the conversion.
dst: PhantomData<Dst>,
}
Expand Down
163 changes: 163 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,49 @@ pub unsafe trait TryFromBytes {
candidate: Maybe<'_, Self, A>,
) -> bool;

/// Is a given source a valid instance of `Self`?
///
/// # Safety
///
/// Unsafe code may assume that, if `is_mut_src_valid(src)` returns true,
/// `*src` is a bit-valid instance of `Self`, and that the size of `Src` is
/// greater than or equal to the size 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_mut_src_valid<Src>(src: &mut Src) -> bool
where
Src: IntoBytes,
Self: Sized,
{
util::assert_dst_not_bigger_than_src::<Src, Self>();

let c_ptr = Ptr::from_mut(src);

// SAFETY: This is a pointer cast, satisfying the following properties:
// - `p as *mut Self` addresses a subset of the `bytes` addressed by
// `c_ptr`, because we assert above that the size of `Self` is less
// than or equal to the size of `Src`.
// - `p as *mut Self` is a provenance-preserving cast
// - The aliasing of `c_ptr` is neither `Any` nor `Shared`, so we do not
// need to reason about the ranges of `UnsafeCell`s.
#[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_initialized() };

Self::is_bit_valid(c_ptr)
}

/// Attempts to interpret the given `candidate` as a `&Self` without
/// copying.
///
Expand Down Expand Up @@ -4409,6 +4452,82 @@ macro_rules! transmute_mut {
}}
}

/// Conditionally transmutes a value of one type to a value of another type of
/// the same size.
///
/// This macro behaves like an invocation of this function:
///
/// ```ignore
/// const fn try_transmute<Src, Dst>(src: Src) -> Result<Dst, ValidityError<Src, Dst>>
/// where
/// Src: IntoBytes,
/// Dst: TryFromBytes,
/// size_of::<Src>() == size_of::<Dst>(),
/// {
/// # /*
/// ...
/// # */
/// }
/// ```
///
/// However, unlike a function, this macro can only be invoked when the types of
/// `Src` and `Dst` are completely concrete. The types `Src` and `Dst` are
/// inferred from the calling context; they cannot be explicitly specified in
/// the macro invocation.
///
/// Note that the `Src` produced by the expression `$e` will *not* be dropped.
/// Semantically, its bits will be copied into a new value of type `Dst`, the
/// original `Src` will be forgotten, and the value of type `Dst` will be
/// returned.
///
/// # Examples
///
/// ```
/// # use zerocopy::try_transmute;
/// assert_eq!(try_transmute!(0u8), Ok(false));
/// assert_eq!(try_transmute!(1u8), Ok(true));
///
/// let maybe_bool: Result<bool, _> = try_transmute!(255u8);
/// assert_eq!(maybe_bool.unwrap_err().into_src(), 255u8);
///
/// ```
#[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, clippy::missing_transmute_annotations)]
$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 Expand Up @@ -5607,6 +5726,50 @@ mod tests {
assert_eq!(*y, 0);
}

#[test]
fn test_try_transmute() {
// Test that memory is transmuted with transmuted as expected.
let array_of_bools = [false, true, false, true, false, true, false, true];
let array_of_arrays = [[0, 1], [0, 1], [0, 1], [0, 1]];
let x: Result<[[u8; 2]; 4], _> = try_transmute!(array_of_bools);
assert_eq!(x, Ok(array_of_arrays));
let x: Result<[bool; 8], _> = try_transmute!(array_of_arrays);
assert_eq!(x, Ok(array_of_bools));

// Test that `transmute!` works with `!NoCell` types.
let x: usize = transmute!(UnsafeCell::new(1usize));
assert_eq!(x, 1);
let x: UnsafeCell<usize> = transmute!(1usize);
assert_eq!(x.into_inner(), 1);
let x: UnsafeCell<isize> = transmute!(UnsafeCell::new(1usize));
assert_eq!(x.into_inner(), 1);

#[derive(FromBytes, IntoBytes, Debug, PartialEq)]
#[repr(transparent)]
struct PanicOnDrop<T>(T);

impl<T> Drop for PanicOnDrop<T> {
fn drop(&mut self) {
panic!("PanicOnDrop dropped");
}
}

// Since `try_transmute!` semantically moves its argument on failure,
// the `PanicOnDrop` is not dropped, and thus this shouldn't panic.
let x: Result<usize, _> = try_transmute!(PanicOnDrop(1usize));
assert_eq!(x, Ok(1));

// Since `try_transmute!` semantically returns ownership of its argument
// on failure, the `PanicOnDrop` is returned rather than dropped, and
// thus this shouldn't panic.
let y: Result<bool, _> = try_transmute!(PanicOnDrop(2u8));
// We have to use `map_err` instead of comparing against
// `Err(PanicOnDrop(2u8))` because the latter would create and then drop
// its `PanicOnDrop` temporary, which would cause a panic.
assert_eq!(y.as_ref().map_err(|p| &p.src.0), Err::<&bool, _>(&2u8));
mem::forget(y);
}

#[test]
fn test_transmute_mut() {
// Test that memory is transmuted as expected.
Expand Down
27 changes: 27 additions & 0 deletions src/macro_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ use core::{marker::PhantomData, mem::ManuallyDrop};
#[cfg(__INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)]
use core::ptr::{self, NonNull};

use crate::ValidityError;

/// A compile-time check that should be one particular value.
pub trait ShouldBe<const VALUE: bool> {}

Expand Down Expand Up @@ -409,6 +411,31 @@ pub unsafe fn transmute_mut<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>(
unsafe { &mut *dst }
}

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

let src = ManuallyDrop::new(src);

// SAFETY: By contract on `is_mut_src_valid`, we have confirmed both that
// `Dst` is no larger than `Src`, and that `src` is a bit-valid instance of
// `Dst`. These conditions are preserved through the `ManuallyDrop<T>`
// wrapper, which is documented to have identical and layout bit validity to
// its inner value [1].
//
// [1]: https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html
Ok(unsafe { core::mem::transmute_copy(&*src) })
}

/// A function which emits a warning if its return value is not used.
#[must_use]
#[inline(always)]
Expand Down
17 changes: 17 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,23 @@ where
const_assert!(<T as ConstAssert>::DST_IS_NOT_ZST);
}

/// Assert at compile time that the size of `Dst` <= the size of `Src`.
pub(crate) const fn assert_dst_not_bigger_than_src<Src, Dst>() {
trait ConstAssert {
const DST_NOT_BIGGER_THAN_SRC: bool;
}

impl<Src, Dst> ConstAssert for (Src, Dst) {
const DST_NOT_BIGGER_THAN_SRC: bool = {
let dst_bigger_than_src = mem::size_of::<Dst>() > mem::size_of::<Src>();
const_assert!(!dst_bigger_than_src);
!dst_bigger_than_src
};
}

const_assert!(<(Src, Dst) as ConstAssert>::DST_NOT_BIGGER_THAN_SRC);
}

/// Since we support multiple versions of Rust, there are often features which
/// have been stabilized in the most recent stable release which do not yet
/// exist (stably) on our MSRV. This module provides polyfills for those
Expand Down

0 comments on commit 80bdd61

Please sign in to comment.