Skip to content

Commit 5b9fb7a

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 d4dc99b commit 5b9fb7a

File tree

16 files changed

+270
-81
lines changed

16 files changed

+270
-81
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -392,24 +392,25 @@ lint_improper_ctypes_slice_reason = slices have no C equivalent
392392
393393
lint_improper_ctypes_str_help = consider using `*const u8` and a length instead
394394
lint_improper_ctypes_str_reason = string slices have no C equivalent
395-
lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct
396395
397-
lint_improper_ctypes_struct_fieldless_reason = this struct has no fields
396+
lint_improper_ctypes_struct_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
398397
lint_improper_ctypes_struct_dueto = this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field
399398
400-
lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
399+
lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct
400+
lint_improper_ctypes_struct_fieldless_reason = `{$ty}` has no fields
401401
402-
lint_improper_ctypes_struct_layout_reason = this struct has unspecified layout
403-
lint_improper_ctypes_struct_non_exhaustive = this struct is non-exhaustive
404-
lint_improper_ctypes_struct_zst = this struct contains only zero-sized fields
402+
lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `{$ty}`
403+
lint_improper_ctypes_struct_layout_reason = `{$ty}` has unspecified layout
404+
lint_improper_ctypes_struct_non_exhaustive = `{$ty}` is non-exhaustive
405+
lint_improper_ctypes_struct_zst = `{$ty}` contains only zero-sized fields
405406
406407
lint_improper_ctypes_tuple_help = consider using a struct instead
407-
408408
lint_improper_ctypes_tuple_reason = tuples have unspecified layout
409-
lint_improper_ctypes_union_fieldless_help = consider adding a member to this union
410409
410+
lint_improper_ctypes_union_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
411+
lint_improper_ctypes_union_fieldless_help = consider adding a member to this union
411412
lint_improper_ctypes_union_fieldless_reason = this union has no fields
412-
lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union
413+
lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` attribute to this union
413414
414415
lint_improper_ctypes_union_layout_reason = this union has unspecified layout
415416
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 & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -182,18 +182,20 @@ fn get_type_from_field<'tcx>(
182182
cx.tcx.try_normalize_erasing_regions(cx.typing_env(), field_ty).unwrap_or(field_ty)
183183
}
184184

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

209211
if variant.field_list_has_applicable_non_exhaustive() {
210-
return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive_variant);
212+
return (false, true);
211213
}
212214

213-
ControlFlow::Continue(())
215+
(false, false)
214216
}
215217

216218
fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool {
@@ -1002,34 +1004,114 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10021004
) -> FfiResult<'tcx> {
10031005
use FfiResult::*;
10041006

1005-
let transparent_with_all_zst_fields = if def.repr().transparent() {
1006-
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
1007-
// Transparent newtypes have at most one non-ZST field which needs to be checked..
1008-
let field_ty = get_type_from_field(self.cx, field, args);
1009-
return self.visit_type(state.get_next(ty), field_ty);
1007+
let mut ffires_accumulator = FfiSafe;
1008+
1009+
let (transparent_with_all_zst_fields, field_list) =
1010+
if !matches!(def.adt_kind(), AdtKind::Enum) && def.repr().transparent() {
1011+
// determine if there is 0 or 1 non-1ZST field, and which it is.
1012+
// (note: for enums, "transparent" means 1-variant)
1013+
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
1014+
// Transparent newtypes have at most one non-ZST field which needs to be checked later
1015+
(false, vec![field])
1016+
} else {
1017+
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
1018+
// `PhantomData`).
1019+
(true, variant.fields.iter().collect::<Vec<_>>())
1020+
}
10101021
} else {
1011-
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
1012-
// `PhantomData`).
1013-
true
1014-
}
1015-
} else {
1016-
false
1017-
};
1022+
(false, variant.fields.iter().collect::<Vec<_>>())
1023+
};
10181024

10191025
// We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
10201026
let mut all_phantom = !variant.fields.is_empty();
1021-
for field in &variant.fields {
1027+
let mut fields_ok_list = vec![true; field_list.len()];
1028+
1029+
for (field_i, field) in field_list.into_iter().enumerate() {
10221030
let field_ty = get_type_from_field(self.cx, field, args);
1023-
all_phantom &= match self.visit_type(state.get_next(ty), field_ty) {
1024-
FfiSafe => false,
1031+
let ffi_res = self.visit_type(state.get_next(ty), field_ty);
1032+
1033+
// checking that this is not an FfiUnsafe due to an unit type:
1034+
// visit_type should be smart enough to not consider it unsafe if called from another ADT
1035+
#[cfg(debug_assertions)]
1036+
if let FfiUnsafe(ref reasons) = ffi_res {
1037+
if let (1, Some(FfiUnsafeExplanation { reason, .. })) =
1038+
(reasons.len(), reasons.first())
1039+
{
1040+
let FfiUnsafeReason { ty, .. } = reason.as_ref();
1041+
debug_assert!(!ty.is_unit());
1042+
}
1043+
}
1044+
1045+
all_phantom &= match ffi_res {
10251046
FfiPhantom(..) => true,
1047+
FfiSafe => false,
10261048
r @ FfiUnsafe { .. } => {
1027-
return r.wrap_all(ty, fluent::lint_improper_ctypes_struct_dueto, None);
1049+
fields_ok_list[field_i] = false;
1050+
ffires_accumulator += r;
1051+
false
10281052
}
10291053
}
10301054
}
10311055

1032-
if all_phantom {
1056+
// if we have bad fields, also report a possible transparent_with_all_zst_fields
1057+
// (if this combination is somehow possible)
1058+
// otherwise, having all fields be phantoms
1059+
// takes priority over transparent_with_all_zst_fields
1060+
if let FfiUnsafe(explanations) = ffires_accumulator {
1061+
// we assume the repr() of this ADT is either non-packed C or transparent.
1062+
debug_assert!(def.repr().c() || def.repr().transparent() || def.repr().int.is_some());
1063+
1064+
if def.repr().transparent() || matches!(def.adt_kind(), AdtKind::Enum) {
1065+
let field_ffires = FfiUnsafe(explanations).wrap_all(
1066+
ty,
1067+
fluent::lint_improper_ctypes_struct_dueto,
1068+
None,
1069+
);
1070+
if transparent_with_all_zst_fields {
1071+
field_ffires
1072+
+ FfiResult::new_with_reason(
1073+
ty,
1074+
fluent::lint_improper_ctypes_struct_zst,
1075+
None,
1076+
)
1077+
} else {
1078+
field_ffires
1079+
}
1080+
} else {
1081+
// since we have a repr(C) struct/union, there's a chance that we have some unsafe fields,
1082+
// but also exactly one non-1ZST field that is FFI-safe:
1083+
// we want to suggest repr(transparent) here.
1084+
// (FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises)
1085+
let non_1zst_fields = super::map_non_1zst_fields(self.cx.tcx, variant);
1086+
let (last_non_1zst, non_1zst_count) = non_1zst_fields.into_iter().enumerate().fold(
1087+
(None, 0_usize),
1088+
|(prev_nz, count), (field_i, is_nz)| {
1089+
if is_nz { (Some(field_i), count + 1) } else { (prev_nz, count) }
1090+
},
1091+
);
1092+
let help = if non_1zst_count == 1
1093+
&& last_non_1zst.map(|field_i| fields_ok_list[field_i]) == Some(true)
1094+
{
1095+
match def.adt_kind() {
1096+
AdtKind::Struct => {
1097+
Some(fluent::lint_improper_ctypes_struct_consider_transparent)
1098+
}
1099+
AdtKind::Union => {
1100+
Some(fluent::lint_improper_ctypes_union_consider_transparent)
1101+
}
1102+
AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"),
1103+
}
1104+
} else {
1105+
None
1106+
};
1107+
1108+
FfiUnsafe(explanations).wrap_all(
1109+
ty,
1110+
fluent::lint_improper_ctypes_struct_dueto,
1111+
help,
1112+
)
1113+
}
1114+
} else if all_phantom {
10331115
FfiPhantom(ty)
10341116
} else if transparent_with_all_zst_fields {
10351117
FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_struct_zst, None)
@@ -1141,22 +1223,41 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
11411223

11421224
let non_exhaustive = def.variant_list_has_applicable_non_exhaustive();
11431225
// Check the contained variants.
1144-
let ret = def.variants().iter().try_for_each(|variant| {
1145-
check_non_exhaustive_variant(non_exhaustive, variant)
1146-
.map_break(|reason| FfiResult::new_with_reason(ty, reason, None))?;
11471226

1148-
match self.visit_variant_fields(state, ty, def, variant, args) {
1149-
FfiSafe => ControlFlow::Continue(()),
1150-
r => ControlFlow::Break(r),
1151-
}
1227+
let (mut nonexhaustive_flag, mut nonexhaustive_variant_flag) = (false, false);
1228+
def.variants().iter().for_each(|variant| {
1229+
let (nonex_enum, nonex_var) = flag_non_exhaustive_variant(non_exhaustive, variant);
1230+
nonexhaustive_flag |= nonex_enum;
1231+
nonexhaustive_variant_flag |= nonex_var;
11521232
});
1153-
if let ControlFlow::Break(result) = ret {
1233+
1234+
// "nonexhaustive" lints only happen outside of the crate defining the enum, so no CItemKind override
1235+
// (meaning: the fault lies in the function call, not the enum)
1236+
if nonexhaustive_flag {
1237+
FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_non_exhaustive, None)
1238+
} else if nonexhaustive_variant_flag {
1239+
FfiResult::new_with_reason(
1240+
ty,
1241+
fluent::lint_improper_ctypes_non_exhaustive_variant,
1242+
None,
1243+
)
1244+
} else {
1245+
let ffires = def
1246+
.variants()
1247+
.iter()
1248+
.map(|variant| {
1249+
let variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1250+
// FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
1251+
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1252+
variant_res.forbid_phantom()
1253+
})
1254+
.reduce(|r1, r2| r1 + r2)
1255+
.unwrap(); // always at least one variant if we hit this branch
1256+
11541257
// this enum is visited in the middle of another lint,
11551258
// so we override the "cause type" of the lint
1156-
// (for more detail, see comment in ``visit_struct_union`` before its call to ``result.with_overrides``)
1157-
result.with_overrides(Some(ty))
1158-
} else {
1159-
FfiSafe
1259+
// (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
1260+
ffires.with_overrides(Some(ty))
11601261
}
11611262
}
11621263

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)