Skip to content

Commit

Permalink
Use AlignmentError in Unalign's failure conditions (#1198)
Browse files Browse the repository at this point in the history
This make's `Unalign`'s methods consistent with zerocopy's other
methods, and, in the case of `Unalign::try_deref_mut`, allows the
original `&mut Unalign<T>` to be reused in the event of failure.

Makes progress towards #1139
  • Loading branch information
jswrenn authored May 6, 2024
1 parent 8d321c7 commit 1d90765
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 16 deletions.
39 changes: 35 additions & 4 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,12 +613,43 @@ mod _conversions {
c
}
}

/// `Ptr<'a, T, (_, _, _)>` → `Ptr<'a, Unalign<T>, (_, Aligned, _)>`
impl<'a, T, I> Ptr<'a, T, I>
where
I: Invariants,
{
/// Converts a `Ptr` an unaligned `T` into a `Ptr` to an aligned
/// `Unalign<T>`.
pub(crate) fn into_unalign(
self,
) -> Ptr<'a, crate::Unalign<T>, (I::Aliasing, Aligned, I::Validity)> {
// SAFETY: We define `Unalign<T>` to be a `#[repr(C, packed)]` type
// wrapping a single `T` field. Thus, `Unalign<T>` has the same size
// as `T` and contains `UnsafeCell`s at the same locations as `T`.
// The cast is implemented in the form `|p: *mut T| p as *mut U`,
// where `U` is `Unalign<T>`.
let ptr = unsafe {
#[allow(clippy::as_conversions)]
self.cast_unsized(|p: *mut T| p as *mut crate::Unalign<T>)
};
// SAFETY: We define `Unalign<T>` to be a `#[repr(C, packed)]` type
// wrapping a single `T` field, thus `Unalign<T>` has exactly the
// same validity as `T`.
let ptr = unsafe { ptr.assume_validity::<I::Validity>() };
// SAFETY: We define `Unalign<T>` to be a `#[repr(C, packed)]` type
// wrapping a single `T` field, thus `Unalign<T>` is always
// trivially aligned.
let ptr = unsafe { ptr.assume_alignment::<Aligned>() };
ptr
}
}
}

/// State transitions between invariants.
mod _transitions {
use super::*;
use crate::{TryFromBytes, ValidityError};
use crate::{AlignmentError, TryFromBytes, ValidityError};

impl<'a, T, I> Ptr<'a, T, I>
where
Expand Down Expand Up @@ -745,16 +776,16 @@ mod _transitions {
/// on success.
pub(crate) fn bikeshed_try_into_aligned(
self,
) -> Option<Ptr<'a, T, (I::Aliasing, Aligned, I::Validity)>>
) -> Result<Ptr<'a, T, (I::Aliasing, Aligned, I::Validity)>, AlignmentError<Self, T>>
where
T: Sized,
{
if !crate::util::aligned_to::<_, T>(self.as_non_null()) {
return None;
return Err(AlignmentError::new(self));
}

// SAFETY: We just checked the alignment.
Some(unsafe { self.assume_alignment::<Aligned>() })
Ok(unsafe { self.assume_alignment::<Aligned>() })
}

/// Recalls that `self`'s referent is validly-aligned for `T`.
Expand Down
30 changes: 18 additions & 12 deletions src/wrappers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,29 +136,35 @@ impl<T> Unalign<T> {
/// not properly aligned.
///
/// If `self` does not satisfy `mem::align_of::<T>()`, then it is unsound to
/// return a reference to the wrapped `T`, and `try_deref` returns `None`.
/// return a reference to the wrapped `T`, and `try_deref` returns `Err`.
///
/// If `T: Unaligned`, then `Unalign<T>` implements [`Deref`], and callers
/// may prefer [`Deref::deref`], which is infallible.
#[inline(always)]
pub fn try_deref(&self) -> Option<&T> {
pub fn try_deref(&self) -> Result<&T, AlignmentError<&Self, T>> {
let inner = Ptr::from_ref(self).transparent_wrapper_into_inner();
inner.bikeshed_try_into_aligned().map(Ptr::as_ref)
match inner.bikeshed_try_into_aligned() {
Ok(aligned) => Ok(aligned.as_ref()),
Err(err) => Err(err.map_src(|src| src.into_unalign().as_ref())),
}
}

/// Attempts to return a mutable reference to the wrapped `T`, failing if
/// `self` is not properly aligned.
///
/// If `self` does not satisfy `mem::align_of::<T>()`, then it is unsound to
/// return a reference to the wrapped `T`, and `try_deref_mut` returns
/// `None`.
/// `Err`.
///
/// If `T: Unaligned`, then `Unalign<T>` implements [`DerefMut`], and
/// callers may prefer [`DerefMut::deref_mut`], which is infallible.
#[inline(always)]
pub fn try_deref_mut(&mut self) -> Option<&mut T> {
pub fn try_deref_mut(&mut self) -> Result<&mut T, AlignmentError<&mut Self, T>> {
let inner = Ptr::from_mut(self).transparent_wrapper_into_inner();
inner.bikeshed_try_into_aligned().map(Ptr::as_mut)
match inner.bikeshed_try_into_aligned() {
Ok(aligned) => Ok(aligned.as_mut()),
Err(err) => Err(err.map_src(|src| src.into_unalign().as_mut())),
}
}

/// Returns a reference to the wrapped `T` without checking alignment.
Expand Down Expand Up @@ -423,8 +429,8 @@ mod tests {

// Test methods that depend on alignment (when alignment is satisfied).
let mut u: Align<_, AU64> = Align::new(Unalign::new(AU64(123)));
assert_eq!(u.t.try_deref(), Some(&AU64(123)));
assert_eq!(u.t.try_deref_mut(), Some(&mut AU64(123)));
assert_eq!(u.t.try_deref().unwrap(), &AU64(123));
assert_eq!(u.t.try_deref_mut().unwrap(), &mut AU64(123));
// SAFETY: The `Align<_, AU64>` guarantees proper alignment.
assert_eq!(unsafe { u.t.deref_unchecked() }, &AU64(123));
// SAFETY: The `Align<_, AU64>` guarantees proper alignment.
Expand All @@ -435,13 +441,13 @@ mod tests {
// Test methods that depend on alignment (when alignment is not
// satisfied).
let mut u: ForceUnalign<_, AU64> = ForceUnalign::new(Unalign::new(AU64(123)));
assert_eq!(u.t.try_deref(), None);
assert_eq!(u.t.try_deref_mut(), None);
assert!(matches!(u.t.try_deref(), Err(AlignmentError { .. })));
assert!(matches!(u.t.try_deref_mut(), Err(AlignmentError { .. })));

// Test methods that depend on `T: Unaligned`.
let mut u = Unalign::new(123u8);
assert_eq!(u.try_deref(), Some(&123));
assert_eq!(u.try_deref_mut(), Some(&mut 123));
assert_eq!(u.try_deref(), Ok(&123));
assert_eq!(u.try_deref_mut(), Ok(&mut 123));
assert_eq!(u.deref(), &123);
assert_eq!(u.deref_mut(), &mut 123);
*u = 21;
Expand Down

0 comments on commit 1d90765

Please sign in to comment.