Skip to content

Commit 0e7277e

Browse files
committed
lint ImproperCTypes: deal with uninhabited types
1 parent d931eef commit 0e7277e

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
@@ -437,6 +437,11 @@ lint_improper_ctypes_struct_zst = `{$ty}` contains only zero-sized fields
437437
lint_improper_ctypes_tuple_help = consider using a struct instead
438438
lint_improper_ctypes_tuple_reason = tuples have unspecified layout
439439
440+
lint_improper_ctypes_uninhabited_enum = zero-variant enums and other uninhabited types are not allowed in function arguments and static variables
441+
lint_improper_ctypes_uninhabited_enum_deep = zero-variant enums and other uninhabited types are only allowed in function returns if used directly
442+
lint_improper_ctypes_uninhabited_never = the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables
443+
lint_improper_ctypes_uninhabited_never_deep = the never type (`!`) and other uninhabited types are only allowed in function returns if used directly
444+
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
440445
441446
lint_improper_ctypes_union_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
442447
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.
@@ -554,6 +597,45 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
554597
all_ffires
555598
}
556599

600+
/// Checks whether an uninhabited type (one without valid values) is safe-ish to have here
601+
fn visit_uninhabited(
602+
&mut self,
603+
state: CTypesVisitorState,
604+
outer_ty: Option<Ty<'tcx>>,
605+
ty: Ty<'tcx>,
606+
) -> FfiResult<'tcx> {
607+
if state.is_being_defined()
608+
|| (state.is_in_function_return()
609+
&& matches!(outer_ty.map(|ty| ty.kind()), None | Some(ty::FnPtr(..)),))
610+
{
611+
FfiResult::FfiSafe
612+
} else {
613+
let help = if state.is_in_function_return() {
614+
Some(fluent::lint_improper_ctypes_uninhabited_use_direct)
615+
} else {
616+
None
617+
};
618+
let desc = match ty.kind() {
619+
ty::Adt(..) => {
620+
if state.is_in_function_return() {
621+
fluent::lint_improper_ctypes_uninhabited_enum_deep
622+
} else {
623+
fluent::lint_improper_ctypes_uninhabited_enum
624+
}
625+
}
626+
ty::Never => {
627+
if state.is_in_function_return() {
628+
fluent::lint_improper_ctypes_uninhabited_never_deep
629+
} else {
630+
fluent::lint_improper_ctypes_uninhabited_never
631+
}
632+
}
633+
r @ _ => bug!("unexpected ty_kind in uninhabited type handling: {:?}", r),
634+
};
635+
FfiResult::new_with_reason(ty, desc, help)
636+
}
637+
}
638+
557639
/// Checks if a simple numeric (int, float) type has an actual portable definition
558640
/// for the compile target
559641
fn visit_numeric(&mut self, ty: Ty<'tcx>) -> FfiResult<'tcx> {
@@ -721,23 +803,45 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
721803
args: GenericArgsRef<'tcx>,
722804
) -> FfiResult<'tcx> {
723805
use FfiResult::*;
724-
let (transparent_with_all_zst_fields, field_list) = if def.repr().transparent() {
725-
// determine if there is 0 or 1 non-1ZST field, and which it is.
726-
// (note: enums are not allowed to br transparent)
727806

728-
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
729-
// Transparent newtypes have at most one non-ZST field which needs to be checked later
730-
(false, vec![field])
807+
let mut ffires_accumulator = FfiSafe;
808+
809+
let (transparent_with_all_zst_fields, field_list) =
810+
if !matches!(def.adt_kind(), AdtKind::Enum) && def.repr().transparent() {
811+
// determine if there is 0 or 1 non-1ZST field, and which it is.
812+
// (note: for enums, "transparent" means 1-variant)
813+
if ty.is_privately_uninhabited(self.cx.tcx, self.cx.typing_env()) {
814+
// let's consider transparent structs are considered unsafe if uninhabited,
815+
// even if that is because of fields otherwise ignored in FFI-safety checks
816+
// FIXME: and also maybe this should be "!is_inhabited_from" but from where?
817+
ffires_accumulator += variant
818+
.fields
819+
.iter()
820+
.map(|field| {
821+
let field_ty = get_type_from_field(self.cx, field, args);
822+
let mut field_res = self.visit_type(state, Some(ty), field_ty);
823+
field_res.take_with_core_note(&[
824+
fluent::lint_improper_ctypes_uninhabited_enum,
825+
fluent::lint_improper_ctypes_uninhabited_enum_deep,
826+
fluent::lint_improper_ctypes_uninhabited_never,
827+
fluent::lint_improper_ctypes_uninhabited_never_deep,
828+
])
829+
})
830+
.reduce(|r1, r2| r1 + r2)
831+
.unwrap() // if uninhabited, then >0 fields
832+
}
833+
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
834+
// Transparent newtypes have at most one non-ZST field which needs to be checked later
835+
(false, vec![field])
836+
} else {
837+
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
838+
// `PhantomData`).
839+
(true, variant.fields.iter().collect::<Vec<_>>())
840+
}
731841
} else {
732-
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
733-
// `PhantomData`).
734-
(true, variant.fields.iter().collect::<Vec<_>>())
735-
}
736-
} else {
737-
(false, variant.fields.iter().collect::<Vec<_>>())
738-
};
842+
(false, variant.fields.iter().collect::<Vec<_>>())
843+
};
739844

740-
let mut field_ffires = FfiSafe;
741845
// We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
742846
let mut all_phantom = !variant.fields.is_empty();
743847
let mut fields_ok_list = vec![true; field_list.len()];
@@ -763,7 +867,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
763867
FfiSafe => false,
764868
r @ FfiUnsafe { .. } => {
765869
fields_ok_list[field_i] = false;
766-
field_ffires += r;
870+
ffires_accumulator += r;
767871
false
768872
}
769873
}
@@ -773,7 +877,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
773877
// (if this combination is somehow possible)
774878
// otherwide, having all fields be phantoms
775879
// takes priority over transparent_with_all_zst_fields
776-
if let FfiUnsafe(explanations) = field_ffires {
880+
if let FfiUnsafe(explanations) = ffires_accumulator {
777881
// we assume the repr() of this ADT is either non-packed C or transparent.
778882
debug_assert!(
779883
(def.repr().c() && !def.repr().packed())
@@ -812,14 +916,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
812916
let help = if non_1zst_count == 1
813917
&& last_non_1zst.map(|field_i| fields_ok_list[field_i]) == Some(true)
814918
{
815-
match def.adt_kind() {
816-
AdtKind::Struct => {
817-
Some(fluent::lint_improper_ctypes_struct_consider_transparent)
818-
}
819-
AdtKind::Union => {
820-
Some(fluent::lint_improper_ctypes_union_consider_transparent)
919+
if ty.is_privately_uninhabited(self.cx.tcx, self.cx.typing_env()) {
920+
// uninhabited types can't be helped by being turned transparent
921+
None
922+
} else {
923+
match def.adt_kind() {
924+
AdtKind::Struct => {
925+
Some(fluent::lint_improper_ctypes_struct_consider_transparent)
926+
}
927+
AdtKind::Union => {
928+
Some(fluent::lint_improper_ctypes_union_consider_transparent)
929+
}
930+
AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"),
821931
}
822-
AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"),
823932
}
824933
} else {
825934
None
@@ -927,8 +1036,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
9271036

9281037
if def.variants().is_empty() {
9291038
// Empty enums are implicitely handled as the never type:
930-
// FIXME think about the FFI-safety of functions that use that
931-
return FfiSafe;
1039+
return self.visit_uninhabited(state, outer_ty, ty);
9321040
}
9331041
// Check for a repr() attribute to specify the size of the
9341042
// discriminant.
@@ -970,18 +1078,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
9701078
None,
9711079
)
9721080
} else {
973-
let ffires = def
1081+
// small caveat to checking the variants: we authorise up to n-1 invariants
1082+
// to be unsafe because uninhabited.
1083+
// so for now let's isolate those unsafeties
1084+
let mut variants_uninhabited_ffires = vec![FfiSafe; def.variants().len()];
1085+
1086+
let mut ffires = def
9741087
.variants()
9751088
.iter()
976-
.map(|variant| {
977-
self.visit_variant_fields(state, ty, def, variant, args)
978-
// FIXME: check that enums allow any (up to all) variants to be phantoms?
979-
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
980-
.forbid_phantom()
1089+
.enumerate()
1090+
.map(|(variant_i, variant)| {
1091+
let mut variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1092+
variants_uninhabited_ffires[variant_i] = variant_res.take_with_core_note(&[
1093+
fluent::lint_improper_ctypes_uninhabited_enum,
1094+
fluent::lint_improper_ctypes_uninhabited_enum_deep,
1095+
fluent::lint_improper_ctypes_uninhabited_never,
1096+
fluent::lint_improper_ctypes_uninhabited_never_deep,
1097+
]);
1098+
// FIXME: check that enums allow any (up to all) variants to be phantoms?
1099+
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1100+
variant_res.forbid_phantom()
9811101
})
9821102
.reduce(|r1, r2| r1 + r2)
9831103
.unwrap(); // always at least one variant if we hit this branch
9841104

1105+
if variants_uninhabited_ffires.iter().all(|res| matches!(res, FfiUnsafe(..))) {
1106+
// if the enum is uninhabited, because all its variants are uninhabited
1107+
ffires += variants_uninhabited_ffires.into_iter().reduce(|r1, r2| r1 + r2).unwrap();
1108+
}
1109+
9851110
// if outer_ty.is_some() || !state.is_being_defined() then this enum is visited in the middle of another lint,
9861111
// so we override the "cause type" of the lint
9871112
// (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
@@ -1075,7 +1200,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10751200
ty::Int(..) | ty::Uint(..) | ty::Float(..) => self.visit_numeric(ty),
10761201

10771202
// Primitive types with a stable representation.
1078-
ty::Bool | ty::Never => FfiSafe,
1203+
ty::Bool => FfiSafe,
10791204

10801205
ty::Slice(_) => FfiResult::new_with_reason(
10811206
ty,
@@ -1194,6 +1319,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
11941319

11951320
ty::Foreign(..) => FfiSafe,
11961321

1322+
ty::Never => self.visit_uninhabited(state, outer_ty, ty),
1323+
11971324
// This is only half of the checking-for-opaque-aliases story:
11981325
// since they are liable to vanish on normalisation, we need a specific to find them through
11991326
// 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
@@ -79,7 +79,7 @@ struct Field(());
7979
enum NonExhaustive {}
8080

8181
extern "C" {
82-
fn zf(x: Z);
82+
fn zf(x: Z); //~ ERROR `extern` block uses type `Z`
8383
fn uf(x: U); //~ ERROR `extern` block uses type `U`
8484
fn bf(x: B); //~ ERROR `extern` block uses type `B`
8585
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:82: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:10: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:83: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:84:14
@@ -281,5 +294,5 @@ LL | fn result_unit_t_e(x: Result<(), ()>);
281294
= help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
282295
= note: enum has no representation hint
283296

284-
error: aborting due to 29 previous errors
297+
error: aborting due to 30 previous errors
285298

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)