Skip to content

Commit 1d52131

Browse files
committed
lint: rework some ImproperCTypes messages (especially around indirections to !Sized)
1 parent f021d99 commit 1d52131

14 files changed

+195
-67
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,6 @@ lint_improper_ctypes_128bit = 128-bit integers don't currently have a known stab
359359
lint_improper_ctypes_array_help = consider passing a pointer to the array
360360
361361
lint_improper_ctypes_array_reason = passing raw arrays by value is not FFI-safe
362-
lint_improper_ctypes_box = box cannot be represented as a single pointer
363362
364363
lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead
365364
@@ -377,7 +376,9 @@ lint_improper_ctypes_enum_repr_help =
377376
lint_improper_ctypes_enum_repr_reason = enum has no representation hint
378377
lint_improper_ctypes_fnptr_help = consider using an `extern fn(...) -> ...` function pointer instead
379378
379+
lint_improper_ctypes_fnptr_indirect_reason = the function pointer to `{$ty}` is FFI-unsafe due to `{$inner_ty}`
380380
lint_improper_ctypes_fnptr_reason = this function pointer has Rust-specific calling convention
381+
381382
lint_improper_ctypes_non_exhaustive = this enum is non-exhaustive
382383
lint_improper_ctypes_non_exhaustive_variant = this enum has non-exhaustive variants
383384
@@ -388,7 +389,11 @@ lint_improper_ctypes_opaque = opaque types have no C equivalent
388389
lint_improper_ctypes_pat_help = consider using the base type instead
389390
390391
lint_improper_ctypes_pat_reason = pattern types have no C equivalent
391-
lint_improper_ctypes_slice_help = consider using a raw pointer instead
392+
393+
lint_improper_ctypes_sized_ptr_to_unsafe_type =
394+
this reference (`{$ty}`) is can safely be translated into a C pointer, but `{$inner_ty}` itself is not FFI-safe
395+
396+
lint_improper_ctypes_slice_help = consider using a raw pointer to the slice's first element (and a length) instead
392397
393398
lint_improper_ctypes_slice_reason = slices have no C equivalent
394399
lint_improper_ctypes_str_help = consider using `*const u8` and a length instead
@@ -414,6 +419,10 @@ lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[re
414419
lint_improper_ctypes_union_layout_reason = this union has unspecified layout
415420
lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive
416421
422+
lint_improper_ctypes_unsized_box = this box for an unsized rust type contains metadata, which makes it incompatible with a C pointer
423+
lint_improper_ctypes_unsized_ptr = this pointer to an unsized rust type contains metadata, which makes it incompatible with a C pointer
424+
lint_improper_ctypes_unsized_ref = this reference to an unsized rust type contains metadata, which makes it incompatible with a C pointer
425+
417426
lint_incomplete_include =
418427
include macro expected single expression in source
419428

compiler/rustc_lint/src/types.rs

Lines changed: 115 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -732,8 +732,6 @@ enum FfiResult<'tcx> {
732732
reason: DiagMessage,
733733
help: Option<DiagMessage>,
734734
},
735-
// NOTE: this `allow` is only here for one retroactively-added commit
736-
#[allow(dead_code)]
737735
FfiUnsafeWrapper {
738736
ty: Ty<'tcx>,
739737
reason: DiagMessage,
@@ -1044,16 +1042,47 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10441042

10451043
match *ty.kind() {
10461044
ty::Adt(def, args) => {
1047-
if let Some(boxed) = ty.boxed_ty()
1048-
&& matches!(self.mode, CItemKind::Definition)
1049-
{
1050-
if boxed.is_sized(tcx, self.cx.typing_env()) {
1051-
return FfiSafe;
1045+
if let Some(inner_ty) = ty.boxed_ty() {
1046+
if inner_ty.is_sized(tcx, self.cx.typing_env())
1047+
|| matches!(inner_ty.kind(), ty::Foreign(..))
1048+
{
1049+
// discussion on declaration vs definition:
1050+
// see the `ty::RawPtr(inner_ty, _) | ty::Ref(_, inner_ty, _)` arm
1051+
// of this `match *ty.kind()` block
1052+
if matches!(self.mode, CItemKind::Definition) {
1053+
return FfiSafe;
1054+
} else {
1055+
let inner_res = self.check_type_for_ffi(acc, inner_ty);
1056+
return match inner_res {
1057+
FfiUnsafe { .. } | FfiUnsafeWrapper { .. } => FfiUnsafeWrapper {
1058+
ty,
1059+
reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type,
1060+
wrapped: Box::new(inner_res),
1061+
help: None,
1062+
},
1063+
_ => inner_res,
1064+
};
1065+
}
10521066
} else {
1067+
let help = match inner_ty.kind() {
1068+
ty::Str => Some(fluent::lint_improper_ctypes_str_help),
1069+
ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help),
1070+
ty::Adt(def, _)
1071+
if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union)
1072+
&& matches!(
1073+
tcx.get_diagnostic_name(def.did()),
1074+
Some(sym::cstring_type | sym::cstr_type)
1075+
)
1076+
&& !acc.base_ty.is_mutable_ptr() =>
1077+
{
1078+
Some(fluent::lint_improper_ctypes_cstr_help)
1079+
}
1080+
_ => None,
1081+
};
10531082
return FfiUnsafe {
10541083
ty,
1055-
reason: fluent::lint_improper_ctypes_box,
1056-
help: None,
1084+
reason: fluent::lint_improper_ctypes_unsized_box,
1085+
help,
10571086
};
10581087
}
10591088
}
@@ -1209,15 +1238,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12091238
help: Some(fluent::lint_improper_ctypes_tuple_help),
12101239
},
12111240

1212-
ty::RawPtr(ty, _) | ty::Ref(_, ty, _)
1213-
if {
1214-
matches!(self.mode, CItemKind::Definition)
1215-
&& ty.is_sized(self.cx.tcx, self.cx.typing_env())
1216-
} =>
1217-
{
1218-
FfiSafe
1219-
}
1220-
12211241
ty::RawPtr(ty, _)
12221242
if match ty.kind() {
12231243
ty::Tuple(tuple) => tuple.is_empty(),
@@ -1227,7 +1247,66 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12271247
FfiSafe
12281248
}
12291249

1230-
ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(acc, ty),
1250+
ty::RawPtr(inner_ty, _) | ty::Ref(_, inner_ty, _) => {
1251+
if inner_ty.is_sized(tcx, self.cx.typing_env())
1252+
|| matches!(inner_ty.kind(), ty::Foreign(..))
1253+
{
1254+
// there's a nuance on what this lint should do for function definitions
1255+
// (touched upon in https://github.com/rust-lang/rust/issues/66220 and https://github.com/rust-lang/rust/pull/72700)
1256+
//
1257+
// (`extern "C" fn fn_name(...) {...}`) versus declarations (`extern "C" {fn fn_name(...);}`).
1258+
// The big question is: what does "ABI safety" mean? if you have something translated to a C pointer
1259+
// (which has a stable layout) but points to FFI-unsafe type, is it safe?
1260+
// on one hand, the function's ABI will match that of a similar C-declared function API,
1261+
// on the other, dereferencing the pointer in not-rust will be painful.
1262+
// In this code, the opinion is split between function declarations and function definitions.
1263+
// For declarations, we see this as unsafe, but for definitions, we see this as safe.
1264+
// This is mostly because, for extern function declarations, the actual definition of the function is written somewhere else,
1265+
// so the fact that a pointer's pointee should be treated as opaque to one side or the other can be explicitely written out.
1266+
// For extern function definitions, however, both callee and some callers can be written in rust,
1267+
// so developers need to keep as much typing information as possible.
1268+
if matches!(self.mode, CItemKind::Definition) {
1269+
return FfiSafe;
1270+
} else if matches!(ty.kind(), ty::RawPtr(..))
1271+
&& matches!(inner_ty.kind(), ty::Tuple(tuple) if tuple.is_empty())
1272+
{
1273+
FfiSafe
1274+
} else {
1275+
let inner_res = self.check_type_for_ffi(acc, inner_ty);
1276+
return match inner_res {
1277+
FfiSafe => inner_res,
1278+
_ => FfiUnsafeWrapper {
1279+
ty,
1280+
reason: fluent::lint_improper_ctypes_sized_ptr_to_unsafe_type,
1281+
wrapped: Box::new(inner_res),
1282+
help: None,
1283+
},
1284+
};
1285+
}
1286+
} else {
1287+
let help = match inner_ty.kind() {
1288+
ty::Str => Some(fluent::lint_improper_ctypes_str_help),
1289+
ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help),
1290+
ty::Adt(def, _)
1291+
if matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union)
1292+
&& matches!(
1293+
tcx.get_diagnostic_name(def.did()),
1294+
Some(sym::cstring_type | sym::cstr_type)
1295+
)
1296+
&& !acc.base_ty.is_mutable_ptr() =>
1297+
{
1298+
Some(fluent::lint_improper_ctypes_cstr_help)
1299+
}
1300+
_ => None,
1301+
};
1302+
let reason = match ty.kind() {
1303+
ty::RawPtr(..) => fluent::lint_improper_ctypes_unsized_ptr,
1304+
ty::Ref(..) => fluent::lint_improper_ctypes_unsized_ref,
1305+
_ => unreachable!(),
1306+
};
1307+
FfiUnsafe { ty, reason, help }
1308+
}
1309+
}
12311310

12321311
ty::Array(inner_ty, _) => self.check_type_for_ffi(acc, inner_ty),
12331312

@@ -1245,7 +1324,14 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12451324
for arg in sig.inputs() {
12461325
match self.check_type_for_ffi(acc, *arg) {
12471326
FfiSafe => {}
1248-
r => return r,
1327+
r => {
1328+
return FfiUnsafeWrapper {
1329+
ty,
1330+
reason: fluent::lint_improper_ctypes_fnptr_indirect_reason,
1331+
help: None,
1332+
wrapped: Box::new(r),
1333+
};
1334+
}
12491335
}
12501336
}
12511337

@@ -1254,7 +1340,15 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12541340
return FfiSafe;
12551341
}
12561342

1257-
self.check_type_for_ffi(acc, ret_ty)
1343+
match self.check_type_for_ffi(acc, ret_ty) {
1344+
r @ (FfiSafe | FfiPhantom(_)) => r,
1345+
r => FfiUnsafeWrapper {
1346+
ty: ty.clone(),
1347+
reason: fluent::lint_improper_ctypes_fnptr_indirect_reason,
1348+
help: None,
1349+
wrapped: Box::new(r),
1350+
},
1351+
}
12581352
}
12591353

12601354
ty::Foreign(..) => FfiSafe,

tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ warning: `extern` fn uses type `CStr`, which is not FFI-safe
44
LL | type Foo = extern "C" fn(::std::ffi::CStr);
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
66
|
7+
= note: the function pointer to `extern "C" fn(CStr)` is FFI-unsafe due to `CStr`
78
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
89
= note: `CStr`/`CString` do not have a guaranteed layout
910
= note: `#[warn(improper_ctypes_definitions)]` on by default
@@ -14,6 +15,7 @@ warning: `extern` block uses type `CStr`, which is not FFI-safe
1415
LL | fn meh(blah: Foo);
1516
| ^^^ not FFI-safe
1617
|
18+
= note: the function pointer to `extern "C" fn(CStr)` is FFI-unsafe due to `CStr`
1719
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
1820
= note: `CStr`/`CString` do not have a guaranteed layout
1921
= note: `#[warn(improper_ctypes)]` on by default

tests/ui/extern/extern-C-str-arg-ice-80125.stderr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ warning: `extern` fn uses type `str`, which is not FFI-safe
44
LL | type ExternCallback = extern "C" fn(*const u8, u32, str);
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
66
|
7+
= note: the function pointer to `extern "C" fn(*const u8, u32, str)` is FFI-unsafe due to `str`
78
= help: consider using `*const u8` and a length instead
89
= note: string slices have no C equivalent
910
= note: `#[warn(improper_ctypes_definitions)]` on by default
@@ -14,6 +15,7 @@ warning: `extern` fn uses type `str`, which is not FFI-safe
1415
LL | pub extern "C" fn register_something(bind: ExternCallback) -> Struct {
1516
| ^^^^^^^^^^^^^^ not FFI-safe
1617
|
18+
= note: the function pointer to `extern "C" fn(*const u8, u32, str)` is FFI-unsafe due to `str`
1719
= help: consider using `*const u8` and a length instead
1820
= note: string slices have no C equivalent
1921

tests/ui/lint/extern-C-fnptr-lints-slices.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// It's an improper ctype (a slice) arg in an extern "C" fnptr.
44

55
pub type F = extern "C" fn(&[u8]);
6-
//~^ ERROR: `extern` fn uses type `[u8]`, which is not FFI-safe
6+
//~^ ERROR: `extern` fn uses type `&[u8]`, which is not FFI-safe
77

88

99
fn main() {}

tests/ui/lint/extern-C-fnptr-lints-slices.stderr

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
error: `extern` fn uses type `[u8]`, which is not FFI-safe
1+
error: `extern` fn uses type `&[u8]`, which is not FFI-safe
22
--> $DIR/extern-C-fnptr-lints-slices.rs:5:14
33
|
44
LL | pub type F = extern "C" fn(&[u8]);
55
| ^^^^^^^^^^^^^^^^^^^^ not FFI-safe
66
|
7-
= help: consider using a raw pointer instead
8-
= note: slices have no C equivalent
7+
= note: the function pointer to `for<'a> extern "C" fn(&'a [u8])` is FFI-unsafe due to `&[u8]`
8+
= help: consider using a raw pointer to the slice's first element (and a length) instead
9+
= note: this reference to an unsized rust type contains metadata, which makes it incompatible with a C pointer
910
note: the lint level is defined here
1011
--> $DIR/extern-C-fnptr-lints-slices.rs:1:8
1112
|

tests/ui/lint/lint-ctypes-73249-2.stderr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ error: `extern` block uses type `Qux`, which is not FFI-safe
44
LL | fn lint_me() -> A<()>;
55
| ^^^^^ not FFI-safe
66
|
7+
= note: this reference (`&Qux`) is can safely be translated into a C pointer, but `Qux` itself is not FFI-safe
78
= note: opaque types have no C equivalent
89
note: the lint level is defined here
910
--> $DIR/lint-ctypes-73249-2.rs:2:9

0 commit comments

Comments
 (0)