@@ -182,18 +182,20 @@ fn get_type_from_field<'tcx>(
182182 cx. tcx . try_normalize_erasing_regions ( cx. typing_env ( ) , field_ty) . unwrap_or ( field_ty)
183183}
184184
185- /// Check a variant of a non-exhaustive enum for improper ctypes
185+ /// Check a variant of a non-exhaustive enum for improper ctypes.
186+ /// Returns two bools: "we have FFI-unsafety due to non-exhaustive enum" and
187+ /// "we have FFI-unsafety due to a non-exhaustive enum variant".
186188///
187189/// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added".
188190/// This includes linting, on a best-effort basis. There are valid additions that are unlikely.
189191///
190192/// Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely",
191193/// so we don't need the lint to account for it.
192194/// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }.
193- pub ( crate ) fn check_non_exhaustive_variant (
195+ pub ( crate ) fn flag_non_exhaustive_variant (
194196 non_exhaustive_variant_list : bool ,
195197 variant : & ty:: VariantDef ,
196- ) -> ControlFlow < DiagMessage , ( ) > {
198+ ) -> ( bool , bool ) {
197199 // non_exhaustive suggests it is possible that someone might break ABI
198200 // see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344
199201 // so warn on complex enums being used outside their crate
@@ -202,15 +204,15 @@ pub(crate) fn check_non_exhaustive_variant(
202204 // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }`
203205 // but exempt enums with unit ctors like C's (e.g. from rust-bindgen)
204206 if variant_has_complex_ctor ( variant) {
205- return ControlFlow :: Break ( fluent :: lint_improper_ctypes_non_exhaustive ) ;
207+ return ( true , false ) ;
206208 }
207209 }
208210
209211 if variant. field_list_has_applicable_non_exhaustive ( ) {
210- return ControlFlow :: Break ( fluent :: lint_improper_ctypes_non_exhaustive_variant ) ;
212+ return ( false , true ) ;
211213 }
212214
213- ControlFlow :: Continue ( ( ) )
215+ ( false , false )
214216}
215217
216218fn variant_has_complex_ctor ( variant : & ty:: VariantDef ) -> bool {
@@ -1002,34 +1004,114 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10021004 ) -> FfiResult < ' tcx > {
10031005 use FfiResult :: * ;
10041006
1005- let transparent_with_all_zst_fields = if def. repr ( ) . transparent ( ) {
1006- if let Some ( field) = super :: transparent_newtype_field ( self . cx . tcx , variant) {
1007- // Transparent newtypes have at most one non-ZST field which needs to be checked..
1008- let field_ty = get_type_from_field ( self . cx , field, args) ;
1009- return self . visit_type ( state. get_next ( ty) , field_ty) ;
1007+ let mut ffires_accumulator = FfiSafe ;
1008+
1009+ let ( transparent_with_all_zst_fields, field_list) =
1010+ if !matches ! ( def. adt_kind( ) , AdtKind :: Enum ) && def. repr ( ) . transparent ( ) {
1011+ // determine if there is 0 or 1 non-1ZST field, and which it is.
1012+ // (note: for enums, "transparent" means 1-variant)
1013+ if let Some ( field) = super :: transparent_newtype_field ( self . cx . tcx , variant) {
1014+ // Transparent newtypes have at most one non-ZST field which needs to be checked later
1015+ ( false , vec ! [ field] )
1016+ } else {
1017+ // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
1018+ // `PhantomData`).
1019+ ( true , variant. fields . iter ( ) . collect :: < Vec < _ > > ( ) )
1020+ }
10101021 } else {
1011- // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
1012- // `PhantomData`).
1013- true
1014- }
1015- } else {
1016- false
1017- } ;
1022+ ( false , variant. fields . iter ( ) . collect :: < Vec < _ > > ( ) )
1023+ } ;
10181024
10191025 // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
10201026 let mut all_phantom = !variant. fields . is_empty ( ) ;
1021- for field in & variant. fields {
1027+ let mut fields_ok_list = vec ! [ true ; field_list. len( ) ] ;
1028+
1029+ for ( field_i, field) in field_list. into_iter ( ) . enumerate ( ) {
10221030 let field_ty = get_type_from_field ( self . cx , field, args) ;
1023- all_phantom &= match self . visit_type ( state. get_next ( ty) , field_ty) {
1024- FfiSafe => false ,
1031+ let ffi_res = self . visit_type ( state. get_next ( ty) , field_ty) ;
1032+
1033+ // checking that this is not an FfiUnsafe due to an unit type:
1034+ // visit_type should be smart enough to not consider it unsafe if called from another ADT
1035+ #[ cfg( debug_assertions) ]
1036+ if let FfiUnsafe ( ref reasons) = ffi_res {
1037+ if let ( 1 , Some ( FfiUnsafeExplanation { reason, .. } ) ) =
1038+ ( reasons. len ( ) , reasons. first ( ) )
1039+ {
1040+ let FfiUnsafeReason { ty, .. } = reason. as_ref ( ) ;
1041+ debug_assert ! ( !ty. is_unit( ) ) ;
1042+ }
1043+ }
1044+
1045+ all_phantom &= match ffi_res {
10251046 FfiPhantom ( ..) => true ,
1047+ FfiSafe => false ,
10261048 r @ FfiUnsafe { .. } => {
1027- return r. wrap_all ( ty, fluent:: lint_improper_ctypes_struct_dueto, None ) ;
1049+ fields_ok_list[ field_i] = false ;
1050+ ffires_accumulator += r;
1051+ false
10281052 }
10291053 }
10301054 }
10311055
1032- if all_phantom {
1056+ // if we have bad fields, also report a possible transparent_with_all_zst_fields
1057+ // (if this combination is somehow possible)
1058+ // otherwise, having all fields be phantoms
1059+ // takes priority over transparent_with_all_zst_fields
1060+ if let FfiUnsafe ( explanations) = ffires_accumulator {
1061+ // we assume the repr() of this ADT is either non-packed C or transparent.
1062+ debug_assert ! ( def. repr( ) . c( ) || def. repr( ) . transparent( ) || def. repr( ) . int. is_some( ) ) ;
1063+
1064+ if def. repr ( ) . transparent ( ) || matches ! ( def. adt_kind( ) , AdtKind :: Enum ) {
1065+ let field_ffires = FfiUnsafe ( explanations) . wrap_all (
1066+ ty,
1067+ fluent:: lint_improper_ctypes_struct_dueto,
1068+ None ,
1069+ ) ;
1070+ if transparent_with_all_zst_fields {
1071+ field_ffires
1072+ + FfiResult :: new_with_reason (
1073+ ty,
1074+ fluent:: lint_improper_ctypes_struct_zst,
1075+ None ,
1076+ )
1077+ } else {
1078+ field_ffires
1079+ }
1080+ } else {
1081+ // since we have a repr(C) struct/union, there's a chance that we have some unsafe fields,
1082+ // but also exactly one non-1ZST field that is FFI-safe:
1083+ // we want to suggest repr(transparent) here.
1084+ // (FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises)
1085+ let non_1zst_fields = super :: map_non_1zst_fields ( self . cx . tcx , variant) ;
1086+ let ( last_non_1zst, non_1zst_count) = non_1zst_fields. into_iter ( ) . enumerate ( ) . fold (
1087+ ( None , 0_usize ) ,
1088+ |( prev_nz, count) , ( field_i, is_nz) | {
1089+ if is_nz { ( Some ( field_i) , count + 1 ) } else { ( prev_nz, count) }
1090+ } ,
1091+ ) ;
1092+ let help = if non_1zst_count == 1
1093+ && last_non_1zst. map ( |field_i| fields_ok_list[ field_i] ) == Some ( true )
1094+ {
1095+ match def. adt_kind ( ) {
1096+ AdtKind :: Struct => {
1097+ Some ( fluent:: lint_improper_ctypes_struct_consider_transparent)
1098+ }
1099+ AdtKind :: Union => {
1100+ Some ( fluent:: lint_improper_ctypes_union_consider_transparent)
1101+ }
1102+ AdtKind :: Enum => bug ! ( "cannot suggest an enum to be repr(transparent)" ) ,
1103+ }
1104+ } else {
1105+ None
1106+ } ;
1107+
1108+ FfiUnsafe ( explanations) . wrap_all (
1109+ ty,
1110+ fluent:: lint_improper_ctypes_struct_dueto,
1111+ help,
1112+ )
1113+ }
1114+ } else if all_phantom {
10331115 FfiPhantom ( ty)
10341116 } else if transparent_with_all_zst_fields {
10351117 FfiResult :: new_with_reason ( ty, fluent:: lint_improper_ctypes_struct_zst, None )
@@ -1141,22 +1223,41 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
11411223
11421224 let non_exhaustive = def. variant_list_has_applicable_non_exhaustive ( ) ;
11431225 // Check the contained variants.
1144- let ret = def. variants ( ) . iter ( ) . try_for_each ( |variant| {
1145- check_non_exhaustive_variant ( non_exhaustive, variant)
1146- . map_break ( |reason| FfiResult :: new_with_reason ( ty, reason, None ) ) ?;
11471226
1148- match self . visit_variant_fields ( state, ty, def, variant, args) {
1149- FfiSafe => ControlFlow :: Continue ( ( ) ) ,
1150- r => ControlFlow :: Break ( r) ,
1151- }
1227+ let ( mut nonexhaustive_flag, mut nonexhaustive_variant_flag) = ( false , false ) ;
1228+ def. variants ( ) . iter ( ) . for_each ( |variant| {
1229+ let ( nonex_enum, nonex_var) = flag_non_exhaustive_variant ( non_exhaustive, variant) ;
1230+ nonexhaustive_flag |= nonex_enum;
1231+ nonexhaustive_variant_flag |= nonex_var;
11521232 } ) ;
1153- if let ControlFlow :: Break ( result) = ret {
1233+
1234+ // "nonexhaustive" lints only happen outside of the crate defining the enum, so no CItemKind override
1235+ // (meaning: the fault lies in the function call, not the enum)
1236+ if nonexhaustive_flag {
1237+ FfiResult :: new_with_reason ( ty, fluent:: lint_improper_ctypes_non_exhaustive, None )
1238+ } else if nonexhaustive_variant_flag {
1239+ FfiResult :: new_with_reason (
1240+ ty,
1241+ fluent:: lint_improper_ctypes_non_exhaustive_variant,
1242+ None ,
1243+ )
1244+ } else {
1245+ let ffires = def
1246+ . variants ( )
1247+ . iter ( )
1248+ . map ( |variant| {
1249+ let variant_res = self . visit_variant_fields ( state, ty, def, variant, args) ;
1250+ // FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
1251+ // (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1252+ variant_res. forbid_phantom ( )
1253+ } )
1254+ . reduce ( |r1, r2| r1 + r2)
1255+ . unwrap ( ) ; // always at least one variant if we hit this branch
1256+
11541257 // this enum is visited in the middle of another lint,
11551258 // so we override the "cause type" of the lint
1156- // (for more detail, see comment in ``visit_struct_union`` before its call to ``result.with_overrides``)
1157- result. with_overrides ( Some ( ty) )
1158- } else {
1159- FfiSafe
1259+ // (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
1260+ ffires. with_overrides ( Some ( ty) )
11601261 }
11611262 }
11621263
0 commit comments