Skip to content

Commit 601a34d

Browse files
committed
Auto merge of rust-lang#113457 - davidtwco:lint-ctypes-issue-113436, r=oli-obk
lint/ctypes: fix `()` return type checks Fixes rust-lang#113436. `()` 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 a 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 after [consultation with t-lang](rust-lang#113436 (comment)), I've fixed the bugs and inconsistencies and made `()` FFI-safe within types. I also refactor a function, but that's not too exciting.
2 parents bd9785c + 24f90fd commit 601a34d

File tree

5 files changed

+136
-46
lines changed

5 files changed

+136
-46
lines changed

compiler/rustc_lint/messages.ftl

-2
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,6 @@ lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead
267267
lint_improper_ctypes_char_reason = the `char` type has no C equivalent
268268
lint_improper_ctypes_dyn = trait objects have no C equivalent
269269
270-
lint_improper_ctypes_enum_phantomdata = this enum contains a PhantomData field
271-
272270
lint_improper_ctypes_enum_repr_help =
273271
consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
274272

compiler/rustc_lint/src/types.rs

+39-44
Original file line numberDiff line numberDiff line change
@@ -985,39 +985,43 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
985985
) -> FfiResult<'tcx> {
986986
use FfiResult::*;
987987

988-
let transparent_safety = def.repr().transparent().then(|| {
989-
// Can assume that at most one field is not a ZST, so only check
990-
// that field's type for FFI-safety.
988+
let transparent_with_all_zst_fields = if def.repr().transparent() {
991989
if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) {
992-
return self.check_field_type_for_ffi(cache, field, args);
990+
// Transparent newtypes have at most one non-ZST field which needs to be checked..
991+
match self.check_field_type_for_ffi(cache, field, args) {
992+
FfiUnsafe { ty, .. } if ty.is_unit() => (),
993+
r => return r,
994+
}
995+
996+
false
993997
} else {
994-
// All fields are ZSTs; this means that the type should behave
995-
// like (), which is FFI-unsafe... except if all fields are PhantomData,
996-
// which is tested for below
997-
FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None }
998+
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
999+
// `PhantomData`).
1000+
true
9981001
}
999-
});
1000-
// We can't completely trust repr(C) markings; make sure the fields are
1001-
// actually safe.
1002+
} else {
1003+
false
1004+
};
1005+
1006+
// We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
10021007
let mut all_phantom = !variant.fields.is_empty();
10031008
for field in &variant.fields {
1004-
match self.check_field_type_for_ffi(cache, &field, args) {
1005-
FfiSafe => {
1006-
all_phantom = false;
1007-
}
1008-
FfiPhantom(..) if !def.repr().transparent() && def.is_enum() => {
1009-
return FfiUnsafe {
1010-
ty,
1011-
reason: fluent::lint_improper_ctypes_enum_phantomdata,
1012-
help: None,
1013-
};
1014-
}
1015-
FfiPhantom(..) => {}
1016-
r => return transparent_safety.unwrap_or(r),
1009+
all_phantom &= match self.check_field_type_for_ffi(cache, &field, args) {
1010+
FfiSafe => false,
1011+
// `()` fields are FFI-safe!
1012+
FfiUnsafe { ty, .. } if ty.is_unit() => false,
1013+
FfiPhantom(..) => true,
1014+
r @ FfiUnsafe { .. } => return r,
10171015
}
10181016
}
10191017

1020-
if all_phantom { FfiPhantom(ty) } else { transparent_safety.unwrap_or(FfiSafe) }
1018+
if all_phantom {
1019+
FfiPhantom(ty)
1020+
} else if transparent_with_all_zst_fields {
1021+
FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None }
1022+
} else {
1023+
FfiSafe
1024+
}
10211025
}
10221026

10231027
/// Checks if the given type is "ffi-safe" (has a stable, well-defined
@@ -1220,25 +1224,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12201224
}
12211225

12221226
let sig = tcx.erase_late_bound_regions(sig);
1223-
if !sig.output().is_unit() {
1224-
let r = self.check_type_for_ffi(cache, sig.output());
1225-
match r {
1226-
FfiSafe => {}
1227-
_ => {
1228-
return r;
1229-
}
1230-
}
1231-
}
12321227
for arg in sig.inputs() {
1233-
let r = self.check_type_for_ffi(cache, *arg);
1234-
match r {
1228+
match self.check_type_for_ffi(cache, *arg) {
12351229
FfiSafe => {}
1236-
_ => {
1237-
return r;
1238-
}
1230+
r => return r,
12391231
}
12401232
}
1241-
FfiSafe
1233+
1234+
let ret_ty = sig.output();
1235+
if ret_ty.is_unit() {
1236+
return FfiSafe;
1237+
}
1238+
1239+
self.check_type_for_ffi(cache, ret_ty)
12421240
}
12431241

12441242
ty::Foreign(..) => FfiSafe,
@@ -1354,7 +1352,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
13541352
}
13551353

13561354
// 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
1355+
// the caller (where it would make more sense) so that normalization has definitely
13581356
// happened.
13591357
if is_return_type && ty.is_unit() {
13601358
return;
@@ -1370,9 +1368,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
13701368
None,
13711369
);
13721370
}
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() => {}
13761371
FfiResult::FfiUnsafe { ty, reason, help } => {
13771372
self.emit_ffi_unsafe_type_lint(ty, sp, reason, help);
13781373
}

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

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#![deny(improper_ctypes_definitions)]
2+
3+
#[repr(C)]
4+
pub struct Foo {
5+
a: u8,
6+
b: (),
7+
}
8+
9+
extern "C" fn foo(x: Foo) -> Foo {
10+
todo!()
11+
}
12+
13+
struct NotSafe(u32);
14+
15+
#[repr(C)]
16+
pub struct Bar {
17+
a: u8,
18+
b: (),
19+
c: NotSafe,
20+
}
21+
22+
extern "C" fn bar(x: Bar) -> Bar {
23+
//~^ ERROR `extern` fn uses type `NotSafe`, which is not FFI-safe
24+
//~^^ ERROR `extern` fn uses type `NotSafe`, which is not FFI-safe
25+
todo!()
26+
}
27+
28+
fn main() {}
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
error: `extern` fn uses type `NotSafe`, which is not FFI-safe
2+
--> $DIR/lint-ctypes-113436-1.rs:22:22
3+
|
4+
LL | extern "C" fn bar(x: Bar) -> Bar {
5+
| ^^^ not FFI-safe
6+
|
7+
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
8+
= note: this struct has unspecified layout
9+
note: the type is defined here
10+
--> $DIR/lint-ctypes-113436-1.rs:13:1
11+
|
12+
LL | struct NotSafe(u32);
13+
| ^^^^^^^^^^^^^^
14+
note: the lint level is defined here
15+
--> $DIR/lint-ctypes-113436-1.rs:1:9
16+
|
17+
LL | #![deny(improper_ctypes_definitions)]
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
19+
20+
error: `extern` fn uses type `NotSafe`, which is not FFI-safe
21+
--> $DIR/lint-ctypes-113436-1.rs:22:30
22+
|
23+
LL | extern "C" fn bar(x: Bar) -> Bar {
24+
| ^^^ not FFI-safe
25+
|
26+
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
27+
= note: this struct has unspecified layout
28+
note: the type is defined here
29+
--> $DIR/lint-ctypes-113436-1.rs:13:1
30+
|
31+
LL | struct NotSafe(u32);
32+
| ^^^^^^^^^^^^^^
33+
34+
error: aborting due to 2 previous errors
35+

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

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// check-pass
2+
#![deny(improper_ctypes_definitions)]
3+
4+
#[repr(C)]
5+
pub struct Wrap<T>(T);
6+
7+
#[repr(transparent)]
8+
pub struct TransparentWrap<T>(T);
9+
10+
pub extern "C" fn f() -> Wrap<()> {
11+
todo!()
12+
}
13+
14+
const _: extern "C" fn() -> Wrap<()> = f;
15+
16+
pub extern "C" fn ff() -> Wrap<Wrap<()>> {
17+
todo!()
18+
}
19+
20+
const _: extern "C" fn() -> Wrap<Wrap<()>> = ff;
21+
22+
pub extern "C" fn g() -> TransparentWrap<()> {
23+
todo!()
24+
}
25+
26+
const _: extern "C" fn() -> TransparentWrap<()> = g;
27+
28+
pub extern "C" fn gg() -> TransparentWrap<TransparentWrap<()>> {
29+
todo!()
30+
}
31+
32+
const _: extern "C" fn() -> TransparentWrap<TransparentWrap<()>> = gg;
33+
34+
fn main() {}

0 commit comments

Comments
 (0)