@@ -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