Skip to content

Commit 9c19cbe

Browse files
djkoloskijoshlf
authored andcommitted
[zerocopy-derive] Add support for untagged unions
This change adds support for deriving Unaligned, FromBytes, and AsBytes for untagged unions. In addition, this commit makes a small number of related changes for cleanup purposes: - Implements `Unaligned` for `bool`. - Adds a path dependency for `zerocopy-derive` for easier local development. - Explicitly requires the `visit` feature of `syn`. - Updates the `compiletest_rs` dependency to 0.7 for the bless feature. - Updates existing compiletest failures with new compiler errors. Change-Id: I5dcba0ebcdb6c3c9436c1bbdadba93b4cf8624a7 Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/639087 Reviewed-by: Joshua Liebow-Feeser <joshlf@google.com> Commit-Queue: David Koloski <dkoloski@google.com>
1 parent 908126e commit 9c19cbe

File tree

11 files changed

+451
-98
lines changed

11 files changed

+451
-98
lines changed

Cargo.toml.crates-io

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ simd = []
2424
simd-nightly = ["simd"]
2525

2626
[dependencies]
27-
zerocopy-derive = "0.3.1"
27+
zerocopy-derive = { version = "0.3.1", path = "zerocopy-derive" }
2828

2929
[dependencies.byteorder]
3030
version = "1.3"

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ pub unsafe trait Unaligned {
485485
Self: Sized;
486486
}
487487

488-
impl_for_types!(Unaligned, u8, i8);
488+
impl_for_types!(Unaligned, u8, i8, bool);
489489
impl_for_composite_types!(Unaligned);
490490

491491
// SIMD support

zerocopy-derive/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ rustc_macro("zerocopy-derive") {
2929
"tests/struct_as_bytes.rs",
3030
"tests/struct_from_bytes.rs",
3131
"tests/struct_unaligned.rs",
32+
"tests/union_as_bytes.rs",
33+
"tests/union_from_bytes.rs",
34+
"tests/union_unaligned.rs",
3235

3336
# "tests/compiletest.rs" is intentionally omitted since it needs to be able
3437
# to invoke rustc and there's currently no way for test code to allow

zerocopy-derive/Cargo.toml.crates-io

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ syn = { version = "1.0.5", features = ["visit"] }
2525

2626
[dev-dependencies]
2727
zerocopy = { path = "../" }
28-
compiletest_rs = "0.3"
28+
compiletest_rs = "0.7"

zerocopy-derive/src/lib.rs

Lines changed: 119 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@
77
mod ext;
88
mod repr;
99

10-
use proc_macro;
1110
use proc_macro2::Span;
1211
use quote::quote;
1312
use syn::visit::{self, Visit};
1413
use syn::{
15-
parse_quote, punctuated::Punctuated, token::Comma, Data, DataEnum, DataStruct, DeriveInput,
16-
Error, GenericParam, Ident, Lifetime, Type, TypePath,
14+
parse_quote, punctuated::Punctuated, token::Comma, Data, DataEnum, DataStruct, DataUnion,
15+
DeriveInput, Error, GenericParam, Ident, Lifetime, Type, TypePath,
1716
};
1817

1918
use ext::*;
@@ -42,8 +41,9 @@ pub fn derive_from_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream
4241
match &ast.data {
4342
Data::Struct(strct) => derive_from_bytes_struct(&ast, strct),
4443
Data::Enum(enm) => derive_from_bytes_enum(&ast, enm),
45-
Data::Union(_) => Error::new(Span::call_site(), "unsupported on unions").to_compile_error(),
46-
}.into()
44+
Data::Union(unn) => derive_from_bytes_union(&ast, unn),
45+
}
46+
.into()
4747
}
4848

4949
#[proc_macro_derive(AsBytes)]
@@ -52,8 +52,9 @@ pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
5252
match &ast.data {
5353
Data::Struct(strct) => derive_as_bytes_struct(&ast, strct),
5454
Data::Enum(enm) => derive_as_bytes_enum(&ast, enm),
55-
Data::Union(_) => Error::new(Span::call_site(), "unsupported on unions").to_compile_error(),
56-
}.into()
55+
Data::Union(unn) => derive_as_bytes_union(&ast, unn),
56+
}
57+
.into()
5758
}
5859

5960
#[proc_macro_derive(Unaligned)]
@@ -62,8 +63,9 @@ pub fn derive_unaligned(ts: proc_macro::TokenStream) -> proc_macro::TokenStream
6263
match &ast.data {
6364
Data::Struct(strct) => derive_unaligned_struct(&ast, strct),
6465
Data::Enum(enm) => derive_unaligned_enum(&ast, enm),
65-
Data::Union(_) => Error::new(Span::call_site(), "unsupported on unions").to_compile_error(),
66-
}.into()
66+
Data::Union(unn) => derive_unaligned_union(&ast, unn),
67+
}
68+
.into()
6769
}
6870

6971
// Unwrap a Result<_, Vec<Error>>, converting any Err value into a TokenStream
@@ -77,11 +79,18 @@ macro_rules! try_or_print {
7779
};
7880
}
7981

82+
const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[
83+
&[StructRepr::C],
84+
&[StructRepr::Transparent],
85+
&[StructRepr::Packed],
86+
&[StructRepr::C, StructRepr::Packed],
87+
];
88+
8089
// A struct is FromBytes if:
8190
// - all fields are FromBytes
8291

8392
fn derive_from_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
84-
impl_block(ast, strct, "FromBytes", true, false)
93+
impl_block(ast, strct, "FromBytes", true, PaddingCheck::None)
8594
}
8695

8796
// An enum is FromBytes if:
@@ -124,7 +133,7 @@ fn derive_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Tok
124133
.to_compile_error();
125134
}
126135

127-
impl_block(ast, enm, "FromBytes", true, false)
136+
impl_block(ast, enm, "FromBytes", true, PaddingCheck::None)
128137
}
129138

130139
#[rustfmt::skip]
@@ -151,6 +160,13 @@ const ENUM_FROM_BYTES_CFG: Config<EnumRepr> = {
151160
}
152161
};
153162

163+
// Like structs, unions are FromBytes if
164+
// - all fields are FromBytes
165+
166+
fn derive_from_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
167+
impl_block(ast, unn, "FromBytes", true, PaddingCheck::None)
168+
}
169+
154170
// A struct is AsBytes if:
155171
// - all fields are AsBytes
156172
// - repr(C) or repr(transparent) and
@@ -164,35 +180,26 @@ fn derive_as_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2:
164180
.to_compile_error();
165181
}
166182

167-
let reprs = try_or_print!(STRUCT_AS_BYTES_CFG.validate_reprs(ast));
183+
// TODO(https://github.com/rust-lang/reference/pull/1163): If this PR gets
184+
// merged, remove this TODO comment. Otherwise, if the repr(packed) guarantees
185+
// turn out to not be as strong as we're relying on them to be (namely, that
186+
// repr(packed) or repr(packed(1)) guarantee no inter-field padding), we will
187+
// need to change what guarantees we accept from repr(packed).
168188

169-
let require_size_check = match reprs.as_slice() {
170-
[StructRepr::C] | [StructRepr::Transparent] => true,
171-
[StructRepr::Packed] | [StructRepr::C, StructRepr::Packed] => false,
172-
// validate_reprs has already validated that it's one of the preceding
173-
// patterns
174-
_ => unreachable!(),
175-
};
189+
let reprs = try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast));
190+
let padding_check =
191+
if reprs.contains(&StructRepr::Packed) { PaddingCheck::None } else { PaddingCheck::Struct };
176192

177-
impl_block(ast, strct, "AsBytes", true, require_size_check)
193+
impl_block(ast, strct, "AsBytes", true, padding_check)
178194
}
179195

180-
#[rustfmt::skip]
181-
const STRUCT_AS_BYTES_CFG: Config<StructRepr> = {
182-
use StructRepr::*;
183-
Config {
184-
// NOTE: Since disallowed_but_legal_combinations is empty, this message
185-
// will never actually be emitted.
186-
allowed_combinations_message: r#"AsBytes requires repr of "C", "transparent", or "packed""#,
187-
derive_unaligned: false,
188-
allowed_combinations: &[
189-
&[C],
190-
&[Transparent],
191-
&[C, Packed],
192-
&[Packed],
193-
],
194-
disallowed_but_legal_combinations: &[],
195-
}
196+
const STRUCT_UNION_AS_BYTES_CFG: Config<StructRepr> = Config {
197+
// NOTE: Since disallowed_but_legal_combinations is empty, this message
198+
// will never actually be emitted.
199+
allowed_combinations_message: r#"AsBytes requires either a) repr "C" or "transparent" with all fields implementing AsBytes or, b) repr "packed""#,
200+
derive_unaligned: false,
201+
allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS,
202+
disallowed_but_legal_combinations: &[],
196203
};
197204

198205
// An enum is AsBytes if it is C-like and has a defined repr
@@ -206,7 +213,7 @@ fn derive_as_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Token
206213
// We don't care what the repr is; we only care that it is one of the
207214
// allowed ones.
208215
let _: Vec<repr::EnumRepr> = try_or_print!(ENUM_AS_BYTES_CFG.validate_reprs(ast));
209-
impl_block(ast, enm, "AsBytes", false, false)
216+
impl_block(ast, enm, "AsBytes", false, PaddingCheck::None)
210217
}
211218

212219
#[rustfmt::skip]
@@ -234,43 +241,48 @@ const ENUM_AS_BYTES_CFG: Config<EnumRepr> = {
234241
}
235242
};
236243

244+
// A union is AsBytes if:
245+
// - all fields are AsBytes
246+
// - repr(C), repr(transparent), or repr(packed)
247+
// - no padding (size of union equals size of each field type)
248+
249+
fn derive_as_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
250+
// TODO: Support type parameters.
251+
if !ast.generics.params.is_empty() {
252+
return Error::new(Span::call_site(), "unsupported on types with type parameters")
253+
.to_compile_error();
254+
}
255+
256+
// TODO(https://github.com/rust-lang/reference/pull/1163): If this PR gets
257+
// merged, remove this TODO comment. Otherwise, if the repr(packed) guarantees
258+
// turn out to not be as strong as we're relying on them to be (namely, that
259+
// repr(packed) or repr(packed(1)) guarantee no inter-field padding), we will
260+
// need to change what guarantees we accept from repr(packed).
261+
try_or_print!(STRUCT_UNION_AS_BYTES_CFG.validate_reprs(ast));
262+
263+
impl_block(ast, unn, "AsBytes", true, PaddingCheck::Union)
264+
}
265+
237266
// A struct is Unaligned if:
238267
// - repr(align) is no more than 1 and either
239268
// - repr(C) or repr(transparent) and
240269
// - all fields Unaligned
241270
// - repr(packed)
242271

243272
fn derive_unaligned_struct(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream {
244-
let reprs = try_or_print!(STRUCT_UNALIGNED_CFG.validate_reprs(ast));
273+
let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast));
274+
let require_trait_bound = !reprs.contains(&StructRepr::Packed);
245275

246-
let require_trait_bound = match reprs.as_slice() {
247-
[StructRepr::C] | [StructRepr::Transparent] => true,
248-
[StructRepr::Packed] | [StructRepr::C, StructRepr::Packed] => false,
249-
// validate_reprs has already validated that it's one of the preceding
250-
// patterns
251-
_ => unreachable!(),
252-
};
253-
254-
impl_block(ast, strct, "Unaligned", require_trait_bound, false)
276+
impl_block(ast, strct, "Unaligned", require_trait_bound, PaddingCheck::None)
255277
}
256278

257-
#[rustfmt::skip]
258-
const STRUCT_UNALIGNED_CFG: Config<StructRepr> = {
259-
use StructRepr::*;
260-
Config {
261-
// NOTE: Since disallowed_but_legal_combinations is empty, this message
262-
// will never actually be emitted.
263-
allowed_combinations_message:
264-
r#"Unaligned requires either a) repr "C" or "transparent" with all fields implementing Unaligned or, b) repr "packed""#,
265-
derive_unaligned: true,
266-
allowed_combinations: &[
267-
&[C],
268-
&[Transparent],
269-
&[Packed],
270-
&[C, Packed],
271-
],
272-
disallowed_but_legal_combinations: &[],
273-
}
279+
const STRUCT_UNION_UNALIGNED_CFG: Config<StructRepr> = Config {
280+
// NOTE: Since disallowed_but_legal_combinations is empty, this message
281+
// will never actually be emitted.
282+
allowed_combinations_message: r#"Unaligned requires either a) repr "C" or "transparent" with all fields implementing Unaligned or, b) repr "packed""#,
283+
derive_unaligned: true,
284+
allowed_combinations: STRUCT_UNION_ALLOWED_REPR_COMBINATIONS,
285+
disallowed_but_legal_combinations: &[],
274286
};
275287

276288
// An enum is Unaligned if:
@@ -292,7 +304,7 @@ fn derive_unaligned_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2::Toke
292304
// of true for require_trait_bounds doesn't really do anything. But it's
293305
// marginally more future-proof in case that restriction is lifted in the
294306
// future.
295-
impl_block(ast, enm, "Unaligned", true, false)
307+
impl_block(ast, enm, "Unaligned", true, PaddingCheck::None)
296308
}
297309

298310
#[rustfmt::skip]
@@ -320,12 +332,35 @@ const ENUM_UNALIGNED_CFG: Config<EnumRepr> = {
320332
}
321333
};
322334

335+
// Like structs, a union is Unaligned if:
336+
// - repr(align) is no more than 1 and either
337+
// - repr(C) or repr(transparent) and
338+
// - all fields Unaligned
339+
// - repr(packed)
340+
341+
fn derive_unaligned_union(ast: &DeriveInput, unn: &DataUnion) -> proc_macro2::TokenStream {
342+
let reprs = try_or_print!(STRUCT_UNION_UNALIGNED_CFG.validate_reprs(ast));
343+
let require_trait_bound = !reprs.contains(&StructRepr::Packed);
344+
345+
impl_block(ast, unn, "Unaligned", require_trait_bound, PaddingCheck::None)
346+
}
347+
348+
// This enum describes what kind of padding check needs to be generated for the associated impl
349+
enum PaddingCheck {
350+
// No additional padding check is required
351+
None,
352+
// Check that the sum of the fields' sizes exactly equals the struct's size
353+
Struct,
354+
// Check that the size of each field exactly equals the union's size
355+
Union,
356+
}
357+
323358
fn impl_block<D: DataExt>(
324359
input: &DeriveInput,
325360
data: &D,
326361
trait_name: &str,
327362
require_trait_bound: bool,
328-
require_size_check: bool,
363+
padding_check: PaddingCheck,
329364
) -> proc_macro2::TokenStream {
330365
// In this documentation, we will refer to this hypothetical struct:
331366
//
@@ -528,8 +563,9 @@ fn impl_block<D: DataExt>(
528563
quote!()
529564
};
530565

531-
let size_check_body = if require_size_check && !field_types.is_empty() {
532-
quote!(
566+
let size_check_body = match (field_types.is_empty(), padding_check) {
567+
(true, _) | (false, PaddingCheck::None) => quote!(),
568+
(false, PaddingCheck::Struct) => quote!(
533569
const _: () = {
534570
trait HasPadding<const HAS_PADDING: bool> {}
535571
fn assert_no_padding<T: HasPadding<false>>() {}
@@ -540,9 +576,19 @@ fn impl_block<D: DataExt>(
540576
impl HasPadding<HAS_PADDING> for #type_ident {}
541577
let _ = assert_no_padding::<#type_ident>;
542578
};
543-
)
544-
} else {
545-
quote!()
579+
),
580+
(false, PaddingCheck::Union) => quote!(
581+
const _: () = {
582+
trait FieldsAreSameSize<const FIELDS_ARE_SAME_SIZE: bool> {}
583+
fn assert_fields_are_same_size<T: FieldsAreSameSize<true>>() {}
584+
585+
const COMPOSITE_TYPE_SIZE: usize = ::core::mem::size_of::<#type_ident>();
586+
const FIELDS_ARE_SAME_SIZE: bool = true
587+
#(&& (::core::mem::size_of::<#field_types>() == COMPOSITE_TYPE_SIZE))*;
588+
impl FieldsAreSameSize<FIELDS_ARE_SAME_SIZE> for #type_ident {}
589+
let _ = assert_fields_are_same_size::<#type_ident>;
590+
};
591+
),
546592
};
547593

548594
quote! {
@@ -587,7 +633,7 @@ mod tests {
587633
&& elements_are_sorted_and_deduped(&config.disallowed_but_legal_combinations)
588634
}
589635

590-
assert!(config_is_sorted(&STRUCT_UNALIGNED_CFG));
636+
assert!(config_is_sorted(&STRUCT_UNION_UNALIGNED_CFG));
591637
assert!(config_is_sorted(&ENUM_FROM_BYTES_CFG));
592638
assert!(config_is_sorted(&ENUM_UNALIGNED_CFG));
593639
}
@@ -605,7 +651,7 @@ mod tests {
605651
overlap(config.allowed_combinations, config.disallowed_but_legal_combinations)
606652
}
607653

608-
assert!(!config_overlaps(&STRUCT_UNALIGNED_CFG));
654+
assert!(!config_overlaps(&STRUCT_UNION_UNALIGNED_CFG));
609655
assert!(!config_overlaps(&ENUM_FROM_BYTES_CFG));
610656
assert!(!config_overlaps(&ENUM_UNALIGNED_CFG));
611657
}

zerocopy-derive/tests/compiletest.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use compiletest_rs::{common::Mode, Config};
99
#[test]
1010
fn ui() {
1111
let mut config = Config {
12+
// Uncomment to bless tests
13+
// bless: true,
1214
mode: Mode::Ui,
1315
src_base: PathBuf::from("tests/ui"),
1416
target_rustcflags: Some("-L target/debug -L target/debug/deps".to_string()),

0 commit comments

Comments
 (0)