Skip to content

Commit b74bc7b

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 d409c7e commit b74bc7b

File tree

7 files changed

+282
-20
lines changed

7 files changed

+282
-20
lines changed

compiler/rustc_lint/messages.ftl

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

compiler/rustc_lint/src/types/improper_ctypes.rs

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,6 @@ impl<'tcx> FfiResult<'tcx> {
389389
/// if the note at their core reason is one in a provided list.
390390
/// If the FfiResult is not FfiUnsafe, or if no reasons are plucked,
391391
/// then return FfiSafe.
392-
#[expect(unused)]
393392
fn take_with_core_note(&mut self, notes: &[DiagMessage]) -> Self {
394393
match self {
395394
Self::FfiUnsafe(this) => {
@@ -902,6 +901,20 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
902901
all_ffires
903902
}
904903

904+
/// Checks whether an uninhabited type (one without valid values) is safe-ish to have here.
905+
fn visit_uninhabited(&self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> {
906+
if state.is_in_function_return() {
907+
FfiResult::FfiSafe
908+
} else {
909+
let desc = match ty.kind() {
910+
ty::Adt(..) => fluent::lint_improper_ctypes_uninhabited_enum,
911+
ty::Never => fluent::lint_improper_ctypes_uninhabited_never,
912+
r @ _ => bug!("unexpected ty_kind in uninhabited type handling: {:?}", r),
913+
};
914+
FfiResult::new_with_reason(ty, desc, None)
915+
}
916+
}
917+
905918
/// Return the right help for Cstring and Cstr-linked unsafety.
906919
fn visit_cstr(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> {
907920
debug_assert!(matches!(ty.kind(), ty::Adt(def, _)
@@ -1022,6 +1035,24 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10221035
if !matches!(def.adt_kind(), AdtKind::Enum) && def.repr().transparent() {
10231036
// determine if there is 0 or 1 non-1ZST field, and which it is.
10241037
// (note: for enums, "transparent" means 1-variant)
1038+
if ty.is_privately_uninhabited(self.cx.tcx, self.cx.typing_env()) {
1039+
// let's consider transparent structs to be maybe unsafe if uninhabited,
1040+
// even if that is because of fields otherwise ignored in FFI-safety checks
1041+
// FIXME(ctypes): and also maybe this should be "!is_inhabited_from" but from where?
1042+
ffires_accumulator += variant
1043+
.fields
1044+
.iter()
1045+
.map(|field| {
1046+
let field_ty = get_type_from_field(self.cx, field, args);
1047+
let mut field_res = self.visit_type(state.get_next(ty), field_ty);
1048+
field_res.take_with_core_note(&[
1049+
fluent::lint_improper_ctypes_uninhabited_enum,
1050+
fluent::lint_improper_ctypes_uninhabited_never,
1051+
])
1052+
})
1053+
.reduce(|r1, r2| r1 + r2)
1054+
.unwrap() // if uninhabited, then >0 fields
1055+
}
10251056
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
10261057
// Transparent newtypes have at most one non-ZST field which needs to be checked later
10271058
(false, vec![field])
@@ -1212,8 +1243,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12121243
use FfiResult::*;
12131244

12141245
if def.variants().is_empty() {
1215-
// Empty enums are okay... although sort of useless.
1216-
return FfiSafe;
1246+
// Empty enums are implicitly handled as the never type:
1247+
return self.visit_uninhabited(state, ty);
12171248
}
12181249
// Check for a repr() attribute to specify the size of the
12191250
// discriminant.
@@ -1242,7 +1273,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12421273
nonexhaustive_flag |= nonex_enum;
12431274
nonexhaustive_variant_flag |= nonex_var;
12441275
});
1245-
12461276
// "nonexhaustive" lints only happen outside of the crate defining the enum, so no CItemKind override
12471277
// (meaning: the fault lies in the function call, not the enum)
12481278
if nonexhaustive_flag {
@@ -1254,18 +1284,33 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12541284
None,
12551285
)
12561286
} else {
1257-
let ffires = def
1287+
// small caveat to checking the variants: we authorise up to n-1 invariants
1288+
// to be unsafe because uninhabited.
1289+
// so for now let's isolate those unsafeties
1290+
let mut variants_uninhabited_ffires = vec![FfiSafe; def.variants().len()];
1291+
1292+
let mut ffires = def
12581293
.variants()
12591294
.iter()
1260-
.map(|variant| {
1261-
let variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1295+
.enumerate()
1296+
.map(|(variant_i, variant)| {
1297+
let mut variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1298+
variants_uninhabited_ffires[variant_i] = variant_res.take_with_core_note(&[
1299+
fluent::lint_improper_ctypes_uninhabited_enum,
1300+
fluent::lint_improper_ctypes_uninhabited_never,
1301+
]);
12621302
// FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
12631303
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
12641304
variant_res.forbid_phantom()
12651305
})
12661306
.reduce(|r1, r2| r1 + r2)
12671307
.unwrap(); // always at least one variant if we hit this branch
12681308

1309+
if variants_uninhabited_ffires.iter().all(|res| matches!(res, FfiUnsafe(..))) {
1310+
// if the enum is uninhabited, because all its variants are uninhabited
1311+
ffires += variants_uninhabited_ffires.into_iter().reduce(|r1, r2| r1 + r2).unwrap();
1312+
}
1313+
12691314
// this enum is visited in the middle of another lint,
12701315
// so we override the "cause type" of the lint
12711316
// (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
@@ -1432,7 +1477,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
14321477

14331478
ty::Foreign(..) => FfiSafe,
14341479

1435-
ty::Never => FfiSafe,
1480+
ty::Never => self.visit_uninhabited(state, ty),
14361481

14371482
// While opaque types are checked for earlier, if a projection in a struct field
14381483
// 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+

0 commit comments

Comments
 (0)