Skip to content

Commit 58d58ea

Browse files
committed
ImproperCTypes: handle uninhabited types
Add some logic to the type checking to refuse uninhabited types as arguments, and treat uninhabited variants of an enum as FFI-safe if at least one variant is inhabited.
1 parent 7d195f6 commit 58d58ea

File tree

7 files changed

+281
-19
lines changed

7 files changed

+281
-19
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,9 @@ lint_improper_ctypes_struct_zst = `{$ty}` contains only zero-sized fields
413413
lint_improper_ctypes_tuple_help = consider using a struct instead
414414
lint_improper_ctypes_tuple_reason = tuples have unspecified layout
415415
416+
lint_improper_ctypes_uninhabited_enum = zero-variant enums and other uninhabited types are not allowed in function arguments and static variables
417+
lint_improper_ctypes_uninhabited_never = the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables
418+
416419
lint_improper_ctypes_union_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
417420
lint_improper_ctypes_union_fieldless_help = consider adding a member to this union
418421
lint_improper_ctypes_union_fieldless_reason = this union has no fields

compiler/rustc_lint/src/types/improper_ctypes.rs

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ impl<'tcx> FfiResult<'tcx> {
173173
/// if the note at their core reason is one in a provided list.
174174
/// If the FfiResult is not FfiUnsafe, or if no reasons are plucked,
175175
/// then return FfiSafe.
176-
#[expect(unused)]
177176
fn take_with_core_note(&mut self, notes: &[DiagMessage]) -> Self {
178177
match self {
179178
Self::FfiUnsafe(this) => {
@@ -574,6 +573,20 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
574573
all_ffires
575574
}
576575

576+
/// Checks whether an uninhabited type (one without valid values) is safe-ish to have here.
577+
fn visit_uninhabited(&self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> {
578+
if state.is_in_function_return() {
579+
FfiResult::FfiSafe
580+
} else {
581+
let desc = match ty.kind() {
582+
ty::Adt(..) => fluent::lint_improper_ctypes_uninhabited_enum,
583+
ty::Never => fluent::lint_improper_ctypes_uninhabited_never,
584+
r @ _ => bug!("unexpected ty_kind in uninhabited type handling: {:?}", r),
585+
};
586+
FfiResult::new_with_reason(ty, desc, None)
587+
}
588+
}
589+
577590
/// Checks if a simple numeric (int, float) type has an actual portable definition
578591
/// for the compile target.
579592
fn visit_numeric(&self, ty: Ty<'tcx>) -> FfiResult<'tcx> {
@@ -724,6 +737,24 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
724737
if !matches!(def.adt_kind(), AdtKind::Enum) && def.repr().transparent() {
725738
// determine if there is 0 or 1 non-1ZST field, and which it is.
726739
// (note: for enums, "transparent" means 1-variant)
740+
if ty.is_privately_uninhabited(self.cx.tcx, self.cx.typing_env()) {
741+
// let's consider transparent structs to be maybe unsafe if uninhabited,
742+
// even if that is because of fields otherwise ignored in FFI-safety checks
743+
// FIXME(ctypes): and also maybe this should be "!is_inhabited_from" but from where?
744+
ffires_accumulator += variant
745+
.fields
746+
.iter()
747+
.map(|field| {
748+
let field_ty = get_type_from_field(self.cx, field, args);
749+
let mut field_res = self.visit_type(state, Some(ty), field_ty);
750+
field_res.take_with_core_note(&[
751+
fluent::lint_improper_ctypes_uninhabited_enum,
752+
fluent::lint_improper_ctypes_uninhabited_never,
753+
])
754+
})
755+
.reduce(|r1, r2| r1 + r2)
756+
.unwrap() // if uninhabited, then >0 fields
757+
}
727758
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
728759
// Transparent newtypes have at most one non-ZST field which needs to be checked later
729760
(false, vec![field])
@@ -918,8 +949,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
918949
use FfiResult::*;
919950

920951
if def.variants().is_empty() {
921-
// Empty enums are okay... although sort of useless.
922-
return FfiSafe;
952+
// Empty enums are implicitly handled as the never type:
953+
return self.visit_uninhabited(state, ty);
923954
}
924955
// Check for a repr() attribute to specify the size of the
925956
// discriminant.
@@ -960,19 +991,33 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
960991
None,
961992
)
962993
} else {
994+
// small caveat to checking the variants: we authorise up to n-1 invariants
995+
// to be unsafe because uninhabited.
996+
// so for now let's isolate those unsafeties
997+
let mut variants_uninhabited_ffires = vec![FfiSafe; def.variants().len()];
963998

964-
let ffires = def
999+
let mut ffires = def
9651000
.variants()
9661001
.iter()
967-
.map(|variant| {
968-
let variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1002+
.enumerate()
1003+
.map(|(variant_i, variant)| {
1004+
let mut variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1005+
variants_uninhabited_ffires[variant_i] = variant_res.take_with_core_note(&[
1006+
fluent::lint_improper_ctypes_uninhabited_enum,
1007+
fluent::lint_improper_ctypes_uninhabited_never,
1008+
]);
9691009
// FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
9701010
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
9711011
variant_res.forbid_phantom()
9721012
})
9731013
.reduce(|r1, r2| r1 + r2)
9741014
.unwrap(); // always at least one variant if we hit this branch
9751015

1016+
if variants_uninhabited_ffires.iter().all(|res| matches!(res, FfiUnsafe(..))) {
1017+
// if the enum is uninhabited, because all its variants are uninhabited
1018+
ffires += variants_uninhabited_ffires.into_iter().reduce(|r1, r2| r1 + r2).unwrap();
1019+
}
1020+
9761021
// this enum is visited in the middle of another lint,
9771022
// so we override the "cause type" of the lint
9781023
// (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
@@ -1154,7 +1199,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
11541199

11551200
ty::Foreign(..) => FfiSafe,
11561201

1157-
ty::Never => FfiSafe,
1202+
ty::Never => self.visit_uninhabited(state, ty),
11581203

11591204
// While opaque types are checked for earlier, if a projection in a struct field
11601205
// normalizes to an opaque type, then it will reach this branch.

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

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

8080
extern "C" {
81-
fn zf(x: Z);
81+
fn zf(x: Z); //~ ERROR `extern` block uses type `Z`
8282
fn uf(x: U); //~ ERROR `extern` block uses type `U`
8383
fn bf(x: B); //~ ERROR `extern` block uses type `B`
8484
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:81: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:82: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:83: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

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#![feature(never_type)]
2+
3+
#![allow(dead_code, unused_variables)]
4+
#![deny(improper_ctypes)]
5+
#![deny(improper_c_fn_definitions, improper_c_callbacks)]
6+
7+
use std::mem::transmute;
8+
9+
enum Uninhabited{}
10+
11+
#[repr(C)]
12+
struct AlsoUninhabited{
13+
a: Uninhabited,
14+
b: i32,
15+
}
16+
17+
#[repr(C)]
18+
enum Inhabited{
19+
OhNo(Uninhabited),
20+
OhYes(i32),
21+
}
22+
23+
struct EmptyRust;
24+
25+
#[repr(transparent)]
26+
struct HalfHiddenUninhabited {
27+
is_this_a_tuple: (i8,i8),
28+
zst_inh: EmptyRust,
29+
zst_uninh: !,
30+
}
31+
32+
extern "C" {
33+
34+
fn bad_entry(e: AlsoUninhabited); //~ ERROR: uses type `AlsoUninhabited`
35+
fn bad_exit()->AlsoUninhabited;
36+
37+
fn bad0_entry(e: Uninhabited); //~ ERROR: uses type `Uninhabited`
38+
fn bad0_exit()->Uninhabited;
39+
40+
fn good_entry(e: Inhabited);
41+
fn good_exit()->Inhabited;
42+
43+
fn never_entry(e:!); //~ ERROR: uses type `!`
44+
fn never_exit()->!;
45+
46+
}
47+
48+
extern "C" fn impl_bad_entry(e: AlsoUninhabited) {} //~ ERROR: uses type `AlsoUninhabited`
49+
extern "C" fn impl_bad_exit()->AlsoUninhabited {
50+
AlsoUninhabited{
51+
a: impl_bad0_exit(),
52+
b: 0,
53+
}
54+
}
55+
56+
extern "C" fn impl_bad0_entry(e: Uninhabited) {} //~ ERROR: uses type `Uninhabited`
57+
extern "C" fn impl_bad0_exit()->Uninhabited {
58+
unsafe{transmute(())} //~ WARN: does not permit zero-initialization
59+
}
60+
61+
extern "C" fn impl_good_entry(e: Inhabited) {}
62+
extern "C" fn impl_good_exit() -> Inhabited {
63+
Inhabited::OhYes(0)
64+
}
65+
66+
extern "C" fn impl_never_entry(e:!){} //~ ERROR: uses type `!`
67+
extern "C" fn impl_never_exit()->! {
68+
loop{}
69+
}
70+
71+
extern "C" fn weird_pattern(e:HalfHiddenUninhabited){}
72+
//~^ ERROR: uses type `HalfHiddenUninhabited`
73+
74+
75+
fn main(){}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
error: `extern` block uses type `AlsoUninhabited`, which is not FFI-safe
2+
--> $DIR/lint_uninhabited.rs:34:17
3+
|
4+
LL | fn bad_entry(e: AlsoUninhabited);
5+
| ^^^^^^^^^^^^^^^ not FFI-safe
6+
|
7+
= help: `AlsoUninhabited` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
8+
= note: this struct/enum/union (`AlsoUninhabited`) is FFI-unsafe due to a `Uninhabited` field
9+
note: the type is defined here
10+
--> $DIR/lint_uninhabited.rs:12:1
11+
|
12+
LL | struct AlsoUninhabited{
13+
| ^^^^^^^^^^^^^^^^^^^^^^
14+
= note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables
15+
note: the type is defined here
16+
--> $DIR/lint_uninhabited.rs:9:1
17+
|
18+
LL | enum Uninhabited{}
19+
| ^^^^^^^^^^^^^^^^
20+
note: the lint level is defined here
21+
--> $DIR/lint_uninhabited.rs:4:9
22+
|
23+
LL | #![deny(improper_ctypes)]
24+
| ^^^^^^^^^^^^^^^
25+
26+
error: `extern` block uses type `Uninhabited`, which is not FFI-safe
27+
--> $DIR/lint_uninhabited.rs:37:18
28+
|
29+
LL | fn bad0_entry(e: Uninhabited);
30+
| ^^^^^^^^^^^ not FFI-safe
31+
|
32+
= note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables
33+
note: the type is defined here
34+
--> $DIR/lint_uninhabited.rs:9:1
35+
|
36+
LL | enum Uninhabited{}
37+
| ^^^^^^^^^^^^^^^^
38+
39+
error: `extern` block uses type `!`, which is not FFI-safe
40+
--> $DIR/lint_uninhabited.rs:43:18
41+
|
42+
LL | fn never_entry(e:!);
43+
| ^ not FFI-safe
44+
|
45+
= note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables
46+
47+
error: `extern` fn uses type `AlsoUninhabited`, which is not FFI-safe
48+
--> $DIR/lint_uninhabited.rs:48:33
49+
|
50+
LL | extern "C" fn impl_bad_entry(e: AlsoUninhabited) {}
51+
| ^^^^^^^^^^^^^^^ not FFI-safe
52+
|
53+
= help: `AlsoUninhabited` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
54+
= note: this struct/enum/union (`AlsoUninhabited`) is FFI-unsafe due to a `Uninhabited` field
55+
note: the type is defined here
56+
--> $DIR/lint_uninhabited.rs:12:1
57+
|
58+
LL | struct AlsoUninhabited{
59+
| ^^^^^^^^^^^^^^^^^^^^^^
60+
= note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables
61+
note: the type is defined here
62+
--> $DIR/lint_uninhabited.rs:9:1
63+
|
64+
LL | enum Uninhabited{}
65+
| ^^^^^^^^^^^^^^^^
66+
note: the lint level is defined here
67+
--> $DIR/lint_uninhabited.rs:5:9
68+
|
69+
LL | #![deny(improper_c_fn_definitions, improper_c_callbacks)]
70+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
71+
72+
error: `extern` fn uses type `Uninhabited`, which is not FFI-safe
73+
--> $DIR/lint_uninhabited.rs:56:34
74+
|
75+
LL | extern "C" fn impl_bad0_entry(e: Uninhabited) {}
76+
| ^^^^^^^^^^^ not FFI-safe
77+
|
78+
= note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables
79+
note: the type is defined here
80+
--> $DIR/lint_uninhabited.rs:9:1
81+
|
82+
LL | enum Uninhabited{}
83+
| ^^^^^^^^^^^^^^^^
84+
85+
warning: the type `Uninhabited` does not permit zero-initialization
86+
--> $DIR/lint_uninhabited.rs:58:12
87+
|
88+
LL | unsafe{transmute(())}
89+
| ^^^^^^^^^^^^^ this code causes undefined behavior when executed
90+
|
91+
note: enums with no inhabited variants have no valid value
92+
--> $DIR/lint_uninhabited.rs:9:1
93+
|
94+
LL | enum Uninhabited{}
95+
| ^^^^^^^^^^^^^^^^
96+
= note: `#[warn(invalid_value)]` on by default
97+
98+
error: `extern` fn uses type `!`, which is not FFI-safe
99+
--> $DIR/lint_uninhabited.rs:66:34
100+
|
101+
LL | extern "C" fn impl_never_entry(e:!){}
102+
| ^ not FFI-safe
103+
|
104+
= note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables
105+
106+
error: `extern` fn uses type `HalfHiddenUninhabited`, which is not FFI-safe
107+
--> $DIR/lint_uninhabited.rs:71:31
108+
|
109+
LL | extern "C" fn weird_pattern(e:HalfHiddenUninhabited){}
110+
| ^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
111+
|
112+
= note: this struct/enum/union (`HalfHiddenUninhabited`) is FFI-unsafe due to a `!` field
113+
note: the type is defined here
114+
--> $DIR/lint_uninhabited.rs:26:1
115+
|
116+
LL | struct HalfHiddenUninhabited {
117+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
118+
= note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables
119+
120+
error: aborting due to 7 previous errors; 1 warning emitted
121+

tests/ui/structs-enums/foreign-struct.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
//@ run-pass
22
#![allow(dead_code)]
3-
#![allow(non_camel_case_types)]
43

54
// Passing enums by value
65

7-
8-
pub enum void {}
6+
#[repr(C)]
7+
pub enum PoorQualityAnyEnum {
8+
None = 0,
9+
Int = 1,
10+
Long = 2,
11+
Float = 17,
12+
Double = 18,
13+
}
914

1015
mod bindgen {
11-
use super::void;
16+
use super::PoorQualityAnyEnum;
1217

1318
extern "C" {
14-
pub fn printf(v: void);
19+
pub fn printf(v: PoorQualityAnyEnum);
1520
}
1621
}
1722

0 commit comments

Comments
 (0)