Skip to content

Commit ea28664

Browse files
authored
[derive] Correctly handle #[repr(align)] enums (google#1784)
* [derive] Don't support IntoBytes on repr(Rust) types Closes google#1764 * [derive] Correctly handle #[repr(align)] enums Closes google#1758
1 parent 005d1d3 commit ea28664

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

zerocopy-derive/src/enum.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use syn::{parse_quote, DataEnum, Error, Fields, Generics, Ident};
1313
use crate::{derive_try_from_bytes_inner, repr::EnumRepr, Trait};
1414

1515
/// Generates a tag enum for the given enum. This generates an enum with the
16-
/// same `repr`s, variants, and corresponding discriminants, but none of the
17-
/// fields.
16+
/// same non-align `repr`s, variants, and corresponding discriminants, but none
17+
/// of the fields.
1818
pub(crate) fn generate_tag_enum(repr: &EnumRepr, data: &DataEnum) -> TokenStream {
1919
let variants = data.variants.iter().map(|v| {
2020
let ident = &v.ident;
@@ -25,6 +25,14 @@ pub(crate) fn generate_tag_enum(repr: &EnumRepr, data: &DataEnum) -> TokenStream
2525
}
2626
});
2727

28+
// Don't include any `repr(align)` when generating the tag enum, as that
29+
// could add padding after the tag but before any variants, which is not the
30+
// correct behavior.
31+
let repr = match repr {
32+
EnumRepr::Transparent(span) => quote::quote_spanned! { *span => #[repr(transparent)] },
33+
EnumRepr::Compound(c, _) => quote! { #c },
34+
};
35+
2836
quote! {
2937
#repr
3038
#[allow(dead_code)]

zerocopy-derive/tests/enum_to_bytes.rs

+10
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,13 @@ enum HasData32 {
121121
}
122122

123123
util_assert_impl_all!(HasData: imp::IntoBytes);
124+
125+
// After #1752 landed but before #1758 was fixed, this failed to compile because
126+
// the padding check treated the tag type as being `#[repr(u8, align(2))] struct
127+
// Tag { A }`, which is two bytes long, rather than the correct `#[repr(u8)]
128+
// struct Tag { A }`, which is one byte long.
129+
#[derive(imp::IntoBytes)]
130+
#[repr(u8, align(2))]
131+
enum BadTagWouldHavePadding {
132+
A(u8, u16),
133+
}

zerocopy-derive/tests/enum_try_from_bytes.rs

+78
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ fn test_weird_discriminants() {
178178
);
179179
}
180180

181+
// Technically non-portable since this is only `IntoBytes` if the discriminant
182+
// is an `i32` or `u32`, but we'll cross that bridge when we get to it...
181183
#[derive(
182184
Eq, PartialEq, Debug, imp::KnownLayout, imp::Immutable, imp::TryFromBytes, imp::IntoBytes,
183185
)]
@@ -205,6 +207,43 @@ fn test_has_fields() {
205207
);
206208
}
207209

210+
#[derive(Eq, PartialEq, Debug, imp::KnownLayout, imp::Immutable, imp::TryFromBytes)]
211+
#[repr(C, align(16))]
212+
enum HasFieldsAligned {
213+
A(u32),
214+
B { foo: ::core::num::NonZeroU32 },
215+
}
216+
217+
util_assert_impl_all!(HasFieldsAligned: imp::TryFromBytes);
218+
219+
#[test]
220+
fn test_has_fields_aligned() {
221+
const SIZE: usize = ::core::mem::size_of::<HasFieldsAligned>();
222+
223+
#[derive(imp::IntoBytes)]
224+
#[repr(C)]
225+
struct BytesOfHasFieldsAligned {
226+
has_fields: HasFields,
227+
padding: [u8; 8],
228+
}
229+
230+
let wrap = |has_fields| BytesOfHasFieldsAligned { has_fields, padding: [0; 8] };
231+
232+
let bytes: [u8; SIZE] = ::zerocopy::transmute!(wrap(HasFields::A(10)));
233+
imp::assert_eq!(
234+
<HasFieldsAligned as imp::TryFromBytes>::try_read_from_bytes(&bytes[..]),
235+
imp::Ok(HasFieldsAligned::A(10)),
236+
);
237+
238+
let bytes: [u8; SIZE] = ::zerocopy::transmute!(wrap(HasFields::B {
239+
foo: ::core::num::NonZeroU32::new(123456).unwrap()
240+
}));
241+
imp::assert_eq!(
242+
<HasFieldsAligned as imp::TryFromBytes>::try_read_from_bytes(&bytes[..]),
243+
imp::Ok(HasFieldsAligned::B { foo: ::core::num::NonZeroU32::new(123456).unwrap() }),
244+
);
245+
}
246+
208247
#[derive(
209248
Eq, PartialEq, Debug, imp::KnownLayout, imp::Immutable, imp::TryFromBytes, imp::IntoBytes,
210249
)]
@@ -233,6 +272,45 @@ fn test_has_fields_primitive() {
233272
);
234273
}
235274

275+
#[derive(Eq, PartialEq, Debug, imp::KnownLayout, imp::Immutable, imp::TryFromBytes)]
276+
#[repr(u32, align(16))]
277+
enum HasFieldsPrimitiveAligned {
278+
A(u32),
279+
B { foo: ::core::num::NonZeroU32 },
280+
}
281+
282+
util_assert_impl_all!(HasFieldsPrimitiveAligned: imp::TryFromBytes);
283+
284+
#[test]
285+
fn test_has_fields_primitive_aligned() {
286+
const SIZE: usize = ::core::mem::size_of::<HasFieldsPrimitiveAligned>();
287+
288+
#[derive(imp::IntoBytes)]
289+
#[repr(C)]
290+
struct BytesOfHasFieldsPrimitiveAligned {
291+
has_fields: HasFieldsPrimitive,
292+
padding: [u8; 8],
293+
}
294+
295+
let wrap = |has_fields| BytesOfHasFieldsPrimitiveAligned { has_fields, padding: [0; 8] };
296+
297+
let bytes: [u8; SIZE] = ::zerocopy::transmute!(wrap(HasFieldsPrimitive::A(10)));
298+
imp::assert_eq!(
299+
<HasFieldsPrimitiveAligned as imp::TryFromBytes>::try_read_from_bytes(&bytes[..]),
300+
imp::Ok(HasFieldsPrimitiveAligned::A(10)),
301+
);
302+
303+
let bytes: [u8; SIZE] = ::zerocopy::transmute!(wrap(HasFieldsPrimitive::B {
304+
foo: ::core::num::NonZeroU32::new(123456).unwrap()
305+
}));
306+
imp::assert_eq!(
307+
<HasFieldsPrimitiveAligned as imp::TryFromBytes>::try_read_from_bytes(&bytes[..]),
308+
imp::Ok(HasFieldsPrimitiveAligned::B {
309+
foo: ::core::num::NonZeroU32::new(123456).unwrap()
310+
}),
311+
);
312+
}
313+
236314
#[derive(imp::TryFromBytes)]
237315
#[repr(align(4), u32)]
238316
enum HasReprAlignFirst {

0 commit comments

Comments
 (0)