Skip to content
Open

[WIP] #2864

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
59 changes: 30 additions & 29 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -863,29 +863,16 @@ unsafe impl<T: TryFromBytes + ?Sized> TryFromBytes for UnsafeCell<T> {
}

#[inline]
fn is_bit_valid<A: invariant::Reference>(candidate: Maybe<'_, Self, A>) -> bool {
// The only way to implement this function is using an exclusive-aliased
// pointer. `UnsafeCell`s cannot be read via shared-aliased pointers
// (other than by using `unsafe` code, which we can't use since we can't
// guarantee how our users are accessing or modifying the `UnsafeCell`).
//
// `is_bit_valid` is documented as panicking or failing to monomorphize
// if called with a shared-aliased pointer on a type containing an
// `UnsafeCell`. In practice, it will always be a monomorphization error.
// Since `is_bit_valid` is `#[doc(hidden)]` and only called directly
// from this crate, we only need to worry about our own code incorrectly
// calling `UnsafeCell::is_bit_valid`. The post-monomorphization error
// makes it easier to test that this is truly the case, and also means
// that if we make a mistake, it will cause downstream code to fail to
// compile, which will immediately surface the mistake and give us a
// chance to fix it quickly.
let c = candidate.into_exclusive_or_pme();

// SAFETY: Since `UnsafeCell<T>` and `T` have the same layout and bit
// validity, `UnsafeCell<T>` is bit-valid exactly when its wrapped `T`
// is. Thus, this is a sound implementation of
// `UnsafeCell::is_bit_valid`.
T::is_bit_valid(c.get_mut())
#[inline]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The #[inline] attribute is duplicated. Please remove one of them.

fn is_bit_valid(candidate: Maybe<'_, Self>) -> bool {
let candidate = unsafe {
candidate.transmute_unchecked::<
crate::wrappers::ReadOnly<T>,
_,
crate::pointer::cast::CastUnsized
>()
};
T::is_bit_valid(candidate)
}
}

Expand Down Expand Up @@ -1339,17 +1326,17 @@ mod tests {

pub(super) trait TestIsBitValidShared<T: ?Sized> {
#[allow(clippy::needless_lifetimes)]
fn test_is_bit_valid_shared<'ptr, A: invariant::Reference>(
fn test_is_bit_valid_shared<'ptr>(
&self,
candidate: Maybe<'ptr, T, A>,
candidate: Maybe<'ptr, T>,
) -> Option<bool>;
}

impl<T: TryFromBytes + Immutable + ?Sized> TestIsBitValidShared<T> for AutorefWrapper<T> {
#[allow(clippy::needless_lifetimes)]
fn test_is_bit_valid_shared<'ptr, A: invariant::Reference>(
fn test_is_bit_valid_shared<'ptr>(
&self,
candidate: Maybe<'ptr, T, A>,
candidate: Maybe<'ptr, T>,
) -> Option<bool> {
Some(T::is_bit_valid(candidate))
}
Expand Down Expand Up @@ -1455,9 +1442,9 @@ mod tests {
#[allow(unused, non_local_definitions)]
impl AutorefWrapper<$ty> {
#[allow(clippy::needless_lifetimes)]
fn test_is_bit_valid_shared<'ptr, A: invariant::Reference>(
fn test_is_bit_valid_shared<'ptr>(
&mut self,
candidate: Maybe<'ptr, $ty, A>,
candidate: Maybe<'ptr, $ty>,
) -> Option<bool> {
assert_on_allowlist!(
test_is_bit_valid_shared($ty):
Expand Down Expand Up @@ -1564,6 +1551,13 @@ mod tests {
// necessarily `IntoBytes`, but that's the corner we've
// backed ourselves into by using `Ptr::from_ref`.
let c = unsafe { c.assume_initialized() };
let c = unsafe {
c.transmute_unchecked::<
crate::wrappers::ReadOnly<$ty>,
_,
crate::pointer::cast::CastUnsized
>()
};
let res = w.test_is_bit_valid_shared(c);
if let Some(res) = res {
assert!(res, "{}::is_bit_valid({:?}) (shared `Ptr`): got false, expected true", stringify!($ty), val);
Expand All @@ -1575,6 +1569,13 @@ mod tests {
// necessarily `IntoBytes`, but that's the corner we've
// backed ourselves into by using `Ptr::from_ref`.
let c = unsafe { c.assume_initialized() };
let c = unsafe {
c.transmute_unchecked::<
crate::wrappers::ReadOnly<$ty>,
_,
crate::pointer::cast::CastUnsized
>().assume_aliasing::<crate::pointer::invariant::Shared>()
};
let res = <$ty as TryFromBytes>::is_bit_valid(c);
assert!(res, "{}::is_bit_valid({:?}) (exclusive `Ptr`): got false, expected true", stringify!($ty), val);

Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1532,7 +1532,7 @@ pub use zerocopy_derive::TryFromBytes;
not(no_zerocopy_diagnostic_on_unimplemented_1_78_0),
diagnostic::on_unimplemented(note = "Consider adding `#[derive(TryFromBytes)]` to `{Self}`")
)]
pub unsafe trait TryFromBytes {
pub unsafe trait TryFromBytes: KnownLayout {
// The `Self: Sized` bound makes it so that `TryFromBytes` is still object
// safe.
#[doc(hidden)]
Expand Down Expand Up @@ -1562,7 +1562,7 @@ pub unsafe trait TryFromBytes {
/// [`UnsafeCell`]: core::cell::UnsafeCell
/// [`Shared`]: invariant::Shared
#[doc(hidden)]
fn is_bit_valid<A: invariant::Reference>(candidate: Maybe<'_, Self, A>) -> bool;
fn is_bit_valid(candidate: Maybe<'_, Self>) -> bool;

/// Attempts to interpret the given `source` as a `&Self`.
///
Expand Down
8 changes: 7 additions & 1 deletion src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,13 @@ mod _transitions {
// This call may panic. If that happens, it doesn't cause any
// soundness issues, as we have not generated any invalid state
// which we need to fix before returning.
if T::is_bit_valid(self.reborrow().forget_aligned()) {
let candidate = unsafe {
self.reborrow()
.forget_aligned()
.transmute_unchecked::<crate::wrappers::ReadOnly<T>, _, crate::pointer::cast::CastUnsized>()
.assume_aliasing::<crate::pointer::invariant::Shared>()
};
if T::is_bit_valid(candidate) {
// SAFETY: If `T::is_bit_valid`, code may assume that `self`
// contains a bit-valid instance of `T`. By `T:
// TryTransmuteFromPtr<T, I::Aliasing, I::Validity, Valid>`, so
Expand Down
32 changes: 19 additions & 13 deletions src/util/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,15 @@ macro_rules! unsafe_impl {
fn only_derive_is_allowed_to_implement_this_trait() {}

#[inline]
fn is_bit_valid<AA: crate::pointer::invariant::Reference>($candidate: Maybe<'_, Self, AA>) -> bool {
fn is_bit_valid($candidate: Maybe<'_, Self>) -> bool {
$is_bit_valid
}
};
(@method TryFromBytes) => {
#[allow(clippy::missing_inline_in_public_items)]
#[cfg_attr(all(coverage_nightly, __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS), coverage(off))]
fn only_derive_is_allowed_to_implement_this_trait() {}
#[inline(always)] fn is_bit_valid<AA: crate::pointer::invariant::Reference>(_: Maybe<'_, Self, AA>) -> bool { true }
#[inline(always)] fn is_bit_valid(_: Maybe<'_, Self>) -> bool { true }
};
(@method $trait:ident) => {
#[allow(clippy::missing_inline_in_public_items, dead_code)]
Expand Down Expand Up @@ -218,12 +218,15 @@ macro_rules! impl_for_transmute_from {
TryFromBytes for $ty:ty [UnsafeCell<$repr:ty>]
) => {
#[inline]
fn is_bit_valid<A: crate::pointer::invariant::Reference>(candidate: Maybe<'_, Self, A>) -> bool {
let c: Maybe<'_, Self, crate::pointer::invariant::Exclusive> = candidate.into_exclusive_or_pme();
let c: Maybe<'_, $repr, _> = c.transmute::<_, _, (_, (_, (BecauseExclusive, BecauseExclusive)))>();
// SAFETY: This macro ensures that `$repr` and `Self` have the same
// size and bit validity. Thus, a bit-valid instance of `$repr` is
// also a bit-valid instance of `Self`.
#[inline]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The #[inline] attribute is duplicated. Please remove one of them.

fn is_bit_valid(candidate: Maybe<'_, Self>) -> bool {
let c = unsafe {
candidate.transmute_unchecked::<
crate::wrappers::ReadOnly<$repr>,
_,
crate::pointer::cast::CastUnsized
>()
};
<$repr as TryFromBytes>::is_bit_valid(c)
}
};
Expand All @@ -233,11 +236,14 @@ macro_rules! impl_for_transmute_from {
TryFromBytes for $ty:ty [<$repr:ty>]
) => {
#[inline]
fn is_bit_valid<A: crate::pointer::invariant::Reference>(candidate: $crate::Maybe<'_, Self, A>) -> bool {
// SAFETY: This macro ensures that `$repr` and `Self` have the same
// size and bit validity. Thus, a bit-valid instance of `$repr` is
// also a bit-valid instance of `Self`.
<$repr as TryFromBytes>::is_bit_valid(candidate.transmute())
fn is_bit_valid(candidate: $crate::Maybe<'_, Self>) -> bool {
<$repr as TryFromBytes>::is_bit_valid(unsafe {
candidate.transmute_unchecked::<
crate::wrappers::ReadOnly<$repr>,
_,
crate::pointer::cast::CastUnsized
>()
})
}
};
(
Expand Down
12 changes: 9 additions & 3 deletions src/wrappers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,14 @@ const _: () = unsafe {
impl_or_verify!(T => Unaligned for Unalign<T>);
impl_or_verify!(T: Immutable => Immutable for Unalign<T>);
impl_or_verify!(
T: TryFromBytes => TryFromBytes for Unalign<T>;
|c| T::is_bit_valid(c.transmute())
T: TryFromBytes + KnownLayout => TryFromBytes for Unalign<T>;
|c| T::is_bit_valid(unsafe {
c.transmute_unchecked::<
crate::wrappers::ReadOnly<T>,
_,
crate::pointer::cast::CastUnsized
>()
})
);
impl_or_verify!(T: FromZeros => FromZeros for Unalign<T>);
impl_or_verify!(T: FromBytes => FromBytes for Unalign<T>);
Expand Down Expand Up @@ -615,7 +621,7 @@ const _: () = unsafe {
const _: () = unsafe {
impl_or_verify!(T: ?Sized + Unaligned => Unaligned for ReadOnly<T>);
impl_or_verify!(
T: ?Sized + TryFromBytes => TryFromBytes for ReadOnly<T>;
T: ?Sized + TryFromBytes + KnownLayout => TryFromBytes for ReadOnly<T>;
|c| T::is_bit_valid(c.transmute::<_, _, BecauseImmutable>())
);
impl_or_verify!(T: ?Sized + FromZeros => FromZeros for ReadOnly<T>);
Expand Down
72 changes: 48 additions & 24 deletions zerocopy-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -879,21 +879,36 @@ fn derive_try_from_bytes_struct(
// validity of a struct is just the composition of the bit
// validities of its fields, so this is a sound implementation
// of `is_bit_valid`.
fn is_bit_valid<___ZerocopyAliasing>(
mut candidate: #zerocopy_crate::Maybe<Self, ___ZerocopyAliasing>,
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference,
{
fn is_bit_valid(
mut candidate: #zerocopy_crate::Maybe<Self>,
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool {
use #zerocopy_crate::util::macro_util::core_reexport;
use #zerocopy_crate::pointer::PtrInner;

// SAFETY: `ReadOnly<Self>` has the same memory layout as `Self`.
let mut candidate = unsafe {
candidate.transmute_unchecked::<
Self,
_,
#zerocopy_crate::pointer::cast::CastUnsized
>()
};

true #(&& {
let field_candidate = candidate.reborrow().project::<
_,
{ #zerocopy_crate::ident_id!(#field_names) }
>();

// SAFETY: `ReadOnly<T>` has the same memory layout as `T`.
let field_candidate = unsafe {
field_candidate.transmute_unchecked::<
#zerocopy_crate::wrappers::ReadOnly<#field_tys>,
_,
#zerocopy_crate::pointer::cast::CastUnsized
>()
Comment on lines +905 to +909
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's an extra space at the beginning of this line, causing inconsistent indentation within the unsafe block. Please remove it for better code formatting.

Suggested change
field_candidate.transmute_unchecked::<
#zerocopy_crate::wrappers::ReadOnly<#field_tys>,
_,
#zerocopy_crate::pointer::cast::CastUnsized
>()
field_candidate.transmute_unchecked::<
#zerocopy_crate::wrappers::ReadOnly<#field_tys>,
_,
#zerocopy_crate::pointer::cast::CastUnsized
>()

};

<#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
Expand Down Expand Up @@ -933,15 +948,21 @@ fn derive_try_from_bytes_union(
// The bit validity of a union is not yet well defined in Rust,
// but it is guaranteed to be no more strict than this
// definition. See #696 for a more in-depth discussion.
fn is_bit_valid<___ZerocopyAliasing>(
mut candidate: #zerocopy_crate::Maybe<'_, Self,___ZerocopyAliasing>
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference,
{
fn is_bit_valid(
mut candidate: #zerocopy_crate::Maybe<'_, Self>
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool {
use #zerocopy_crate::util::macro_util::core_reexport;
use #zerocopy_crate::pointer::PtrInner;

// SAFETY: `ReadOnly<Self>` has the same memory layout as `Self`.
let mut candidate = unsafe {
candidate.transmute_unchecked::<
Self,
_,
#zerocopy_crate::pointer::cast::CastUnsized
>()
};

false #(|| {
// SAFETY:
// - Since `Self: Immutable` is enforced by
Expand All @@ -959,6 +980,15 @@ fn derive_try_from_bytes_union(
>()
};

// SAFETY: `ReadOnly<T>` has the same memory layout as `T`.
let field_candidate = unsafe {
field_candidate.transmute_unchecked::<
#zerocopy_crate::wrappers::ReadOnly<#field_tys>,
_,
#zerocopy_crate::pointer::cast::CastUnsized
>()
};

<#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
Expand Down Expand Up @@ -1040,12 +1070,9 @@ fn try_gen_trivial_is_bit_valid(
if matches!(top_level, Trait::FromBytes) && ast.generics.params.is_empty() {
Some(quote!(
// SAFETY: See inline.
fn is_bit_valid<___ZerocopyAliasing>(
_candidate: #zerocopy_crate::Maybe<Self, ___ZerocopyAliasing>,
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference,
{
fn is_bit_valid(
_candidate: #zerocopy_crate::Maybe<Self>,
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool {
if false {
fn assert_is_from_bytes<T>()
where
Expand Down Expand Up @@ -1083,12 +1110,9 @@ unsafe fn gen_trivial_is_bit_valid_unchecked(zerocopy_crate: &Path) -> proc_macr
quote!(
// SAFETY: The caller of `gen_trivial_is_bit_valid_unchecked` has
// promised that all initialized bit patterns are valid for `Self`.
fn is_bit_valid<___ZerocopyAliasing>(
_candidate: #zerocopy_crate::Maybe<Self, ___ZerocopyAliasing>,
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference,
{
fn is_bit_valid(
_candidate: #zerocopy_crate::Maybe<Self>,
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool {
true
}
)
Expand Down
Loading