Skip to content

Commit d2b309b

Browse files
committed
lint ImproperCTypes: deal with uninhabited types
1 parent 51ab082 commit d2b309b

File tree

9 files changed

+438
-46
lines changed

9 files changed

+438
-46
lines changed

compiler/rustc_lint/messages.ftl

+5
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,11 @@ lint_improper_ctypes_struct_zst = `{$ty}` contains only zero-sized fields
431431
lint_improper_ctypes_tuple_help = consider using a struct instead
432432
lint_improper_ctypes_tuple_reason = tuples have unspecified layout
433433
434+
lint_improper_ctypes_uninhabited_enum = zero-variant enums and other uninhabited types are not allowed in function arguments and static variables
435+
lint_improper_ctypes_uninhabited_enum_deep = zero-variant enums and other uninhabited types are only allowed in function returns if used directly
436+
lint_improper_ctypes_uninhabited_never = the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables
437+
lint_improper_ctypes_uninhabited_never_deep = the never type (`!`) and other uninhabited types are only allowed in function returns if used directly
438+
lint_improper_ctypes_uninhabited_use_direct = if you meant to have a function that never returns, consider setting its return type to the never type (`!`) or a zero-variant enum
434439
435440
lint_improper_ctypes_union_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
436441
lint_improper_ctypes_union_fieldless_help = consider adding a member to this union

compiler/rustc_lint/src/types/improper_ctypes.rs

+159-32
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,49 @@ impl<'tcx> FfiResult<'tcx> {
205205
}
206206
}
207207

208+
/// Selectively "pluck" some explanations out of a FfiResult::FfiUnsafe,
209+
/// if the note at their core reason is one in a provided list.
210+
/// if the FfiResult is not FfiUnsafe, or if no reasons are plucked,
211+
/// then return FfiSafe.
212+
fn take_with_core_note(&mut self, notes: &[DiagMessage]) -> Self {
213+
match self {
214+
Self::FfiUnsafe(ref mut this) => {
215+
let mut remaining_explanations = vec![];
216+
std::mem::swap(this, &mut remaining_explanations);
217+
let mut filtered_explanations = vec![];
218+
let mut remaining_explanations = remaining_explanations
219+
.into_iter()
220+
.filter_map(|explanation| {
221+
let mut reason = explanation.reason.as_ref();
222+
while let Some(ref inner) = reason.inner {
223+
reason = inner.as_ref();
224+
}
225+
let mut does_remain = true;
226+
for note_match in notes {
227+
if note_match == &reason.note {
228+
does_remain = false;
229+
break;
230+
}
231+
}
232+
if does_remain {
233+
Some(explanation)
234+
} else {
235+
filtered_explanations.push(explanation);
236+
None
237+
}
238+
})
239+
.collect::<Vec<_>>();
240+
std::mem::swap(this, &mut remaining_explanations);
241+
if filtered_explanations.len() > 0 {
242+
Self::FfiUnsafe(filtered_explanations)
243+
} else {
244+
Self::FfiSafe
245+
}
246+
}
247+
_ => Self::FfiSafe,
248+
}
249+
}
250+
208251
/// wrap around code that generates FfiResults "from a different cause".
209252
/// for instance, if we have a repr(C) struct in a function's argument, FFI unsafeties inside the struct
210253
/// are to be blamed on the struct and not the members.
@@ -577,6 +620,45 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
577620
// all_ffires.with_overrides(override_cause_ty)
578621
}
579622

623+
/// Checks whether an uninhabited type (one without valid values) is safe-ish to have here
624+
fn visit_uninhabited(
625+
&mut self,
626+
state: CTypesVisitorState,
627+
outer_ty: Option<Ty<'tcx>>,
628+
ty: Ty<'tcx>,
629+
) -> FfiResult<'tcx> {
630+
if state.is_being_defined()
631+
|| (state.is_in_function_return()
632+
&& matches!(outer_ty.map(|ty| ty.kind()), None | Some(ty::FnPtr(..)),))
633+
{
634+
FfiResult::FfiSafe
635+
} else {
636+
let help = if state.is_in_function_return() {
637+
Some(fluent::lint_improper_ctypes_uninhabited_use_direct)
638+
} else {
639+
None
640+
};
641+
let desc = match ty.kind() {
642+
ty::Adt(..) => {
643+
if state.is_in_function_return() {
644+
fluent::lint_improper_ctypes_uninhabited_enum_deep
645+
} else {
646+
fluent::lint_improper_ctypes_uninhabited_enum
647+
}
648+
}
649+
ty::Never => {
650+
if state.is_in_function_return() {
651+
fluent::lint_improper_ctypes_uninhabited_never_deep
652+
} else {
653+
fluent::lint_improper_ctypes_uninhabited_never
654+
}
655+
}
656+
r @ _ => bug!("unexpected ty_kind in uninhabited type handling: {:?}", r),
657+
};
658+
FfiResult::new_with_reason(ty, desc, help)
659+
}
660+
}
661+
580662
/// Checks if a simple numeric (int, float) type has an actual portable definition
581663
/// for the compile target
582664
fn visit_numeric(&mut self, ty: Ty<'tcx>) -> FfiResult<'tcx> {
@@ -778,23 +860,45 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
778860
args: GenericArgsRef<'tcx>,
779861
) -> FfiResult<'tcx> {
780862
use FfiResult::*;
781-
let (transparent_with_all_zst_fields, field_list) = if def.repr().transparent() {
782-
// determine if there is 0 or 1 non-1ZST field, and which it is.
783-
// (note: enums are not allowed to br transparent)
784863

785-
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
786-
// Transparent newtypes have at most one non-ZST field which needs to be checked later
787-
(false, vec![field])
864+
let mut ffires_accumulator = FfiSafe;
865+
866+
let (transparent_with_all_zst_fields, field_list) =
867+
if !matches!(def.adt_kind(), AdtKind::Enum) && def.repr().transparent() {
868+
// determine if there is 0 or 1 non-1ZST field, and which it is.
869+
// (note: for enums, "transparent" means 1-variant)
870+
if ty.is_privately_uninhabited(self.cx.tcx, self.cx.typing_env()) {
871+
// let's consider transparent structs are considered unsafe if uninhabited,
872+
// even if that is because of fields otherwise ignored in FFI-safety checks
873+
// FIXME: and also maybe this should be "!is_inhabited_from" but from where?
874+
ffires_accumulator += variant
875+
.fields
876+
.iter()
877+
.map(|field| {
878+
let field_ty = get_type_from_field(self.cx, field, args);
879+
let mut field_res = self.visit_type(state, Some(ty), field_ty);
880+
field_res.take_with_core_note(&[
881+
fluent::lint_improper_ctypes_uninhabited_enum,
882+
fluent::lint_improper_ctypes_uninhabited_enum_deep,
883+
fluent::lint_improper_ctypes_uninhabited_never,
884+
fluent::lint_improper_ctypes_uninhabited_never_deep,
885+
])
886+
})
887+
.reduce(|r1, r2| r1 + r2)
888+
.unwrap() // if uninhabited, then >0 fields
889+
}
890+
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
891+
// Transparent newtypes have at most one non-ZST field which needs to be checked later
892+
(false, vec![field])
893+
} else {
894+
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
895+
// `PhantomData`).
896+
(true, variant.fields.iter().collect::<Vec<_>>())
897+
}
788898
} else {
789-
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
790-
// `PhantomData`).
791-
(true, variant.fields.iter().collect::<Vec<_>>())
792-
}
793-
} else {
794-
(false, variant.fields.iter().collect::<Vec<_>>())
795-
};
899+
(false, variant.fields.iter().collect::<Vec<_>>())
900+
};
796901

797-
let mut field_ffires = FfiSafe;
798902
// We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
799903
let mut all_phantom = !variant.fields.is_empty();
800904
let mut fields_ok_list = vec![true; field_list.len()];
@@ -820,7 +924,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
820924
FfiSafe => false,
821925
r @ FfiUnsafe { .. } => {
822926
fields_ok_list[field_i] = false;
823-
field_ffires += r;
927+
ffires_accumulator += r;
824928
false
825929
}
826930
}
@@ -830,7 +934,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
830934
// (if somehow possible)
831935
// otherwide, having all fields be phantoms
832936
// takes priority over transparent_with_all_zst_fields
833-
if let FfiUnsafe(explanations) = field_ffires {
937+
if let FfiUnsafe(explanations) = ffires_accumulator {
834938
// we assume the repr() of this ADT is either non-packed C or transparent.
835939
debug_assert!(
836940
(def.repr().c() && !def.repr().packed())
@@ -869,14 +973,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
869973
let help = if non_1zst_count == 1
870974
&& last_non_1zst.map(|field_i| fields_ok_list[field_i]) == Some(true)
871975
{
872-
match def.adt_kind() {
873-
AdtKind::Struct => {
874-
Some(fluent::lint_improper_ctypes_struct_consider_transparent)
875-
}
876-
AdtKind::Union => {
877-
Some(fluent::lint_improper_ctypes_union_consider_transparent)
976+
if ty.is_privately_uninhabited(self.cx.tcx, self.cx.typing_env()) {
977+
// uninhabited types can't be helped by being turned transparent
978+
None
979+
} else {
980+
match def.adt_kind() {
981+
AdtKind::Struct => {
982+
Some(fluent::lint_improper_ctypes_struct_consider_transparent)
983+
}
984+
AdtKind::Union => {
985+
Some(fluent::lint_improper_ctypes_union_consider_transparent)
986+
}
987+
AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"),
878988
}
879-
AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"),
880989
}
881990
} else {
882991
None
@@ -987,8 +1096,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
9871096

9881097
if def.variants().is_empty() {
9891098
// Empty enums are implicitely handled as the never type:
990-
// FIXME think about the FFI-safety of functions that use that
991-
return FfiSafe;
1099+
return self.visit_uninhabited(state, outer_ty, ty);
9921100
}
9931101
// Check for a repr() attribute to specify the size of the
9941102
// discriminant.
@@ -1026,18 +1134,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10261134
None,
10271135
)
10281136
} else {
1029-
let ffires = def
1137+
// small caveat to checking the variants: we authorise up to n-1 invariants
1138+
// to be unsafe because uninhabited.
1139+
// so for now let's isolate those unsafeties
1140+
let mut variants_uninhabited_ffires = vec![FfiSafe; def.variants().len()];
1141+
1142+
let mut ffires = def
10301143
.variants()
10311144
.iter()
1032-
.map(|variant| {
1033-
self.visit_variant_fields(state, ty, def, variant, args)
1034-
// FIXME: check that enums allow any (up to all) variants to be phantoms?
1035-
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1036-
.forbid_phantom()
1145+
.enumerate()
1146+
.map(|(variant_i, variant)| {
1147+
let mut variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1148+
variants_uninhabited_ffires[variant_i] = variant_res.take_with_core_note(&[
1149+
fluent::lint_improper_ctypes_uninhabited_enum,
1150+
fluent::lint_improper_ctypes_uninhabited_enum_deep,
1151+
fluent::lint_improper_ctypes_uninhabited_never,
1152+
fluent::lint_improper_ctypes_uninhabited_never_deep,
1153+
]);
1154+
// FIXME: check that enums allow any (up to all) variants to be phantoms?
1155+
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1156+
variant_res.forbid_phantom()
10371157
})
10381158
.reduce(|r1, r2| r1 + r2)
10391159
.unwrap(); // always at least one variant if we hit this branch
10401160

1161+
if variants_uninhabited_ffires.iter().all(|res| matches!(res, FfiUnsafe(..))) {
1162+
// if the enum is uninhabited, because all its variants are uninhabited
1163+
ffires += variants_uninhabited_ffires.into_iter().reduce(|r1, r2| r1 + r2).unwrap();
1164+
}
1165+
10411166
// if outer_ty.is_some() || !state.is_being_defined() then this enum is visited in the middle of another lint,
10421167
// so we override the "cause type" of the lint
10431168
// (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
@@ -1132,7 +1257,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
11321257
ty::Int(..) | ty::Uint(..) | ty::Float(..) => self.visit_numeric(ty),
11331258

11341259
// Primitive types with a stable representation.
1135-
ty::Bool | ty::Never => FfiSafe,
1260+
ty::Bool => FfiSafe,
11361261

11371262
ty::Slice(_) => FfiResult::new_with_reason(
11381263
ty,
@@ -1251,6 +1376,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12511376

12521377
ty::Foreign(..) => FfiSafe,
12531378

1379+
ty::Never => self.visit_uninhabited(state, outer_ty, ty),
1380+
12541381
// This is only half of the checking-for-opaque-aliases story:
12551382
// since they are liable to vanish on normalisation, we need a specific to find them through
12561383
// other aliases, which is called in the next branch of this `match ty.kind()` statement

tests/ui/lint/improper_ctypes/lint-enum.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ struct Field(());
6464
enum NonExhaustive {}
6565

6666
extern "C" {
67-
fn zf(x: Z);
67+
fn zf(x: Z); //~ ERROR `extern` block uses type `Z`
6868
fn uf(x: U); //~ ERROR `extern` block uses type `U`
6969
fn bf(x: B); //~ ERROR `extern` block uses type `B`
7070
fn tf(x: T); //~ ERROR `extern` block uses type `T`

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

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
error: `extern` block uses type `Z`, which is not FFI-safe
2+
--> $DIR/lint-enum.rs:67:14
3+
|
4+
LL | fn zf(x: Z);
5+
| ^ not FFI-safe
6+
|
7+
= note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables
8+
note: the type is defined here
9+
--> $DIR/lint-enum.rs:8:1
10+
|
11+
LL | enum Z {}
12+
| ^^^^^^
13+
note: the lint level is defined here
14+
--> $DIR/lint-enum.rs:2:9
15+
|
16+
LL | #![deny(improper_ctypes)]
17+
| ^^^^^^^^^^^^^^^
18+
119
error: `extern` block uses type `U`, which is not FFI-safe
220
--> $DIR/lint-enum.rs:68:14
321
|
@@ -11,11 +29,6 @@ note: the type is defined here
1129
|
1230
LL | enum U {
1331
| ^^^^^^
14-
note: the lint level is defined here
15-
--> $DIR/lint-enum.rs:2:9
16-
|
17-
LL | #![deny(improper_ctypes)]
18-
| ^^^^^^^^^^^^^^^
1932

2033
error: `extern` block uses type `B`, which is not FFI-safe
2134
--> $DIR/lint-enum.rs:69:14
@@ -246,5 +259,5 @@ LL | fn result_unit_t_e(x: Result<(), ()>);
246259
= help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
247260
= note: enum has no representation hint
248261

249-
error: aborting due to 26 previous errors
262+
error: aborting due to 27 previous errors
250263

tests/ui/lint/improper_ctypes/lint-tykind-fuzz.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ extern "C" fn all_ty_kinds<'a,const N:usize,T>(
148148
// Ref[Struct]
149149
e3: &StructWithDyn, //~ ERROR: uses type `&StructWithDyn`
150150
// Never
151-
x:!,
151+
x:!, //~ ERROR: uses type `!`
152152
//r1: &u8, r2: *const u8, r3: Box<u8>,
153153
// FnPtr
154154
f2: fn(u8)->u8, //~ ERROR: uses type `fn(u8) -> u8`

tests/ui/lint/improper_ctypes/lint-tykind-fuzz.stderr

+9-1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ LL | e3: &StructWithDyn,
135135
|
136136
= note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer
137137

138+
error: `extern` fn uses type `!`, which is not FFI-safe
139+
--> $DIR/lint-tykind-fuzz.rs:151:5
140+
|
141+
LL | x:!,
142+
| ^ not FFI-safe
143+
|
144+
= note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables
145+
138146
error: `extern` fn uses type `fn(u8) -> u8`, which is not FFI-safe
139147
--> $DIR/lint-tykind-fuzz.rs:154:7
140148
|
@@ -487,5 +495,5 @@ LL | | }
487495
|
488496
= note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer
489497

490-
error: aborting due to 51 previous errors; 1 warning emitted
498+
error: aborting due to 52 previous errors; 1 warning emitted
491499

0 commit comments

Comments
 (0)