@@ -36,18 +36,20 @@ fn get_type_from_field<'tcx>(
36
36
cx. tcx . try_normalize_erasing_regions ( cx. typing_env ( ) , field_ty) . unwrap_or ( field_ty)
37
37
}
38
38
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".
40
42
///
41
43
/// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added".
42
44
/// This includes linting, on a best-effort basis. There are valid additions that are unlikely.
43
45
///
44
46
/// Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely",
45
47
/// so we don't need the lint to account for it.
46
48
/// 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 (
48
50
non_exhaustive_variant_list : bool ,
49
51
variant : & ty:: VariantDef ,
50
- ) -> ControlFlow < DiagMessage , ( ) > {
52
+ ) -> ( bool , bool ) {
51
53
// non_exhaustive suggests it is possible that someone might break ABI
52
54
// see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344
53
55
// so warn on complex enums being used outside their crate
@@ -56,15 +58,15 @@ pub(crate) fn check_non_exhaustive_variant(
56
58
// with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }`
57
59
// but exempt enums with unit ctors like C's (e.g. from rust-bindgen)
58
60
if variant_has_complex_ctor ( variant) {
59
- return ControlFlow :: Break ( fluent :: lint_improper_ctypes_non_exhaustive ) ;
61
+ return ( true , false ) ;
60
62
}
61
63
}
62
64
63
65
if variant. field_list_has_applicable_non_exhaustive ( ) {
64
- return ControlFlow :: Break ( fluent :: lint_improper_ctypes_non_exhaustive_variant ) ;
66
+ return ( false , true ) ;
65
67
}
66
68
67
- ControlFlow :: Continue ( ( ) )
69
+ ( false , false )
68
70
}
69
71
70
72
fn variant_has_complex_ctor ( variant : & ty:: VariantDef ) -> bool {
@@ -707,46 +709,118 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
707
709
) -> FfiResult < ' tcx > {
708
710
use FfiResult :: * ;
709
711
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
+ }
721
726
} 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
+ } ;
729
729
730
730
// We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
731
731
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 ( ) {
733
735
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 {
738
751
FfiPhantom ( ..) => true ,
752
+ FfiSafe => false ,
739
753
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
745
757
}
746
758
}
747
759
}
748
760
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 {
750
824
FfiPhantom ( ty)
751
825
} else if transparent_with_all_zst_fields {
752
826
FfiResult :: new_with_reason ( ty, fluent:: lint_improper_ctypes_struct_zst, None )
@@ -858,22 +932,42 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
858
932
859
933
let non_exhaustive = def. variant_list_has_applicable_non_exhaustive ( ) ;
860
934
// 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 ) ) ?;
864
935
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;
869
941
} ) ;
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
+
871
967
// this enum is visited in the middle of another lint,
872
968
// 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) )
877
971
}
878
972
}
879
973
0 commit comments