Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

check non_exhaustive attr and private fields for transparent types #99020

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3132,6 +3132,60 @@ declare_lint! {
"detects unexpected names and values in `#[cfg]` conditions",
}

declare_lint! {
/// The `repr_transparent_external_private_fields` lint
/// detects types marked `#[repr(transparent)]` that (transitively)
/// contain an external ZST type marked `#[non_exhaustive]` or containing
/// private fields
///
/// ### Example
///
/// ```rust,ignore (needs external crate)
/// #![deny(repr_transparent_external_private_fields)]
/// use foo::NonExhaustiveZst;
///
/// #[repr(transparent)]
/// struct Bar(u32, ([u32; 0], NonExhaustiveZst));
/// ```
///
/// This will produce:
///
/// ```text
/// error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
/// --> src/main.rs:5:28
/// |
/// 5 | struct Bar(u32, ([u32; 0], NonExhaustiveZst));
/// | ^^^^^^^^^^^^^^^^
/// |
/// note: the lint level is defined here
/// --> src/main.rs:1:9
/// |
/// 1 | #![deny(repr_transparent_external_private_fields)]
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/// = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
/// = note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
/// = note: this struct contains `NonExhaustiveZst`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future.
/// ```
///
/// ### Explanation
///
/// Previous, Rust accepted fields that contain external private zero-sized types,
/// even though it should not be a breaking change to add a non-zero-sized field to
/// that private type.
///
/// This is a [future-incompatible] lint to transition this
/// to a hard error in the future. See [issue #78586] for more details.
///
/// [issue #78586]: https://github.com/rust-lang/rust/issues/78586
/// [future-incompatible]: ../index.md#future-incompatible-lints
pub REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
Warn,
"tranparent type contains an external ZST that is marked #[non_exhaustive] or contains private fields",
@future_incompatible = FutureIncompatibleInfo {
reference: "issue #78586 <https://github.com/rust-lang/rust/issues/78586>",
};
}

declare_lint_pass! {
/// Does nothing as a lint pass, but registers some `Lint`s
/// that are used by other parts of the compiler.
Expand Down Expand Up @@ -3237,6 +3291,7 @@ declare_lint_pass! {
DEPRECATED_WHERE_CLAUSE_LOCATION,
TEST_UNSTABLE_LINT,
FFI_UNWIND_CALLS,
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
]
}

Expand Down
71 changes: 66 additions & 5 deletions compiler/rustc_typeck/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt};
use rustc_infer::traits::Obligation;
use rustc_lint::builtin::REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::layout::{LayoutError, MAX_SIMD_LANES};
use rustc_middle::ty::subst::GenericArgKind;
Expand Down Expand Up @@ -1318,7 +1319,8 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, adt: ty::AdtD
}
}

// For each field, figure out if it's known to be a ZST and align(1)
// For each field, figure out if it's known to be a ZST and align(1), with "known"
// respecting #[non_exhaustive] attributes.
let field_infos = adt.all_fields().map(|field| {
let ty = field.ty(tcx, InternalSubsts::identity_for_item(tcx, field.did));
let param_env = tcx.param_env(field.did);
Expand All @@ -1327,16 +1329,56 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, adt: ty::AdtD
let span = tcx.hir().span_if_local(field.did).unwrap();
let zst = layout.map_or(false, |layout| layout.is_zst());
let align1 = layout.map_or(false, |layout| layout.align.abi.bytes() == 1);
(span, zst, align1)
if !zst {
return (span, zst, align1, None);
}

fn check_non_exhaustive<'tcx>(
tcx: TyCtxt<'tcx>,
t: Ty<'tcx>,
) -> ControlFlow<(&'static str, DefId, SubstsRef<'tcx>, bool)> {
match t.kind() {
ty::Tuple(list) => list.iter().try_for_each(|t| check_non_exhaustive(tcx, t)),
ty::Array(ty, _) => check_non_exhaustive(tcx, *ty),
ty::Adt(def, subst) => {
if !def.did().is_local() {
let non_exhaustive = def.is_variant_list_non_exhaustive()
|| def
.variants()
.iter()
.any(ty::VariantDef::is_field_list_non_exhaustive);
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
if non_exhaustive || has_priv {
return ControlFlow::Break((
def.descr(),
def.did(),
subst,
non_exhaustive,
));
}
}
def.all_fields()
.map(|field| field.ty(tcx, subst))
.try_for_each(|t| check_non_exhaustive(tcx, t))
}
_ => ControlFlow::Continue(()),
}
}

(span, zst, align1, check_non_exhaustive(tcx, ty).break_value())
});

let non_zst_fields =
field_infos.clone().filter_map(|(span, zst, _align1)| if !zst { Some(span) } else { None });
let non_zst_fields = field_infos
.clone()
.filter_map(|(span, zst, _align1, _non_exhaustive)| if !zst { Some(span) } else { None });
let non_zst_count = non_zst_fields.clone().count();
if non_zst_count >= 2 {
bad_non_zero_sized_fields(tcx, adt, non_zst_count, non_zst_fields, sp);
}
for (span, zst, align1) in field_infos {
let incompatible_zst_fields =
field_infos.clone().filter(|(_, _, _, opt)| opt.is_some()).count();
let incompat = incompatible_zst_fields + non_zst_count >= 2 && non_zst_count < 2;
for (span, zst, align1, non_exhaustive) in field_infos {
if zst && !align1 {
struct_span_err!(
tcx.sess,
Expand All @@ -1348,6 +1390,25 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, adt: ty::AdtD
.span_label(span, "has alignment larger than 1")
.emit();
}
if incompat && let Some((descr, def_id, substs, non_exhaustive)) = non_exhaustive {
tcx.struct_span_lint_hir(
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
tcx.hir().local_def_id_to_hir_id(adt.did().expect_local()),
span,
|lint| {
let note = if non_exhaustive {
"is marked with `#[non_exhaustive]`"
} else {
"contains private fields"
};
let field_ty = tcx.def_path_str_with_substs(def_id, substs);
lint.build("zero-sized fields in repr(transparent) cannot contain external non-exhaustive types")
.note(format!("this {descr} contains `{field_ty}`, which {note}, \
and makes it not a breaking change to become non-zero-sized in the future."))
.emit();
},
)
}
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/test/ui/repr/auxiliary/repr-transparent-non-exhaustive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![crate_type = "lib"]

pub struct Private { _priv: () }

#[non_exhaustive]
pub struct NonExhaustive {}

#[non_exhaustive]
pub enum NonExhaustiveEnum {}

pub enum NonExhaustiveVariant {
#[non_exhaustive]
A,
}

pub struct ExternalIndirection<T> {
pub x: T,
}
96 changes: 96 additions & 0 deletions src/test/ui/repr/repr-transparent-non-exhaustive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#![deny(repr_transparent_external_private_fields)]

// aux-build: repr-transparent-non-exhaustive.rs
extern crate repr_transparent_non_exhaustive;

use repr_transparent_non_exhaustive::{
Private,
NonExhaustive,
NonExhaustiveEnum,
NonExhaustiveVariant,
ExternalIndirection,
};

pub struct InternalPrivate {
_priv: (),
}

#[non_exhaustive]
pub struct InternalNonExhaustive;

pub struct InternalIndirection<T> {
x: T,
}

pub type Sized = i32;

#[repr(transparent)]
pub struct T1(Sized, InternalPrivate);
#[repr(transparent)]
pub struct T2(Sized, InternalNonExhaustive);
#[repr(transparent)]
pub struct T3(Sized, InternalIndirection<(InternalPrivate, InternalNonExhaustive)>);
#[repr(transparent)]
pub struct T4(Sized, ExternalIndirection<(InternalPrivate, InternalNonExhaustive)>);

#[repr(transparent)]
pub struct T5(Sized, Private);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

#[repr(transparent)]
pub struct T6(Sized, NonExhaustive);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

#[repr(transparent)]
pub struct T7(Sized, NonExhaustiveEnum);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

#[repr(transparent)]
pub struct T8(Sized, NonExhaustiveVariant);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

#[repr(transparent)]
pub struct T9(Sized, InternalIndirection<Private>);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

#[repr(transparent)]
pub struct T10(Sized, InternalIndirection<NonExhaustive>);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

#[repr(transparent)]
pub struct T11(Sized, InternalIndirection<NonExhaustiveEnum>);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

#[repr(transparent)]
pub struct T12(Sized, InternalIndirection<NonExhaustiveVariant>);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

#[repr(transparent)]
pub struct T13(Sized, ExternalIndirection<Private>);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

#[repr(transparent)]
pub struct T14(Sized, ExternalIndirection<NonExhaustive>);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

#[repr(transparent)]
pub struct T15(Sized, ExternalIndirection<NonExhaustiveEnum>);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

#[repr(transparent)]
pub struct T16(Sized, ExternalIndirection<NonExhaustiveVariant>);
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
//~| WARN this was previously accepted by the compiler

fn main() {}
Loading