@@ -177,37 +177,6 @@ fn get_type_from_field<'tcx>(
177177 cx. tcx . try_normalize_erasing_regions ( cx. typing_env ( ) , field_ty) . unwrap_or ( field_ty)
178178}
179179
180- /// Check a variant of a non-exhaustive enum for improper ctypes
181- ///
182- /// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added".
183- /// This includes linting, on a best-effort basis. There are valid additions that are unlikely.
184- ///
185- /// Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely",
186- /// so we don't need the lint to account for it.
187- /// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }.
188- pub ( crate ) fn check_non_exhaustive_variant (
189- non_exhaustive_variant_list : bool ,
190- variant : & ty:: VariantDef ,
191- ) -> ControlFlow < DiagMessage , ( ) > {
192- // non_exhaustive suggests it is possible that someone might break ABI
193- // see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344
194- // so warn on complex enums being used outside their crate
195- if non_exhaustive_variant_list {
196- // which is why we only warn about really_tagged_union reprs from https://rust.tf/rfc2195
197- // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }`
198- // but exempt enums with unit ctors like C's (e.g. from rust-bindgen)
199- if variant_has_complex_ctor ( variant) {
200- return ControlFlow :: Break ( fluent:: lint_improper_ctypes_non_exhaustive) ;
201- }
202- }
203-
204- if variant. field_list_has_applicable_non_exhaustive ( ) {
205- return ControlFlow :: Break ( fluent:: lint_improper_ctypes_non_exhaustive_variant) ;
206- }
207-
208- ControlFlow :: Continue ( ( ) )
209- }
210-
211180fn variant_has_complex_ctor ( variant : & ty:: VariantDef ) -> bool {
212181 // CtorKind::Const means a "unit" ctor
213182 !matches ! ( variant. ctor_kind( ) , Some ( CtorKind :: Const ) )
@@ -378,7 +347,6 @@ impl<'tcx> FfiResult<'tcx> {
378347 }
379348 /// If the FfiPhantom variant, turns it into a FfiUnsafe version.
380349 /// Otherwise, keep unchanged.
381- #[ expect( unused) ]
382350 fn forbid_phantom ( self ) -> Self {
383351 match self {
384352 Self :: FfiPhantom ( ty) => {
@@ -960,34 +928,113 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
960928 ) -> FfiResult < ' tcx > {
961929 use FfiResult :: * ;
962930
963- let transparent_with_all_zst_fields = if def. repr ( ) . transparent ( ) {
964- if let Some ( field) = super :: transparent_newtype_field ( self . cx . tcx , variant) {
965- // Transparent newtypes have at most one non-ZST field which needs to be checked..
966- let field_ty = get_type_from_field ( self . cx , field, args) ;
967- return self . visit_type ( state. get_next ( ty) , field_ty) ;
931+ let mut ffires_accumulator = FfiSafe ;
932+
933+ let ( transparent_with_all_zst_fields, field_list) =
934+ if !matches ! ( def. adt_kind( ) , AdtKind :: Enum ) && def. repr ( ) . transparent ( ) {
935+ // determine if there is 0 or 1 non-1ZST field, and which it is.
936+ // (note: for enums, "transparent" means 1-variant)
937+ if let Some ( field) = super :: transparent_newtype_field ( self . cx . tcx , variant) {
938+ // Transparent newtypes have at most one non-ZST field which needs to be checked later
939+ ( false , vec ! [ field] )
940+ } else {
941+ // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
942+ // `PhantomData`).
943+ ( true , variant. fields . iter ( ) . collect :: < Vec < _ > > ( ) )
944+ }
968945 } else {
969- // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
970- // `PhantomData`).
971- true
972- }
973- } else {
974- false
975- } ;
946+ ( false , variant. fields . iter ( ) . collect :: < Vec < _ > > ( ) )
947+ } ;
976948
977949 // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
978950 let mut all_phantom = !variant. fields . is_empty ( ) ;
979- for field in & variant. fields {
951+ let mut fields_ok_list = vec ! [ true ; field_list. len( ) ] ;
952+
953+ for ( field_i, field) in field_list. into_iter ( ) . enumerate ( ) {
980954 let field_ty = get_type_from_field ( self . cx , field, args) ;
981- all_phantom &= match self . visit_type ( state. get_next ( ty) , field_ty) {
982- FfiSafe => false ,
955+ let ffi_res = self . visit_type ( state. get_next ( ty) , field_ty) ;
956+
957+ // checking that this is not an FfiUnsafe due to an unit type:
958+ // visit_type should be smart enough to not consider it unsafe if called from another ADT
959+ #[ cfg( debug_assertions) ]
960+ if let FfiUnsafe ( ref reasons) = ffi_res {
961+ if let ( 1 , Some ( FfiUnsafeExplanation { reason, .. } ) ) =
962+ ( reasons. len ( ) , reasons. first ( ) )
963+ {
964+ let FfiUnsafeReason { ty, .. } = reason. as_ref ( ) ;
965+ debug_assert ! ( !ty. is_unit( ) ) ;
966+ }
967+ }
968+
969+ all_phantom &= match ffi_res {
983970 FfiPhantom ( ..) => true ,
971+ FfiSafe => false ,
984972 r @ FfiUnsafe { .. } => {
985- return r. wrap_all ( ty, fluent:: lint_improper_ctypes_struct_dueto, None ) ;
973+ fields_ok_list[ field_i] = false ;
974+ ffires_accumulator += r;
975+ false
986976 }
987977 }
988978 }
989979
990- if all_phantom {
980+ // if we have bad fields, also report a possible transparent_with_all_zst_fields
981+ // (if this combination is somehow possible)
982+ // otherwise, having all fields be phantoms
983+ // takes priority over transparent_with_all_zst_fields
984+ if let FfiUnsafe ( explanations) = ffires_accumulator {
985+ debug_assert ! ( def. repr( ) . c( ) || def. repr( ) . transparent( ) || def. repr( ) . int. is_some( ) ) ;
986+
987+ if def. repr ( ) . transparent ( ) || matches ! ( def. adt_kind( ) , AdtKind :: Enum ) {
988+ let field_ffires = FfiUnsafe ( explanations) . wrap_all (
989+ ty,
990+ fluent:: lint_improper_ctypes_struct_dueto,
991+ None ,
992+ ) ;
993+ if transparent_with_all_zst_fields {
994+ field_ffires
995+ + FfiResult :: new_with_reason (
996+ ty,
997+ fluent:: lint_improper_ctypes_struct_zst,
998+ None ,
999+ )
1000+ } else {
1001+ field_ffires
1002+ }
1003+ } else {
1004+ // since we have a repr(C) struct/union, there's a chance that we have some unsafe fields,
1005+ // but also exactly one non-1ZST field that is FFI-safe:
1006+ // we want to suggest repr(transparent) here.
1007+ // (FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises)
1008+ let non_1zst_fields = super :: map_non_1zst_fields ( self . cx . tcx , variant) ;
1009+ let ( last_non_1zst, non_1zst_count) = non_1zst_fields. into_iter ( ) . enumerate ( ) . fold (
1010+ ( None , 0_usize ) ,
1011+ |( prev_nz, count) , ( field_i, is_nz) | {
1012+ if is_nz { ( Some ( field_i) , count + 1 ) } else { ( prev_nz, count) }
1013+ } ,
1014+ ) ;
1015+ let help = if non_1zst_count == 1
1016+ && last_non_1zst. map ( |field_i| fields_ok_list[ field_i] ) == Some ( true )
1017+ {
1018+ match def. adt_kind ( ) {
1019+ AdtKind :: Struct => {
1020+ Some ( fluent:: lint_improper_ctypes_struct_consider_transparent)
1021+ }
1022+ AdtKind :: Union => {
1023+ Some ( fluent:: lint_improper_ctypes_union_consider_transparent)
1024+ }
1025+ AdtKind :: Enum => bug ! ( "cannot suggest an enum to be repr(transparent)" ) ,
1026+ }
1027+ } else {
1028+ None
1029+ } ;
1030+
1031+ FfiUnsafe ( explanations) . wrap_all (
1032+ ty,
1033+ fluent:: lint_improper_ctypes_struct_dueto,
1034+ help,
1035+ )
1036+ }
1037+ } else if all_phantom {
9911038 FfiPhantom ( ty)
9921039 } else if transparent_with_all_zst_fields {
9931040 FfiResult :: new_with_reason ( ty, fluent:: lint_improper_ctypes_struct_zst, None )
@@ -1093,24 +1140,58 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10931140 // FIXME(ctypes): connect `def.repr().int` to visit_numeric
10941141 // (for now it's OK, `repr(char)` doesn't exist and visit_numeric doesn't warn on anything else)
10951142
1096- let non_exhaustive = def. variant_list_has_applicable_non_exhaustive ( ) ;
1143+ let enum_non_exhaustive = def. variant_list_has_applicable_non_exhaustive ( ) ;
10971144 // Check the contained variants.
1098- let ret = def. variants ( ) . iter ( ) . try_for_each ( |variant| {
1099- check_non_exhaustive_variant ( non_exhaustive, variant)
1100- . map_break ( |reason| FfiResult :: new_with_reason ( ty, reason, None ) ) ?;
11011145
1102- match self . visit_variant_fields ( state, ty, def, variant, args) {
1103- FfiSafe => ControlFlow :: Continue ( ( ) ) ,
1104- r => ControlFlow :: Break ( r) ,
1105- }
1146+ // non_exhaustive suggests it is possible that someone might break ABI
1147+ // See: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344
1148+ // so warn on complex enums being used outside their crate.
1149+ //
1150+ // We treat `#[non_exhaustive]` enum variants as unsafe if the enum is passed by-value,
1151+ // as additions it will change it size.
1152+ //
1153+ // We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added".
1154+ // This includes linting, on a best-effort basis. There are valid additions that are unlikely.
1155+ //
1156+ // Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely",
1157+ // so we don't need the lint to account for it.
1158+ // e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }.
1159+ // Which is why we only warn about really_tagged_union reprs from https://rust.tf/rfc2195
1160+ // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }`
1161+ // but exempt enums with unit ctors like C's (e.g. from rust-bindgen)
1162+
1163+ let ( mut improper_on_nonexhaustive_flag, mut nonexhaustive_variant_flag) = ( false , false ) ;
1164+ def. variants ( ) . iter ( ) . for_each ( |variant| {
1165+ improper_on_nonexhaustive_flag |=
1166+ enum_non_exhaustive && variant_has_complex_ctor ( variant) ;
1167+ nonexhaustive_variant_flag |= variant. field_list_has_applicable_non_exhaustive ( ) ;
11061168 } ) ;
1107- if let ControlFlow :: Break ( result) = ret {
1169+
1170+ if improper_on_nonexhaustive_flag {
1171+ FfiResult :: new_with_reason ( ty, fluent:: lint_improper_ctypes_non_exhaustive, None )
1172+ } else if nonexhaustive_variant_flag {
1173+ FfiResult :: new_with_reason (
1174+ ty,
1175+ fluent:: lint_improper_ctypes_non_exhaustive_variant,
1176+ None ,
1177+ )
1178+ } else {
1179+ let ffires = def
1180+ . variants ( )
1181+ . iter ( )
1182+ . map ( |variant| {
1183+ let variant_res = self . visit_variant_fields ( state, ty, def, variant, args) ;
1184+ // FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
1185+ // (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1186+ variant_res. forbid_phantom ( )
1187+ } )
1188+ . reduce ( |r1, r2| r1 + r2)
1189+ . unwrap ( ) ; // always at least one variant if we hit this branch
1190+
11081191 // this enum is visited in the middle of another lint,
11091192 // so we override the "cause type" of the lint
1110- // (for more detail, see comment in ``visit_struct_union`` before its call to ``result.with_overrides``)
1111- result. with_overrides ( Some ( ty) )
1112- } else {
1113- FfiSafe
1193+ // (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
1194+ ffires. with_overrides ( Some ( ty) )
11141195 }
11151196 }
11161197
0 commit comments