Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TryFromBytes::try_read_from #808

Merged
merged 1 commit into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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