diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 26c87f14cd737..49ed3e92519e4 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -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> { // 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)); + 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), + ) + }) } _ => 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() { // 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()), diff --git a/src/tools/miri/tests/pass/function_calls/abi_compat.rs b/src/tools/miri/tests/pass/function_calls/abi_compat.rs index cd48bd2accb27..5bedc220ab210 100644 --- a/src/tools/miri/tests/pass/function_calls/abi_compat.rs +++ b/src/tools/miri/tests/pass/function_calls/abi_compat.rs @@ -62,13 +62,13 @@ fn test_abi_newtype() { struct Wrapper2a((), T); #[repr(transparent)] #[derive(Copy, Clone)] - struct Wrapper3(Zst, T, [u8; 0]); + struct Wrapper3(Zst, T, [(); 42]); let t = T::default(); test_abi_compat(t, Wrapper(t)); test_abi_compat(t, Wrapper2(t, ())); test_abi_compat(t, Wrapper2a((), t)); - test_abi_compat(t, Wrapper3(Zst, t, [])); + test_abi_compat(t, Wrapper3(Zst, t, [(); 42])); test_abi_compat(t, mem::MaybeUninit::new(t)); // MaybeUninit is `repr(transparent)` } diff --git a/tests/codegen-llvm/repr/transparent.rs b/tests/codegen-llvm/repr/transparent.rs index 29b627462a4d9..36da81d8ae1ba 100644 --- a/tests/codegen-llvm/repr/transparent.rs +++ b/tests/codegen-llvm/repr/transparent.rs @@ -47,7 +47,7 @@ pub extern "C" fn test_WithZst(_: WithZst) -> WithZst { } #[repr(transparent)] -pub struct WithZeroSizedArray(*const f32, [i8; 0]); +pub struct WithZeroSizedArray(*const f32, [(); 42]); // CHECK: define{{.*}}ptr @test_WithZeroSizedArray(ptr noundef %_1) #[no_mangle] diff --git a/tests/ui/abi/compatibility.rs b/tests/ui/abi/compatibility.rs index 6071ad9bb435b..a537d333d126a 100644 --- a/tests/ui/abi/compatibility.rs +++ b/tests/ui/abi/compatibility.rs @@ -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); } }; } @@ -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); // `#[repr(C)]` enums should not change ABI based on individual variant inhabitedness. @@ -220,7 +223,7 @@ struct TransparentWrapper1(T); #[repr(transparent)] struct TransparentWrapper2((), Zst, T); #[repr(transparent)] -struct TransparentWrapper3(T, [u8; 0], PhantomData); +struct TransparentWrapper3(T, [Zst; 42], PhantomData); #[repr(transparent)] union TransparentWrapperUnion { 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, $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, $t); test_abi_compatible!(either_err_zst, Either<$t, Zst>, $t); test_abi_compatible!(either_ok_zst, Either, $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); } } } diff --git a/tests/ui/layout/homogeneous-aggr-transparent.rs b/tests/ui/layout/homogeneous-aggr-transparent.rs index d968d609835f7..2c8c93cdd7c3e 100644 --- a/tests/ui/layout/homogeneous-aggr-transparent.rs +++ b/tests/ui/layout/homogeneous-aggr-transparent.rs @@ -14,7 +14,7 @@ struct Wrapper1(T); #[repr(transparent)] struct Wrapper2((), Zst, T); #[repr(transparent)] -struct Wrapper3(T, [u8; 0], PhantomData); +struct Wrapper3(T, [Zst; 42], PhantomData); #[repr(transparent)] union WrapperUnion { nothing: (),