Skip to content
Draft
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
36 changes: 29 additions & 7 deletions zerocopy-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,10 @@ fn derive_known_layout_inner(
_top_level: Trait,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let is_repr_c_struct = match &ast.data {
let is_repr_c_or_transparent_struct = match &ast.data {
Data::Struct(..) => {
let repr = StructUnionRepr::from_attrs(&ast.attrs)?;
if repr.is_c() {
if repr.is_c() || repr.is_transparent() {
Some(repr)
} else {
None
Expand All @@ -191,7 +191,7 @@ fn derive_known_layout_inner(
let (self_bounds, inner_extras, outer_extras) = if let (
Some(repr),
Some((trailing_field, leading_fields)),
) = (is_repr_c_struct, fields.split_last())
) = (is_repr_c_or_transparent_struct, fields.split_last())
{
let (_vis, trailing_field_name, trailing_field_ty) = trailing_field;
let leading_fields_tys = leading_fields.iter().map(|(_vis, _name, ty)| ty);
Expand Down Expand Up @@ -283,9 +283,15 @@ fn derive_known_layout_inner(
// correctly accounted for.
//
// We respect all three of these preconditions here. This
// expansion is only used if `is_repr_c_struct`, we enumerate
// the fields in order, and we extract the values of `align(N)`
// and `packed(N)`.
// expansion is only used if `is_repr_c_or_transparent_struct`
// is some, we enumerate the fields in order, and we extract the
// values of `align(N)` and `packed(N)`.
//
// Note that `DstLayout::for_repr_c_struct` is also sound for
// `repr(transparent)` structs, since `repr(transparent)`
// guarantees that the struct has the same layout as its only
// non-ZST field, which is also what `repr(C)` would guarantee
// for the same fields.
const LAYOUT: #zerocopy_crate::DstLayout = {
use #zerocopy_crate::util::macro_util::core_reexport::num::NonZeroUsize;
use #zerocopy_crate::{DstLayout, KnownLayout};
Expand Down Expand Up @@ -358,6 +364,21 @@ fn derive_known_layout_inner(
<#trailing_field_ty as #zerocopy_crate::KnownLayout>::MaybeUninit
});

// For `repr(transparent)` structs, we prefer to use `repr(C)` for
// the shadow struct. This is because `repr(transparent)` has strict
// requirements about ZST fields (they must have alignment <= the
// non-ZST field). Our shadow struct wraps fields in `MaybeUninit`
// or `ManuallyDrop`, which might subtly change whether rustc
// considers them compatible with `repr(transparent)` rules, even if
// layout is identical. `repr(C)` is guaranteed to produce the same
// layout as `repr(transparent)` for valid `repr(transparent)` types
// (ZSTs + one non-ZST field), but is more permissive.
let shadow_repr = if repr.is_transparent() {
quote!(#[repr(C)])
} else {
quote!(#repr)
};

quote! {
#(#field_defs)*

Expand All @@ -370,12 +391,13 @@ fn derive_known_layout_inner(
// except that they admit uninit bytes. We indirect through
// `Field` to ensure that occurrences of `Self` resolve to
// `#ty`, not `__ZerocopyKnownLayoutMaybeUninit` (see #2116).
#repr
#shadow_repr
#[doc(hidden)]
// Required on some rustc versions due to a lint that is only
// triggered when `derive(KnownLayout)` is applied to `repr(C)`
// structs that are generated by macros. See #2177 for details.
#[allow(private_bounds)]
#[allow(missing_debug_implementations)]
#vis struct __ZerocopyKnownLayoutMaybeUninit<#params> (
#(#zerocopy_crate::util::macro_util::core_reexport::mem::MaybeUninit<
<#ident #ty_generics as
Expand Down
44 changes: 44 additions & 0 deletions zerocopy-derive/tests/struct_known_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,47 @@ struct RawIdentifier {
}

util_assert_impl_all!(RawIdentifier: imp::KnownLayout);

// Deriving `KnownLayout` should work for `repr(transparent)` structs with
// unsized fields.

#[derive(imp::KnownLayout)]
#[repr(transparent)]
struct TransparentUnsized {
a: [u8],
}

util_assert_impl_all!(TransparentUnsized: imp::KnownLayout);

#[derive(imp::KnownLayout)]
#[repr(transparent)]
struct TransparentGeneric<T: ?imp::Sized> {
a: T,
}

util_assert_impl_all!(TransparentGeneric<[u8]>: imp::KnownLayout);

#[derive(imp::KnownLayout)]
#[repr(transparent)]
struct TransparentZsts {
a: (),
c: imp::PhantomData<u8>,
b: [u8],
}

util_assert_impl_all!(TransparentZsts: imp::KnownLayout);

// `repr(transparent)` allows ZSTs with the same or smaller alignment than the
// non-ZST field.
#[derive(imp::KnownLayout)]
#[repr(align(1))]
struct Align1;

#[derive(imp::KnownLayout)]
#[repr(transparent)]
struct TransparentMaxAlignZst {
a: Align1,
b: u16,
}

util_assert_impl_all!(TransparentMaxAlignZst: imp::KnownLayout);