Skip to content

Commit 17a8cff

Browse files
committed
Add MaybeValid type
`MaybeValid<T>` is a `T` which might not be valid. It is similar to `MaybeUninit<T>`, but it is slightly more strict: any byte in `T` which is guaranteed to be initialized is also guaranteed to be initialized in `MaybeValid<T>` (see the doc comment for a more precise definition). `MaybeValid` is a building block of the `TryFromBytes` design outlined in #5. Makes progress on #5
1 parent fd7dcba commit 17a8cff

File tree

1 file changed

+322
-0
lines changed

1 file changed

+322
-0
lines changed

src/lib.rs

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,237 @@ safety_comment! {
18261826
assert_unaligned!(mem::MaybeUninit<()>, MaybeUninit<u8>);
18271827
}
18281828

1829+
/// A value which might or might not constitute a valid instance of `T`.
1830+
///
1831+
/// `MaybeValid<T>` has the same layout (size and alignment) and field offsets
1832+
/// as `T`. Unlike `T`, it may contain any bit pattern, except that
1833+
/// uninitialized bytes may only appear in `MaybeValid<T>` at byte offsets where
1834+
/// they may appear in `T`. This is a dynamic property: if, at a particular byte
1835+
/// offset, a valid enum discriminant is set, the subsequent bytes may only have
1836+
/// uninitialized bytes as specified by the corresponding enum variant.
1837+
///
1838+
/// Formally, given `m: MaybeValid<T>` and a byte offset, `b` in the range `[0,
1839+
/// size_of_val(m))`:
1840+
/// - If, in all valid instances `t: T`, the byte at offset `b` in `t` is
1841+
/// initialized, then the byte at offset `b` within `m` is guaranteed to be
1842+
/// initialized.
1843+
/// - Let `c` be the contents of the byte range `[0, b)` in `m`. Let `TT` be the
1844+
/// subset of valid instances of `T` which contain `c` in the offset range
1845+
/// `[0, b)`. If, for all instances of `t: T` in `TT`, the byte at offset `b`
1846+
/// in `t` is initialized, then the byte at offset `b` in `m` is guaranteed to
1847+
/// be initialized.
1848+
///
1849+
/// Pragmatically, this means that if `m` is guaranteed to contain an enum
1850+
/// type at a particular offset, and the enum discriminant stored in `m`
1851+
/// corresponds to a valid variant of that enum type, then it is guaranteed
1852+
/// that the appropriate bytes of `m` are initialized as defined by that
1853+
/// variant's bit validity (although note that the variant may contain another
1854+
/// enum type, in which case the same rules apply depending on the state of
1855+
/// its discriminant, and so on recursively).
1856+
///
1857+
/// # Safety
1858+
///
1859+
/// Unsafe code may assume that an instance of `MaybeValid` satisfies the
1860+
/// constraints described above. Unsafe code may produce a `MaybeValid` or
1861+
/// modify the bytes of an existing `MaybeValid` so long as these constraints
1862+
/// are upheld. It is unsound to produce a `MaybeValid` which fails to uphold
1863+
/// these constraints.
1864+
#[repr(transparent)]
1865+
pub struct MaybeValid<T: AsMaybeUninit + ?Sized> {
1866+
inner: MaybeUninit<T>,
1867+
}
1868+
1869+
safety_comment! {
1870+
/// SAFETY:
1871+
/// - `AsBytes`: `MaybeValid` requires that, if a byte in `T` is always
1872+
/// initialized, the equivalent byte in `MaybeValid<T>` must be
1873+
/// initialized. `T: AsBytes` implies that all bytes in `T` must always be
1874+
/// initialized, and so all bytes in `MaybeValid<T>` must always be
1875+
/// initialized, and so `MaybeValid<T>` satisfies `AsBytes`. `T: AsBytes`
1876+
/// implies that `[T]: AsBytes`, so this holds is a sufficient bound for
1877+
/// `MaybeValid<[T]>` too.
1878+
/// - `Unaligned`: `MaybeValid<T>` and `MaybeValid<[T]>` have the same
1879+
/// alignment as `T`.
1880+
///
1881+
/// TODO(#5): Implement `FromZeroes` and `FromBytes` for `MaybeValid<T>` and
1882+
/// `MaybeValid<[T]>`.
1883+
unsafe_impl!(T: AsBytes => AsBytes for MaybeValid<T>);
1884+
unsafe_impl!(T: AsBytes => AsBytes for MaybeValid<[T]>);
1885+
unsafe_impl!(T: Unaligned => Unaligned for MaybeValid<T>);
1886+
unsafe_impl!(T: Unaligned => Unaligned for MaybeValid<[T]>);
1887+
}
1888+
1889+
unsafe_impl_known_layout!(T => #[repr([T])] MaybeValid<[T]>);
1890+
1891+
// SAFETY: See safety comment on `MaybeUninit`.
1892+
unsafe impl<T> AsMaybeUninit for MaybeValid<[T]> {
1893+
// SAFETY:
1894+
// - `MaybeUninit` has no bit validity requirements and `[U]` has the same
1895+
// bit validity requirements as `U`, so `[MaybeUninit<T>]` has no bit
1896+
// validity requirements. Thus, it is sound to write uninitialized bytes
1897+
// at every offset.
1898+
// - `MaybeValid<U>` is `repr(transparent)`, and thus has the same layout
1899+
// and field offsets as its contained field of type `U::MaybeUninit`. In
1900+
// this case, `U = [T]`, and so `U::MaybeUninit = [MaybeUninit<T>]`. Thus,
1901+
// `MaybeValid<[T]>` has the same layout and field offsets as
1902+
// `[MaybeUninit<T>]`, which is what we set `MaybeUninit` to here. Thus,
1903+
// they trivially have the same alignment.
1904+
// - By the same token, their raw pointer types are trivially `as` castable
1905+
// and preserve size.
1906+
// - By the same token, `[MaybeUninit<T>]` contains `UnsafeCell`s at the
1907+
// same byte ranges as `MaybeValid<[T]>` does.
1908+
type MaybeUninit = [MaybeUninit<T>];
1909+
1910+
// SAFETY: `as` preserves pointer address and provenance.
1911+
#[allow(clippy::as_conversions)]
1912+
fn raw_from_maybe_uninit(maybe_uninit: *const [MaybeUninit<T>]) -> *const MaybeValid<[T]> {
1913+
maybe_uninit as *const MaybeValid<[T]>
1914+
}
1915+
1916+
// SAFETY: `as` preserves pointer address and provenance.
1917+
#[allow(clippy::as_conversions)]
1918+
fn raw_mut_from_maybe_uninit(maybe_uninit: *mut [MaybeUninit<T>]) -> *mut MaybeValid<[T]> {
1919+
maybe_uninit as *mut MaybeValid<[T]>
1920+
}
1921+
1922+
// SAFETY: `as` preserves pointer address and provenance.
1923+
#[allow(clippy::as_conversions)]
1924+
fn raw_maybe_uninit_from(s: *const MaybeValid<[T]>) -> *const [MaybeUninit<T>] {
1925+
s as *const [MaybeUninit<T>]
1926+
}
1927+
}
1928+
1929+
impl<T> Default for MaybeValid<T> {
1930+
fn default() -> MaybeValid<T> {
1931+
// SAFETY: All of the bytes of `inner` are initialized to 0, and so the
1932+
// safety invariant on `MaybeValid` is upheld.
1933+
MaybeValid { inner: MaybeUninit::zeroed() }
1934+
}
1935+
}
1936+
1937+
impl<T: AsMaybeUninit + ?Sized> MaybeValid<T> {
1938+
/// Converts this `&MaybeValid<T>` to a `&T`.
1939+
///
1940+
/// # Safety
1941+
///
1942+
/// `self` must contain a valid `T`.
1943+
pub unsafe fn assume_valid_ref(&self) -> &T {
1944+
// SAFETY: The caller has promised that `self` contains a valid `T`.
1945+
// Since `Self` is `repr(transparent)`, it has the same layout as
1946+
// `MaybeUninit<T>`, which in turn is guaranteed to have the same layout
1947+
// as `T`. Thus, it is sound to treat `self.inner` as containing a valid
1948+
// `T`.
1949+
unsafe { self.inner.assume_init_ref() }
1950+
}
1951+
1952+
/// Converts this `&mut MaybeValid<T>` to a `&mut T`.
1953+
///
1954+
/// # Safety
1955+
///
1956+
/// `self` must contain a valid `T`.
1957+
pub unsafe fn assume_valid_mut(&mut self) -> &mut T {
1958+
// SAFETY: The caller has promised that `self` contains a valid `T`.
1959+
// Since `Self` is `repr(transparent)`, it has the same layout as
1960+
// `MaybeUninit<T>`, which in turn is guaranteed to have the same layout
1961+
// as `T`. Thus, it is sound to treat `self.inner` as containing a valid
1962+
// `T`.
1963+
unsafe { self.inner.assume_init_mut() }
1964+
}
1965+
1966+
/// Gets a view of this `&T` as a `&MaybeValid<T>`.
1967+
///
1968+
/// There is no mutable equivalent to this function, as producing a `&mut
1969+
/// MaybeValid<T>` from a `&mut T` would allow safe code to write invalid
1970+
/// values which would be accessible through `&mut T`.
1971+
pub fn from_ref(r: &T) -> &MaybeValid<T> {
1972+
let m: *const MaybeUninit<T> = MaybeUninit::from_ref(r);
1973+
#[allow(clippy::as_conversions)]
1974+
let ptr = m as *const MaybeValid<T>;
1975+
// SAFETY: Since `Self` is `repr(transparent)`, it has the same layout
1976+
// as `MaybeUninit<T>`, so the size and alignment here are valid.
1977+
//
1978+
// `MaybeValid<T>`'s bit validity constraints are weaker than those of
1979+
// `T`, so this is guaranteed not to produce an invalid `MaybeValid<T>`.
1980+
// If it were possible to write a different value for `MaybeValid<T>`
1981+
// through the returned reference, it could result in an invalid value
1982+
// being exposed via the `&T`. Luckily, the only way for mutation to
1983+
// happen is if `T` contains an `UnsafeCell` and the caller uses it to
1984+
// perform interior mutation. Importantly, `T` containing an
1985+
// `UnsafeCell` does not permit interior mutation through
1986+
// `MaybeValid<T>`, so it doesn't permit writing uninitialized or
1987+
// otherwise invalid values which would be visible through the original
1988+
// `&T`.
1989+
unsafe { &*ptr }
1990+
}
1991+
}
1992+
1993+
impl<T> MaybeValid<T> {
1994+
/// Converts this `MaybeValid<T>` to a `T`.
1995+
///
1996+
/// # Safety
1997+
///
1998+
/// `self` must contain a valid `T`.
1999+
pub const unsafe fn assume_valid(self) -> T {
2000+
// SAFETY: The caller has promised that `self` contains a valid `T`.
2001+
// Since `Self` is `repr(transparent)`, it has the same layout as
2002+
// `MaybeUninit<T>`, which in turn is guaranteed to have the same layout
2003+
// as `T`. Thus, it is sound to treat `self.inner` as containing a valid
2004+
// `T`.
2005+
unsafe { self.inner.assume_init() }
2006+
}
2007+
}
2008+
2009+
impl<T> MaybeValid<[T]> {
2010+
/// Converts a `MaybeValid<[T]>` to a `[MaybeValid<T>]`.
2011+
///
2012+
/// `MaybeValid<T>` has the same layout as `T`, so these layouts are
2013+
/// equivalent.
2014+
pub const fn as_slice_of_maybe_valids(&self) -> &[MaybeValid<T>] {
2015+
let inner: &[<T as AsMaybeUninit>::MaybeUninit] = &self.inner.inner;
2016+
let inner_ptr: *const [<T as AsMaybeUninit>::MaybeUninit] = inner;
2017+
// Note: this Clippy warning is only emitted on our MSRV (1.61), but not
2018+
// on later versions of Clippy. Thus, we consider it spurious.
2019+
#[allow(clippy::as_conversions)]
2020+
let ret_ptr = inner_ptr as *const [MaybeValid<T>];
2021+
// SAFETY: Since `inner` is a `&[MaybeUninit<T>]`, and `MaybeValid<T>`
2022+
// is a `repr(transparent)` struct around `MaybeUninit<T>`, `inner` has
2023+
// the same layout as `&[MaybeValid<T>]`.
2024+
unsafe { &*ret_ptr }
2025+
}
2026+
}
2027+
2028+
impl<const N: usize, T> MaybeValid<[T; N]> {
2029+
/// Converts a `MaybeValid<[T; N]>` to a `MaybeValid<[T]>`.
2030+
// TODO(#64): Make this `const` once our MSRV is >= 1.64.0 (when
2031+
// `slice_from_raw_parts` was stabilized as `const`).
2032+
pub fn as_slice(&self) -> &MaybeValid<[T]> {
2033+
let base: *const MaybeValid<[T; N]> = self;
2034+
let slice_of_t: *const [T] = ptr::slice_from_raw_parts(base.cast::<T>(), N);
2035+
// Note: this Clippy warning is only emitted on our MSRV (1.61), but not
2036+
// on later versions of Clippy. Thus, we consider it spurious.
2037+
#[allow(clippy::as_conversions)]
2038+
let mv_of_slice = slice_of_t as *const MaybeValid<[T]>;
2039+
// SAFETY: `MaybeValid<T>` is a `repr(transparent)` wrapper around
2040+
// `MaybeUninit<T>`, which in turn has the same layout as `T`. Thus, the
2041+
// trailing slices of `[T]` and of `MaybeValid<[T]>` both have element
2042+
// type `T`. Since the number of elements is preserved during an `as`
2043+
// cast of slice/DST pointers, the resulting `*const MaybeValid<[T]>`
2044+
// has the same number of elements - and thus the same length - as the
2045+
// original `*const [T]`.
2046+
//
2047+
// Thanks to their layouts, `MaybeValid<[T; N]>` and `MaybeValid<[T]>`
2048+
// have the same alignment, so `mv_of_slice` is guaranteed to be
2049+
// aligned.
2050+
unsafe { &*mv_of_slice }
2051+
}
2052+
}
2053+
2054+
impl<T> Debug for MaybeValid<T> {
2055+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2056+
f.pad(core::any::type_name::<Self>())
2057+
}
2058+
}
2059+
18292060
/// A type with no alignment requirement.
18302061
///
18312062
/// An `Unalign` wraps a `T`, removing any alignment requirement. `Unalign<T>`
@@ -3784,6 +4015,91 @@ mod tests {
37844015
assert_eq!(unsafe { m.assume_init_ref() }, &Cell::new(2));
37854016
}
37864017

4018+
#[test]
4019+
fn test_maybe_valid() {
4020+
let m = MaybeValid::<usize>::default();
4021+
// SAFETY: all bit patterns are valid `usize`s, and `m` is initialized.
4022+
let u = unsafe { m.assume_valid() };
4023+
// This ensures that Miri can see whether `u` (and thus `m`) has been
4024+
// properly initialized.
4025+
assert_eq!(u, u);
4026+
4027+
fn bytes_to_maybe_valid(bytes: &mut [u8]) -> &mut MaybeValid<[u8]> {
4028+
// SAFETY: `MaybeValid<[u8]>` has the same layout as `[u8]`, and
4029+
// `bytes` is initialized.
4030+
unsafe {
4031+
#[allow(clippy::as_conversions)]
4032+
let m = &mut *(bytes as *mut [u8] as *mut MaybeValid<[u8]>);
4033+
m
4034+
}
4035+
}
4036+
4037+
let mut bytes = [0u8, 1, 2];
4038+
let m = bytes_to_maybe_valid(&mut bytes[..]);
4039+
4040+
// SAFETY: `m` was created from a valid `[u8]`.
4041+
let r = unsafe { m.assume_valid_ref() };
4042+
assert_eq!(r.len(), 3);
4043+
assert_eq!(r, [0, 1, 2]);
4044+
4045+
// SAFETY: `m` was created from a valid `[u8]`.
4046+
let r = unsafe { m.assume_valid_mut() };
4047+
assert_eq!(r.len(), 3);
4048+
assert_eq!(r, [0, 1, 2]);
4049+
4050+
r[0] = 1;
4051+
assert_eq!(bytes, [1, 1, 2]);
4052+
4053+
let mut bytes = [0u8, 1, 2];
4054+
let m = bytes_to_maybe_valid(&mut bytes[..]);
4055+
let slc = m.as_slice_of_maybe_valids();
4056+
assert_eq!(slc.len(), 3);
4057+
for i in 0u8..3 {
4058+
// SAFETY: `m` was created from a valid `[u8]`.
4059+
let u = unsafe { slc[usize::from(i)].assume_valid_ref() };
4060+
assert_eq!(u, &i);
4061+
}
4062+
}
4063+
4064+
#[test]
4065+
fn test_maybe_valid_as_slice() {
4066+
let mut m = MaybeValid::<[u8; 3]>::default();
4067+
// SAFETY: all bit patterns are valid `[u8; 3]`s, and `m` is
4068+
// initialized.
4069+
unsafe { *m.assume_valid_mut() = [0, 1, 2] };
4070+
4071+
let slc = m.as_slice().as_slice_of_maybe_valids();
4072+
assert_eq!(slc.len(), 3);
4073+
4074+
for i in 0u8..3 {
4075+
// SAFETY: `m` was initialized as a valid `[u8; 3]`.
4076+
let u = unsafe { slc[usize::from(i)].assume_valid_ref() };
4077+
assert_eq!(u, &i);
4078+
}
4079+
}
4080+
4081+
#[test]
4082+
fn test_maybe_valid_from_ref() {
4083+
use core::cell::Cell;
4084+
4085+
let u = 1usize;
4086+
let m = MaybeValid::from_ref(&u);
4087+
// SAFETY: `m` was constructed from a valid `&usize`.
4088+
assert_eq!(unsafe { m.assume_valid_ref() }, &1usize);
4089+
4090+
// Test that interior mutability doesn't affect correctness or
4091+
// soundness.
4092+
4093+
let c = Cell::new(1usize);
4094+
let m = MaybeValid::from_ref(&c);
4095+
// SAFETY: `m` was constructed from a valid `&usize`.
4096+
assert_eq!(unsafe { m.assume_valid_ref() }, &Cell::new(1));
4097+
4098+
c.set(2);
4099+
// SAFETY: `m` was constructed from a valid `&usize`.
4100+
assert_eq!(unsafe { m.assume_valid_ref() }, &Cell::new(2));
4101+
}
4102+
37874103
#[test]
37884104
fn test_unalign() {
37894105
// Test methods that don't depend on alignment.
@@ -4719,6 +5035,12 @@ mod tests {
47195035
assert_impls!(MaybeUninit<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
47205036
assert_impls!(MaybeUninit<MaybeUninit<NotZerocopy>>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
47215037

5038+
assert_impls!(MaybeValid<u8>: Unaligned, AsBytes, !FromZeroes, !FromBytes);
5039+
assert_impls!(MaybeValid<MaybeValid<u8>>: Unaligned, AsBytes, !FromZeroes, !FromBytes);
5040+
assert_impls!(MaybeValid<[u8]>: Unaligned, AsBytes, !FromZeroes, !FromBytes);
5041+
assert_impls!(MaybeValid<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
5042+
assert_impls!(MaybeValid<MaybeValid<NotZerocopy>>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
5043+
47225044
assert_impls!(Wrapping<u8>: FromZeroes, FromBytes, AsBytes, Unaligned);
47235045
assert_impls!(Wrapping<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
47245046

0 commit comments

Comments
 (0)