Skip to content

Commit d1ed3c7

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 c1d3db3 commit d1ed3c7

File tree

7 files changed

+307
-34
lines changed

7 files changed

+307
-34
lines changed

compiler/rustc_lint/messages.ftl

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

compiler/rustc_lint/src/types/improper_ctypes.rs

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_middle::ty::{
1414
TypeVisitable, TypeVisitableExt,
1515
};
1616
use rustc_session::{declare_lint, declare_lint_pass};
17-
use rustc_span::def_id::LocalDefId;
17+
use rustc_span::def_id::{DefId, LocalDefId};
1818
use rustc_span::{Span, sym};
1919
use tracing::debug;
2020

@@ -360,7 +360,6 @@ impl<'tcx> FfiResult<'tcx> {
360360
/// if the note at their core reason is one in a provided list.
361361
/// If the FfiResult is not FfiUnsafe, or if no reasons are plucked,
362362
/// then return FfiSafe.
363-
#[expect(unused)]
364363
fn take_with_core_note(&mut self, notes: &[DiagMessage]) -> Self {
365364
match self {
366365
Self::FfiUnsafe(this) => {
@@ -805,14 +804,30 @@ impl VisitorState {
805804
/// and ``visit_*`` methods to recurse.
806805
struct ImproperCTypesVisitor<'a, 'tcx> {
807806
cx: &'a LateContext<'tcx>,
807+
/// The module id of the item being checked for FFI-safety
808+
mod_id: DefId,
808809
/// To prevent problems with recursive types,
809810
/// add a types-in-check cache.
810811
ty_cache: FxHashSet<Ty<'tcx>>,
811812
}
812813

813814
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
814-
fn new(cx: &'a LateContext<'tcx>) -> Self {
815-
Self { cx, ty_cache: FxHashSet::default() }
815+
fn new(cx: &'a LateContext<'tcx>, mod_id: DefId) -> Self {
816+
Self { cx, mod_id, ty_cache: FxHashSet::default() }
817+
}
818+
819+
/// Checks whether an uninhabited type (one without valid values) is safe-ish to have here.
820+
fn visit_uninhabited(&self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> {
821+
if state.is_in_function_return() {
822+
FfiResult::FfiSafe
823+
} else {
824+
let desc = match ty.kind() {
825+
ty::Adt(..) => fluent::lint_improper_ctypes_uninhabited_enum,
826+
ty::Never => fluent::lint_improper_ctypes_uninhabited_never,
827+
r @ _ => bug!("unexpected ty_kind in uninhabited type handling: {:?}", r),
828+
};
829+
FfiResult::new_with_reason(ty, desc, None)
830+
}
816831
}
817832

818833
/// Return the right help for Cstring and Cstr-linked unsafety.
@@ -948,6 +963,23 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
948963
if !matches!(def.adt_kind(), AdtKind::Enum) && def.repr().transparent() {
949964
// determine if there is 0 or 1 non-1ZST field, and which it is.
950965
// (note: for enums, "transparent" means 1-variant)
966+
if !ty.is_inhabited_from(self.cx.tcx, self.mod_id, self.cx.typing_env()) {
967+
// let's consider transparent structs to be maybe unsafe if uninhabited,
968+
// even if that is because of fields otherwise ignored in FFI-safety checks
969+
ffires_accumulator += variant
970+
.fields
971+
.iter()
972+
.map(|field| {
973+
let field_ty = get_type_from_field(self.cx, field, args);
974+
let mut field_res = self.visit_type(state.get_next(ty), field_ty);
975+
field_res.take_with_core_note(&[
976+
fluent::lint_improper_ctypes_uninhabited_enum,
977+
fluent::lint_improper_ctypes_uninhabited_never,
978+
])
979+
})
980+
.reduce(|r1, r2| r1 + r2)
981+
.unwrap() // if uninhabited, then >0 fields
982+
}
951983
if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) {
952984
// Transparent newtypes have at most one non-ZST field which needs to be checked later
953985
(false, vec![field])
@@ -1133,8 +1165,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
11331165
use FfiResult::*;
11341166

11351167
if def.variants().is_empty() {
1136-
// Empty enums are okay... although sort of useless.
1137-
return FfiSafe;
1168+
// Empty enums are implicitly handled as the never type:
1169+
return self.visit_uninhabited(state, ty);
11381170
}
11391171
// Check for a repr() attribute to specify the size of the
11401172
// discriminant.
@@ -1190,18 +1222,33 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
11901222
None,
11911223
)
11921224
} else {
1193-
let ffires = def
1225+
// small caveat to checking the variants: we authorise up to n-1 invariants
1226+
// to be unsafe because uninhabited.
1227+
// so for now let's isolate those unsafeties
1228+
let mut variants_uninhabited_ffires = vec![FfiSafe; def.variants().len()];
1229+
1230+
let mut ffires = def
11941231
.variants()
11951232
.iter()
1196-
.map(|variant| {
1197-
let variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1233+
.enumerate()
1234+
.map(|(variant_i, variant)| {
1235+
let mut variant_res = self.visit_variant_fields(state, ty, def, variant, args);
1236+
variants_uninhabited_ffires[variant_i] = variant_res.take_with_core_note(&[
1237+
fluent::lint_improper_ctypes_uninhabited_enum,
1238+
fluent::lint_improper_ctypes_uninhabited_never,
1239+
]);
11981240
// FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
11991241
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
12001242
variant_res.forbid_phantom()
12011243
})
12021244
.reduce(|r1, r2| r1 + r2)
12031245
.unwrap(); // always at least one variant if we hit this branch
12041246

1247+
if variants_uninhabited_ffires.iter().all(|res| matches!(res, FfiUnsafe(..))) {
1248+
// if the enum is uninhabited, because all its variants are uninhabited
1249+
ffires += variants_uninhabited_ffires.into_iter().reduce(|r1, r2| r1 + r2).unwrap();
1250+
}
1251+
12051252
// this enum is visited in the middle of another lint,
12061253
// so we override the "cause type" of the lint
12071254
// (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
@@ -1366,7 +1413,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
13661413

13671414
ty::Foreign(..) => FfiSafe,
13681415

1369-
ty::Never => FfiSafe,
1416+
ty::Never => self.visit_uninhabited(state, ty),
13701417

13711418
// While opaque types are checked for earlier, if a projection in a struct field
13721419
// normalizes to an opaque type, then it will reach this branch.
@@ -1443,6 +1490,7 @@ impl<'tcx> ImproperCTypesLint {
14431490
current_depth: usize,
14441491
depths: Vec<usize>,
14451492
decls: Vec<&'tcx hir::FnDecl<'tcx>>,
1493+
hir_ids: Vec<hir::HirId>,
14461494
tys: Vec<Ty<'tcx>>,
14471495
}
14481496

@@ -1455,6 +1503,7 @@ impl<'tcx> ImproperCTypesLint {
14551503
{
14561504
self.decls.push(*decl);
14571505
self.depths.push(self.current_depth);
1506+
self.hir_ids.push(ty.hir_id);
14581507
}
14591508

14601509
hir::intravisit::walk_ty(self, ty);
@@ -1477,6 +1526,7 @@ impl<'tcx> ImproperCTypesLint {
14771526
}
14781527

14791528
let mut visitor = FnPtrFinder {
1529+
hir_ids: Vec::new(),
14801530
tys: Vec::new(),
14811531
decls: Vec::new(),
14821532
depths: Vec::new(),
@@ -1486,12 +1536,15 @@ impl<'tcx> ImproperCTypesLint {
14861536
visitor.visit_ty_unambig(hir_ty);
14871537

14881538
let all_types = iter::zip(
1489-
visitor.depths.drain(..),
1539+
iter::zip(visitor.depths.drain(..), visitor.hir_ids.drain(..)),
14901540
iter::zip(visitor.tys.drain(..), visitor.decls.drain(..)),
14911541
);
1492-
for (depth, (fn_ptr_ty, decl)) in all_types {
1542+
1543+
for ((depth, hir_id), (fn_ptr_ty, decl)) in all_types {
14931544
let mir_sig = get_fn_sig_from_mir_ty(cx, fn_ptr_ty);
1494-
self.check_foreign_fn(cx, CItemKind::Callback, mir_sig, decl, depth);
1545+
let mod_id = cx.tcx.parent_module(hir_id).to_def_id();
1546+
1547+
self.check_foreign_fn(cx, CItemKind::Callback, mir_sig, decl, mod_id, depth);
14951548
}
14961549
}
14971550

@@ -1533,9 +1586,10 @@ impl<'tcx> ImproperCTypesLint {
15331586
}
15341587

15351588
/// Check that an extern "ABI" static variable is of a ffi-safe type.
1536-
fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) {
1537-
let ty = cx.tcx.type_of(id).instantiate_identity();
1538-
let mut visitor = ImproperCTypesVisitor::new(cx);
1589+
fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::HirId, span: Span) {
1590+
let ty = cx.tcx.type_of(id.owner).instantiate_identity();
1591+
let mod_id = cx.tcx.parent_module(id).to_def_id();
1592+
let mut visitor = ImproperCTypesVisitor::new(cx, mod_id);
15391593
let ffi_res = visitor.check_type(VisitorState::static_var(), ty);
15401594
self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern);
15411595
}
@@ -1547,22 +1601,23 @@ impl<'tcx> ImproperCTypesLint {
15471601
fn_mode: CItemKind,
15481602
sig: Sig<'tcx>,
15491603
decl: &'tcx hir::FnDecl<'_>,
1604+
mod_id: DefId,
15501605
depth: usize,
15511606
) {
15521607
let sig = cx.tcx.instantiate_bound_regions_with_erased(sig);
15531608

15541609
for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
15551610
let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Arg);
15561611
state.depth = depth;
1557-
let mut visitor = ImproperCTypesVisitor::new(cx);
1612+
let mut visitor = ImproperCTypesVisitor::new(cx, mod_id);
15581613
let ffi_res = visitor.check_type(state, *input_ty);
15591614
self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode);
15601615
}
15611616

15621617
if let hir::FnRetTy::Return(ret_hir) = decl.output {
15631618
let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Ret);
15641619
state.depth = depth;
1565-
let mut visitor = ImproperCTypesVisitor::new(cx);
1620+
let mut visitor = ImproperCTypesVisitor::new(cx, mod_id);
15661621
let ffi_res = visitor.check_type(state, sig.output());
15671622
self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode);
15681623
}
@@ -1690,12 +1745,13 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint {
16901745
// their surroundings, and their type is often declared inline
16911746
self.check_fn_for_external_abi_fnptr(cx, it.owner_id.def_id, sig.decl);
16921747
let mir_sig = cx.tcx.fn_sig(it.owner_id.def_id).instantiate_identity();
1748+
let mod_id = cx.tcx.parent_module_from_def_id(it.owner_id.def_id).to_def_id();
16931749
if !abi.is_rustic_abi() {
1694-
self.check_foreign_fn(cx, CItemKind::ImportedExtern, mir_sig, sig.decl, 0);
1750+
self.check_foreign_fn(cx, CItemKind::ImportedExtern, mir_sig, sig.decl, mod_id, 0);
16951751
}
16961752
}
16971753
hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => {
1698-
self.check_foreign_static(cx, it.owner_id, ty.span);
1754+
self.check_foreign_static(cx, it.hir_id(), ty.span);
16991755
}
17001756
hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (),
17011757
}
@@ -1766,9 +1822,10 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint {
17661822
// "the element rendered unsafe" because their unsafety doesn't affect
17671823
// their surroundings, and their type is often declared inline
17681824
self.check_fn_for_external_abi_fnptr(cx, id, decl);
1769-
let mir_sig = cx.tcx.fn_sig(id).instantiate_identity();
17701825
if !abi.is_rustic_abi() {
1771-
self.check_foreign_fn(cx, CItemKind::ExportedFunction, mir_sig, decl, 0);
1826+
let mir_sig = cx.tcx.fn_sig(id).instantiate_identity();
1827+
let mod_id = cx.tcx.parent_module_from_def_id(id).to_def_id();
1828+
self.check_foreign_fn(cx, CItemKind::ExportedFunction, mir_sig, decl, mod_id, 0);
17721829
}
17731830
}
17741831
}

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: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#![feature(never_type)]
2+
3+
#![allow(dead_code, unused_variables)]
4+
#![deny(improper_ctypes, improper_ctypes_definitions)]
5+
6+
use std::mem::transmute;
7+
8+
enum Uninhabited{}
9+
10+
#[repr(C)]
11+
struct AlsoUninhabited{
12+
a: Uninhabited,
13+
b: i32,
14+
}
15+
16+
#[repr(C)]
17+
enum Inhabited{
18+
OhNo(Uninhabited),
19+
OhYes(i32),
20+
}
21+
22+
struct EmptyRust;
23+
24+
#[repr(transparent)]
25+
struct HalfHiddenUninhabited {
26+
is_this_a_tuple: (i8,i8),
27+
zst_inh: EmptyRust,
28+
zst_uninh: !,
29+
}
30+
31+
extern "C" {
32+
33+
fn bad_entry(e: AlsoUninhabited); //~ ERROR: uses type `AlsoUninhabited`
34+
fn bad_exit()->AlsoUninhabited;
35+
36+
fn bad0_entry(e: Uninhabited); //~ ERROR: uses type `Uninhabited`
37+
fn bad0_exit()->Uninhabited;
38+
39+
fn good_entry(e: Inhabited);
40+
fn good_exit()->Inhabited;
41+
42+
fn never_entry(e:!); //~ ERROR: uses type `!`
43+
fn never_exit()->!;
44+
45+
}
46+
47+
extern "C" fn impl_bad_entry(e: AlsoUninhabited) {} //~ ERROR: uses type `AlsoUninhabited`
48+
extern "C" fn impl_bad_exit()->AlsoUninhabited {
49+
AlsoUninhabited{
50+
a: impl_bad0_exit(),
51+
b: 0,
52+
}
53+
}
54+
55+
extern "C" fn impl_bad0_entry(e: Uninhabited) {} //~ ERROR: uses type `Uninhabited`
56+
extern "C" fn impl_bad0_exit()->Uninhabited {
57+
unsafe{transmute(())} //~ WARN: does not permit zero-initialization
58+
}
59+
60+
extern "C" fn impl_good_entry(e: Inhabited) {}
61+
extern "C" fn impl_good_exit() -> Inhabited {
62+
Inhabited::OhYes(0)
63+
}
64+
65+
extern "C" fn impl_never_entry(e:!){} //~ ERROR: uses type `!`
66+
extern "C" fn impl_never_exit()->! {
67+
loop{}
68+
}
69+
70+
extern "C" fn weird_pattern(e:HalfHiddenUninhabited){}
71+
//~^ ERROR: uses type `HalfHiddenUninhabited`
72+
73+
74+
fn main(){}

0 commit comments

Comments
 (0)