Skip to content

Commit b0a8477

Browse files
authored
[derive] Fix handling of repr(packed) (google#1781)
While we're here, improve error messages and add tests for deriving `IntoBytes` on unions. Closes google#1763
1 parent 8e0de3f commit b0a8477

14 files changed

+413
-177
lines changed

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6268,7 +6268,7 @@ mod tests {
62686268
assert_impl_all!(Foo<u8>: Unaligned);
62696269

62706270
#[derive(IntoBytes, FromBytes, Unaligned)]
6271-
#[repr(packed)]
6271+
#[repr(C, packed)]
62726272
#[allow(dead_code)] // We never construct this type
62736273
struct Bar<T, U> {
62746274
_t: T,

zerocopy-derive/src/lib.rs

+16-11
Original file line numberDiff line numberDiff line change
@@ -753,22 +753,21 @@ fn derive_into_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> Result<Tok
753753
let is_packed_1 = repr.is_packed_1();
754754
let num_fields = strct.fields().len();
755755

756-
let (padding_check, require_unaligned_fields) = if is_transparent || is_packed_1 {
756+
let (padding_check, require_unaligned_fields) = if is_transparent || (is_c && is_packed_1) {
757757
// No padding check needed.
758758
// - repr(transparent): The layout and ABI of the whole struct is the
759759
// same as its only non-ZST field (meaning there's no padding outside
760760
// of that field) and we require that field to be `IntoBytes` (meaning
761761
// there's no padding in that field).
762-
// - repr(packed): Any inter-field padding bytes are removed, meaning
762+
// - repr(C, packed): Any inter-field padding bytes are removed, meaning
763763
// that any padding bytes would need to come from the fields, all of
764764
// which we require to be `IntoBytes` (meaning they don't have any
765765
// padding).
766766
(None, false)
767-
} else if is_c && num_fields <= 1 {
767+
} else if is_c && !repr.is_align_gt_1() && num_fields <= 1 {
768768
// No padding check needed. A repr(C) struct with zero or one field has
769-
// no padding.
770-
//
771-
// TODO(#1763): This is probably unsound! Fix it.
769+
// no padding unless #[repr(align)] explicitly adds padding, which we
770+
// check for in this branch's condition.
772771
(None, false)
773772
} else if ast.generics.params.is_empty() {
774773
// Since there are no generics, we can emit a padding check. This is
@@ -777,16 +776,15 @@ fn derive_into_bytes_struct(ast: &DeriveInput, strct: &DataStruct) -> Result<Tok
777776
//
778777
// TODO(#1764): This is probably unsound! Fix it.
779778
(Some(PaddingCheck::Struct), false)
780-
} else if is_c {
779+
} else if is_c && !repr.is_align_gt_1() {
781780
// We can't use a padding check since there are generic type arguments.
782781
// Instead, we require all field types to implement `Unaligned`. This
783782
// ensures that the `repr(C)` layout algorithm will not insert any
784-
// padding.
783+
// padding unless #[repr(align)] explicitly adds padding, which we check
784+
// for in this branch's condition.
785785
//
786786
// TODO(#10): Support type parameters for non-transparent, non-packed
787787
// structs without requiring `Unaligned`.
788-
//
789-
// TODO(#1763): This is probably unsound! Fix it.
790788
(None, true)
791789
} else {
792790
return Err(Error::new(Span::call_site(), "must have a non-align #[repr(...)] attribute or #[repr(packed)] in order to guarantee this type's memory layout"));
@@ -842,9 +840,16 @@ fn derive_into_bytes_union(ast: &DeriveInput, unn: &DataUnion) -> Result<TokenSt
842840
return Err(Error::new(Span::call_site(), "unsupported on types with type parameters"));
843841
}
844842

843+
// Because we don't support generics, we don't need to worry about
844+
// special-casing different reprs. So long as there is *some* repr which
845+
// guarantees the layout, our `PaddingCheck::Union` guarantees that there is
846+
// no padding.
845847
let repr = StructUnionRepr::from_attrs(&ast.attrs)?;
846848
if !repr.is_c() && !repr.is_transparent() && !repr.is_packed_1() {
847-
return Err(Error::new(Span::call_site(), "must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout"));
849+
return Err(Error::new(
850+
Span::call_site(),
851+
"must be #[repr(C)], #[repr(packed)], or #[repr(transparent)]",
852+
));
848853
}
849854

850855
Ok(impl_block(

zerocopy-derive/src/repr.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,11 @@ impl<Prim, Packed> Repr<Prim, Packed> {
132132
None
133133
}
134134
}
135-
}
136135

137-
impl<Prim, Packed> Repr<Prim, Packed> {
136+
pub(crate) fn is_align_gt_1(&self) -> bool {
137+
self.get_align().map(|n| n.t.get() > 1).unwrap_or(false)
138+
}
139+
138140
/// When deriving `Unaligned`, validate that the decorated type has no
139141
/// `#[repr(align(N))]` attribute where `N > 1`. If no such attribute exists
140142
/// (including if `N == 1`), this returns `Ok(())`, and otherwise it returns

zerocopy-derive/tests/struct_to_bytes.rs

+1-32
Original file line numberDiff line numberDiff line change
@@ -102,37 +102,6 @@ struct CPackedGeneric<T, U: ?imp::Sized> {
102102
util_assert_impl_all!(CPackedGeneric<u8, util::AU16>: imp::IntoBytes);
103103
util_assert_impl_all!(CPackedGeneric<u8, [util::AU16]>: imp::IntoBytes);
104104

105-
#[derive(imp::IntoBytes)]
106-
#[repr(packed)]
107-
struct Packed {
108-
a: u8,
109-
// NOTE: The `u16` type is not guaranteed to have alignment 2, although it
110-
// does on many platforms. However, to fix this would require a custom type
111-
// with a `#[repr(align(2))]` attribute, and `#[repr(packed)]` types are not
112-
// allowed to transitively contain `#[repr(align(...))]` types. Thus, we
113-
// have no choice but to use `u16` here. Luckily, these tests run in CI on
114-
// platforms on which `u16` has alignment 2, so this isn't that big of a
115-
// deal.
116-
b: u16,
117-
}
118-
119-
util_assert_impl_all!(Packed: imp::IntoBytes);
120-
121-
#[derive(imp::IntoBytes)]
122-
#[repr(packed)]
123-
struct PackedGeneric<T, U: ?imp::Sized> {
124-
t: T,
125-
// Unsized types stored in `repr(packed)` structs must not be dropped
126-
// because dropping them in-place might be unsound depending on the
127-
// alignment of the outer struct. Sized types can be dropped by first being
128-
// moved to an aligned stack variable, but this isn't possible with unsized
129-
// types.
130-
u: imp::ManuallyDrop<U>,
131-
}
132-
133-
util_assert_impl_all!(PackedGeneric<u8, util::AU16>: imp::IntoBytes);
134-
util_assert_impl_all!(PackedGeneric<u8, [util::AU16]>: imp::IntoBytes);
135-
136105
#[derive(imp::IntoBytes)]
137106
#[repr(C)]
138107
struct ReprCGenericOneField<T: ?imp::Sized> {
@@ -188,7 +157,7 @@ util_assert_impl_all!(WithParams<'static, 'static, u8, 42>: imp::IntoBytes);
188157
pub struct IndexEntryFlags(u8);
189158

190159
#[derive(imp::IntoBytes)]
191-
#[repr(packed)]
160+
#[repr(C, packed)]
192161
pub struct IndexEntry<const SIZE_BLOCK_ID: usize> {
193162
block_number: imp::native_endian::U64,
194163
flags: IndexEntryFlags,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2024 The Fuchsia Authors
2+
//
3+
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4+
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5+
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6+
// This file may not be copied, modified, or distributed except according to
7+
// those terms.
8+
9+
// This file contains tests which trigger errors on MSRV during a different
10+
// compiler pass compared to the stable or nightly toolchains.
11+
12+
#[macro_use]
13+
extern crate zerocopy;
14+
15+
#[path = "../include.rs"]
16+
mod util;
17+
18+
use self::util::util::AU16;
19+
use zerocopy::IntoBytes;
20+
21+
fn main() {}
22+
23+
// `repr(C, packed(2))` is not equivalent to `repr(C, packed)`.
24+
#[derive(IntoBytes)]
25+
#[repr(C, packed(2))]
26+
struct IntoBytes1<T> {
27+
t0: T,
28+
// Add a second field to avoid triggering the "repr(C) struct with one
29+
// field" special case.
30+
t1: T,
31+
}
32+
33+
fn is_into_bytes_1<T: IntoBytes>() {
34+
if false {
35+
is_into_bytes_1::<IntoBytes1<AU16>>();
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
warning: unused `#[macro_use]` import
2+
--> tests/ui-msrv/msrv_specific.rs:12:1
3+
|
4+
12 | #[macro_use]
5+
| ^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(unused_imports)]` on by default
8+
9+
error[E0277]: the trait bound `AU16: Unaligned` is not satisfied
10+
--> tests/ui-msrv/msrv_specific.rs:35:9
11+
|
12+
35 | is_into_bytes_1::<IntoBytes1<AU16>>();
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Unaligned` is not implemented for `AU16`
14+
|
15+
note: required because of the requirements on the impl of `zerocopy::IntoBytes` for `IntoBytes1<AU16>`
16+
--> tests/ui-msrv/msrv_specific.rs:24:10
17+
|
18+
24 | #[derive(IntoBytes)]
19+
| ^^^^^^^^^
20+
note: required by a bound in `is_into_bytes_1`
21+
--> tests/ui-msrv/msrv_specific.rs:33:23
22+
|
23+
33 | fn is_into_bytes_1<T: IntoBytes>() {
24+
| ^^^^^^^^^ required by this bound in `is_into_bytes_1`
25+
= note: this error originates in the derive macro `IntoBytes` (in Nightly builds, run with -Z macro-backtrace for more info)

zerocopy-derive/tests/ui-msrv/struct.stderr

+34-18
Original file line numberDiff line numberDiff line change
@@ -20,62 +20,78 @@ error: must have a non-align #[repr(...)] attribute or #[repr(packed)] in order
2020
|
2121
= note: this error originates in the derive macro `IntoBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
2222

23+
error: must have a non-align #[repr(...)] attribute or #[repr(packed)] in order to guarantee this type's memory layout
24+
--> tests/ui-msrv/struct.rs:164:10
25+
|
26+
164 | #[derive(IntoBytes)]
27+
| ^^^^^^^^^
28+
|
29+
= note: this error originates in the derive macro `IntoBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
30+
31+
error: must have a non-align #[repr(...)] attribute or #[repr(packed)] in order to guarantee this type's memory layout
32+
--> tests/ui-msrv/struct.rs:187:10
33+
|
34+
187 | #[derive(IntoBytes)]
35+
| ^^^^^^^^^
36+
|
37+
= note: this error originates in the derive macro `IntoBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
38+
2339
error: cannot derive `Unaligned` on type with alignment greater than 1
24-
--> tests/ui-msrv/struct.rs:167:11
40+
--> tests/ui-msrv/struct.rs:198:11
2541
|
26-
167 | #[repr(C, align(2))]
42+
198 | #[repr(C, align(2))]
2743
| ^^^^^
2844

2945
error: this conflicts with another representation hint
30-
--> tests/ui-msrv/struct.rs:171:8
46+
--> tests/ui-msrv/struct.rs:202:8
3147
|
32-
171 | #[repr(transparent, align(2))]
48+
202 | #[repr(transparent, align(2))]
3349
| ^^^^^^^^^^^
3450

3551
error: this conflicts with another representation hint
36-
--> tests/ui-msrv/struct.rs:177:16
52+
--> tests/ui-msrv/struct.rs:208:16
3753
|
38-
177 | #[repr(packed, align(2))]
54+
208 | #[repr(packed, align(2))]
3955
| ^^^^^
4056

4157
error: this conflicts with another representation hint
42-
--> tests/ui-msrv/struct.rs:181:18
58+
--> tests/ui-msrv/struct.rs:212:18
4359
|
44-
181 | #[repr(align(1), align(2))]
60+
212 | #[repr(align(1), align(2))]
4561
| ^^^^^
4662

4763
error: this conflicts with another representation hint
48-
--> tests/ui-msrv/struct.rs:185:18
64+
--> tests/ui-msrv/struct.rs:216:18
4965
|
50-
185 | #[repr(align(2), align(4))]
66+
216 | #[repr(align(2), align(4))]
5167
| ^^^^^
5268

5369
error: must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment
54-
--> tests/ui-msrv/struct.rs:188:10
70+
--> tests/ui-msrv/struct.rs:219:10
5571
|
56-
188 | #[derive(Unaligned)]
72+
219 | #[derive(Unaligned)]
5773
| ^^^^^^^^^
5874
|
5975
= note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info)
6076

6177
error: must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment
62-
--> tests/ui-msrv/struct.rs:191:10
78+
--> tests/ui-msrv/struct.rs:222:10
6379
|
64-
191 | #[derive(Unaligned)]
80+
222 | #[derive(Unaligned)]
6581
| ^^^^^^^^^
6682
|
6783
= note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info)
6884

6985
error: this conflicts with another representation hint
70-
--> tests/ui-msrv/struct.rs:201:8
86+
--> tests/ui-msrv/struct.rs:232:8
7187
|
72-
201 | #[repr(C, packed(2))]
88+
232 | #[repr(C, packed(2))]
7389
| ^
7490

7591
error[E0692]: transparent struct cannot have other repr hints
76-
--> tests/ui-msrv/struct.rs:171:8
92+
--> tests/ui-msrv/struct.rs:202:8
7793
|
78-
171 | #[repr(transparent, align(2))]
94+
202 | #[repr(transparent, align(2))]
7995
| ^^^^^^^^^^^ ^^^^^^^^
8096

8197
error[E0277]: the size for values of type `[u8]` cannot be known at compilation time

zerocopy-derive/tests/ui-msrv/union.stderr

+32-16
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,61 @@ error: unsupported on types with type parameters
66
|
77
= note: this error originates in the derive macro `IntoBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
88

9+
error: must be #[repr(C)], #[repr(packed)], or #[repr(transparent)]
10+
--> tests/ui-msrv/union.rs:47:10
11+
|
12+
47 | #[derive(IntoBytes)]
13+
| ^^^^^^^^^
14+
|
15+
= note: this error originates in the derive macro `IntoBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
16+
17+
error: must be #[repr(C)], #[repr(packed)], or #[repr(transparent)]
18+
--> tests/ui-msrv/union.rs:53:10
19+
|
20+
53 | #[derive(IntoBytes)]
21+
| ^^^^^^^^^
22+
|
23+
= note: this error originates in the derive macro `IntoBytes` (in Nightly builds, run with -Z macro-backtrace for more info)
24+
925
error: cannot derive `Unaligned` on type with alignment greater than 1
10-
--> tests/ui-msrv/union.rs:51:11
26+
--> tests/ui-msrv/union.rs:64:11
1127
|
12-
51 | #[repr(C, align(2))]
28+
64 | #[repr(C, align(2))]
1329
| ^^^^^
1430

1531
error: this conflicts with another representation hint
16-
--> tests/ui-msrv/union.rs:67:16
32+
--> tests/ui-msrv/union.rs:80:16
1733
|
18-
67 | #[repr(packed, align(2))]
34+
80 | #[repr(packed, align(2))]
1935
| ^^^^^
2036

2137
error: this conflicts with another representation hint
22-
--> tests/ui-msrv/union.rs:73:18
38+
--> tests/ui-msrv/union.rs:86:18
2339
|
24-
73 | #[repr(align(1), align(2))]
40+
86 | #[repr(align(1), align(2))]
2541
| ^^^^^
2642

2743
error: this conflicts with another representation hint
28-
--> tests/ui-msrv/union.rs:79:18
44+
--> tests/ui-msrv/union.rs:92:18
2945
|
30-
79 | #[repr(align(2), align(4))]
46+
92 | #[repr(align(2), align(4))]
3147
| ^^^^^
3248

3349
error: must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment
34-
--> tests/ui-msrv/union.rs:84:10
50+
--> tests/ui-msrv/union.rs:97:10
3551
|
36-
84 | #[derive(Unaligned)]
52+
97 | #[derive(Unaligned)]
3753
| ^^^^^^^^^
3854
|
3955
= note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info)
4056

4157
error: must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment
42-
--> tests/ui-msrv/union.rs:90:10
43-
|
44-
90 | #[derive(Unaligned)]
45-
| ^^^^^^^^^
46-
|
47-
= note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info)
58+
--> tests/ui-msrv/union.rs:103:10
59+
|
60+
103 | #[derive(Unaligned)]
61+
| ^^^^^^^^^
62+
|
63+
= note: this error originates in the derive macro `Unaligned` (in Nightly builds, run with -Z macro-backtrace for more info)
4864

4965
error[E0277]: the trait bound `UnsafeCell<()>: zerocopy::Immutable` is not satisfied
5066
--> tests/ui-msrv/union.rs:24:10

0 commit comments

Comments
 (0)