-
-
Notifications
You must be signed in to change notification settings - Fork 14.9k
repr(transparent): don't consider most length-0 arrays trivial
#155984
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1709,6 +1709,10 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) | |||||||||||||||
| "`repr(C)` types", | ||||||||||||||||
| "is a `#[repr(C)]` type, so it is not guaranteed to be zero-sized on all targets.", | ||||||||||||||||
| ), | ||||||||||||||||
| UnsuitedReason::Array => ( | ||||||||||||||||
| "array types with non-trivial element types", | ||||||||||||||||
| "is an array with a non-trivial element type, so it is not guaranteed to have a trivial ABI in all situations.", | ||||||||||||||||
| ), | ||||||||||||||||
| }; | ||||||||||||||||
| Diag::new( | ||||||||||||||||
| dcx, | ||||||||||||||||
|
|
@@ -1785,19 +1789,43 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) | |||||||||||||||
| NonExhaustive, | ||||||||||||||||
| PrivateField, | ||||||||||||||||
| ReprC, | ||||||||||||||||
| Array, | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| fn check_unsuited<'tcx>( | ||||||||||||||||
| tcx: TyCtxt<'tcx>, | ||||||||||||||||
| typing_env: ty::TypingEnv<'tcx>, | ||||||||||||||||
| ty: Ty<'tcx>, | ||||||||||||||||
| inside_repr_rust_packed_1: bool, | ||||||||||||||||
| ) -> ControlFlow<UnsuitedInfo<'tcx>> { | ||||||||||||||||
| // We can encounter projections during traversal, so ensure the type is normalized. | ||||||||||||||||
| let ty = | ||||||||||||||||
| tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty); | ||||||||||||||||
| match ty.kind() { | ||||||||||||||||
| ty::Tuple(list) => list.iter().try_for_each(|t| check_unsuited(tcx, typing_env, t)), | ||||||||||||||||
| ty::Array(ty, _) => check_unsuited(tcx, typing_env, *ty), | ||||||||||||||||
| ty::Tuple(list) => list | ||||||||||||||||
| .iter() | ||||||||||||||||
| .try_for_each(|t| check_unsuited(tcx, typing_env, t, inside_repr_rust_packed_1)), | ||||||||||||||||
| ty::Array(elem_ty, len) => { | ||||||||||||||||
| // If we are inside a `#[repr(Rust, packed(1))]` ADT, | ||||||||||||||||
| // the alignment is guaranteed to be 1 and Rust has full control over the ABI. | ||||||||||||||||
| // Therefore, we can allow any length-0 array. | ||||||||||||||||
| // This special case is needed to support the `ghost` crate. | ||||||||||||||||
| if inside_repr_rust_packed_1 | ||||||||||||||||
| && let ty::ConstKind::Value(v) = len.kind() | ||||||||||||||||
| && v.try_to_target_usize(tcx) == Some(0) | ||||||||||||||||
| { | ||||||||||||||||
| return ControlFlow::Continue(()); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| let elem_layout = tcx.layout_of(typing_env.as_query_input(*elem_ty)); | ||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
| let elem_trivial = elem_layout.is_ok_and(|layout| layout.is_1zst()); | ||||||||||||||||
|
|
||||||||||||||||
| if elem_trivial { | ||||||||||||||||
| check_unsuited(tcx, typing_env, *elem_ty, inside_repr_rust_packed_1) | ||||||||||||||||
| } else { | ||||||||||||||||
| ControlFlow::Break(UnsuitedInfo { ty, reason: UnsuitedReason::Array }) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| ty::Adt(def, args) => { | ||||||||||||||||
| if !def.did().is_local() && !find_attr!(tcx, def.did(), RustcPubTransparent(_)) { | ||||||||||||||||
| let non_exhaustive = def.is_variant_list_non_exhaustive() | ||||||||||||||||
|
|
@@ -1814,12 +1842,22 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) | |||||||||||||||
| }); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| if def.repr().c() { | ||||||||||||||||
|
|
||||||||||||||||
| let repr = def.repr(); | ||||||||||||||||
|
|
||||||||||||||||
| if repr.c() { | ||||||||||||||||
| return ControlFlow::Break(UnsuitedInfo { ty, reason: UnsuitedReason::ReprC }); | ||||||||||||||||
| } | ||||||||||||||||
| def.all_fields() | ||||||||||||||||
| .map(|field| field.ty(tcx, args)) | ||||||||||||||||
| .try_for_each(|t| check_unsuited(tcx, typing_env, t)) | ||||||||||||||||
|
|
||||||||||||||||
| def.all_fields().map(|field| field.ty(tcx, args)).try_for_each(|t| { | ||||||||||||||||
| check_unsuited( | ||||||||||||||||
| tcx, | ||||||||||||||||
| typing_env, | ||||||||||||||||
| t, | ||||||||||||||||
| inside_repr_rust_packed_1 | ||||||||||||||||
| || def.repr().pack.is_some_and(|a| a.bytes() == 1), | ||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will also be
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There isn't, sadly
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One verbose way to achieve this would be an exhaustive field match on
Comment on lines
+1857
to
+1858
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||||||||||||||||
| ) | ||||||||||||||||
| }) | ||||||||||||||||
| } | ||||||||||||||||
| _ => ControlFlow::Continue(()), | ||||||||||||||||
| } | ||||||||||||||||
|
|
@@ -1828,11 +1866,22 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) | |||||||||||||||
| let mut prev_unsuited_1zst = false; | ||||||||||||||||
| for field in field_infos { | ||||||||||||||||
| if field.trivial | ||||||||||||||||
| && let Some(unsuited) = check_unsuited(tcx, typing_env, field.ty).break_value() | ||||||||||||||||
| && let Some(unsuited) = check_unsuited(tcx, typing_env, field.ty, false).break_value() | ||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
| { | ||||||||||||||||
| // If there are any non-trivial fields, then there can be no non-exhaustive 1-zsts. | ||||||||||||||||
| // Otherwise, it's only an issue if there's >1 non-exhaustive 1-zst. | ||||||||||||||||
| if non_trivial_count > 0 || prev_unsuited_1zst { | ||||||||||||||||
| if matches!(unsuited.reason, UnsuitedReason::Array) { | ||||||||||||||||
| #[derive(Diagnostic)] | ||||||||||||||||
| #[diag("😱 Crater REGRESSION 😱")] | ||||||||||||||||
| struct CraterFail { | ||||||||||||||||
| #[primary_span] | ||||||||||||||||
| span: Span, | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| tcx.dcx().emit_err(CraterFail { span: field.span }); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| tcx.emit_node_span_lint( | ||||||||||||||||
| REPR_TRANSPARENT_NON_ZST_FIELDS, | ||||||||||||||||
| tcx.local_def_id_to_hir_id(adt.did().expect_local()), | ||||||||||||||||
|
|
||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add tests at least for
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -145,11 +145,13 @@ macro_rules! test_abi_compatible { | |
| use super::*; | ||
| // Declaring a `type` doesn't even check well-formedness, so we also declare a function. | ||
| fn check_wf(_x: $t1, _y: $t2) {} | ||
| // Test argument and return value, `Rust` and `C` ABIs. | ||
| // Test argument and return value in various ABIs. | ||
| #[rustc_abi(assert_eq)] | ||
| type TestRust = (fn($t1) -> $t1, fn($t2) -> $t2); | ||
| #[rustc_abi(assert_eq)] | ||
| type TestC = (extern "C" fn($t1) -> $t1, extern "C" fn($t2) -> $t2); | ||
| #[rustc_abi(assert_eq)] | ||
| type TestSystem = (extern "system" fn($t1) -> $t1, extern "system" fn($t2) -> $t2); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has nothing to do with repr(transparent), does it?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the whole business of the Windows ABI passing some empty structs by pointer, I figured that an extra check in this area can't hurt. I don't think this PR would break anything there, but that's what tests are for
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe make this a separate PR then so we don't mix up the discussion. We run this test on lots of targets, and for almost all of them "system" is the same as "C". So I think this is mostly a waste of time. However you make a good point -- it would make sense to also test some of the target-specific ABIs here. We can just add those under |
||
| } | ||
| }; | ||
| } | ||
|
|
@@ -200,7 +202,8 @@ test_abi_compatible!(isize_int, isize, i64); | |
|
|
||
| // Compatibility of 1-ZST. | ||
| test_abi_compatible!(zst_unit, Zst, ()); | ||
| test_abi_compatible!(zst_array, Zst, [u8; 0]); | ||
| test_abi_compatible!(zst_array, Zst, [(); 0]); | ||
| test_abi_compatible!(zst_array_2, Zst, [(); 42]); | ||
| test_abi_compatible!(nonzero_int, NonZero<i32>, i32); | ||
|
|
||
| // `#[repr(C)]` enums should not change ABI based on individual variant inhabitedness. | ||
|
|
@@ -220,7 +223,7 @@ struct TransparentWrapper1<T: ?Sized>(T); | |
| #[repr(transparent)] | ||
| struct TransparentWrapper2<T: ?Sized>((), Zst, T); | ||
| #[repr(transparent)] | ||
| struct TransparentWrapper3<T>(T, [u8; 0], PhantomData<u64>); | ||
| struct TransparentWrapper3<T>(T, [Zst; 42], PhantomData<u64>); | ||
| #[repr(transparent)] | ||
| union TransparentWrapperUnion<T> { | ||
| nothing: (), | ||
|
|
@@ -299,14 +302,14 @@ macro_rules! test_nonnull { | |
| test_abi_compatible!(result_ok_unit, Result<(), $t>, $t); | ||
| test_abi_compatible!(result_err_zst, Result<$t, Zst>, $t); | ||
| test_abi_compatible!(result_ok_zst, Result<Zst, $t>, $t); | ||
| test_abi_compatible!(result_err_arr, Result<$t, [i8; 0]>, $t); | ||
| test_abi_compatible!(result_ok_arr, Result<[i8; 0], $t>, $t); | ||
| test_abi_compatible!(result_err_arr, Result<$t, [(); 42]>, $t); | ||
| test_abi_compatible!(result_ok_arr, Result<[(); 42], $t>, $t); | ||
| test_abi_compatible!(result_err_void, Result<$t, Void>, $t); | ||
| test_abi_compatible!(result_ok_void, Result<Void, $t>, $t); | ||
| test_abi_compatible!(either_err_zst, Either<$t, Zst>, $t); | ||
| test_abi_compatible!(either_ok_zst, Either<Zst, $t>, $t); | ||
| test_abi_compatible!(either2_err_zst, Either2<$t, Zst>, $t); | ||
| test_abi_compatible!(either2_err_arr, Either2<$t, [i8; 0]>, $t); | ||
| test_abi_compatible!(either2_err_arr, Either2<$t, [(); 42]>, $t); | ||
| } | ||
| } | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why only 0-length arrays?
View changes since the review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#[repr(Rust, packed(1))]guarantees alignment 1, but not size 0. If the element type contains e.g.repr(C), it might not be portably 0-sized. Theif elem_trivial { check_unsuited(...) }check below allows the non-0-length arrays that we can safely allow.I suppose we could allow types like
#[repr(Rust, packed(1))] struct Foo([[u128; 0]; 42]), which this PR currently rejects. But again, that would conflict with your preference for less complexity.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only ever call
check_trivialon types that we already know to be zero-sized. So I think you can remove the length check and everything will remain correct. Or am I missing something?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
… on the current target. The element type could be an empty
repr(C)struct. We have to reject this:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. That warrants a comment in the code and a test case.