Skip to content

Commit

Permalink
[wip] ptr nocell
Browse files Browse the repository at this point in the history
  • Loading branch information
jswrenn authored and joshlf committed Feb 17, 2024
1 parent 1221b31 commit 049610a
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 39 deletions.
37 changes: 26 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1210,12 +1210,6 @@ pub unsafe trait NoCell {
/// including changes that only affect soundness, which may cause code which
/// uses those items to silently become unsound.
///
/// ## `UnsafeCell`
///
/// `TryFromBytes` may not be implemented for any type which contains an
/// [`UnsafeCell`]. This is a temporary limitation and will be lifted once
/// [`try_from_ref`] has a [`NoCell`] bound.
///
/// [undefined behavior]: https://raphlinus.github.io/programming/rust/2018/08/17/undefined-behavior.html
/// [github-repo]: https://github.com/google/zerocopy
/// [`try_from_ref`]: TryFromBytes::try_from_ref
Expand Down Expand Up @@ -1264,7 +1258,7 @@ pub unsafe trait TryFromBytes {
#[doc(hidden)] // TODO(#5): Finalize name before remove this attribute.
fn try_from_ref(bytes: &[u8]) -> Option<&Self>
where
Self: KnownLayout,
Self: KnownLayout + NoCell,
{
let candidate = Ptr::from_ref(bytes).try_cast_into_no_leftover::<Self>()?;

Expand Down Expand Up @@ -1293,7 +1287,7 @@ pub unsafe trait TryFromBytes {
#[doc(hidden)] // TODO(#5): Finalize name before remove this attribute.
fn try_read_from(bytes: &[u8]) -> Option<Self>
where
Self: Sized,
Self: Sized + NoCell,
{
let candidate = MaybeUninit::<Self>::read_from(bytes)?;
let c_ptr = Ptr::from_maybe_uninit_ref(&candidate);
Expand Down Expand Up @@ -3358,6 +3352,19 @@ safety_comment! {
unsafe_impl!(Option<NonZeroIsize>: TryFromBytes, FromZeros, FromBytes, IntoBytes);
}

safety_comment! {
/// SAFETY:
/// While it's not fully documented, the consensus is that `Box<T>` does not
/// contain any `UnsafeCell`s for `T: Sized` [1].
///
/// [1] https://github.com/rust-lang/unsafe-code-guidelines/issues/492
#[cfg(feature = "alloc")]
unsafe_impl!(
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
T: Sized => NoCell for Box<T>
);
}

safety_comment! {
/// SAFETY:
/// The following types can be transmuted from `[0u8; size_of::<T>()]`. [1]
Expand Down Expand Up @@ -3704,6 +3711,14 @@ safety_comment! {
});
unsafe_impl!(T => FromZeros for *mut T);
}

safety_comment! {
/// SAFETY:
///
/// TODO
unsafe_impl!(T: ?Sized => NoCell for NonNull<T>);
}

safety_comment! {
/// SAFETY:
/// Reference types do not contain any `UnsafeCell`s.
Expand Down Expand Up @@ -8406,14 +8421,14 @@ mod tests {
) -> (NotZerocopy, NotZerocopy);

#[cfg(feature = "alloc")]
assert_impls!(Option<Box<UnsafeCell<NotZerocopy>>>: KnownLayout, TryFromBytes, FromZeros, !NoCell, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<Box<UnsafeCell<NotZerocopy>>>: KnownLayout, NoCell, TryFromBytes, FromZeros, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<Box<[UnsafeCell<NotZerocopy>]>>: KnownLayout, !NoCell, !TryFromBytes, !FromZeros, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<&'static UnsafeCell<NotZerocopy>>: KnownLayout, NoCell, TryFromBytes, FromZeros, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<&'static [UnsafeCell<NotZerocopy>]>: KnownLayout, NoCell, !TryFromBytes, !FromZeros, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<&'static mut UnsafeCell<NotZerocopy>>: KnownLayout, NoCell, TryFromBytes, FromZeros, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<&'static mut [UnsafeCell<NotZerocopy>]>: KnownLayout, NoCell, !TryFromBytes, !FromZeros, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<NonNull<UnsafeCell<NotZerocopy>>>: KnownLayout, TryFromBytes, FromZeros, !NoCell, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<NonNull<[UnsafeCell<NotZerocopy>]>>: KnownLayout, !NoCell, !TryFromBytes, !FromZeros, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<NonNull<UnsafeCell<NotZerocopy>>>: KnownLayout, TryFromBytes, FromZeros, NoCell, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<NonNull<[UnsafeCell<NotZerocopy>]>>: KnownLayout, NoCell, !TryFromBytes, !FromZeros, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<fn()>: KnownLayout, NoCell, FromZeros, !TryFromBytes, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<FnManyArgs>: KnownLayout, NoCell, FromZeros, !TryFromBytes, !FromBytes, !IntoBytes, !Unaligned);
assert_impls!(Option<extern "C" fn()>: KnownLayout, NoCell, FromZeros, !TryFromBytes, !FromBytes, !IntoBytes, !Unaligned);
Expand Down
1 change: 1 addition & 0 deletions src/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ where
/// Checks if the referent is zeroed.
pub(crate) fn is_zeroed<T, I>(ptr: Ptr<'_, T, I>) -> bool
where
T: crate::NoCell,
I: invariant::Invariants<Aliasing = invariant::Shared, Validity = invariant::Initialized>,
{
ptr.as_bytes().as_ref().iter().all(|&byte| byte == 0)
Expand Down
83 changes: 71 additions & 12 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use core::ptr::NonNull;

use crate::{util::AsAddress, KnownLayout, _CastType};
use crate::{util::AsAddress, KnownLayout, NoCell, _CastType};

/// Module used to gate access to [`Ptr`]'s fields.
mod def {
Expand Down Expand Up @@ -70,6 +70,12 @@ mod def {
/// initialized (but not necessarily validly so) in all of the
/// initialized ranges of `T`.
/// - If [`invariant::Valid`], the pointer's referent is bit-valid.
/// 10. During the lifetime 'a, no code will load or store this memory
/// region treating `UnsafeCell`s as existing at different ranges
/// than they exist in `T`.
/// 11. During the lifetime 'a, no reference will exist to this memory
/// which treats `UnsafeCell`s as existing at different ranges than
/// they exist in `T`.
// SAFETY: `NonNull<T>` is covariant over `T` [1].
//
// [1]: https://doc.rust-lang.org/std/ptr/struct.NonNull.html
Expand Down Expand Up @@ -104,6 +110,12 @@ mod def {
/// [`ALIGNMENT_INVARIANT`](invariant::Alignment).
/// 8. `ptr` conforms to the validity invariant of
/// [`VALIDITY_INVARIANT`](invariant::Validity).
/// 9. During the lifetime 'a, no code will load or store this memory
/// region treating `UnsafeCell`s as existing at different ranges
/// than they exist in `T`.
/// 10. During the lifetime 'a, no reference will exist to this memory
/// which treats `UnsafeCell`s as existing at different ranges than
/// they exist in `T`.
pub(super) unsafe fn new(ptr: NonNull<T>) -> Ptr<'a, T, I> {
// SAFETY: The caller has promised to satisfy all safety invariants
// of `Ptr`.
Expand Down Expand Up @@ -408,6 +420,8 @@ mod _conversions {
// invariant of `invariant::Aligned`.
// 8. `ptr`, by invariant on `&'a T`, conforms to the validity
// invariant of `invariant::Valid`.
// 9. The referents of `Ptr<T>` and `&T` have `UnsafeCell`s at
// identical ranges.
unsafe { Self::new(ptr) }
}
}
Expand Down Expand Up @@ -457,7 +471,7 @@ mod _conversions {
/// `&'a MaybeUninit<T>` → `Ptr<'a, T>`
impl<'a, T> Ptr<'a, T, (invariant::Shared, invariant::Aligned, invariant::AnyValidity)>
where
T: 'a,
T: 'a + NoCell,
{
/// Constructs a `Ptr` from a shared reference to a [`MaybeUninit<T>`].
///
Expand Down Expand Up @@ -504,6 +518,8 @@ mod _conversions {
// 8. The returned `Ptr` has validity `invariant::AnyValidity`,
// which is always upheld regardless of the contents of its
// referent.
// 9. TODO. `T: NoCell`. I wonder if this whole method could be
// written as a call to `cast_unsized`.
//
// [1] Per https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#layout-1:
//
Expand Down Expand Up @@ -643,9 +659,11 @@ mod _casts {
/// provenance-preserving transformation of a pointer-like value into
/// another pointer-like value to the same allocation.
///
/// For all kinds of casts, the caller must promise that the the size of
/// the object referenced by the resulting pointer is less than or equal
/// to the size of the object referenced by `self`.
/// For all kinds of casts, the caller must promise that:
/// - the the size of the object referenced by the resulting pointer is
/// less than or equal to the size of the object referenced by `self`.
/// - `UnsafeCell`s in `U` exist at ranges identical to those at which
/// `UnsafeCell`s exist in `T`.
///
/// For pointer-to-pointer casts, it suffices for the caller to
/// additionally promise that `cast(p)` is implemented as:
Expand Down Expand Up @@ -704,6 +722,11 @@ mod _casts {
// `AnyAlignment`.
// 8. `ptr`, trivially, conforms to the validity invariant of
// `AnyValidity`.
// 9. During the lifetime 'a, no code will reference or load or
// store this memory region treating `UnsafeCell`s as existing at
// different ranges than they exist in `U`. This is true by
// invariant on Ptr<'a, T, I>, and preserved through the cast to
// `U` by contract on the caller.
unsafe { Ptr::new(ptr) }
}
}
Expand All @@ -712,6 +735,7 @@ mod _casts {
where
T: 'a,
I: Invariants<Validity = invariant::Initialized>,
T: NoCell,
{
/// Casts this pointer-to-initialized into a pointer-to-bytes.
#[allow(clippy::wrong_self_convention)]
Expand All @@ -723,6 +747,8 @@ mod _casts {
// `slice_from_raw_parts_mut`.
// - The size of the object referenced by the resulting pointer is
// exactly equal to the size of the object referenced by `self`.
// - `T` and `[u8]` trivially contain `UnsafeCell`s at identical
// ranges [u8]`, because both are `NoCell`.
let ptr: Ptr<'a, [u8], _> = unsafe {
self.cast_unsized(|p: *mut T| {
#[allow(clippy::as_conversions)]
Expand Down Expand Up @@ -789,6 +815,12 @@ mod _casts {
// 8. By the above lemma, `slice` conforms to the validity invariant
// of `I::Validity`, because the operations that produced `slice`
// from `self` do not impact validity.
// 9. During the lifetime 'a, no code will reference or load or
// store this memory region treating `UnsafeCell`s as existing at
// different ranges than they exist in `[T]`. This is true by
// invariant on Ptr<'a, [T; N], I>, and preserved through the
// cast to `[T]` because `[T]` has `UnsafeCell`s at exactly the
// same ranges as `[T; N]`.
unsafe { Ptr::new(slice) }
}
}
Expand Down Expand Up @@ -824,6 +856,8 @@ mod _casts {
&self,
cast_type: _CastType,
) -> Option<(Ptr<'a, U, (I::Aliasing, invariant::Aligned, invariant::AnyValidity)>, usize)>
where
U: NoCell,
{
// PANICS: By invariant, the byte range addressed by `self.ptr` does
// not wrap around the address space. This implies that the sum of
Expand Down Expand Up @@ -898,6 +932,11 @@ mod _casts {
// aligned for `U`.
// 8. `ptr`, trivially, conforms to the validity invariant of
// `AnyValidity`.
// 9. During the lifetime 'a, no code will reference or load or
// store this memory region treating `UnsafeCell`s as existing at
// different ranges than they exist in `U`. This is true by
// invariant on Ptr<'a, T, I>, preserved through the cast by the
// bound `U: NoCell`.
Some((unsafe { Ptr::new(ptr) }, split_at))
}

Expand All @@ -915,7 +954,10 @@ mod _casts {
#[inline(always)]
pub(crate) fn try_cast_into_no_leftover<U: 'a + ?Sized + KnownLayout>(
&self,
) -> Option<Ptr<'a, U, (I::Aliasing, invariant::Aligned, invariant::AnyValidity)>> {
) -> Option<Ptr<'a, U, (I::Aliasing, invariant::Aligned, invariant::AnyValidity)>>
where
U: NoCell,
{
// TODO(#67): Remove this allow. See NonNulSlicelExt for more
// details.
#[allow(unstable_name_collisions)]
Expand All @@ -942,11 +984,15 @@ mod _project {
///
/// ## Preconditions
///
/// The caller promises that `projector` projects a field of its
/// argument. Its argument will be `self` casted to a raw pointer. The
/// pointer it returns must reference only a subset of `self`'s bytes.
///
/// The caller also promises that `T` is a struct or union type.
/// The caller promises that:
/// - `projector` projects a field of its argument. Its argument will be
/// `self` casted to a raw pointer. The pointer it returns must
/// reference only a subset of `self`'s bytes.
/// - `T` is a struct or union type.
/// - The projected pointer contains `UnsafeCell`s at exactly the same
/// ranges at which `UnsafeCell`s appear in the projected-from type.
/// This is necessarily true for projections of struct fields, but not
/// for all projections of union fields.
///
/// ## Postconditions
///
Expand Down Expand Up @@ -1012,6 +1058,13 @@ mod _project {
// must be initialized if it is initialized in *any* instance of
// the type. Thus, if `self`'s referent is as-initialized as `T`,
// then it is at least as-initialized as each of its fields.
// 9. During the lifetime 'a, no code will reference or load or
// store this memory region treating `UnsafeCell`s as existing at
// different ranges than they exist in `U`. This is true by
// invariant on Ptr<'a, T, I>, preserved by precondition on
// `cast`. The field type cannot contain `UnsafeCell`s at ranges
// that are not `UnsafeCell` in its parent struct/enum type,
// because the field type is contained in the struct/enum type.
unsafe { Ptr::new(field) }
}
}
Expand Down Expand Up @@ -1138,6 +1191,12 @@ mod _project {
// 8. `elem`, conditionally, conforms to the validity invariant of
// `VALIDITY_INVARIANT`. If `elem` is projected from data valid
// for `[T]`, `elem` will be valid for `T`.
// 9. During the lifetime 'a, no code will reference or load or
// store this memory region treating `UnsafeCell`s as
// existing at different ranges than they exist in `T`. This
// property holds by invariant on `Ptr<'a, [T], I>`, and is
// preserved because `Ptr<'a, T, I>` is an element within
// `Ptr<'a, [T], I>`.
unsafe { Ptr::new(elem) }
})
}
Expand All @@ -1158,7 +1217,7 @@ mod tests {

// - If `size_of::<T>() == 0`, `N == 4`
// - Else, `N == 4 * size_of::<T>()`
fn test<T: ?Sized + KnownLayout + FromBytes, const N: usize>() {
fn test<T: ?Sized + KnownLayout + NoCell + FromBytes, const N: usize>() {
let mut bytes = [MaybeUninit::<u8>::uninit(); N];
let initialized = [MaybeUninit::new(0u8); N];
for start in 0..=bytes.len() {
Expand Down
21 changes: 18 additions & 3 deletions zerocopy-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,10 @@ fn derive_try_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_m
fn is_bit_valid(candidate: ::zerocopy::Maybe<Self>) -> bool {
true #(&& {
// SAFETY: `project` is a field projection of `candidate`,
// and `Self` is a struct type.
// and `Self` is a struct type. The candidate will have
// `UnsafeCell`s at exactly the same ranges as its
// projection, because the projection is a field of the
// candidate struct.
let field_candidate = unsafe {
let project = |slf: *mut Self|
::zerocopy::macro_util::core_reexport::ptr::addr_of_mut!((*slf).#field_names);
Expand Down Expand Up @@ -390,6 +393,7 @@ fn derive_try_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_m
// - any of its fields are `TryFromBytes`

fn derive_try_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
let self_type_trait_bounds = SelfBounds::All(&[Trait::NoCell]);
let extras = Some({
let fields = unn.fields();
let field_names = fields.iter().map(|(name, _ty)| name);
Expand All @@ -403,7 +407,10 @@ fn derive_try_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro
fn is_bit_valid(candidate: ::zerocopy::Maybe<Self>) -> bool {
false #(|| {
// SAFETY: `project` is a field projection of `candidate`,
// and `Self` is a union type.
// and `Self` is a union type. The candidate and projection
// agree exactly on where their `UnsafeCell` ranges are,
// because `Self: NoCell` is enforced by
// `self_type_trait_bounds`.
let field_candidate = unsafe {
let project = |slf: *mut Self|
::zerocopy::macro_util::core_reexport::ptr::addr_of_mut!((*slf).#field_names);
Expand All @@ -416,7 +423,15 @@ fn derive_try_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro
}
)
});
impl_block(ast, unn, Trait::TryFromBytes, FieldBounds::ALL_SELF, SelfBounds::None, None, extras)
impl_block(
ast,
unn,
Trait::TryFromBytes,
FieldBounds::ALL_SELF,
self_type_trait_bounds,
None,
extras,
)
}

const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[
Expand Down
Loading

0 comments on commit 049610a

Please sign in to comment.