Skip to content

Commit 8718f75

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 100f567 commit 8718f75

File tree

16 files changed

+270
-86
lines changed

16 files changed

+270
-86
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: 136 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -183,18 +183,20 @@ fn get_type_from_field<'tcx>(
183183
cx.tcx.try_normalize_erasing_regions(cx.typing_env(), field_ty).unwrap_or(field_ty)
184184
}
185185

186-
/// Check a variant of a non-exhaustive enum for improper ctypes
186+
/// Check a variant of a non-exhaustive enum for improper ctypes.
187+
/// Returns two bools: "we have FFI-unsafety due to non-exhaustive enum" and
188+
/// "we have FFI-unsafety due to a non-exhaustive enum variant".
187189
///
188190
/// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added".
189191
/// This includes linting, on a best-effort basis. There are valid additions that are unlikely.
190192
///
191193
/// Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely",
192194
/// so we don't need the lint to account for it.
193195
/// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }.
194-
pub(crate) fn check_non_exhaustive_variant(
196+
pub(crate) fn flag_non_exhaustive_variant(
195197
non_exhaustive_variant_list: bool,
196198
variant: &ty::VariantDef,
197-
) -> ControlFlow<DiagMessage, ()> {
199+
) -> (bool, bool) {
198200
// non_exhaustive suggests it is possible that someone might break ABI
199201
// see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344
200202
// so warn on complex enums being used outside their crate
@@ -203,15 +205,15 @@ pub(crate) fn check_non_exhaustive_variant(
203205
// with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }`
204206
// but exempt enums with unit ctors like C's (e.g. from rust-bindgen)
205207
if variant_has_complex_ctor(variant) {
206-
return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive);
208+
return (true, false);
207209
}
208210
}
209211

210212
if variant.field_list_has_applicable_non_exhaustive() {
211-
return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive_variant);
213+
return (false, true);
212214
}
213215

214-
ControlFlow::Continue(())
216+
(false, false)
215217
}
216218

217219
fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool {
@@ -866,39 +868,114 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
866868
) -> FfiResult<'tcx> {
867869
use FfiResult::*;
868870

869-
let transparent_with_all_zst_fields = if def.repr().transparent() {
870-
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
871-
// Transparent newtypes have at most one non-ZST field which needs to be checked..
872-
let field_ty = get_type_from_field(self.cx, field, args);
873-
return self.visit_type(state, Some(ty), field_ty);
871+
let mut ffires_accumulator = FfiSafe;
872+
873+
let (transparent_with_all_zst_fields, field_list) =
874+
if !matches!(def.adt_kind(), AdtKind::Enum) && def.repr().transparent() {
875+
// determine if there is 0 or 1 non-1ZST field, and which it is.
876+
// (note: for enums, "transparent" means 1-variant)
877+
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
878+
// Transparent newtypes have at most one non-ZST field which needs to be checked later
879+
(false, vec![field])
880+
} else {
881+
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
882+
// `PhantomData`).
883+
(true, variant.fields.iter().collect::<Vec<_>>())
884+
}
874885
} else {
875-
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
876-
// `PhantomData`).
877-
true
878-
}
879-
} else {
880-
false
881-
};
886+
(false, variant.fields.iter().collect::<Vec<_>>())
887+
};
882888

883889
// We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
884890
let mut all_phantom = !variant.fields.is_empty();
885-
for field in &variant.fields {
891+
let mut fields_ok_list = vec![true; field_list.len()];
892+
893+
for (field_i, field) in field_list.into_iter().enumerate() {
886894
let field_ty = get_type_from_field(self.cx, field, args);
887-
all_phantom &= match self.visit_type(state, Some(ty), field_ty) {
888-
FfiSafe => false,
889-
// `()` fields are FFI-safe!
895+
let ffi_res = self.visit_type(state, Some(ty), field_ty);
896+
897+
// checking that this is not an FfiUnsafe due to an unit type:
898+
// visit_type should be smart enough to not consider it unsafe if called from another ADT
899+
#[cfg(debug_assertions)]
900+
if let FfiUnsafe(ref reasons) = ffi_res {
901+
if let (1, Some(FfiUnsafeExplanation { reason, .. })) =
902+
(reasons.len(), reasons.first())
903+
{
904+
let FfiUnsafeReason { ty, .. } = reason.as_ref();
905+
debug_assert!(!ty.is_unit());
906+
}
907+
}
908+
909+
all_phantom &= match ffi_res {
890910
FfiPhantom(..) => true,
911+
FfiSafe => false,
891912
r @ FfiUnsafe { .. } => {
892-
return r.wrap_all(
893-
ty,
894-
fluent::lint_improper_ctypes_struct_dueto,
895-
None,
896-
);
913+
fields_ok_list[field_i] = false;
914+
ffires_accumulator += r;
915+
false
897916
}
898917
}
899918
}
900919

901-
if all_phantom {
920+
// if we have bad fields, also report a possible transparent_with_all_zst_fields
921+
// (if this combination is somehow possible)
922+
// otherwise, having all fields be phantoms
923+
// takes priority over transparent_with_all_zst_fields
924+
if let FfiUnsafe(explanations) = ffires_accumulator {
925+
// we assume the repr() of this ADT is either non-packed C or transparent.
926+
debug_assert!(def.repr().c() || def.repr().transparent() || def.repr().int.is_some());
927+
928+
if def.repr().transparent() || matches!(def.adt_kind(), AdtKind::Enum) {
929+
let field_ffires = FfiUnsafe(explanations).wrap_all(
930+
ty,
931+
fluent::lint_improper_ctypes_struct_dueto,
932+
None,
933+
);
934+
if transparent_with_all_zst_fields {
935+
field_ffires
936+
+ FfiResult::new_with_reason(
937+
ty,
938+
fluent::lint_improper_ctypes_struct_zst,
939+
None,
940+
)
941+
} else {
942+
field_ffires
943+
}
944+
} else {
945+
// since we have a repr(C) struct/union, there's a chance that we have some unsafe fields,
946+
// but also exactly one non-1ZST field that is FFI-safe:
947+
// we want to suggest repr(transparent) here.
948+
// (FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises)
949+
let non_1zst_fields = super::map_non_1zst_fields(self.cx.tcx, variant);
950+
let (last_non_1zst, non_1zst_count) = non_1zst_fields.into_iter().enumerate().fold(
951+
(None, 0_usize),
952+
|(prev_nz, count), (field_i, is_nz)| {
953+
if is_nz { (Some(field_i), count + 1) } else { (prev_nz, count) }
954+
},
955+
);
956+
let help = if non_1zst_count == 1
957+
&& last_non_1zst.map(|field_i| fields_ok_list[field_i]) == Some(true)
958+
{
959+
match def.adt_kind() {
960+
AdtKind::Struct => {
961+
Some(fluent::lint_improper_ctypes_struct_consider_transparent)
962+
}
963+
AdtKind::Union => {
964+
Some(fluent::lint_improper_ctypes_union_consider_transparent)
965+
}
966+
AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"),
967+
}
968+
} else {
969+
None
970+
};
971+
972+
FfiUnsafe(explanations).wrap_all(
973+
ty,
974+
fluent::lint_improper_ctypes_struct_dueto,
975+
help,
976+
)
977+
}
978+
} else if all_phantom {
902979
FfiPhantom(ty)
903980
} else if transparent_with_all_zst_fields {
904981
FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_struct_zst, None)
@@ -1010,22 +1087,41 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10101087

10111088
let non_exhaustive = def.variant_list_has_applicable_non_exhaustive();
10121089
// Check the contained variants.
1013-
let ret = def.variants().iter().try_for_each(|variant| {
1014-
check_non_exhaustive_variant(non_exhaustive, variant)
1015-
.map_break(|reason| FfiResult::new_with_reason(ty, reason, None))?;
10161090

1017-
match self.visit_variant_fields(state, ty, def, variant, args) {
1018-
FfiSafe => ControlFlow::Continue(()),
1019-
r => ControlFlow::Break(r),
1020-
}
1091+
let (mut nonexhaustive_flag, mut nonexhaustive_variant_flag) = (false, false);
1092+
def.variants().iter().for_each(|variant| {
1093+
let (nonex_enum, nonex_var) = flag_non_exhaustive_variant(non_exhaustive, variant);
1094+
nonexhaustive_flag |= nonex_enum;
1095+
nonexhaustive_variant_flag |= nonex_var;
10211096
});
1022-
if let ControlFlow::Break(result) = ret {
1097+
1098+
// "nonexhaustive" lints only happen outside of the crate defining the enum, so no CItemKind override
1099+
// (meaning: the fault lies in the function call, not the enum)
1100+
if nonexhaustive_flag {
1101+
FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_non_exhaustive, None)
1102+
} else if nonexhaustive_variant_flag {
1103+
FfiResult::new_with_reason(
1104+
ty,
1105+
fluent::lint_improper_ctypes_non_exhaustive_variant,
1106+
None,
1107+
)
1108+
} else {
1109+
let ffires = def
1110+
.variants()
1111+
.iter()
1112+
.map(|variant| {
1113+
let variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1114+
// FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
1115+
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1116+
variant_res.forbid_phantom()
1117+
})
1118+
.reduce(|r1, r2| r1 + r2)
1119+
.unwrap(); // always at least one variant if we hit this branch
1120+
10231121
// this enum is visited in the middle of another lint,
10241122
// so we override the "cause type" of the lint
1025-
// (for more detail, see comment in ``visit_struct_union`` before its call to ``result.with_overrides``)
1026-
result.with_overrides(Some(ty))
1027-
} else {
1028-
FfiSafe
1123+
// (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
1124+
ffires.with_overrides(Some(ty))
10291125
}
10301126
}
10311127

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

tests/ui/lint/improper_ctypes/lint-94223.stderr

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ LL | pub static BAD: extern "C" fn(FfiUnsafe) = f;
9090
| ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
9191
|
9292
= note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe`
93-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
94-
= note: this struct has unspecified layout
93+
= help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe`
94+
= note: `FfiUnsafe` has unspecified layout
9595
note: the type is defined here
9696
--> $DIR/lint-94223.rs:34:1
9797
|
@@ -105,8 +105,8 @@ LL | pub static BAD_TWICE: Result<extern "C" fn(FfiUnsafe), extern "C" fn(FfiUns
105105
| ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
106106
|
107107
= note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe`
108-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
109-
= note: this struct has unspecified layout
108+
= help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe`
109+
= note: `FfiUnsafe` has unspecified layout
110110
note: the type is defined here
111111
--> $DIR/lint-94223.rs:34:1
112112
|
@@ -120,8 +120,8 @@ LL | pub static BAD_TWICE: Result<extern "C" fn(FfiUnsafe), extern "C" fn(FfiUns
120120
| ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
121121
|
122122
= note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe`
123-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
124-
= note: this struct has unspecified layout
123+
= help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe`
124+
= note: `FfiUnsafe` has unspecified layout
125125
note: the type is defined here
126126
--> $DIR/lint-94223.rs:34:1
127127
|
@@ -135,8 +135,8 @@ LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f;
135135
| ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
136136
|
137137
= note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe`
138-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
139-
= note: this struct has unspecified layout
138+
= help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe`
139+
= note: `FfiUnsafe` has unspecified layout
140140
note: the type is defined here
141141
--> $DIR/lint-94223.rs:34:1
142142
|

0 commit comments

Comments
 (0)