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
29 changes: 28 additions & 1 deletion zerocopy-derive/src/derive/into_bytes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use proc_macro2::{Span, TokenStream};
use quote::quote;
use quote::{quote, ToTokens};
use syn::{Data, DataEnum, DataStruct, DataUnion, Error, Type};

use crate::{
Expand Down Expand Up @@ -68,6 +68,23 @@ fn derive_into_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> Result<TokenStream
} else {
(Some(PaddingCheck::Struct), false)
}
} else if is_c && !repr.is_align_gt_1() && all_fields_same_type(strct) {
// All fields have the same syntactic type `T`, which under
// `repr(C)` without `#[repr(align)]` is sufficient to prove no
// padding. The `repr(C)` layout algorithm places each field at
// an offset that is a multiple of that field's alignment; with
// every field having the same type, the struct's alignment
// equals `align_of::<T>()`, field `i` sits at offset `i *
// size_of::<T>()` (always a multiple of `align_of::<T>()`), and
// the struct's total size is `N * size_of::<T>()` (also a
// multiple of its alignment). Hence there is no inter-field or
// trailing padding, and requiring `T: IntoBytes` (so that `T`
// itself is padding-free) proves the struct is padding-free.
//
// We prefer this to the `Unaligned`-requiring branch below
// because it accepts strictly more types without giving up any
// soundness guarantees.
(None, false)
} else if is_c && !repr.is_align_gt_1() {
// We can't use a padding check since there are generic type arguments.
// Instead, we require all field types to implement `Unaligned`. This
Expand Down Expand Up @@ -96,6 +113,16 @@ fn derive_into_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> Result<TokenStream
.build())
}

fn all_fields_same_type(strct: &DataStruct) -> bool {
let fields = strct.fields();
let mut fields = fields.into_iter().map(|(_, _, ty)| ty.into_token_stream().to_string());
if let Some(first) = fields.next() {
fields.all(|field| field == first)
} else {
true
}
}

fn derive_into_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
if !repr.is_c() && !repr.is_primitive() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#[allow(
deprecated,
private_bounds,
non_local_definitions,
non_camel_case_types,
non_upper_case_globals,
non_snake_case,
non_ascii_idents,
clippy::missing_inline_in_public_items,
)]
#[deny(ambiguous_associated_items)]
#[automatically_derived]
const _: () = {
unsafe impl<T> ::zerocopy::IntoBytes for Foo<T>
where
T: ::zerocopy::IntoBytes,
T: ::zerocopy::IntoBytes,
{
fn only_derive_is_allowed_to_implement_this_trait() {}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#[allow(
deprecated,
private_bounds,
non_local_definitions,
non_camel_case_types,
non_upper_case_globals,
non_snake_case,
non_ascii_idents,
clippy::missing_inline_in_public_items,
)]
#[deny(ambiguous_associated_items)]
#[automatically_derived]
const _: () = {
unsafe impl<P: Config> ::zerocopy::IntoBytes for Foo<P>
where
P::BaseField: ::zerocopy::IntoBytes,
P::BaseField: ::zerocopy::IntoBytes,
P::BaseField: ::zerocopy::IntoBytes,
{
fn only_derive_is_allowed_to_implement_this_trait() {}
}
};
35 changes: 35 additions & 0 deletions zerocopy-derive/src/output_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,41 @@ fn test_into_bytes_struct_trailing() {
}
}

#[test]
fn test_into_bytes_struct_homogeneous_generic() {
// A `#[repr(C)]` generic struct whose fields all share the same
// syntactic type has no padding, so the emitted `IntoBytes` impl
// bounds only on `IntoBytes` for the shared type and omits the
// `Unaligned` bound that the fallback branch would otherwise add.
test! {
IntoBytes {
#[repr(C)]
struct Foo<T> {
a: T,
b: T,
}
} expands to "expected/into_bytes_struct_homogeneous_generic.expected.rs"
}
}

#[test]
fn test_into_bytes_struct_homogeneous_generic_assoc_ty() {
// Exercises the case in which every field is an associated-type
// projection of the single type parameter. Token comparison sees
// the same sequence `P :: BaseField` for every field, so the
// homogeneous branch applies.
test! {
IntoBytes {
#[repr(C)]
struct Foo<P: Config> {
c0: P::BaseField,
c1: P::BaseField,
c2: P::BaseField,
}
} expands to "expected/into_bytes_struct_homogeneous_generic_assoc_ty.expected.rs"
}
}

#[test]
fn test_into_bytes_struct_trailing_generic() {
test! {
Expand Down
27 changes: 27 additions & 0 deletions zerocopy-derive/tests/struct_to_bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,33 @@ struct ReprCGenericOneField<T: ?imp::Sized> {
util_assert_impl_all!(ReprCGenericOneField<util::AU16>: imp::IntoBytes);
util_assert_impl_all!(ReprCGenericOneField<[util::AU16]>: imp::IntoBytes);

// When every field of a generic `repr(C)` struct has the same syntactic
// type, the struct has no padding regardless of the alignment of that
// type, so `IntoBytes` can be derived requiring only that the shared
// type itself is `IntoBytes` (no `Unaligned` bound needed).

#[derive(imp::IntoBytes)]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(C)]
struct ReprCGenericHomogeneousTuple<T>(T, T);

util_assert_impl_all!(ReprCGenericHomogeneousTuple<u8>: imp::IntoBytes);
util_assert_impl_all!(ReprCGenericHomogeneousTuple<u64>: imp::IntoBytes);
util_assert_impl_all!(ReprCGenericHomogeneousTuple<util::AU16>: imp::IntoBytes);

#[derive(imp::IntoBytes)]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(C)]
struct ReprCGenericHomogeneousNamed<T> {
x: T,
y: T,
z: T,
}

util_assert_impl_all!(ReprCGenericHomogeneousNamed<u8>: imp::IntoBytes);
util_assert_impl_all!(ReprCGenericHomogeneousNamed<u64>: imp::IntoBytes);
util_assert_impl_all!(ReprCGenericHomogeneousNamed<util::AU16>: imp::IntoBytes);

#[derive(imp::IntoBytes)]
#[zerocopy(crate = "zerocopy_renamed")]
#[repr(C)]
Expand Down
Loading