@@ -36,18 +36,20 @@ fn get_type_from_field<'tcx>(
3636    cx. tcx . try_normalize_erasing_regions ( cx. typing_env ( ) ,  field_ty) . unwrap_or ( field_ty) 
3737} 
3838
39- /// Check a variant of a non-exhaustive enum for improper ctypes 
39+ /// Check a variant of a non-exhaustive enum for improper ctypes. 
40+ /// Returns two bools: "we have FFI-unsafety due to non-exhaustive enum" and 
41+ /// "we have FFI-unsafety due to a non-exhaustive enum variant". 
4042/// 
4143/// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added". 
4244/// This includes linting, on a best-effort basis. There are valid additions that are unlikely. 
4345/// 
4446/// Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely", 
4547/// so we don't need the lint to account for it. 
4648/// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }. 
47- pub ( crate )  fn  check_non_exhaustive_variant ( 
49+ pub ( crate )  fn  flag_non_exhaustive_variant ( 
4850    non_exhaustive_variant_list :  bool , 
4951    variant :  & ty:: VariantDef , 
50- )  -> ControlFlow < DiagMessage ,   ( ) >  { 
52+ )  -> ( bool ,   bool )  { 
5153    // non_exhaustive suggests it is possible that someone might break ABI 
5254    // see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344 
5355    // so warn on complex enums being used outside their crate 
@@ -56,15 +58,15 @@ pub(crate) fn check_non_exhaustive_variant(
5658        // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }` 
5759        // but exempt enums with unit ctors like C's (e.g. from rust-bindgen) 
5860        if  variant_has_complex_ctor ( variant)  { 
59-             return  ControlFlow :: Break ( fluent :: lint_improper_ctypes_non_exhaustive ) ; 
61+             return  ( true ,   false ) ; 
6062        } 
6163    } 
6264
6365    if  variant. field_list_has_applicable_non_exhaustive ( )  { 
64-         return  ControlFlow :: Break ( fluent :: lint_improper_ctypes_non_exhaustive_variant ) ; 
66+         return  ( false ,   true ) ; 
6567    } 
6668
67-     ControlFlow :: Continue ( ( ) ) 
69+     ( false ,   false ) 
6870} 
6971
7072fn  variant_has_complex_ctor ( variant :  & ty:: VariantDef )  -> bool  { 
@@ -707,46 +709,118 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
707709    )  -> FfiResult < ' tcx >  { 
708710        use  FfiResult :: * ; 
709711
710-         let  transparent_with_all_zst_fields = if  def. repr ( ) . transparent ( )  { 
711-             if  let  Some ( field)  = super :: transparent_newtype_field ( self . cx . tcx ,  variant)  { 
712-                 // Transparent newtypes have at most one non-ZST field which needs to be checked.. 
713-                 let  field_ty = get_type_from_field ( self . cx ,  field,  args) ; 
714-                 return  self . visit_type ( state,  Some ( ty) ,  field_ty) ; 
715-                 //match self.visit_type(state, Some(ty), field_ty) { 
716-                 //    FfiUnsafe { ty, .. } if ty.is_unit() => (), // TODO lol 
717-                 //    r => return r, 
718-                 //} 
719-                 // 
720-                 // false 
712+         let  mut  ffires_accumulator = FfiSafe ; 
713+ 
714+         let  ( transparent_with_all_zst_fields,  field_list)  =
715+             if  !matches ! ( def. adt_kind( ) ,  AdtKind :: Enum )  && def. repr ( ) . transparent ( )  { 
716+                 // determine if there is 0 or 1 non-1ZST field, and which it is. 
717+                 // (note: for enums, "transparent" means 1-variant) 
718+                 if  let  Some ( field)  = super :: transparent_newtype_field ( self . cx . tcx ,  variant)  { 
719+                     // Transparent newtypes have at most one non-ZST field which needs to be checked later 
720+                     ( false ,  vec ! [ field] ) 
721+                 }  else  { 
722+                     // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all 
723+                     // `PhantomData`). 
724+                     ( true ,  variant. fields . iter ( ) . collect :: < Vec < _ > > ( ) ) 
725+                 } 
721726            }  else  { 
722-                 // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all 
723-                 // `PhantomData`). 
724-                 true 
725-             } 
726-         }  else  { 
727-             false 
728-         } ; 
727+                 ( false ,  variant. fields . iter ( ) . collect :: < Vec < _ > > ( ) ) 
728+             } ; 
729729
730730        // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe. 
731731        let  mut  all_phantom = !variant. fields . is_empty ( ) ; 
732-         for  field in  & variant. fields  { 
732+         let  mut  fields_ok_list = vec ! [ true ;  field_list. len( ) ] ; 
733+ 
734+         for  ( field_i,  field)  in  field_list. into_iter ( ) . enumerate ( )  { 
733735            let  field_ty = get_type_from_field ( self . cx ,  field,  args) ; 
734-             all_phantom &= match  self . visit_type ( state,  Some ( ty) ,  field_ty)  { 
735-                 FfiSafe  => false , 
736-                 // `()` fields are FFI-safe! 
737-                 //FfiUnsafe { ty, .. } if ty.is_unit() => false, // TODO get back here 
736+             let  ffi_res = self . visit_type ( state,  Some ( ty) ,  field_ty) ; 
737+ 
738+             // checking that this is not an FfiUnsafe due to an unit type: 
739+             // visit_type should be smart enough to not consider it unsafe if called from another ADT 
740+             #[ cfg( debug_assertions) ]  
741+             if  let  FfiUnsafe ( ref  reasons)  = ffi_res { 
742+                 if  let  ( 1 ,  Some ( FfiUnsafeExplanation  {  reason,  .. } ) )  =
743+                     ( reasons. len ( ) ,  reasons. first ( ) ) 
744+                 { 
745+                     let  FfiUnsafeReason  {  ty,  .. }  = reason. as_ref ( ) ; 
746+                     debug_assert ! ( !ty. is_unit( ) ) ; 
747+                 } 
748+             } 
749+ 
750+             all_phantom &= match  ffi_res { 
738751                FfiPhantom ( ..)  => true , 
752+                 FfiSafe  => false , 
739753                r @ FfiUnsafe  {  .. }  => { 
740-                     return  r. wrap_all ( 
741-                         ty, 
742-                         fluent:: lint_improper_ctypes_struct_dueto, 
743-                         None , 
744-                     ) ; 
754+                     fields_ok_list[ field_i]  = false ; 
755+                     ffires_accumulator += r; 
756+                     false 
745757                } 
746758            } 
747759        } 
748760
749-         if  all_phantom { 
761+         // if we have bad fields, also report a possible transparent_with_all_zst_fields 
762+         // (if this combination is somehow possible) 
763+         // otherwise, having all fields be phantoms 
764+         // takes priority over transparent_with_all_zst_fields 
765+         if  let  FfiUnsafe ( explanations)  = ffires_accumulator { 
766+             // we assume the repr() of this ADT is either non-packed C or transparent. 
767+             debug_assert ! ( 
768+                 def. repr( ) . c( ) 
769+                     || def. repr( ) . transparent( ) 
770+                     || def. repr( ) . int. is_some( ) 
771+             ) ; 
772+ 
773+             if  def. repr ( ) . transparent ( )  || matches ! ( def. adt_kind( ) ,  AdtKind :: Enum )  { 
774+                 let  field_ffires = FfiUnsafe ( explanations) . wrap_all ( 
775+                     ty, 
776+                     fluent:: lint_improper_ctypes_struct_dueto, 
777+                     None , 
778+                 ) ; 
779+                 if  transparent_with_all_zst_fields { 
780+                     field_ffires
781+                         + FfiResult :: new_with_reason ( 
782+                             ty, 
783+                             fluent:: lint_improper_ctypes_struct_zst, 
784+                             None , 
785+                         ) 
786+                 }  else  { 
787+                     field_ffires
788+                 } 
789+             }  else  { 
790+                 // since we have a repr(C) struct/union, there's a chance that we have some unsafe fields, 
791+                 // but also exactly one non-1ZST field that is FFI-safe: 
792+                 // we want to suggest repr(transparent) here. 
793+                 // (FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises) 
794+                 let  non_1zst_fields = super :: map_non_1zst_fields ( self . cx . tcx ,  variant) ; 
795+                 let  ( last_non_1zst,  non_1zst_count)  = non_1zst_fields. into_iter ( ) . enumerate ( ) . fold ( 
796+                     ( None ,  0_usize ) , 
797+                     |( prev_nz,  count) ,  ( field_i,  is_nz) | { 
798+                         if  is_nz {  ( Some ( field_i) ,  count + 1 )  }  else  {  ( prev_nz,  count)  } 
799+                     } , 
800+                 ) ; 
801+                 let  help = if  non_1zst_count == 1 
802+                     && last_non_1zst. map ( |field_i| fields_ok_list[ field_i] )  == Some ( true ) 
803+                 { 
804+                     match  def. adt_kind ( )  { 
805+                         AdtKind :: Struct  => { 
806+                             Some ( fluent:: lint_improper_ctypes_struct_consider_transparent) 
807+                         } 
808+                         AdtKind :: Union  => { 
809+                             Some ( fluent:: lint_improper_ctypes_union_consider_transparent) 
810+                         } 
811+                         AdtKind :: Enum  => bug ! ( "cannot suggest an enum to be repr(transparent)" ) , 
812+                     } 
813+                 }  else  { 
814+                     None 
815+                 } ; 
816+ 
817+                 FfiUnsafe ( explanations) . wrap_all ( 
818+                     ty, 
819+                     fluent:: lint_improper_ctypes_struct_dueto, 
820+                     help, 
821+                 ) 
822+             } 
823+         }  else  if  all_phantom { 
750824            FfiPhantom ( ty) 
751825        }  else  if  transparent_with_all_zst_fields { 
752826            FfiResult :: new_with_reason ( ty,  fluent:: lint_improper_ctypes_struct_zst,  None ) 
@@ -858,22 +932,42 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
858932
859933        let  non_exhaustive = def. variant_list_has_applicable_non_exhaustive ( ) ; 
860934        // Check the contained variants. 
861-         let  ret = def. variants ( ) . iter ( ) . try_for_each ( |variant| { 
862-             check_non_exhaustive_variant ( non_exhaustive,  variant) 
863-                 . map_break ( |reason| FfiResult :: new_with_reason ( ty,  reason,  None ) ) ?; 
864935
865-             match  self . visit_variant_fields ( state,  ty,  def,  variant,  args)  { 
866-                 FfiSafe  => ControlFlow :: Continue ( ( ) ) , 
867-                 r => ControlFlow :: Break ( r) , 
868-             } 
936+         let  ( mut  nonexhaustive_flag,  mut  nonexhaustive_variant_flag)  = ( false ,  false ) ; 
937+         def. variants ( ) . iter ( ) . for_each ( |variant| { 
938+             let  ( nonex_enum,  nonex_var)  = flag_non_exhaustive_variant ( non_exhaustive,  variant) ; 
939+             nonexhaustive_flag |= nonex_enum; 
940+             nonexhaustive_variant_flag |= nonex_var; 
869941        } ) ; 
870-         if  let  ControlFlow :: Break ( result)  = ret { 
942+ 
943+         // "nonexhaustive" lints only happen outside of the crate defining the enum, so no CItemKind override 
944+         // (meaning: the fault lies in the function call, not the enum) 
945+         if  nonexhaustive_flag { 
946+             FfiResult :: new_with_reason ( ty,  fluent:: lint_improper_ctypes_non_exhaustive,  None ) 
947+         }  else  if  nonexhaustive_variant_flag { 
948+             FfiResult :: new_with_reason ( 
949+                 ty, 
950+                 fluent:: lint_improper_ctypes_non_exhaustive_variant, 
951+                 None , 
952+             ) 
953+         }  else  { 
954+ 
955+             let  ffires = def
956+                 . variants ( ) 
957+                 . iter ( ) 
958+                 . map ( |variant| { 
959+                     let  variant_res = self . visit_variant_fields ( state,  ty,  def,  variant,  args) ; 
960+                     // FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms? 
961+                     // (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?) 
962+                     variant_res. forbid_phantom ( ) 
963+                 } ) 
964+                 . reduce ( |r1,  r2| r1 + r2) 
965+                 . unwrap ( ) ;  // always at least one variant if we hit this branch 
966+ 
871967            // this enum is visited in the middle of another lint, 
872968            // so we override the "cause type" of the lint 
873-             // (for more detail, see comment in ``visit_struct_union`` before its call to ``result.with_overrides``) 
874-             result. with_overrides ( Some ( ty) ) 
875-         }  else  { 
876-             FfiSafe 
969+             // (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``) 
970+             ffires. with_overrides ( Some ( ty) ) 
877971        } 
878972    } 
879973
0 commit comments