diff --git a/zerocopy-derive/src/lib.rs b/zerocopy-derive/src/lib.rs index 22e9dba9c6..d17e1b1bad 100644 --- a/zerocopy-derive/src/lib.rs +++ b/zerocopy-derive/src/lib.rs @@ -174,10 +174,10 @@ fn derive_known_layout_inner( _top_level: Trait, zerocopy_crate: &Path, ) -> Result { - 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 @@ -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); @@ -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}; @@ -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)* @@ -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 diff --git a/zerocopy-derive/tests/struct_known_layout.rs b/zerocopy-derive/tests/struct_known_layout.rs index 69427d1d97..a0702c33fe 100644 --- a/zerocopy-derive/tests/struct_known_layout.rs +++ b/zerocopy-derive/tests/struct_known_layout.rs @@ -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 { + a: T, +} + +util_assert_impl_all!(TransparentGeneric<[u8]>: imp::KnownLayout); + +#[derive(imp::KnownLayout)] +#[repr(transparent)] +struct TransparentZsts { + a: (), + c: imp::PhantomData, + 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);