Skip to content

Commit 7810cd2

Browse files
committed
lint ImproperCTypes: deal with uninhabited types
1 parent cde6ad5 commit 7810cd2

File tree

9 files changed

+438
-46
lines changed

9 files changed

+438
-46
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,11 @@ lint_improper_ctypes_struct_zst = `{$ty}` contains only zero-sized fields
439439
lint_improper_ctypes_tuple_help = consider using a struct instead
440440
lint_improper_ctypes_tuple_reason = tuples have unspecified layout
441441
442+
lint_improper_ctypes_uninhabited_enum = zero-variant enums and other uninhabited types are not allowed in function arguments and static variables
443+
lint_improper_ctypes_uninhabited_enum_deep = zero-variant enums and other uninhabited types are only allowed in function returns if used directly
444+
lint_improper_ctypes_uninhabited_never = the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables
445+
lint_improper_ctypes_uninhabited_never_deep = the never type (`!`) and other uninhabited types are only allowed in function returns if used directly
446+
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
442447
443448
lint_improper_ctypes_union_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
444449
lint_improper_ctypes_union_fieldless_help = consider adding a member to this union

compiler/rustc_lint/src/types/improper_ctypes.rs

Lines changed: 159 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,49 @@ impl<'tcx> FfiResult<'tcx> {
189189
}
190190
}
191191

192+
/// Selectively "pluck" some explanations out of a FfiResult::FfiUnsafe,
193+
/// if the note at their core reason is one in a provided list.
194+
/// if the FfiResult is not FfiUnsafe, or if no reasons are plucked,
195+
/// then return FfiSafe.
196+
fn take_with_core_note(&mut self, notes: &[DiagMessage]) -> Self {
197+
match self {
198+
Self::FfiUnsafe(this) => {
199+
let mut remaining_explanations = vec![];
200+
std::mem::swap(this, &mut remaining_explanations);
201+
let mut filtered_explanations = vec![];
202+
let mut remaining_explanations = remaining_explanations
203+
.into_iter()
204+
.filter_map(|explanation| {
205+
let mut reason = explanation.reason.as_ref();
206+
while let Some(ref inner) = reason.inner {
207+
reason = inner.as_ref();
208+
}
209+
let mut does_remain = true;
210+
for note_match in notes {
211+
if note_match == &reason.note {
212+
does_remain = false;
213+
break;
214+
}
215+
}
216+
if does_remain {
217+
Some(explanation)
218+
} else {
219+
filtered_explanations.push(explanation);
220+
None
221+
}
222+
})
223+
.collect::<Vec<_>>();
224+
std::mem::swap(this, &mut remaining_explanations);
225+
if filtered_explanations.len() > 0 {
226+
Self::FfiUnsafe(filtered_explanations)
227+
} else {
228+
Self::FfiSafe
229+
}
230+
}
231+
_ => Self::FfiSafe,
232+
}
233+
}
234+
192235
/// wrap around code that generates FfiResults "from a different cause".
193236
/// for instance, if we have a repr(C) struct in a function's argument, FFI unsafeties inside the struct
194237
/// are to be blamed on the struct and not the members.
@@ -585,6 +628,45 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
585628
all_ffires
586629
}
587630

631+
/// Checks whether an uninhabited type (one without valid values) is safe-ish to have here
632+
fn visit_uninhabited(
633+
&self,
634+
state: CTypesVisitorState,
635+
outer_ty: Option<Ty<'tcx>>,
636+
ty: Ty<'tcx>,
637+
) -> FfiResult<'tcx> {
638+
if state.is_being_defined()
639+
|| (state.is_in_function_return()
640+
&& matches!(outer_ty.map(|ty| ty.kind()), None | Some(ty::FnPtr(..)),))
641+
{
642+
FfiResult::FfiSafe
643+
} else {
644+
let help = if state.is_in_function_return() {
645+
Some(fluent::lint_improper_ctypes_uninhabited_use_direct)
646+
} else {
647+
None
648+
};
649+
let desc = match ty.kind() {
650+
ty::Adt(..) => {
651+
if state.is_in_function_return() {
652+
fluent::lint_improper_ctypes_uninhabited_enum_deep
653+
} else {
654+
fluent::lint_improper_ctypes_uninhabited_enum
655+
}
656+
}
657+
ty::Never => {
658+
if state.is_in_function_return() {
659+
fluent::lint_improper_ctypes_uninhabited_never_deep
660+
} else {
661+
fluent::lint_improper_ctypes_uninhabited_never
662+
}
663+
}
664+
r @ _ => bug!("unexpected ty_kind in uninhabited type handling: {:?}", r),
665+
};
666+
FfiResult::new_with_reason(ty, desc, help)
667+
}
668+
}
669+
588670
/// Checks if a simple numeric (int, float) type has an actual portable definition
589671
/// for the compile target
590672
fn visit_numeric(&self, _ty: Ty<'tcx>) -> FfiResult<'tcx> {
@@ -751,23 +833,45 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
751833
args: GenericArgsRef<'tcx>,
752834
) -> FfiResult<'tcx> {
753835
use FfiResult::*;
754-
let (transparent_with_all_zst_fields, field_list) = if def.repr().transparent() {
755-
// determine if there is 0 or 1 non-1ZST field, and which it is.
756-
// (note: enums are not allowed to br transparent)
757836

758-
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
759-
// Transparent newtypes have at most one non-ZST field which needs to be checked later
760-
(false, vec![field])
837+
let mut ffires_accumulator = FfiSafe;
838+
839+
let (transparent_with_all_zst_fields, field_list) =
840+
if !matches!(def.adt_kind(), AdtKind::Enum) && def.repr().transparent() {
841+
// determine if there is 0 or 1 non-1ZST field, and which it is.
842+
// (note: for enums, "transparent" means 1-variant)
843+
if ty.is_privately_uninhabited(self.cx.tcx, self.cx.typing_env()) {
844+
// let's consider transparent structs are considered unsafe if uninhabited,
845+
// even if that is because of fields otherwise ignored in FFI-safety checks
846+
// FIXME: and also maybe this should be "!is_inhabited_from" but from where?
847+
ffires_accumulator += variant
848+
.fields
849+
.iter()
850+
.map(|field| {
851+
let field_ty = get_type_from_field(self.cx, field, args);
852+
let mut field_res = self.visit_type(state, Some(ty), field_ty);
853+
field_res.take_with_core_note(&[
854+
fluent::lint_improper_ctypes_uninhabited_enum,
855+
fluent::lint_improper_ctypes_uninhabited_enum_deep,
856+
fluent::lint_improper_ctypes_uninhabited_never,
857+
fluent::lint_improper_ctypes_uninhabited_never_deep,
858+
])
859+
})
860+
.reduce(|r1, r2| r1 + r2)
861+
.unwrap() // if uninhabited, then >0 fields
862+
}
863+
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
864+
// Transparent newtypes have at most one non-ZST field which needs to be checked later
865+
(false, vec![field])
866+
} else {
867+
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
868+
// `PhantomData`).
869+
(true, variant.fields.iter().collect::<Vec<_>>())
870+
}
761871
} else {
762-
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
763-
// `PhantomData`).
764-
(true, variant.fields.iter().collect::<Vec<_>>())
765-
}
766-
} else {
767-
(false, variant.fields.iter().collect::<Vec<_>>())
768-
};
872+
(false, variant.fields.iter().collect::<Vec<_>>())
873+
};
769874

770-
let mut field_ffires = FfiSafe;
771875
// We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
772876
let mut all_phantom = !variant.fields.is_empty();
773877
let mut fields_ok_list = vec![true; field_list.len()];
@@ -793,7 +897,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
793897
FfiSafe => false,
794898
r @ FfiUnsafe { .. } => {
795899
fields_ok_list[field_i] = false;
796-
field_ffires += r;
900+
ffires_accumulator += r;
797901
false
798902
}
799903
}
@@ -803,7 +907,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
803907
// (if this combination is somehow possible)
804908
// otherwide, having all fields be phantoms
805909
// takes priority over transparent_with_all_zst_fields
806-
if let FfiUnsafe(explanations) = field_ffires {
910+
if let FfiUnsafe(explanations) = ffires_accumulator {
807911
// we assume the repr() of this ADT is either non-packed C or transparent.
808912
debug_assert!(
809913
(def.repr().c() && !def.repr().packed())
@@ -842,14 +946,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
842946
let help = if non_1zst_count == 1
843947
&& last_non_1zst.map(|field_i| fields_ok_list[field_i]) == Some(true)
844948
{
845-
match def.adt_kind() {
846-
AdtKind::Struct => {
847-
Some(fluent::lint_improper_ctypes_struct_consider_transparent)
848-
}
849-
AdtKind::Union => {
850-
Some(fluent::lint_improper_ctypes_union_consider_transparent)
949+
if ty.is_privately_uninhabited(self.cx.tcx, self.cx.typing_env()) {
950+
// uninhabited types can't be helped by being turned transparent
951+
None
952+
} else {
953+
match def.adt_kind() {
954+
AdtKind::Struct => {
955+
Some(fluent::lint_improper_ctypes_struct_consider_transparent)
956+
}
957+
AdtKind::Union => {
958+
Some(fluent::lint_improper_ctypes_union_consider_transparent)
959+
}
960+
AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"),
851961
}
852-
AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"),
853962
}
854963
} else {
855964
None
@@ -957,8 +1066,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
9571066

9581067
if def.variants().is_empty() {
9591068
// Empty enums are implicitely handled as the never type:
960-
// FIXME think about the FFI-safety of functions that use that
961-
return FfiSafe;
1069+
return self.visit_uninhabited(state, outer_ty, ty);
9621070
}
9631071
// Check for a repr() attribute to specify the size of the
9641072
// discriminant.
@@ -999,18 +1107,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
9991107
None,
10001108
)
10011109
} else {
1002-
let ffires = def
1110+
// small caveat to checking the variants: we authorise up to n-1 invariants
1111+
// to be unsafe because uninhabited.
1112+
// so for now let's isolate those unsafeties
1113+
let mut variants_uninhabited_ffires = vec![FfiSafe; def.variants().len()];
1114+
1115+
let mut ffires = def
10031116
.variants()
10041117
.iter()
1005-
.map(|variant| {
1006-
self.visit_variant_fields(state, ty, def, variant, args)
1007-
// FIXME: check that enums allow any (up to all) variants to be phantoms?
1008-
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1009-
.forbid_phantom()
1118+
.enumerate()
1119+
.map(|(variant_i, variant)| {
1120+
let mut variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1121+
variants_uninhabited_ffires[variant_i] = variant_res.take_with_core_note(&[
1122+
fluent::lint_improper_ctypes_uninhabited_enum,
1123+
fluent::lint_improper_ctypes_uninhabited_enum_deep,
1124+
fluent::lint_improper_ctypes_uninhabited_never,
1125+
fluent::lint_improper_ctypes_uninhabited_never_deep,
1126+
]);
1127+
// FIXME: check that enums allow any (up to all) variants to be phantoms?
1128+
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1129+
variant_res.forbid_phantom()
10101130
})
10111131
.reduce(|r1, r2| r1 + r2)
10121132
.unwrap(); // always at least one variant if we hit this branch
10131133

1134+
if variants_uninhabited_ffires.iter().all(|res| matches!(res, FfiUnsafe(..))) {
1135+
// if the enum is uninhabited, because all its variants are uninhabited
1136+
ffires += variants_uninhabited_ffires.into_iter().reduce(|r1, r2| r1 + r2).unwrap();
1137+
}
1138+
10141139
// if outer_ty.is_some() || !state.is_being_defined() then this enum is visited in the middle of another lint,
10151140
// so we override the "cause type" of the lint
10161141
// (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
@@ -1100,7 +1225,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
11001225
ty::Int(..) | ty::Uint(..) | ty::Float(..) => self.visit_numeric(ty),
11011226

11021227
// Primitive types with a stable representation.
1103-
ty::Bool | ty::Never => FfiSafe,
1228+
ty::Bool => FfiSafe,
11041229

11051230
ty::Slice(inner_ty) => {
11061231
// ty::Slice is used for !Sized arrays, since they are the pointee for actual slices
@@ -1238,6 +1363,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12381363

12391364
ty::Foreign(..) => FfiSafe,
12401365

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ struct Field(());
7777
enum NonExhaustive {}
7878

7979
extern "C" {
80-
fn zf(x: Z);
80+
fn zf(x: Z); //~ ERROR `extern` block uses type `Z`
8181
fn uf(x: U); //~ ERROR `extern` block uses type `U`
8282
fn bf(x: B); //~ ERROR `extern` block uses type `B`
8383
fn tf(x: T); //~ ERROR `extern` block uses type `T`

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

Lines changed: 19 additions & 6 deletions
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:80: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:81: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:82:14
@@ -207,5 +220,5 @@ LL | fn result_unit_t_e(x: Result<(), ()>);
207220
= help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
208221
= note: enum has no representation hint
209222

210-
error: aborting due to 21 previous errors
223+
error: aborting due to 22 previous errors
211224

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 9 additions & 1 deletion
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)