@@ -183,18 +183,20 @@ fn get_type_from_field<'tcx>(
183183 cx. tcx . try_normalize_erasing_regions ( cx. typing_env ( ) , field_ty) . unwrap_or ( field_ty)
184184}
185185
186- /// Check a variant of a non-exhaustive enum for improper ctypes
186+ /// Check a variant of a non-exhaustive enum for improper ctypes.
187+ /// Returns two bools: "we have FFI-unsafety due to non-exhaustive enum" and
188+ /// "we have FFI-unsafety due to a non-exhaustive enum variant".
187189///
188190/// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added".
189191/// This includes linting, on a best-effort basis. There are valid additions that are unlikely.
190192///
191193/// Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely",
192194/// so we don't need the lint to account for it.
193195/// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }.
194- pub ( crate ) fn check_non_exhaustive_variant (
196+ pub ( crate ) fn flag_non_exhaustive_variant (
195197 non_exhaustive_variant_list : bool ,
196198 variant : & ty:: VariantDef ,
197- ) -> ControlFlow < DiagMessage , ( ) > {
199+ ) -> ( bool , bool ) {
198200 // non_exhaustive suggests it is possible that someone might break ABI
199201 // see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344
200202 // so warn on complex enums being used outside their crate
@@ -203,15 +205,15 @@ pub(crate) fn check_non_exhaustive_variant(
203205 // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }`
204206 // but exempt enums with unit ctors like C's (e.g. from rust-bindgen)
205207 if variant_has_complex_ctor ( variant) {
206- return ControlFlow :: Break ( fluent :: lint_improper_ctypes_non_exhaustive ) ;
208+ return ( true , false ) ;
207209 }
208210 }
209211
210212 if variant. field_list_has_applicable_non_exhaustive ( ) {
211- return ControlFlow :: Break ( fluent :: lint_improper_ctypes_non_exhaustive_variant ) ;
213+ return ( false , true ) ;
212214 }
213215
214- ControlFlow :: Continue ( ( ) )
216+ ( false , false )
215217}
216218
217219fn variant_has_complex_ctor ( variant : & ty:: VariantDef ) -> bool {
@@ -866,39 +868,114 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
866868 ) -> FfiResult < ' tcx > {
867869 use FfiResult :: * ;
868870
869- let transparent_with_all_zst_fields = if def. repr ( ) . transparent ( ) {
870- if let Some ( field) = super :: transparent_newtype_field ( self . cx . tcx , variant) {
871- // Transparent newtypes have at most one non-ZST field which needs to be checked..
872- let field_ty = get_type_from_field ( self . cx , field, args) ;
873- return self . visit_type ( state, Some ( ty) , field_ty) ;
871+ let mut ffires_accumulator = FfiSafe ;
872+
873+ let ( transparent_with_all_zst_fields, field_list) =
874+ if !matches ! ( def. adt_kind( ) , AdtKind :: Enum ) && def. repr ( ) . transparent ( ) {
875+ // determine if there is 0 or 1 non-1ZST field, and which it is.
876+ // (note: for enums, "transparent" means 1-variant)
877+ if let Some ( field) = super :: transparent_newtype_field ( self . cx . tcx , variant) {
878+ // Transparent newtypes have at most one non-ZST field which needs to be checked later
879+ ( false , vec ! [ field] )
880+ } else {
881+ // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
882+ // `PhantomData`).
883+ ( true , variant. fields . iter ( ) . collect :: < Vec < _ > > ( ) )
884+ }
874885 } else {
875- // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
876- // `PhantomData`).
877- true
878- }
879- } else {
880- false
881- } ;
886+ ( false , variant. fields . iter ( ) . collect :: < Vec < _ > > ( ) )
887+ } ;
882888
883889 // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
884890 let mut all_phantom = !variant. fields . is_empty ( ) ;
885- for field in & variant. fields {
891+ let mut fields_ok_list = vec ! [ true ; field_list. len( ) ] ;
892+
893+ for ( field_i, field) in field_list. into_iter ( ) . enumerate ( ) {
886894 let field_ty = get_type_from_field ( self . cx , field, args) ;
887- all_phantom &= match self . visit_type ( state, Some ( ty) , field_ty) {
888- FfiSafe => false ,
889- // `()` fields are FFI-safe!
895+ let ffi_res = self . visit_type ( state, Some ( ty) , field_ty) ;
896+
897+ // checking that this is not an FfiUnsafe due to an unit type:
898+ // visit_type should be smart enough to not consider it unsafe if called from another ADT
899+ #[ cfg( debug_assertions) ]
900+ if let FfiUnsafe ( ref reasons) = ffi_res {
901+ if let ( 1 , Some ( FfiUnsafeExplanation { reason, .. } ) ) =
902+ ( reasons. len ( ) , reasons. first ( ) )
903+ {
904+ let FfiUnsafeReason { ty, .. } = reason. as_ref ( ) ;
905+ debug_assert ! ( !ty. is_unit( ) ) ;
906+ }
907+ }
908+
909+ all_phantom &= match ffi_res {
890910 FfiPhantom ( ..) => true ,
911+ FfiSafe => false ,
891912 r @ FfiUnsafe { .. } => {
892- return r. wrap_all (
893- ty,
894- fluent:: lint_improper_ctypes_struct_dueto,
895- None ,
896- ) ;
913+ fields_ok_list[ field_i] = false ;
914+ ffires_accumulator += r;
915+ false
897916 }
898917 }
899918 }
900919
901- if all_phantom {
920+ // if we have bad fields, also report a possible transparent_with_all_zst_fields
921+ // (if this combination is somehow possible)
922+ // otherwise, having all fields be phantoms
923+ // takes priority over transparent_with_all_zst_fields
924+ if let FfiUnsafe ( explanations) = ffires_accumulator {
925+ // we assume the repr() of this ADT is either non-packed C or transparent.
926+ debug_assert ! ( def. repr( ) . c( ) || def. repr( ) . transparent( ) || def. repr( ) . int. is_some( ) ) ;
927+
928+ if def. repr ( ) . transparent ( ) || matches ! ( def. adt_kind( ) , AdtKind :: Enum ) {
929+ let field_ffires = FfiUnsafe ( explanations) . wrap_all (
930+ ty,
931+ fluent:: lint_improper_ctypes_struct_dueto,
932+ None ,
933+ ) ;
934+ if transparent_with_all_zst_fields {
935+ field_ffires
936+ + FfiResult :: new_with_reason (
937+ ty,
938+ fluent:: lint_improper_ctypes_struct_zst,
939+ None ,
940+ )
941+ } else {
942+ field_ffires
943+ }
944+ } else {
945+ // since we have a repr(C) struct/union, there's a chance that we have some unsafe fields,
946+ // but also exactly one non-1ZST field that is FFI-safe:
947+ // we want to suggest repr(transparent) here.
948+ // (FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises)
949+ let non_1zst_fields = super :: map_non_1zst_fields ( self . cx . tcx , variant) ;
950+ let ( last_non_1zst, non_1zst_count) = non_1zst_fields. into_iter ( ) . enumerate ( ) . fold (
951+ ( None , 0_usize ) ,
952+ |( prev_nz, count) , ( field_i, is_nz) | {
953+ if is_nz { ( Some ( field_i) , count + 1 ) } else { ( prev_nz, count) }
954+ } ,
955+ ) ;
956+ let help = if non_1zst_count == 1
957+ && last_non_1zst. map ( |field_i| fields_ok_list[ field_i] ) == Some ( true )
958+ {
959+ match def. adt_kind ( ) {
960+ AdtKind :: Struct => {
961+ Some ( fluent:: lint_improper_ctypes_struct_consider_transparent)
962+ }
963+ AdtKind :: Union => {
964+ Some ( fluent:: lint_improper_ctypes_union_consider_transparent)
965+ }
966+ AdtKind :: Enum => bug ! ( "cannot suggest an enum to be repr(transparent)" ) ,
967+ }
968+ } else {
969+ None
970+ } ;
971+
972+ FfiUnsafe ( explanations) . wrap_all (
973+ ty,
974+ fluent:: lint_improper_ctypes_struct_dueto,
975+ help,
976+ )
977+ }
978+ } else if all_phantom {
902979 FfiPhantom ( ty)
903980 } else if transparent_with_all_zst_fields {
904981 FfiResult :: new_with_reason ( ty, fluent:: lint_improper_ctypes_struct_zst, None )
@@ -1010,22 +1087,41 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10101087
10111088 let non_exhaustive = def. variant_list_has_applicable_non_exhaustive ( ) ;
10121089 // Check the contained variants.
1013- let ret = def. variants ( ) . iter ( ) . try_for_each ( |variant| {
1014- check_non_exhaustive_variant ( non_exhaustive, variant)
1015- . map_break ( |reason| FfiResult :: new_with_reason ( ty, reason, None ) ) ?;
10161090
1017- match self . visit_variant_fields ( state, ty, def, variant, args) {
1018- FfiSafe => ControlFlow :: Continue ( ( ) ) ,
1019- r => ControlFlow :: Break ( r) ,
1020- }
1091+ let ( mut nonexhaustive_flag, mut nonexhaustive_variant_flag) = ( false , false ) ;
1092+ def. variants ( ) . iter ( ) . for_each ( |variant| {
1093+ let ( nonex_enum, nonex_var) = flag_non_exhaustive_variant ( non_exhaustive, variant) ;
1094+ nonexhaustive_flag |= nonex_enum;
1095+ nonexhaustive_variant_flag |= nonex_var;
10211096 } ) ;
1022- if let ControlFlow :: Break ( result) = ret {
1097+
1098+ // "nonexhaustive" lints only happen outside of the crate defining the enum, so no CItemKind override
1099+ // (meaning: the fault lies in the function call, not the enum)
1100+ if nonexhaustive_flag {
1101+ FfiResult :: new_with_reason ( ty, fluent:: lint_improper_ctypes_non_exhaustive, None )
1102+ } else if nonexhaustive_variant_flag {
1103+ FfiResult :: new_with_reason (
1104+ ty,
1105+ fluent:: lint_improper_ctypes_non_exhaustive_variant,
1106+ None ,
1107+ )
1108+ } else {
1109+ let ffires = def
1110+ . variants ( )
1111+ . iter ( )
1112+ . map ( |variant| {
1113+ let variant_res = self . visit_variant_fields ( state, ty, def, variant, args) ;
1114+ // FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
1115+ // (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1116+ variant_res. forbid_phantom ( )
1117+ } )
1118+ . reduce ( |r1, r2| r1 + r2)
1119+ . unwrap ( ) ; // always at least one variant if we hit this branch
1120+
10231121 // this enum is visited in the middle of another lint,
10241122 // so we override the "cause type" of the lint
1025- // (for more detail, see comment in ``visit_struct_union`` before its call to ``result.with_overrides``)
1026- result. with_overrides ( Some ( ty) )
1027- } else {
1028- FfiSafe
1123+ // (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
1124+ ffires. with_overrides ( Some ( ty) )
10291125 }
10301126 }
10311127
0 commit comments