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`.
This commit is contained in:
parent
c7bf469fec
commit
2e93f2c92f
25 changed files with 874 additions and 349 deletions
|
@ -1,17 +1,11 @@
|
|||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_middle::mir::visit::{PlaceContext, Visitor};
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::query::Providers;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_session::lint::builtin::UNALIGNED_REFERENCES;
|
||||
|
||||
use crate::util;
|
||||
use crate::MirLint;
|
||||
|
||||
pub(crate) fn provide(providers: &mut Providers) {
|
||||
*providers = Providers { unsafe_derive_on_repr_packed, ..*providers };
|
||||
}
|
||||
|
||||
pub struct CheckPackedRef;
|
||||
|
||||
impl<'tcx> MirLint<'tcx> for CheckPackedRef {
|
||||
|
@ -30,32 +24,6 @@ struct PackedRefChecker<'a, 'tcx> {
|
|||
source_info: SourceInfo,
|
||||
}
|
||||
|
||||
fn unsafe_derive_on_repr_packed(tcx: TyCtxt<'_>, def_id: LocalDefId) {
|
||||
let lint_hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
|
||||
|
||||
// FIXME: when we make this a hard error, this should have its
|
||||
// own error code.
|
||||
|
||||
let extra = if tcx.generics_of(def_id).own_requires_monomorphization() {
|
||||
"with type or const parameters"
|
||||
} else {
|
||||
"that does not derive `Copy`"
|
||||
};
|
||||
let message = format!(
|
||||
"`{}` can't be derived on this `#[repr(packed)]` struct {}",
|
||||
tcx.item_name(tcx.trait_id_of_impl(def_id.to_def_id()).expect("derived trait name")),
|
||||
extra
|
||||
);
|
||||
|
||||
tcx.struct_span_lint_hir(
|
||||
UNALIGNED_REFERENCES,
|
||||
lint_hir_id,
|
||||
tcx.def_span(def_id),
|
||||
message,
|
||||
|lint| lint,
|
||||
);
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for PackedRefChecker<'_, 'tcx> {
|
||||
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
|
||||
// Make sure we know where in the MIR we are.
|
||||
|
@ -73,14 +41,13 @@ impl<'tcx> Visitor<'tcx> for PackedRefChecker<'_, 'tcx> {
|
|||
if context.is_borrow() {
|
||||
if util::is_disaligned(self.tcx, self.body, self.param_env, *place) {
|
||||
let def_id = self.body.source.instance.def_id();
|
||||
if let Some(impl_def_id) = self
|
||||
.tcx
|
||||
.impl_of_method(def_id)
|
||||
.filter(|&def_id| self.tcx.is_builtin_derive(def_id))
|
||||
if let Some(impl_def_id) = self.tcx.impl_of_method(def_id)
|
||||
&& self.tcx.is_builtin_derive(impl_def_id)
|
||||
{
|
||||
// If a method is defined in the local crate,
|
||||
// the impl containing that method should also be.
|
||||
self.tcx.ensure().unsafe_derive_on_repr_packed(impl_def_id.expect_local());
|
||||
// If we ever reach here it means that the generated derive
|
||||
// code is somehow doing an unaligned reference, which it
|
||||
// shouldn't do.
|
||||
unreachable!();
|
||||
} else {
|
||||
let source_info = self.source_info;
|
||||
let lint_root = self.body.source_scopes[source_info.scope]
|
||||
|
|
|
@ -104,7 +104,6 @@ use rustc_mir_dataflow::rustc_peek;
|
|||
|
||||
pub fn provide(providers: &mut Providers) {
|
||||
check_unsafety::provide(providers);
|
||||
check_packed_ref::provide(providers);
|
||||
coverage::query::provide(providers);
|
||||
ffi_unwind_calls::provide(providers);
|
||||
shim::provide(providers);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue