Skip to content

Commit 2e93f2c

Browse files
committed
Allow more deriving on packed structs.
Currently, deriving on packed structs has some non-trivial limitations, related to the fact that taking references on unaligned fields is UB. The current approach to field accesses in derived code: - Normal case: `&self.0` - In a packed struct that derives `Copy`: `&{self.0}` - In a packed struct that doesn't derive `Copy`: `&self.0` Plus, we disallow deriving any builtin traits other than `Default` for any packed generic type, because it's possible that there might be misaligned fields. This is a fairly broad restriction. Plus, we disallow deriving any builtin traits other than `Default` for most packed types that don't derive `Copy`. (The exceptions are those where the alignments inherently satisfy the packing, e.g. in a type with `repr(packed(N))` where all the fields have alignments of `N` or less anyway. Such types are pretty strange, because the `packed` attribute is not having any effect.) This commit introduces a new, simpler approach to field accesses: - Normal case: `&self.0` - In a packed struct: `&{self.0}` In the latter case, this requires that all fields impl `Copy`, which is a new restriction. This means that the following example compiles under the old approach and doesn't compile under the new approach. ``` #[derive(Debug)] struct NonCopy(u8); #[derive(Debug) #[repr(packed)] struct MyType(NonCopy); ``` (Note that the old approach's support for cases like this was brittle. Changing the `u8` to a `u16` would be enough to stop it working. So not much capability is lost here.) However, the other constraints from the old rules are removed. We can now derive builtin traits for packed generic structs like this: ``` trait Trait { type A; } #[derive(Hash)] #[repr(packed)] pub struct Foo<T: Trait>(T, T::A); ``` To allow this, we add a `T: Copy` bound in the derived impl and a `T::A: Copy` bound in where clauses. So `T` and `T::A` must impl `Copy`. We can now also derive builtin traits for packed structs that don't derive `Copy`, so long as the fields impl `Copy`: ``` #[derive(Hash)] #[repr(packed)] pub struct Foo(u32); ``` This includes types that hand-impl `Copy` rather than deriving it, such as the following, that show up in winapi-0.2: ``` #[derive(Clone)] #[repr(packed)] struct MyType(i32); impl Copy for MyType {} ``` The new approach is simpler to understand and implement, and it avoids the need for the `unsafe_derive_on_repr_packed` check. One exception is required for backwards-compatibility: we allow `[u8]` fields for now. There is a new lint for this, `byte_slice_in_packed_struct_with_derive`.
1 parent c7bf469 commit 2e93f2c

25 files changed

+823
-298
lines changed

compiler/rustc_builtin_macros/src/deriving/bounds.rs

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub fn expand_deriving_copy(
1717
span,
1818
path: path_std!(marker::Copy),
1919
skip_path_as_bound: false,
20+
needs_copy_as_bound_if_packed: false,
2021
additional_bounds: Vec::new(),
2122
supports_unions: true,
2223
methods: Vec::new(),

compiler/rustc_builtin_macros/src/deriving/clone.rs

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub fn expand_deriving_clone(
7373
span,
7474
path: path_std!(clone::Clone),
7575
skip_path_as_bound: false,
76+
needs_copy_as_bound_if_packed: true,
7677
additional_bounds: bounds,
7778
supports_unions: true,
7879
methods: vec![MethodDef {

compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub fn expand_deriving_eq(
2727
span,
2828
path: path_std!(cmp::Eq),
2929
skip_path_as_bound: false,
30+
needs_copy_as_bound_if_packed: true,
3031
additional_bounds: Vec::new(),
3132
supports_unions: true,
3233
methods: vec![MethodDef {

compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub fn expand_deriving_ord(
2020
span,
2121
path: path_std!(cmp::Ord),
2222
skip_path_as_bound: false,
23+
needs_copy_as_bound_if_packed: true,
2324
additional_bounds: Vec::new(),
2425
supports_unions: false,
2526
methods: vec![MethodDef {

compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub fn expand_deriving_partial_eq(
8484
span,
8585
path: path_std!(cmp::PartialEq),
8686
skip_path_as_bound: false,
87+
needs_copy_as_bound_if_packed: true,
8788
additional_bounds: Vec::new(),
8889
supports_unions: false,
8990
methods,

compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub fn expand_deriving_partial_ord(
5959
span,
6060
path: path_std!(cmp::PartialOrd),
6161
skip_path_as_bound: false,
62+
needs_copy_as_bound_if_packed: true,
6263
additional_bounds: vec![],
6364
supports_unions: false,
6465
methods: vec![partial_cmp_def],

compiler/rustc_builtin_macros/src/deriving/debug.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub fn expand_deriving_debug(
2323
span,
2424
path: path_std!(fmt::Debug),
2525
skip_path_as_bound: false,
26+
needs_copy_as_bound_if_packed: true,
2627
additional_bounds: Vec::new(),
2728
supports_unions: false,
2829
methods: vec![MethodDef {

compiler/rustc_builtin_macros/src/deriving/decodable.rs

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub fn expand_deriving_rustc_decodable(
2525
span,
2626
path: Path::new_(vec![krate, sym::Decodable], vec![], PathKind::Global),
2727
skip_path_as_bound: false,
28+
needs_copy_as_bound_if_packed: true,
2829
additional_bounds: Vec::new(),
2930
supports_unions: false,
3031
methods: vec![MethodDef {

compiler/rustc_builtin_macros/src/deriving/default.rs

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub fn expand_deriving_default(
2525
span,
2626
path: Path::new(vec![kw::Default, sym::Default]),
2727
skip_path_as_bound: has_a_default_variant(item),
28+
needs_copy_as_bound_if_packed: false,
2829
additional_bounds: Vec::new(),
2930
supports_unions: false,
3031
methods: vec![MethodDef {

compiler/rustc_builtin_macros/src/deriving/encodable.rs

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ pub fn expand_deriving_rustc_encodable(
109109
span,
110110
path: Path::new_(vec![krate, sym::Encodable], vec![], PathKind::Global),
111111
skip_path_as_bound: false,
112+
needs_copy_as_bound_if_packed: true,
112113
additional_bounds: Vec::new(),
113114
supports_unions: false,
114115
methods: vec![MethodDef {

compiler/rustc_builtin_macros/src/deriving/generic/mod.rs

+87-48
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,12 @@ pub use SubstructureFields::*;
165165
use crate::deriving;
166166
use rustc_ast::ptr::P;
167167
use rustc_ast::{
168-
self as ast, BindingAnnotation, ByRef, EnumDef, Expr, Generics, Mutability, PatKind,
168+
self as ast, BindingAnnotation, ByRef, EnumDef, Expr, GenericArg, GenericParamKind, Generics,
169+
Mutability, PatKind, TyKind, VariantData,
169170
};
170-
use rustc_ast::{GenericArg, GenericParamKind, VariantData};
171171
use rustc_attr as attr;
172172
use rustc_expand::base::{Annotatable, ExtCtxt};
173+
use rustc_session::lint::builtin::BYTE_SLICE_IN_PACKED_STRUCT_WITH_DERIVE;
173174
use rustc_span::symbol::{kw, sym, Ident, Symbol};
174175
use rustc_span::{Span, DUMMY_SP};
175176
use std::cell::RefCell;
@@ -191,6 +192,9 @@ pub struct TraitDef<'a> {
191192
/// Whether to skip adding the current trait as a bound to the type parameters of the type.
192193
pub skip_path_as_bound: bool,
193194

195+
/// Whether `Copy` is needed as an additional bound on type parameters in a packed struct.
196+
pub needs_copy_as_bound_if_packed: bool,
197+
194198
/// Additional bounds required of any type parameters of the type,
195199
/// other than the current trait
196200
pub additional_bounds: Vec<Ty>,
@@ -455,18 +459,6 @@ impl<'a> TraitDef<'a> {
455459
}
456460
false
457461
});
458-
let has_no_type_params = match &item.kind {
459-
ast::ItemKind::Struct(_, generics)
460-
| ast::ItemKind::Enum(_, generics)
461-
| ast::ItemKind::Union(_, generics) => !generics
462-
.params
463-
.iter()
464-
.any(|param| matches!(param.kind, ast::GenericParamKind::Type { .. })),
465-
_ => unreachable!(),
466-
};
467-
let container_id = cx.current_expansion.id.expn_data().parent.expect_local();
468-
let copy_fields =
469-
is_packed && has_no_type_params && cx.resolver.has_derive_copy(container_id);
470462

471463
let newitem = match &item.kind {
472464
ast::ItemKind::Struct(struct_def, generics) => self.expand_struct_def(
@@ -475,7 +467,7 @@ impl<'a> TraitDef<'a> {
475467
item.ident,
476468
generics,
477469
from_scratch,
478-
copy_fields,
470+
is_packed,
479471
),
480472
ast::ItemKind::Enum(enum_def, generics) => {
481473
// We ignore `is_packed` here, because `repr(packed)`
@@ -493,7 +485,7 @@ impl<'a> TraitDef<'a> {
493485
item.ident,
494486
generics,
495487
from_scratch,
496-
copy_fields,
488+
is_packed,
497489
)
498490
} else {
499491
cx.span_err(mitem.span, "this trait cannot be derived for unions");
@@ -565,6 +557,7 @@ impl<'a> TraitDef<'a> {
565557
generics: &Generics,
566558
field_tys: Vec<P<ast::Ty>>,
567559
methods: Vec<P<ast::AssocItem>>,
560+
is_packed: bool,
568561
) -> P<ast::Item> {
569562
let trait_path = self.path.to_path(cx, self.span, type_ident, generics);
570563

@@ -607,20 +600,32 @@ impl<'a> TraitDef<'a> {
607600
.map(|param| match &param.kind {
608601
GenericParamKind::Lifetime { .. } => param.clone(),
609602
GenericParamKind::Type { .. } => {
610-
// I don't think this can be moved out of the loop, since
611-
// a GenericBound requires an ast id
612-
let bounds: Vec<_> =
613-
// extra restrictions on the generics parameters to the
614-
// type being derived upon
615-
self.additional_bounds.iter().map(|p| {
616-
cx.trait_bound(p.to_path(cx, self.span, type_ident, generics))
617-
}).chain(
618-
// require the current trait
619-
self.skip_path_as_bound.not().then(|| cx.trait_bound(trait_path.clone()))
620-
).chain(
621-
// also add in any bounds from the declaration
622-
param.bounds.iter().cloned()
623-
).collect();
603+
// Extra restrictions on the generics parameters to the
604+
// type being derived upon.
605+
let bounds: Vec<_> = self
606+
.additional_bounds
607+
.iter()
608+
.map(|p| cx.trait_bound(p.to_path(cx, self.span, type_ident, generics)))
609+
.chain(
610+
// Add a bound for the current trait.
611+
self.skip_path_as_bound
612+
.not()
613+
.then(|| cx.trait_bound(trait_path.clone())),
614+
)
615+
.chain({
616+
// Add a `Copy` bound if required.
617+
if is_packed && self.needs_copy_as_bound_if_packed {
618+
let p = deriving::path_std!(marker::Copy);
619+
Some(cx.trait_bound(p.to_path(cx, self.span, type_ident, generics)))
620+
} else {
621+
None
622+
}
623+
})
624+
.chain(
625+
// Also add in any bounds from the declaration.
626+
param.bounds.iter().cloned(),
627+
)
628+
.collect();
624629

625630
cx.typaram(param.ident.span.with_ctxt(ctxt), param.ident, bounds, None)
626631
}
@@ -692,9 +697,17 @@ impl<'a> TraitDef<'a> {
692697
.map(|p| cx.trait_bound(p.to_path(cx, self.span, type_ident, generics)))
693698
.collect();
694699

695-
// require the current trait
700+
// Require the current trait.
696701
bounds.push(cx.trait_bound(trait_path.clone()));
697702

703+
// Add a `Copy` bound if required.
704+
if is_packed && self.needs_copy_as_bound_if_packed {
705+
let p = deriving::path_std!(marker::Copy);
706+
bounds.push(
707+
cx.trait_bound(p.to_path(cx, self.span, type_ident, generics)),
708+
);
709+
}
710+
698711
let predicate = ast::WhereBoundPredicate {
699712
span: self.span,
700713
bound_generic_params: field_ty_param.bound_generic_params,
@@ -762,7 +775,7 @@ impl<'a> TraitDef<'a> {
762775
type_ident: Ident,
763776
generics: &Generics,
764777
from_scratch: bool,
765-
copy_fields: bool,
778+
is_packed: bool,
766779
) -> P<ast::Item> {
767780
let field_tys: Vec<P<ast::Ty>> =
768781
struct_def.fields().iter().map(|field| field.ty.clone()).collect();
@@ -790,7 +803,7 @@ impl<'a> TraitDef<'a> {
790803
type_ident,
791804
&selflike_args,
792805
&nonselflike_args,
793-
copy_fields,
806+
is_packed,
794807
)
795808
};
796809

@@ -806,7 +819,7 @@ impl<'a> TraitDef<'a> {
806819
})
807820
.collect();
808821

809-
self.create_derived_impl(cx, type_ident, generics, field_tys, methods)
822+
self.create_derived_impl(cx, type_ident, generics, field_tys, methods, is_packed)
810823
}
811824

812825
fn expand_enum_def(
@@ -861,7 +874,8 @@ impl<'a> TraitDef<'a> {
861874
})
862875
.collect();
863876

864-
self.create_derived_impl(cx, type_ident, generics, field_tys, methods)
877+
let is_packed = false; // enums are never packed
878+
self.create_derived_impl(cx, type_ident, generics, field_tys, methods, is_packed)
865879
}
866880
}
867881

@@ -1011,8 +1025,8 @@ impl<'a> MethodDef<'a> {
10111025
/// ```
10121026
/// But if the struct is `repr(packed)`, we can't use something like
10131027
/// `&self.x` because that might cause an unaligned ref. So for any trait
1014-
/// method that takes a reference, if the struct impls `Copy` then we use a
1015-
/// local block to force a copy:
1028+
/// method that takes a reference, we use a local block to force a copy.
1029+
/// This requires that the field impl `Copy`.
10161030
/// ```
10171031
/// # struct A { x: u8, y: u8 }
10181032
/// impl PartialEq for A {
@@ -1027,10 +1041,6 @@ impl<'a> MethodDef<'a> {
10271041
/// ::core::hash::Hash::hash(&{ self.y }, state)
10281042
/// }
10291043
/// }
1030-
/// ```
1031-
/// If the struct doesn't impl `Copy`, we use the normal `&self.x`. This
1032-
/// only works if the fields match the alignment required by the
1033-
/// `packed(N)` attribute. (We'll get errors later on if not.)
10341044
fn expand_struct_method_body<'b>(
10351045
&self,
10361046
cx: &mut ExtCtxt<'_>,
@@ -1039,12 +1049,12 @@ impl<'a> MethodDef<'a> {
10391049
type_ident: Ident,
10401050
selflike_args: &[P<Expr>],
10411051
nonselflike_args: &[P<Expr>],
1042-
copy_fields: bool,
1052+
is_packed: bool,
10431053
) -> BlockOrExpr {
10441054
assert!(selflike_args.len() == 1 || selflike_args.len() == 2);
10451055

10461056
let selflike_fields =
1047-
trait_.create_struct_field_access_fields(cx, selflike_args, struct_def, copy_fields);
1057+
trait_.create_struct_field_access_fields(cx, selflike_args, struct_def, is_packed);
10481058
self.call_substructure_method(
10491059
cx,
10501060
trait_,
@@ -1514,7 +1524,7 @@ impl<'a> TraitDef<'a> {
15141524
cx: &mut ExtCtxt<'_>,
15151525
selflike_args: &[P<Expr>],
15161526
struct_def: &'a VariantData,
1517-
copy_fields: bool,
1527+
is_packed: bool,
15181528
) -> Vec<FieldInfo> {
15191529
self.create_fields(struct_def, |i, struct_field, sp| {
15201530
selflike_args
@@ -1533,10 +1543,39 @@ impl<'a> TraitDef<'a> {
15331543
}),
15341544
),
15351545
);
1536-
if copy_fields {
1537-
field_expr = cx.expr_block(
1538-
cx.block(struct_field.span, vec![cx.stmt_expr(field_expr)]),
1539-
);
1546+
// In general, fields in packed structs are copied via a
1547+
// block, e.g. `&{self.0}`. The one exception is `[u8]`
1548+
// fields, which cannot be copied and also never cause
1549+
// unaligned references. This exception is allowed to
1550+
// handle the `FlexZeroSlice` type in the `zerovec` crate
1551+
// within `icu4x-0.9.0`.
1552+
//
1553+
// Once use of `icu4x-0.9.0` has dropped sufficiently, this
1554+
// exception should be removed.
1555+
let is_u8_slice = if let TyKind::Slice(ty) = &struct_field.ty.kind &&
1556+
let TyKind::Path(None, rustc_ast::Path { segments, .. }) = &ty.kind &&
1557+
let [seg] = segments.as_slice() &&
1558+
seg.ident.name == sym::u8 && seg.args.is_none()
1559+
{
1560+
true
1561+
} else {
1562+
false
1563+
};
1564+
if is_packed {
1565+
if is_u8_slice {
1566+
cx.sess.parse_sess.buffer_lint_with_diagnostic(
1567+
BYTE_SLICE_IN_PACKED_STRUCT_WITH_DERIVE,
1568+
sp,
1569+
ast::CRATE_NODE_ID,
1570+
"byte slice in a packed struct that derives a built-in trait",
1571+
rustc_lint_defs::BuiltinLintDiagnostics::ByteSliceInPackedStructWithDerive
1572+
);
1573+
} else {
1574+
// Wrap the expression in `{...}`, causing a copy.
1575+
field_expr = cx.expr_block(
1576+
cx.block(struct_field.span, vec![cx.stmt_expr(field_expr)]),
1577+
);
1578+
}
15401579
}
15411580
cx.expr_addr_of(sp, field_expr)
15421581
})

compiler/rustc_builtin_macros/src/deriving/hash.rs

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub fn expand_deriving_hash(
2424
span,
2525
path,
2626
skip_path_as_bound: false,
27+
needs_copy_as_bound_if_packed: true,
2728
additional_bounds: Vec::new(),
2829
supports_unions: false,
2930
methods: vec![MethodDef {

compiler/rustc_lint/src/context.rs

+3
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,9 @@ pub trait LintContext: Sized {
882882
);
883883
}
884884
}
885+
BuiltinLintDiagnostics::ByteSliceInPackedStructWithDerive => {
886+
db.help("consider implementing the trait by hand, or remove the `packed` attribute");
887+
}
885888
}
886889
// Rewrap `db`, and pass control to the user.
887890
decorate(db)

0 commit comments

Comments
 (0)