Skip to content

Commit

Permalink
Add TryFromBytes::try_read_from
Browse files Browse the repository at this point in the history
  • Loading branch information
joshlf committed Jan 23, 2024
1 parent 100ba98 commit a5c66e1
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 2 deletions.
58 changes: 56 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1229,8 +1229,9 @@ pub unsafe trait TryFromBytes {
/// reinterpreted as a `Self`.
///
/// Note that Rust's bit validity rules are still being decided. As such,
/// there exist types whose bit validity is ambiguous. See the
/// `TryFromBytes` docs for a discussion of how these cases are handled.
/// there exist types whose bit validity is ambiguous. See
/// [here][TryFromBytes#what-is-a-valid-instance] for a discussion of how
/// these cases are handled.
// TODO(#251): Require `Self: NoCell` and allow `TryFromBytes` types to
// contain `UnsafeCell`s.
#[inline]
Expand All @@ -1254,6 +1255,36 @@ pub unsafe trait TryFromBytes {

candidate.map(MaybeAligned::as_ref)
}

/// Attempts to read a `Self` from a byte slice.
///
/// `try_read_from` validates that `bytes` contains a valid `Self`. If it
/// does, those bytes are read out of `bytes` and reinterpreted as a `Self`.
///
/// Note that Rust's bit validity rules are still being decided. As such,
/// there exist types whose bit validity is ambiguous. See
/// [here][TryFromBytes#what-is-a-valid-instance] for a discussion of how
/// these cases are handled.
#[inline]
#[doc(hidden)] // TODO(#5): Finalize name before remove this attribute.
fn try_read_from(bytes: &[u8]) -> Option<Self>
where
Self: Sized,
{
let candidate = MaybeUninit::<Self>::read_from(bytes)?;
let c_ptr = Ptr::from_maybe_uninit_ref(&candidate);
// SAFETY: `c_ptr` has no uninitialized sub-ranges because it derived
// from `candidate`, which in turn derives from `bytes: &[u8]`, and is
// therefore at least as-initialized as `Self`.
let c_ptr = unsafe { c_ptr.assume_as_initialized() };

if !Self::is_bit_valid(c_ptr.forget_aligned()) {
return None;
}

// SAFETY: We just validated that `candidate` contains a valid `Self`.
Some(unsafe { candidate.assume_init() })
}
}

/// Types for which a sequence of bytes all set to zero represents a valid
Expand Down Expand Up @@ -6890,6 +6921,29 @@ mod tests {
assert_eq!(bytes, want);
}

#[test]
fn test_try_from_bytes_try_read_from() {
assert_eq!(<bool as TryFromBytes>::try_read_from(&[0]), Some(false));
assert_eq!(<bool as TryFromBytes>::try_read_from(&[1]), Some(true));

// If we don't pass enough bytes, it fails.
assert_eq!(<u8 as TryFromBytes>::try_read_from(&[]), None);

// If we pass too many bytes, it fails.
assert_eq!(<u8 as TryFromBytes>::try_read_from(&[0, 0]), None);

// If we pass an invalid value, it fails.
assert_eq!(<bool as TryFromBytes>::try_read_from(&[2]), None);

// Reading from a misaligned buffer should still succeed. Since `AU64`'s
// alignment is 8, and since we read from two adjacent addresses one
// byte apart, it is guaranteed that at least one of them (though
// possibly both) will be misaligned.
let bytes: [u8; 9] = [0, 0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(<AU64 as TryFromBytes>::try_read_from(&bytes[..8]), Some(AU64(0)));
assert_eq!(<AU64 as TryFromBytes>::try_read_from(&bytes[1..9]), Some(AU64(0)));
}

#[test]
fn test_transmute() {
// Test that memory is transmuted as expected.
Expand Down
61 changes: 61 additions & 0 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ mod _external {

/// Methods for converting to and from `Ptr` and Rust's safe reference types.
mod _conversions {
use core::mem::MaybeUninit;

use super::*;

/// `&'a T` → `Ptr<'a, T>`
Expand Down Expand Up @@ -447,6 +449,65 @@ mod _conversions {
unsafe { raw.as_ref() }
}
}

/// `&'a MaybeUninit<T>` → `Ptr<'a, T>`
impl<'a, T> Ptr<'a, T, (invariant::Shared, invariant::Aligned, invariant::AnyValidity)>
where
T: 'a,
{
/// Constructs a `Ptr` from a shared reference to a [`MaybeUninit<T>`].
///
/// [`MaybeUninit<T>`]: MaybeUninit
#[doc(hidden)]
#[inline]
pub fn from_maybe_uninit_ref(ptr: &'a MaybeUninit<T>) -> Self {
let mu_ptr = core::ptr::NonNull::from(ptr);
let t_ptr = mu_ptr.cast::<T>();
// SAFETY:
// 0. `mu_ptr`, by invariant on `&'a T`, is derived from some valid
// Rust allocation, `A`. `t_ptr` is as well because `.cast()`
// conserves this property.
// 1. `mu_ptr`, by invariant on `&'a T`, has valid provenance for
// `A`. `t_ptr` does as well because `.cast()` conserves this
// property.
// 2. `mu_ptr`, by invariant on `&'a T`, addresses a byte range
// which is entirely contained in `A`. `t_ptr` does as well
// because `.cast()` conserves this property. This relies on the
// fact that `t_ptr` addresses the same number of bytes as
// `mu_ptr`, which is guaranteed because `MaybeUninit<T>` has the
// same size as `T` [1].
// 3. `mu_ptr`, by invariant on `&'a T`, addresses a byte range
// whose length fits in an `isize`. `t_ptr` does as well because
// `.cast()` conserves this property. This relies on the fact
// that `t_ptr` addresses the same number of bytes as `mu_ptr`,
// which is guaranteed because `MaybeUninit<T>` has the same size
// as `T` [1].
// 4. `mu_ptr`, by invariant on `&'a T`, addresses a byte range
// which does not wrap around the address space. `t_ptr` does as
// well because `.cast()` conserves this property. This relies on
// the fact that `t_ptr` addresses the same number of bytes as
// `mu_ptr`, which is guaranteed because `MaybeUninit<T>` has the
// same size as `T` [1].
// 5. `A`, by invariant on `&'a T`, is guaranteed to live for at
// least `'a`.
// 6. `mu_ptr`, by invariant on `&'a T`, conforms to the aliasing
// invariant of `invariant::Shared`. `t_ptr` does as well because
// `.cast()` conserves this property.
// 7. `mu_ptr`, by invariant on `&'a T`, conforms to the alignment
// invariant of `invariant::Aligned`. `t_ptr` does as well
// because `.cast()` conserves alignment, and `MaybeUninit<T>`
// has the same alignment as `T` [1].
// 8. The returned `Ptr` has validity `invariant::AnyValidity`,
// which is always upheld regardless of the contents of its
// referent.
//
// [1] Per https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#layout-1:
//
// `MaybeUninit<T>` is guaranteed to have the same size,
// alignment, and ABI as `T`
unsafe { Self::new(t_ptr) }
}
}
}

/// State transitions between invariants.
Expand Down
1 change: 1 addition & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ pub(crate) mod testutil {
#[derive(
KnownLayout,
NoCell,
TryFromBytes,
FromZeros,
FromBytes,
IntoBytes,
Expand Down

0 comments on commit a5c66e1

Please sign in to comment.