Skip to content

Commit b41bda8

Browse files
committed
lint ImproperCTypes: deal with uninhabited types
1 parent 98488c8 commit b41bda8

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
@@ -441,6 +441,11 @@ lint_improper_ctypes_struct_zst = `{$ty}` contains only zero-sized fields
441441
lint_improper_ctypes_tuple_help = consider using a struct instead
442442
lint_improper_ctypes_tuple_reason = tuples have unspecified layout
443443
444+
lint_improper_ctypes_uninhabited_enum = zero-variant enums and other uninhabited types are not allowed in function arguments and static variables
445+
lint_improper_ctypes_uninhabited_enum_deep = zero-variant enums and other uninhabited types are only allowed in function returns if used directly
446+
lint_improper_ctypes_uninhabited_never = the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables
447+
lint_improper_ctypes_uninhabited_never_deep = the never type (`!`) and other uninhabited types are only allowed in function returns if used directly
448+
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
444449
445450
lint_improper_ctypes_union_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
446451
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
@@ -190,6 +190,49 @@ impl<'tcx> FfiResult<'tcx> {
190190
}
191191
}
192192

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

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

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

772-
let mut field_ffires = FfiSafe;
773877
// We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
774878
let mut all_phantom = !variant.fields.is_empty();
775879
let mut fields_ok_list = vec![true; field_list.len()];
@@ -795,7 +899,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
795899
FfiSafe => false,
796900
r @ FfiUnsafe { .. } => {
797901
fields_ok_list[field_i] = false;
798-
field_ffires += r;
902+
ffires_accumulator += r;
799903
false
800904
}
801905
}
@@ -805,7 +909,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
805909
// (if this combination is somehow possible)
806910
// otherwide, having all fields be phantoms
807911
// takes priority over transparent_with_all_zst_fields
808-
if let FfiUnsafe(explanations) = field_ffires {
912+
if let FfiUnsafe(explanations) = ffires_accumulator {
809913
// we assume the repr() of this ADT is either non-packed C or transparent.
810914
debug_assert!(
811915
(def.repr().c() && !def.repr().packed())
@@ -844,14 +948,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
844948
let help = if non_1zst_count == 1
845949
&& last_non_1zst.map(|field_i| fields_ok_list[field_i]) == Some(true)
846950
{
847-
match def.adt_kind() {
848-
AdtKind::Struct => {
849-
Some(fluent::lint_improper_ctypes_struct_consider_transparent)
850-
}
851-
AdtKind::Union => {
852-
Some(fluent::lint_improper_ctypes_union_consider_transparent)
951+
if ty.is_privately_uninhabited(self.cx.tcx, self.cx.typing_env()) {
952+
// uninhabited types can't be helped by being turned transparent
953+
None
954+
} else {
955+
match def.adt_kind() {
956+
AdtKind::Struct => {
957+
Some(fluent::lint_improper_ctypes_struct_consider_transparent)
958+
}
959+
AdtKind::Union => {
960+
Some(fluent::lint_improper_ctypes_union_consider_transparent)
961+
}
962+
AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"),
853963
}
854-
AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"),
855964
}
856965
} else {
857966
None
@@ -959,8 +1068,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
9591068

9601069
if def.variants().is_empty() {
9611070
// Empty enums are implicitely handled as the never type:
962-
// FIXME think about the FFI-safety of functions that use that
963-
return FfiSafe;
1071+
return self.visit_uninhabited(state, outer_ty, ty);
9641072
}
9651073
// Check for a repr() attribute to specify the size of the
9661074
// discriminant.
@@ -1005,18 +1113,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10051113
None,
10061114
)
10071115
} else {
1008-
let ffires = def
1116+
// small caveat to checking the variants: we authorise up to n-1 invariants
1117+
// to be unsafe because uninhabited.
1118+
// so for now let's isolate those unsafeties
1119+
let mut variants_uninhabited_ffires = vec![FfiSafe; def.variants().len()];
1120+
1121+
let mut ffires = def
10091122
.variants()
10101123
.iter()
1011-
.map(|variant| {
1012-
self.visit_variant_fields(state, ty, def, variant, args)
1013-
// FIXME: check that enums allow any (up to all) variants to be phantoms?
1014-
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1015-
.forbid_phantom()
1124+
.enumerate()
1125+
.map(|(variant_i, variant)| {
1126+
let mut variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1127+
variants_uninhabited_ffires[variant_i] = variant_res.take_with_core_note(&[
1128+
fluent::lint_improper_ctypes_uninhabited_enum,
1129+
fluent::lint_improper_ctypes_uninhabited_enum_deep,
1130+
fluent::lint_improper_ctypes_uninhabited_never,
1131+
fluent::lint_improper_ctypes_uninhabited_never_deep,
1132+
]);
1133+
// FIXME: check that enums allow any (up to all) variants to be phantoms?
1134+
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
1135+
variant_res.forbid_phantom()
10161136
})
10171137
.reduce(|r1, r2| r1 + r2)
10181138
.unwrap(); // always at least one variant if we hit this branch
10191139

1140+
if variants_uninhabited_ffires.iter().all(|res| matches!(res, FfiUnsafe(..))) {
1141+
// if the enum is uninhabited, because all its variants are uninhabited
1142+
ffires += variants_uninhabited_ffires.into_iter().reduce(|r1, r2| r1 + r2).unwrap();
1143+
}
1144+
10201145
// if outer_ty.is_some() || !state.is_being_defined() then this enum is visited in the middle of another lint,
10211146
// so we override the "cause type" of the lint
10221147
// (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
@@ -1106,7 +1231,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
11061231
ty::Int(..) | ty::Uint(..) | ty::Float(..) => self.visit_numeric(ty),
11071232

11081233
// Primitive types with a stable representation.
1109-
ty::Bool | ty::Never => FfiSafe,
1234+
ty::Bool => FfiSafe,
11101235

11111236
ty::Slice(inner_ty) => {
11121237
// ty::Slice is used for !Sized arrays, since they are the pointee for actual slices
@@ -1244,6 +1369,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12441369

12451370
ty::Foreign(..) => FfiSafe,
12461371

1372+
ty::Never => self.visit_uninhabited(state, outer_ty, ty),
1373+
12471374
// This is only half of the checking-for-opaque-aliases story:
12481375
// since they are liable to vanish on normalisation, we need a specific to find them through
12491376
// 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)