From a5c66e1e4a63898db8ee103603d105b29390d24e Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Mon, 22 Jan 2024 17:44:57 -0800 Subject: [PATCH] Add TryFromBytes::try_read_from --- src/lib.rs | 58 +++++++++++++++++++++++++++++++++++++++++-- src/pointer/ptr.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++ src/util.rs | 1 + 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 42a62243c4..f4fb1de894 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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] @@ -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 + where + Self: Sized, + { + let candidate = MaybeUninit::::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 @@ -6890,6 +6921,29 @@ mod tests { assert_eq!(bytes, want); } + #[test] + fn test_try_from_bytes_try_read_from() { + assert_eq!(::try_read_from(&[0]), Some(false)); + assert_eq!(::try_read_from(&[1]), Some(true)); + + // If we don't pass enough bytes, it fails. + assert_eq!(::try_read_from(&[]), None); + + // If we pass too many bytes, it fails. + assert_eq!(::try_read_from(&[0, 0]), None); + + // If we pass an invalid value, it fails. + assert_eq!(::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!(::try_read_from(&bytes[..8]), Some(AU64(0))); + assert_eq!(::try_read_from(&bytes[1..9]), Some(AU64(0))); + } + #[test] fn test_transmute() { // Test that memory is transmuted as expected. diff --git a/src/pointer/ptr.rs b/src/pointer/ptr.rs index c7d2a16ce4..cfdaf10a20 100644 --- a/src/pointer/ptr.rs +++ b/src/pointer/ptr.rs @@ -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>` @@ -447,6 +449,65 @@ mod _conversions { unsafe { raw.as_ref() } } } + + /// `&'a MaybeUninit` → `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`]. + /// + /// [`MaybeUninit`]: MaybeUninit + #[doc(hidden)] + #[inline] + pub fn from_maybe_uninit_ref(ptr: &'a MaybeUninit) -> Self { + let mu_ptr = core::ptr::NonNull::from(ptr); + let t_ptr = mu_ptr.cast::(); + // 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` 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` 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` 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` + // 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` is guaranteed to have the same size, + // alignment, and ABI as `T` + unsafe { Self::new(t_ptr) } + } + } } /// State transitions between invariants. diff --git a/src/util.rs b/src/util.rs index fee7e3989b..73ffae2cf6 100644 --- a/src/util.rs +++ b/src/util.rs @@ -161,6 +161,7 @@ pub(crate) mod testutil { #[derive( KnownLayout, NoCell, + TryFromBytes, FromZeros, FromBytes, IntoBytes,