Skip to content

Commit fe31372

Browse files
committed
ImproperCTypes: change handling of ADTs
A change in how type checking goes through structs/enums/unions, mostly to be able to yield multiple lints if multiple fields are unsafe
1 parent ffbbe4a commit fe31372

16 files changed

+275
-93
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -399,24 +399,25 @@ lint_improper_ctypes_slice_reason = slices have no C equivalent
399399
400400
lint_improper_ctypes_str_help = consider using `*const u8` and a length instead
401401
lint_improper_ctypes_str_reason = string slices have no C equivalent
402-
lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct
403402
404-
lint_improper_ctypes_struct_fieldless_reason = this struct has no fields
403+
lint_improper_ctypes_struct_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
405404
lint_improper_ctypes_struct_dueto = this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field
406405
407-
lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
406+
lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct
407+
lint_improper_ctypes_struct_fieldless_reason = `{$ty}` has no fields
408408
409-
lint_improper_ctypes_struct_layout_reason = this struct has unspecified layout
410-
lint_improper_ctypes_struct_non_exhaustive = this struct is non-exhaustive
411-
lint_improper_ctypes_struct_zst = this struct contains only zero-sized fields
409+
lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `{$ty}`
410+
lint_improper_ctypes_struct_layout_reason = `{$ty}` has unspecified layout
411+
lint_improper_ctypes_struct_non_exhaustive = `{$ty}` is non-exhaustive
412+
lint_improper_ctypes_struct_zst = `{$ty}` contains only zero-sized fields
412413
413414
lint_improper_ctypes_tuple_help = consider using a struct instead
414-
415415
lint_improper_ctypes_tuple_reason = tuples have unspecified layout
416-
lint_improper_ctypes_union_fieldless_help = consider adding a member to this union
417416
417+
lint_improper_ctypes_union_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
418+
lint_improper_ctypes_union_fieldless_help = consider adding a member to this union
418419
lint_improper_ctypes_union_fieldless_reason = this union has no fields
419-
lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union
420+
lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` attribute to this union
420421
421422
lint_improper_ctypes_union_layout_reason = this union has unspecified layout
422423
lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive

compiler/rustc_lint/src/types.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,26 @@ pub(crate) fn transparent_newtype_field<'a, 'tcx>(
707707
})
708708
}
709709

710+
/// for a given ADT variant, list which fields are non-1ZST
711+
/// (`repr(transparent)` guarantees that there is at most one)
712+
pub(crate) fn map_non_1zst_fields<'a, 'tcx>(
713+
tcx: TyCtxt<'tcx>,
714+
variant: &'a ty::VariantDef,
715+
) -> Vec<bool> {
716+
let typing_env = ty::TypingEnv::non_body_analysis(tcx, variant.def_id);
717+
variant
718+
.fields
719+
.iter()
720+
.map(|field| {
721+
let field_ty = tcx.type_of(field.did).instantiate_identity();
722+
let is_1zst = tcx
723+
.layout_of(typing_env.as_query_input(field_ty))
724+
.is_ok_and(|layout| layout.is_1zst());
725+
!is_1zst
726+
})
727+
.collect()
728+
}
729+
710730
/// Is type known to be non-null?
711731
fn ty_is_known_nonnull<'tcx>(
712732
tcx: TyCtxt<'tcx>,

compiler/rustc_lint/src/types/improper_ctypes.rs

Lines changed: 141 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -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

7072
fn 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

tests/ui/extern/extern-C-str-arg-ice-80125.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ warning: `extern` fn uses type `Struct`, which is not FFI-safe
1515
LL | pub extern "C" fn register_something(bind: ExternCallback) -> Struct {
1616
| ^^^^^^ not FFI-safe
1717
|
18-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
19-
= note: this struct has unspecified layout
18+
= help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `Struct`
19+
= note: `Struct` has unspecified layout
2020
note: the type is defined here
2121
--> $DIR/extern-C-str-arg-ice-80125.rs:6:1
2222
|

tests/ui/extern/issue-16250.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ error: `extern` block uses type `Foo`, which is not FFI-safe
44
LL | pub fn foo(x: (Foo));
55
| ^^^ not FFI-safe
66
|
7-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
8-
= note: this struct has unspecified layout
7+
= help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `Foo`
8+
= note: `Foo` has unspecified layout
99
note: the type is defined here
1010
--> $DIR/issue-16250.rs:3:1
1111
|

tests/ui/lint/improper_ctypes/lint-113436-1.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ note: the type is defined here
1010
|
1111
LL | pub struct Bar {
1212
| ^^^^^^^^^^^^^^
13-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
14-
= note: this struct has unspecified layout
13+
= help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `NotSafe`
14+
= note: `NotSafe` has unspecified layout
1515
note: the type is defined here
1616
--> $DIR/lint-113436-1.rs:13:1
1717
|
@@ -35,8 +35,8 @@ note: the type is defined here
3535
|
3636
LL | pub struct Bar {
3737
| ^^^^^^^^^^^^^^
38-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
39-
= note: this struct has unspecified layout
38+
= help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `NotSafe`
39+
= note: `NotSafe` has unspecified layout
4040
note: the type is defined here
4141
--> $DIR/lint-113436-1.rs:13:1
4242
|

tests/ui/lint/improper_ctypes/lint-73249-5.stderr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ error: `extern` block uses type `A`, which is not FFI-safe
44
LL | pub fn lint_me() -> A;
55
| ^ not FFI-safe
66
|
7+
= note: this struct/enum/union (`A`) is FFI-unsafe due to a `Qux` field
8+
note: the type is defined here
9+
--> $DIR/lint-73249-5.rs:16:1
10+
|
11+
LL | pub struct A {
12+
| ^^^^^^^^^^^^
713
= note: opaque types have no C equivalent
814
note: the lint level is defined here
915
--> $DIR/lint-73249-5.rs:2:9

0 commit comments

Comments
 (0)