Auto merge of #113457 - davidtwco:lint-ctypes-issue-113436, r=oli-obk
lint/ctypes: fix `()` return type checks Fixes #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](https://github.com/rust-lang/rust/issues/113436#issuecomment-1640756721), I've fixed the bugs and inconsistencies and made `()` FFI-safe within types. I also refactor a function, but that's not too exciting.
This commit is contained in:
commit
601a34de8c
5 changed files with 136 additions and 46 deletions
|
@ -267,8 +267,6 @@ lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead
|
||||||
lint_improper_ctypes_char_reason = the `char` type has no C equivalent
|
lint_improper_ctypes_char_reason = the `char` type has no C equivalent
|
||||||
lint_improper_ctypes_dyn = trait objects have no C equivalent
|
lint_improper_ctypes_dyn = trait objects have no C equivalent
|
||||||
|
|
||||||
lint_improper_ctypes_enum_phantomdata = this enum contains a PhantomData field
|
|
||||||
|
|
||||||
lint_improper_ctypes_enum_repr_help =
|
lint_improper_ctypes_enum_repr_help =
|
||||||
consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
|
consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum
|
||||||
|
|
||||||
|
|
|
@ -985,39 +985,43 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
||||||
) -> FfiResult<'tcx> {
|
) -> FfiResult<'tcx> {
|
||||||
use FfiResult::*;
|
use FfiResult::*;
|
||||||
|
|
||||||
let transparent_safety = def.repr().transparent().then(|| {
|
let transparent_with_all_zst_fields = if def.repr().transparent() {
|
||||||
// Can assume that at most one field is not a ZST, so only check
|
|
||||||
// that field's type for FFI-safety.
|
|
||||||
if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) {
|
if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) {
|
||||||
return self.check_field_type_for_ffi(cache, field, args);
|
// Transparent newtypes have at most one non-ZST field which needs to be checked..
|
||||||
|
match self.check_field_type_for_ffi(cache, field, args) {
|
||||||
|
FfiUnsafe { ty, .. } if ty.is_unit() => (),
|
||||||
|
r => return r,
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
} else {
|
} else {
|
||||||
// All fields are ZSTs; this means that the type should behave
|
// ..or have only ZST fields, which is FFI-unsafe (unless those fields are all
|
||||||
// like (), which is FFI-unsafe... except if all fields are PhantomData,
|
// `PhantomData`).
|
||||||
// which is tested for below
|
true
|
||||||
FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None }
|
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
// We can't completely trust repr(C) markings; make sure the fields are
|
false
|
||||||
// actually safe.
|
};
|
||||||
|
|
||||||
|
// We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
|
||||||
let mut all_phantom = !variant.fields.is_empty();
|
let mut all_phantom = !variant.fields.is_empty();
|
||||||
for field in &variant.fields {
|
for field in &variant.fields {
|
||||||
match self.check_field_type_for_ffi(cache, &field, args) {
|
all_phantom &= match self.check_field_type_for_ffi(cache, &field, args) {
|
||||||
FfiSafe => {
|
FfiSafe => false,
|
||||||
all_phantom = false;
|
// `()` fields are FFI-safe!
|
||||||
}
|
FfiUnsafe { ty, .. } if ty.is_unit() => false,
|
||||||
FfiPhantom(..) if !def.repr().transparent() && def.is_enum() => {
|
FfiPhantom(..) => true,
|
||||||
return FfiUnsafe {
|
r @ FfiUnsafe { .. } => return r,
|
||||||
ty,
|
|
||||||
reason: fluent::lint_improper_ctypes_enum_phantomdata,
|
|
||||||
help: None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
FfiPhantom(..) => {}
|
|
||||||
r => return transparent_safety.unwrap_or(r),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if all_phantom { FfiPhantom(ty) } else { transparent_safety.unwrap_or(FfiSafe) }
|
if all_phantom {
|
||||||
|
FfiPhantom(ty)
|
||||||
|
} else if transparent_with_all_zst_fields {
|
||||||
|
FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None }
|
||||||
|
} else {
|
||||||
|
FfiSafe
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the given type is "ffi-safe" (has a stable, well-defined
|
/// Checks if the given type is "ffi-safe" (has a stable, well-defined
|
||||||
|
@ -1220,25 +1224,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let sig = tcx.erase_late_bound_regions(sig);
|
let sig = tcx.erase_late_bound_regions(sig);
|
||||||
if !sig.output().is_unit() {
|
|
||||||
let r = self.check_type_for_ffi(cache, sig.output());
|
|
||||||
match r {
|
|
||||||
FfiSafe => {}
|
|
||||||
_ => {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for arg in sig.inputs() {
|
for arg in sig.inputs() {
|
||||||
let r = self.check_type_for_ffi(cache, *arg);
|
match self.check_type_for_ffi(cache, *arg) {
|
||||||
match r {
|
|
||||||
FfiSafe => {}
|
FfiSafe => {}
|
||||||
_ => {
|
r => return r,
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FfiSafe
|
|
||||||
|
let ret_ty = sig.output();
|
||||||
|
if ret_ty.is_unit() {
|
||||||
|
return FfiSafe;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.check_type_for_ffi(cache, ret_ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
ty::Foreign(..) => FfiSafe,
|
ty::Foreign(..) => FfiSafe,
|
||||||
|
@ -1354,7 +1352,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't report FFI errors for unit return types. This check exists here, and not in
|
// Don't report FFI errors for unit return types. This check exists here, and not in
|
||||||
// `check_foreign_fn` (where it would make more sense) so that normalization has definitely
|
// the caller (where it would make more sense) so that normalization has definitely
|
||||||
// happened.
|
// happened.
|
||||||
if is_return_type && ty.is_unit() {
|
if is_return_type && ty.is_unit() {
|
||||||
return;
|
return;
|
||||||
|
@ -1370,9 +1368,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// If `ty` is a `repr(transparent)` newtype, and the non-zero-sized type is a generic
|
|
||||||
// argument, which after substitution, is `()`, then this branch can be hit.
|
|
||||||
FfiResult::FfiUnsafe { ty, .. } if is_return_type && ty.is_unit() => {}
|
|
||||||
FfiResult::FfiUnsafe { ty, reason, help } => {
|
FfiResult::FfiUnsafe { ty, reason, help } => {
|
||||||
self.emit_ffi_unsafe_type_lint(ty, sp, reason, help);
|
self.emit_ffi_unsafe_type_lint(ty, sp, reason, help);
|
||||||
}
|
}
|
||||||
|
|
28
tests/ui/lint/lint-ctypes-113436-1.rs
Normal file
28
tests/ui/lint/lint-ctypes-113436-1.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#![deny(improper_ctypes_definitions)]
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Foo {
|
||||||
|
a: u8,
|
||||||
|
b: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn foo(x: Foo) -> Foo {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NotSafe(u32);
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Bar {
|
||||||
|
a: u8,
|
||||||
|
b: (),
|
||||||
|
c: NotSafe,
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn bar(x: Bar) -> Bar {
|
||||||
|
//~^ ERROR `extern` fn uses type `NotSafe`, which is not FFI-safe
|
||||||
|
//~^^ ERROR `extern` fn uses type `NotSafe`, which is not FFI-safe
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
35
tests/ui/lint/lint-ctypes-113436-1.stderr
Normal file
35
tests/ui/lint/lint-ctypes-113436-1.stderr
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
error: `extern` fn uses type `NotSafe`, which is not FFI-safe
|
||||||
|
--> $DIR/lint-ctypes-113436-1.rs:22:22
|
||||||
|
|
|
||||||
|
LL | extern "C" fn bar(x: Bar) -> Bar {
|
||||||
|
| ^^^ not FFI-safe
|
||||||
|
|
|
||||||
|
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
|
||||||
|
= note: this struct has unspecified layout
|
||||||
|
note: the type is defined here
|
||||||
|
--> $DIR/lint-ctypes-113436-1.rs:13:1
|
||||||
|
|
|
||||||
|
LL | struct NotSafe(u32);
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
note: the lint level is defined here
|
||||||
|
--> $DIR/lint-ctypes-113436-1.rs:1:9
|
||||||
|
|
|
||||||
|
LL | #![deny(improper_ctypes_definitions)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: `extern` fn uses type `NotSafe`, which is not FFI-safe
|
||||||
|
--> $DIR/lint-ctypes-113436-1.rs:22:30
|
||||||
|
|
|
||||||
|
LL | extern "C" fn bar(x: Bar) -> Bar {
|
||||||
|
| ^^^ not FFI-safe
|
||||||
|
|
|
||||||
|
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
|
||||||
|
= note: this struct has unspecified layout
|
||||||
|
note: the type is defined here
|
||||||
|
--> $DIR/lint-ctypes-113436-1.rs:13:1
|
||||||
|
|
|
||||||
|
LL | struct NotSafe(u32);
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
34
tests/ui/lint/lint-ctypes-113436.rs
Normal file
34
tests/ui/lint/lint-ctypes-113436.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// check-pass
|
||||||
|
#![deny(improper_ctypes_definitions)]
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Wrap<T>(T);
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct TransparentWrap<T>(T);
|
||||||
|
|
||||||
|
pub extern "C" fn f() -> Wrap<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: extern "C" fn() -> Wrap<()> = f;
|
||||||
|
|
||||||
|
pub extern "C" fn ff() -> Wrap<Wrap<()>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: extern "C" fn() -> Wrap<Wrap<()>> = ff;
|
||||||
|
|
||||||
|
pub extern "C" fn g() -> TransparentWrap<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: extern "C" fn() -> TransparentWrap<()> = g;
|
||||||
|
|
||||||
|
pub extern "C" fn gg() -> TransparentWrap<TransparentWrap<()>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: extern "C" fn() -> TransparentWrap<TransparentWrap<()>> = gg;
|
||||||
|
|
||||||
|
fn main() {}
|
Loading…
Add table
Add a link
Reference in a new issue