Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 7744a86

Browse files
davidtwcocuviper
authored andcommitted
lint/ctypes: stricter () return type checks
`()` is normally FFI-unsafe, but is FFI-safe when used as a return type. It is also desirable that a transparent newtype for `()` is FFI-safe when used as a return type. In order to support this, when an type was deemed FFI-unsafe, because of a `()` type, and was used in return type - then the type was considered FFI-safe. However, this was the wrong approach - it didn't check that the `()` was part of a transparent newtype! The consequence of this is that the presence of a `()` type in a more complex return type would make it the entire type be considered safe (as long as the `()` type was the first that the lint found) - which is obviously incorrect. Instead, this logic is removed, and a unit return type or a transparent wrapper around a unit is checked for directly for functions and fn-ptrs. Signed-off-by: David Wood <david@davidtw.co> (cherry picked from commit f53cef3)
1 parent e20288d commit 7744a86

File tree

3 files changed

+115
-20
lines changed

3 files changed

+115
-20
lines changed

compiler/rustc_lint/src/types.rs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,30 @@ pub(crate) fn repr_nullable_ptr<'tcx>(
940940
}
941941

942942
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
943+
// Returns `true` if `ty` is a `()`, or a `repr(transparent)` type whose only non-ZST field
944+
// is a generic substituted for `()` - in either case, the type is FFI-safe when used as a
945+
// return type.
946+
pub fn is_unit_or_equivalent(&self, ty: Ty<'tcx>) -> bool {
947+
if ty.is_unit() {
948+
return true;
949+
}
950+
951+
if let ty::Adt(def, substs) = ty.kind() && def.repr().transparent() {
952+
return def.variants()
953+
.iter()
954+
.filter_map(|variant| transparent_newtype_field(self.cx.tcx, variant))
955+
.all(|field| {
956+
let field_ty = field.ty(self.cx.tcx, substs);
957+
!field_ty.has_opaque_types() && {
958+
let field_ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, field_ty);
959+
self.is_unit_or_equivalent(field_ty)
960+
}
961+
});
962+
}
963+
964+
false
965+
}
966+
943967
/// Check if the type is array and emit an unsafe type lint.
944968
fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
945969
if let ty::Array(..) = ty.kind() {
@@ -1217,25 +1241,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12171241
}
12181242

12191243
let sig = tcx.erase_late_bound_regions(sig);
1220-
if !sig.output().is_unit() {
1221-
let r = self.check_type_for_ffi(cache, sig.output());
1222-
match r {
1223-
FfiSafe => {}
1224-
_ => {
1225-
return r;
1226-
}
1227-
}
1228-
}
12291244
for arg in sig.inputs() {
1230-
let r = self.check_type_for_ffi(cache, *arg);
1231-
match r {
1245+
match self.check_type_for_ffi(cache, *arg) {
12321246
FfiSafe => {}
1233-
_ => {
1234-
return r;
1235-
}
1247+
r => return r,
12361248
}
12371249
}
1238-
FfiSafe
1250+
1251+
let ret_ty = sig.output();
1252+
if self.is_unit_or_equivalent(ret_ty) {
1253+
return FfiSafe;
1254+
}
1255+
1256+
self.check_type_for_ffi(cache, ret_ty)
12391257
}
12401258

12411259
ty::Foreign(..) => FfiSafe,
@@ -1354,9 +1372,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
13541372
}
13551373

13561374
// Don't report FFI errors for unit return types. This check exists here, and not in
1357-
// `check_foreign_fn` (where it would make more sense) so that normalization has definitely
1375+
// the caller (where it would make more sense) so that normalization has definitely
13581376
// happened.
1359-
if is_return_type && ty.is_unit() {
1377+
if is_return_type && self.is_unit_or_equivalent(ty) {
13601378
return;
13611379
}
13621380

@@ -1370,9 +1388,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
13701388
None,
13711389
);
13721390
}
1373-
// If `ty` is a `repr(transparent)` newtype, and the non-zero-sized type is a generic
1374-
// argument, which after substitution, is `()`, then this branch can be hit.
1375-
FfiResult::FfiUnsafe { ty, .. } if is_return_type && ty.is_unit() => {}
13761391
FfiResult::FfiUnsafe { ty, reason, help } => {
13771392
self.emit_ffi_unsafe_type_lint(ty, sp, reason, help);
13781393
}

tests/ui/lint/lint-ctypes-113436.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#![deny(improper_ctypes_definitions)]
2+
3+
#[repr(C)]
4+
pub struct Wrap<T>(T);
5+
6+
#[repr(transparent)]
7+
pub struct TransparentWrap<T>(T);
8+
9+
pub extern "C" fn f() -> Wrap<()> {
10+
//~^ ERROR `extern` fn uses type `()`, which is not FFI-safe
11+
todo!()
12+
}
13+
14+
const _: extern "C" fn() -> Wrap<()> = f;
15+
//~^ ERROR `extern` fn uses type `()`, which is not FFI-safe
16+
17+
pub extern "C" fn ff() -> Wrap<Wrap<()>> {
18+
//~^ ERROR `extern` fn uses type `()`, which is not FFI-safe
19+
todo!()
20+
}
21+
22+
const _: extern "C" fn() -> Wrap<Wrap<()>> = ff;
23+
//~^ ERROR `extern` fn uses type `()`, which is not FFI-safe
24+
25+
pub extern "C" fn g() -> TransparentWrap<()> {
26+
todo!()
27+
}
28+
29+
const _: extern "C" fn() -> TransparentWrap<()> = g;
30+
31+
pub extern "C" fn gg() -> TransparentWrap<TransparentWrap<()>> {
32+
todo!()
33+
}
34+
35+
const _: extern "C" fn() -> TransparentWrap<TransparentWrap<()>> = gg;
36+
37+
fn main() {}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
error: `extern` fn uses type `()`, which is not FFI-safe
2+
--> $DIR/lint-ctypes-113436.rs:9:26
3+
|
4+
LL | pub extern "C" fn f() -> Wrap<()> {
5+
| ^^^^^^^^ not FFI-safe
6+
|
7+
= help: consider using a struct instead
8+
= note: tuples have unspecified layout
9+
note: the lint level is defined here
10+
--> $DIR/lint-ctypes-113436.rs:1:9
11+
|
12+
LL | #![deny(improper_ctypes_definitions)]
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
15+
error: `extern` fn uses type `()`, which is not FFI-safe
16+
--> $DIR/lint-ctypes-113436.rs:14:10
17+
|
18+
LL | const _: extern "C" fn() -> Wrap<()> = f;
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
20+
|
21+
= help: consider using a struct instead
22+
= note: tuples have unspecified layout
23+
24+
error: `extern` fn uses type `()`, which is not FFI-safe
25+
--> $DIR/lint-ctypes-113436.rs:17:27
26+
|
27+
LL | pub extern "C" fn ff() -> Wrap<Wrap<()>> {
28+
| ^^^^^^^^^^^^^^ not FFI-safe
29+
|
30+
= help: consider using a struct instead
31+
= note: tuples have unspecified layout
32+
33+
error: `extern` fn uses type `()`, which is not FFI-safe
34+
--> $DIR/lint-ctypes-113436.rs:22:10
35+
|
36+
LL | const _: extern "C" fn() -> Wrap<Wrap<()>> = ff;
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
38+
|
39+
= help: consider using a struct instead
40+
= note: tuples have unspecified layout
41+
42+
error: aborting due to 4 previous errors
43+

0 commit comments

Comments
 (0)