Skip to content

Commit d0ff76b

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 c7b2773 commit d0ff76b

File tree

1 file changed

+319
-0
lines changed

1 file changed

+319
-0
lines changed

src/lib.rs

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,235 @@ safety_comment! {
15701570
assert_unaligned!(mem::MaybeUninit<()>, MaybeUninit<u8>);
15711571
}
15721572

1573+
/// A value which might or might not constitute a valid instance of `T`.
1574+
///
1575+
/// `MaybeValid<T>` has the same layout (size and alignment) and field offsets
1576+
/// as `T`. Unlike `T`, it may contain any bit pattern, except that
1577+
/// uninitialized bytes may only appear in `MaybeValid<T>` at byte offsets where
1578+
/// they may appear in `T`. This is a dynamic property: if, at a particular byte
1579+
/// offset, a valid enum discriminant is set, the subsequent bytes may only have
1580+
/// uninitialized bytes as specified by the corresponding enum variant.
1581+
///
1582+
/// Formally, given `m: MaybeValid<T>` and a byte offset, `b` in the range `[0,
1583+
/// size_of_val(m))`:
1584+
/// - If, in all valid instances `t: T`, the byte at offset `b` in `t` is
1585+
/// initialized, then the byte at offset `b` within `m` is guaranteed to be
1586+
/// initialized.
1587+
/// - Let `c` be the contents of the byte range `[0, b)` in `m`. Let `TT` be the
1588+
/// subset of valid instances of `T` which contain `c` in the offset range
1589+
/// `[0, b)`. If, for all instances of `t: T` in `TT`, the byte at offset `b`
1590+
/// in `t` is initialized, then the byte at offset `b` in `m` is guaranteed to
1591+
/// be initialized.
1592+
///
1593+
/// Pragmatically, this means that if `m` is guaranteed to contain an enum
1594+
/// type at a particular offset, and the enum discriminant stored in `m`
1595+
/// corresponds to a valid variant of that enum type, then it is guaranteed
1596+
/// that the appropriate bytes of `m` are initialized as defined by that
1597+
/// variant's bit validity (although note that the variant may contain another
1598+
/// enum type, in which case the same rules apply depending on the state of
1599+
/// its discriminant, and so on recursively).
1600+
///
1601+
/// # Safety
1602+
///
1603+
/// Unsafe code may assume that an instance of `MaybeValid` satisfies the
1604+
/// constraints described above. Unsafe code may produce a `MaybeValid` or
1605+
/// modify the bytes of an existing `MaybeValid` so long as these constraints
1606+
/// are upheld. It is unsound to produce a `MaybeValid` which fails to uphold
1607+
/// these constraints.
1608+
#[repr(transparent)]
1609+
pub struct MaybeValid<T: AsMaybeUninit + ?Sized> {
1610+
inner: MaybeUninit<T>,
1611+
}
1612+
1613+
safety_comment! {
1614+
/// SAFETY:
1615+
/// - `AsBytes`: `MaybeValid` requires that, if a byte in `T` is always
1616+
/// initialized, the equivalent byte in `MaybeValid<T>` must be
1617+
/// initialized. `T: AsBytes` implies that all bytes in `T` must always be
1618+
/// initialized, and so all bytes in `MaybeValid<T>` must always be
1619+
/// initialized, and so `MaybeValid<T>` satisfies `AsBytes`. `T: AsBytes`
1620+
/// implies that `[T]: AsBytes`, so this holds is a sufficient bound for
1621+
/// `MaybeValid<[T]>` too.
1622+
/// - `Unaligned`: `MaybeValid<T>` and `MaybeValid<[T]>` have the same
1623+
/// alignment as `T`.
1624+
///
1625+
/// TODO(#5): Implement `FromZeroes` and `FromBytes` for `MaybeValid<T>` and
1626+
/// `MaybeValid<[T]>`.
1627+
unsafe_impl!(T: AsBytes => AsBytes for MaybeValid<T>);
1628+
unsafe_impl!(T: AsBytes => AsBytes for MaybeValid<[T]>);
1629+
unsafe_impl!(T: Unaligned => Unaligned for MaybeValid<T>);
1630+
unsafe_impl!(T: Unaligned => Unaligned for MaybeValid<[T]>);
1631+
}
1632+
1633+
// SAFETY: See safety comment on `MaybeUninit`.
1634+
unsafe impl<T> AsMaybeUninit for MaybeValid<[T]> {
1635+
// SAFETY:
1636+
// - `MaybeUninit` has no bit validity requirements and `[U]` has the same
1637+
// bit validity requirements as `U`, so `[MaybeUninit<T>]` has no bit
1638+
// validity requirements. Thus, it is sound to write uninitialized bytes
1639+
// at every offset.
1640+
// - `MaybeValid<U>` is `repr(transparent)`, and thus has the same layout
1641+
// and field offsets as its contained field of type `U::MaybeUninit`. In
1642+
// this case, `U = [T]`, and so `U::MaybeUninit = [MaybeUninit<T>]`. Thus,
1643+
// `MaybeValid<[T]>` has the same layout and field offsets as
1644+
// `[MaybeUninit<T>]`, which is what we set `MaybeUninit` to here. Thus,
1645+
// they trivially have the same alignment.
1646+
// - By the same token, their raw pointer types are trivially `as` castable
1647+
// and preserve size.
1648+
// - By the same token, `[MaybeUninit<T>]` contains `UnsafeCell`s at the
1649+
// same byte ranges as `MaybeValid<[T]>` does.
1650+
type MaybeUninit = [MaybeUninit<T>];
1651+
1652+
// SAFETY: `as` preserves pointer address and provenance.
1653+
#[allow(clippy::as_conversions)]
1654+
fn raw_from_maybe_uninit(maybe_uninit: *const [MaybeUninit<T>]) -> *const MaybeValid<[T]> {
1655+
maybe_uninit as *const MaybeValid<[T]>
1656+
}
1657+
1658+
// SAFETY: `as` preserves pointer address and provenance.
1659+
#[allow(clippy::as_conversions)]
1660+
fn raw_mut_from_maybe_uninit(maybe_uninit: *mut [MaybeUninit<T>]) -> *mut MaybeValid<[T]> {
1661+
maybe_uninit as *mut MaybeValid<[T]>
1662+
}
1663+
1664+
// SAFETY: `as` preserves pointer address and provenance.
1665+
#[allow(clippy::as_conversions)]
1666+
fn raw_maybe_uninit_from(s: *const MaybeValid<[T]>) -> *const [MaybeUninit<T>] {
1667+
s as *const [MaybeUninit<T>]
1668+
}
1669+
}
1670+
1671+
impl<T> Default for MaybeValid<T> {
1672+
fn default() -> MaybeValid<T> {
1673+
// SAFETY: All of the bytes of `inner` are initialized to 0, and so the
1674+
// safety invariant on `MaybeValid` is upheld.
1675+
MaybeValid { inner: MaybeUninit::zeroed() }
1676+
}
1677+
}
1678+
1679+
impl<T: AsMaybeUninit + ?Sized> MaybeValid<T> {
1680+
/// Converts this `&MaybeValid<T>` to a `&T`.
1681+
///
1682+
/// # Safety
1683+
///
1684+
/// `self` must contain a valid `T`.
1685+
pub unsafe fn assume_valid_ref(&self) -> &T {
1686+
// SAFETY: The caller has promised that `self` contains a valid `T`.
1687+
// Since `Self` is `repr(transparent)`, it has the same layout as
1688+
// `MaybeUninit<T>`, which in turn is guaranteed to have the same layout
1689+
// as `T`. Thus, it is sound to treat `self.inner` as containing a valid
1690+
// `T`.
1691+
unsafe { self.inner.assume_init_ref() }
1692+
}
1693+
1694+
/// Converts this `&mut MaybeValid<T>` to a `&mut T`.
1695+
///
1696+
/// # Safety
1697+
///
1698+
/// `self` must contain a valid `T`.
1699+
pub unsafe fn assume_valid_mut(&mut self) -> &mut T {
1700+
// SAFETY: The caller has promised that `self` contains a valid `T`.
1701+
// Since `Self` is `repr(transparent)`, it has the same layout as
1702+
// `MaybeUninit<T>`, which in turn is guaranteed to have the same layout
1703+
// as `T`. Thus, it is sound to treat `self.inner` as containing a valid
1704+
// `T`.
1705+
unsafe { self.inner.assume_init_mut() }
1706+
}
1707+
1708+
/// Gets a view of this `&T` as a `&MaybeValid<T>`.
1709+
///
1710+
/// There is no mutable equivalent to this function, as producing a `&mut
1711+
/// MaybeValid<T>` from a `&mut T` would allow safe code to write invalid
1712+
/// values which would be accessible through `&mut T`.
1713+
pub fn from_ref(r: &T) -> &MaybeValid<T> {
1714+
let m: *const MaybeUninit<T> = MaybeUninit::from_ref(r);
1715+
#[allow(clippy::as_conversions)]
1716+
let ptr = m as *const MaybeValid<T>;
1717+
// SAFETY: Since `Self` is `repr(transparent)`, it has the same layout
1718+
// as `MaybeUninit<T>`, so the size and alignment here are valid.
1719+
//
1720+
// `MaybeValid<T>`'s bit validity constraints are weaker than those of
1721+
// `T`, so this is guaranteed not to produce an invalid `MaybeValid<T>`.
1722+
// If it were possible to write a different value for `MaybeValid<T>`
1723+
// through the returned reference, it could result in an invalid value
1724+
// being exposed via the `&T`. Luckily, the only way for mutation to
1725+
// happen is if `T` contains an `UnsafeCell` and the caller uses it to
1726+
// perform interior mutation. Importantly, `T` containing an
1727+
// `UnsafeCell` does not permit interior mutation through
1728+
// `MaybeValid<T>`, so it doesn't permit writing uninitialized or
1729+
// otherwise invalid values which would be visible through the original
1730+
// `&T`.
1731+
unsafe { &*ptr }
1732+
}
1733+
}
1734+
1735+
impl<T> MaybeValid<T> {
1736+
/// Converts this `MaybeValid<T>` to a `T`.
1737+
///
1738+
/// # Safety
1739+
///
1740+
/// `self` must contain a valid `T`.
1741+
pub const unsafe fn assume_valid(self) -> T {
1742+
// SAFETY: The caller has promised that `self` contains a valid `T`.
1743+
// Since `Self` is `repr(transparent)`, it has the same layout as
1744+
// `MaybeUninit<T>`, which in turn is guaranteed to have the same layout
1745+
// as `T`. Thus, it is sound to treat `self.inner` as containing a valid
1746+
// `T`.
1747+
unsafe { self.inner.assume_init() }
1748+
}
1749+
}
1750+
1751+
impl<T> MaybeValid<[T]> {
1752+
/// Converts a `MaybeValid<[T]>` to a `[MaybeValid<T>]`.
1753+
///
1754+
/// `MaybeValid<T>` has the same layout as `T`, so these layouts are
1755+
/// equivalent.
1756+
pub const fn as_slice_of_maybe_valids(&self) -> &[MaybeValid<T>] {
1757+
let inner: &[<T as AsMaybeUninit>::MaybeUninit] = &self.inner.inner;
1758+
let inner_ptr: *const [<T as AsMaybeUninit>::MaybeUninit] = inner;
1759+
// Note: this Clippy warning is only emitted on our MSRV (1.61), but not
1760+
// on later versions of Clippy. Thus, we consider it spurious.
1761+
#[allow(clippy::as_conversions)]
1762+
let ret_ptr = inner_ptr as *const [MaybeValid<T>];
1763+
// SAFETY: Since `inner` is a `&[MaybeUninit<T>]`, and `MaybeValid<T>`
1764+
// is a `repr(transparent)` struct around `MaybeUninit<T>`, `inner` has
1765+
// the same layout as `&[MaybeValid<T>]`.
1766+
unsafe { &*ret_ptr }
1767+
}
1768+
}
1769+
1770+
impl<const N: usize, T> MaybeValid<[T; N]> {
1771+
/// Converts a `MaybeValid<[T; N]>` to a `MaybeValid<[T]>`.
1772+
// TODO(#64): Make this `const` once our MSRV is >= 1.64.0 (when
1773+
// `slice_from_raw_parts` was stabilized as `const`).
1774+
pub fn as_slice(&self) -> &MaybeValid<[T]> {
1775+
let base: *const MaybeValid<[T; N]> = self;
1776+
let slice_of_t: *const [T] = ptr::slice_from_raw_parts(base.cast::<T>(), N);
1777+
// Note: this Clippy warning is only emitted on our MSRV (1.61), but not
1778+
// on later versions of Clippy. Thus, we consider it spurious.
1779+
#[allow(clippy::as_conversions)]
1780+
let mv_of_slice = slice_of_t as *const MaybeValid<[T]>;
1781+
// SAFETY: `MaybeValid<T>` is a `repr(transparent)` wrapper around
1782+
// `MaybeUninit<T>`, which in turn has the same layout as `T`. Thus, the
1783+
// trailing slices of `[T]` and of `MaybeValid<[T]>` both have element
1784+
// type `T`. Since the number of elements is preserved during an `as`
1785+
// cast of slice/DST pointers, the resulting `*const MaybeValid<[T]>`
1786+
// has the same number of elements - and thus the same length - as the
1787+
// original `*const [T]`.
1788+
//
1789+
// Thanks to their layouts, `MaybeValid<[T; N]>` and `MaybeValid<[T]>`
1790+
// have the same alignment, so `mv_of_slice` is guaranteed to be
1791+
// aligned.
1792+
unsafe { &*mv_of_slice }
1793+
}
1794+
}
1795+
1796+
impl<T> Debug for MaybeValid<T> {
1797+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1798+
f.pad(core::any::type_name::<Self>())
1799+
}
1800+
}
1801+
15731802
/// A type with no alignment requirement.
15741803
///
15751804
/// An `Unalign` wraps a `T`, removing any alignment requirement. `Unalign<T>`
@@ -3604,6 +3833,90 @@ mod tests {
36043833
assert_eq!(unsafe { m.assume_init_ref() }, &Cell::new(2));
36053834
}
36063835

3836+
#[test]
3837+
fn test_maybe_valid() {
3838+
let m = MaybeValid::<usize>::default();
3839+
// SAFETY: all bit patterns are valid `usize`s, and `m` is initialized.
3840+
let u = unsafe { m.assume_valid() };
3841+
// This ensures that Miri can see whether `u` (and thus `m`) has been
3842+
// properly initialized.
3843+
assert_eq!(u, u);
3844+
3845+
fn bytes_to_maybe_valid(bytes: &mut [u8]) -> &mut MaybeValid<[u8]> {
3846+
// SAFETY: `MaybeValid<[u8]>` has the same layout as `[u8]`, and
3847+
// `bytes` is initialized.
3848+
unsafe {
3849+
#[allow(clippy::as_conversions)]
3850+
return &mut *(bytes as *mut [u8] as *mut MaybeValid<[u8]>);
3851+
}
3852+
}
3853+
3854+
let mut bytes = [0u8, 1, 2];
3855+
let m = bytes_to_maybe_valid(&mut bytes[..]);
3856+
3857+
// SAFETY: `m` was created from a valid `[u8]`.
3858+
let r = unsafe { m.assume_valid_ref() };
3859+
assert_eq!(r.len(), 3);
3860+
assert_eq!(r, [0, 1, 2]);
3861+
3862+
// SAFETY: `m` was created from a valid `[u8]`.
3863+
let r = unsafe { m.assume_valid_mut() };
3864+
assert_eq!(r.len(), 3);
3865+
assert_eq!(r, [0, 1, 2]);
3866+
3867+
r[0] = 1;
3868+
assert_eq!(bytes, [1, 1, 2]);
3869+
3870+
let mut bytes = [0u8, 1, 2];
3871+
let m = bytes_to_maybe_valid(&mut bytes[..]);
3872+
let slc = m.as_slice_of_maybe_valids();
3873+
assert_eq!(slc.len(), 3);
3874+
for i in 0u8..3 {
3875+
// SAFETY: `m` was created from a valid `[u8]`.
3876+
let u = unsafe { slc[usize::from(i)].assume_valid_ref() };
3877+
assert_eq!(u, &i);
3878+
}
3879+
}
3880+
3881+
#[test]
3882+
fn test_maybe_valid_as_slice() {
3883+
let mut m = MaybeValid::<[u8; 3]>::default();
3884+
// SAFETY: all bit patterns are valid `[u8; 3]`s, and `m` is
3885+
// initialized.
3886+
unsafe { *m.assume_valid_mut() = [0, 1, 2] };
3887+
3888+
let slc = m.as_slice().as_slice_of_maybe_valids();
3889+
assert_eq!(slc.len(), 3);
3890+
3891+
for i in 0u8..3 {
3892+
// SAFETY: `m` was initialized as a valid `[u8; 3]`.
3893+
let u = unsafe { slc[usize::from(i)].assume_valid_ref() };
3894+
assert_eq!(u, &i);
3895+
}
3896+
}
3897+
3898+
#[test]
3899+
fn test_maybe_valid_from_ref() {
3900+
use core::cell::Cell;
3901+
3902+
let u = 1usize;
3903+
let m = MaybeValid::from_ref(&u);
3904+
// SAFETY: `m` was constructed from a valid `&usize`.
3905+
assert_eq!(unsafe { m.assume_valid_ref() }, &1usize);
3906+
3907+
// Test that interior mutability doesn't affect correctness or
3908+
// soundness.
3909+
3910+
let c = Cell::new(1usize);
3911+
let m = MaybeValid::from_ref(&c);
3912+
// SAFETY: `m` was constructed from a valid `&usize`.
3913+
assert_eq!(unsafe { m.assume_valid_ref() }, &Cell::new(1));
3914+
3915+
c.set(2);
3916+
// SAFETY: `m` was constructed from a valid `&usize`.
3917+
assert_eq!(unsafe { m.assume_valid_ref() }, &Cell::new(2));
3918+
}
3919+
36073920
#[test]
36083921
fn test_unalign() {
36093922
// Test methods that don't depend on alignment.
@@ -4519,6 +4832,12 @@ mod tests {
45194832
assert_impls!(MaybeUninit<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
45204833
assert_impls!(MaybeUninit<MaybeUninit<NotZerocopy>>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
45214834

4835+
assert_impls!(MaybeValid<u8>: Unaligned, AsBytes, !FromZeroes, !FromBytes);
4836+
assert_impls!(MaybeValid<MaybeValid<u8>>: Unaligned, AsBytes, !FromZeroes, !FromBytes);
4837+
assert_impls!(MaybeValid<[u8]>: Unaligned, AsBytes, !FromZeroes, !FromBytes);
4838+
assert_impls!(MaybeValid<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
4839+
assert_impls!(MaybeValid<MaybeValid<NotZerocopy>>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
4840+
45224841
assert_impls!(Wrapping<u8>: FromZeroes, FromBytes, AsBytes, Unaligned);
45234842
assert_impls!(Wrapping<NotZerocopy>: !FromZeroes, !FromBytes, !AsBytes, !Unaligned);
45244843

0 commit comments

Comments
 (0)