Skip to content

Commit 84d388e

Browse files
committed
ImproperCTypes: change cstr linting
another user-visible change: change the messaging and help around CStr/CString lints
1 parent a2dc1fb commit 84d388e

File tree

5 files changed

+134
-52
lines changed

5 files changed

+134
-52
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,8 +363,10 @@ lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead
363363
364364
lint_improper_ctypes_char_reason = the `char` type has no C equivalent
365365
366-
lint_improper_ctypes_cstr_help =
367-
consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
366+
lint_improper_ctypes_cstr_help_const =
367+
consider passing a `*const std::ffi::c_char` instead, converting to/from `{$ty}` as needed
368+
lint_improper_ctypes_cstr_help_mut =
369+
consider passing a `*mut std::ffi::c_char` instead, converting to/from `{$ty}` as needed
368370
lint_improper_ctypes_cstr_reason = `CStr`/`CString` do not have a guaranteed layout
369371
370372
lint_improper_ctypes_dyn = trait objects have no C equivalent
@@ -386,8 +388,8 @@ lint_improper_ctypes_opaque = opaque types have no C equivalent
386388
lint_improper_ctypes_slice_help = consider using a raw pointer instead
387389
388390
lint_improper_ctypes_slice_reason = slices have no C equivalent
389-
lint_improper_ctypes_str_help = consider using `*const u8` and a length instead
390391
392+
lint_improper_ctypes_str_help = consider using `*const u8` and a length instead
391393
lint_improper_ctypes_str_reason = string slices have no C equivalent
392394
lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct
393395
@@ -409,6 +411,10 @@ lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[re
409411
lint_improper_ctypes_union_layout_reason = this union has unspecified layout
410412
lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive
411413
414+
lint_improper_ctypes_unsized_box = this box for an unsized type contains metadata, which makes it incompatible with a C pointer
415+
lint_improper_ctypes_unsized_ptr = this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer
416+
lint_improper_ctypes_unsized_ref = this reference to an unsized type contains metadata, which makes it incompatible with a C pointer
417+
412418
lint_incomplete_include =
413419
include macro expected single expression in source
414420

compiler/rustc_lint/src/types/improper_ctypes.rs

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,6 @@ impl<'tcx> FfiResult<'tcx> {
350350
/// If the FfiUnsafe variant, 'wraps' all reasons,
351351
/// creating new `FfiUnsafeReason`s, putting the originals as their `inner` fields.
352352
/// Otherwise, keep unchanged.
353-
#[expect(unused)]
354353
fn wrap_all(self, ty: Ty<'tcx>, note: DiagMessage, help: Option<DiagMessage>) -> Self {
355354
match self {
356355
Self::FfiUnsafe(this) => {
@@ -705,6 +704,18 @@ impl VisitorState {
705704
fn is_field(&self) -> bool {
706705
self.ephemeral_flags.contains(EphemeralStateFlags::IN_ADT)
707706
}
707+
708+
/// Whether the current type is behind a pointer
709+
#[inline]
710+
fn is_pointee(&self) -> bool {
711+
self.ephemeral_flags.contains(EphemeralStateFlags::IN_PTR)
712+
}
713+
714+
/// Whether the current type is behind a pointer that doesn't allow mutating this
715+
#[inline]
716+
fn is_nonmut_pointee(&self) -> bool {
717+
self.is_pointee() && !self.ephemeral_flags.contains(EphemeralStateFlags::PTR_MUT)
718+
}
708719
}
709720

710721
/// Visitor used to recursively traverse MIR types and evaluate FFI-safety.
@@ -717,13 +728,30 @@ struct ImproperCTypesVisitor<'a, 'tcx> {
717728
cache: FxHashSet<Ty<'tcx>>,
718729
/// The original type being checked, before we recursed
719730
/// to any other types it contains.
720-
base_ty: Ty<'tcx>,
721731
base_fn_mode: CItemKind,
722732
}
723733

724734
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
725-
fn new(cx: &'a LateContext<'tcx>, base_ty: Ty<'tcx>, base_fn_mode: CItemKind) -> Self {
726-
Self { cx, base_ty, base_fn_mode, cache: FxHashSet::default() }
735+
fn new(cx: &'a LateContext<'tcx>, base_fn_mode: CItemKind) -> Self {
736+
Self { cx, base_fn_mode, cache: FxHashSet::default() }
737+
}
738+
739+
/// Return the right help for Cstring and Cstr-linked unsafety.
740+
fn visit_cstr(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> {
741+
debug_assert!(matches!(ty.kind(), ty::Adt(def, _)
742+
if matches!(
743+
self.cx.tcx.get_diagnostic_name(def.did()),
744+
Some(sym::cstring_type | sym::cstr_type)
745+
)
746+
));
747+
748+
let help = if state.is_nonmut_pointee() {
749+
fluent::lint_improper_ctypes_cstr_help_const
750+
} else {
751+
fluent::lint_improper_ctypes_cstr_help_mut
752+
};
753+
754+
FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_cstr_reason, Some(help))
727755
}
728756

729757
/// Checks if the given indirection (box,ref,pointer) is "ffi-safe".
@@ -737,6 +765,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
737765
use FfiResult::*;
738766
let tcx = self.cx.tcx;
739767

768+
if let ty::Adt(def, _) = inner_ty.kind() {
769+
if let Some(diag_name @ (sym::cstring_type | sym::cstr_type)) =
770+
tcx.get_diagnostic_name(def.did())
771+
{
772+
// we have better error messages when checking for C-strings directly
773+
let mut cstr_res = self.visit_cstr(state.get_next(ty), inner_ty); // always unsafe with one depth-one reason.
774+
775+
// Cstr pointer have metadata, CString is Sized
776+
if diag_name == sym::cstr_type {
777+
// we need to override the "type" part of `cstr_res`'s only FfiResultReason
778+
// so it says that it's the use of the indirection that is unsafe
779+
match cstr_res {
780+
FfiResult::FfiUnsafe(ref mut reasons) => {
781+
reasons.first_mut().unwrap().reason.ty = ty;
782+
}
783+
_ => unreachable!(),
784+
}
785+
let note = match indirection_kind {
786+
IndirectionKind::RawPtr => fluent::lint_improper_ctypes_unsized_ptr,
787+
IndirectionKind::Ref => fluent::lint_improper_ctypes_unsized_ref,
788+
IndirectionKind::Box => fluent::lint_improper_ctypes_unsized_box,
789+
};
790+
return cstr_res.wrap_all(ty, note, None);
791+
} else {
792+
return cstr_res;
793+
}
794+
}
795+
}
796+
740797
match indirection_kind {
741798
IndirectionKind::Box => {
742799
// FIXME(ctypes): this logic is broken, but it still fits the current tests:
@@ -977,13 +1034,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
9771034
AdtKind::Struct | AdtKind::Union => {
9781035
if let Some(sym::cstring_type | sym::cstr_type) =
9791036
tcx.get_diagnostic_name(def.did())
980-
&& !self.base_ty.is_mutable_ptr()
9811037
{
982-
return FfiResult::new_with_reason(
983-
ty,
984-
fluent::lint_improper_ctypes_cstr_reason,
985-
Some(fluent::lint_improper_ctypes_cstr_help),
986-
);
1038+
return self.visit_cstr(state, ty);
9871039
}
9881040
self.visit_struct_or_union(state, ty, def, args)
9891041
}
@@ -1232,7 +1284,7 @@ impl<'tcx> ImproperCTypesLint {
12321284
iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)),
12331285
);
12341286
for (depth, (fn_ptr_ty, span)) in all_types {
1235-
let mut visitor = ImproperCTypesVisitor::new(cx, fn_ptr_ty, fn_mode);
1287+
let mut visitor = ImproperCTypesVisitor::new(cx, fn_mode);
12361288
let bridge_state = VisitorState { depth, ..state };
12371289
// FIXME(ctypes): make a check_for_fnptr
12381290
let ffi_res = visitor.check_type(bridge_state, fn_ptr_ty);
@@ -1283,7 +1335,7 @@ impl<'tcx> ImproperCTypesLint {
12831335

12841336
fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) {
12851337
let ty = cx.tcx.type_of(id).instantiate_identity();
1286-
let mut visitor = ImproperCTypesVisitor::new(cx, ty, CItemKind::ImportedExtern);
1338+
let mut visitor = ImproperCTypesVisitor::new(cx, CItemKind::ImportedExtern);
12871339
let ffi_res = visitor.check_type(VisitorState::static_var(), ty);
12881340
self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern);
12891341
}
@@ -1301,14 +1353,14 @@ impl<'tcx> ImproperCTypesLint {
13011353

13021354
for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
13031355
let visit_state = VisitorState::argument_from_fnmode(fn_mode);
1304-
let mut visitor = ImproperCTypesVisitor::new(cx, *input_ty, fn_mode);
1356+
let mut visitor = ImproperCTypesVisitor::new(cx, fn_mode);
13051357
let ffi_res = visitor.check_type(visit_state, *input_ty);
13061358
self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode);
13071359
}
13081360

13091361
if let hir::FnRetTy::Return(ret_hir) = decl.output {
13101362
let visit_state = VisitorState::return_from_fnmode(fn_mode);
1311-
let mut visitor = ImproperCTypesVisitor::new(cx, sig.output(), fn_mode);
1363+
let mut visitor = ImproperCTypesVisitor::new(cx, fn_mode);
13121364
let ffi_res = visitor.check_type(visit_state, sig.output());
13131365
self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode);
13141366
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ warning: `extern` callback uses type `CStr`, which is not FFI-safe
44
LL | type Foo = extern "C" fn(::std::ffi::CStr);
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
66
|
7-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
7+
= help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CStr` as needed
88
= note: `CStr`/`CString` do not have a guaranteed layout
99
= note: `#[warn(improper_c_callbacks)]` (part of `#[warn(improper_c_boundaries)]`) on by default
1010

@@ -14,7 +14,7 @@ warning: `extern` block uses type `CStr`, which is not FFI-safe
1414
LL | fn meh(blah: Foo);
1515
| ^^^ not FFI-safe
1616
|
17-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
17+
= help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CStr` as needed
1818
= note: `CStr`/`CString` do not have a guaranteed layout
1919
= note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default
2020

tests/ui/lint/improper-ctypes/lint-cstr.rs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,35 @@ use std::ffi::{CStr, CString};
66
extern "C" {
77
fn take_cstr(s: CStr);
88
//~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe
9-
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
9+
//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CStr` as needed
1010
fn take_cstr_ref(s: &CStr);
11-
//~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe
12-
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
11+
//~^ ERROR `extern` block uses type `&CStr`, which is not FFI-safe
12+
//~| HELP consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed
1313
fn take_cstring(s: CString);
1414
//~^ ERROR `extern` block uses type `CString`, which is not FFI-safe
15-
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
15+
//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed
1616
fn take_cstring_ref(s: &CString);
1717
//~^ ERROR `extern` block uses type `CString`, which is not FFI-safe
18-
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
18+
//~| HELP consider passing a `*const std::ffi::c_char` instead, converting to/from `CString` as needed
1919

20-
fn no_special_help_for_mut_cstring(s: *mut CString);
20+
fn take_cstring_ptr_mut(s: *mut CString);
2121
//~^ ERROR `extern` block uses type `CString`, which is not FFI-safe
22-
//~| HELP consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
22+
//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed
2323

24-
fn no_special_help_for_mut_cstring_ref(s: &mut CString);
24+
fn take_cstring_ref_mut(s: &mut CString);
2525
//~^ ERROR `extern` block uses type `CString`, which is not FFI-safe
26-
//~| HELP consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
26+
//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed
2727
}
2828

2929
extern "C" fn rust_take_cstr_ref(s: &CStr) {}
30-
//~^ ERROR `extern` fn uses type `CStr`, which is not FFI-safe
31-
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
30+
//~^ ERROR `extern` fn uses type `&CStr`, which is not FFI-safe
31+
//~| HELP consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed
3232
extern "C" fn rust_take_cstring(s: CString) {}
3333
//~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe
34-
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
35-
extern "C" fn rust_no_special_help_for_mut_cstring(s: *mut CString) {}
36-
extern "C" fn rust_no_special_help_for_mut_cstring_ref(s: &mut CString) {}
34+
//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed
35+
extern "C" fn rust_take_cstring_ptr_mut(s: *mut CString) {}
36+
//~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe
37+
//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed
38+
extern "C" fn rust_take_cstring_ref_mut(s: &mut CString) {}
39+
//~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe
40+
//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed

tests/ui/lint/improper-ctypes/lint-cstr.stderr

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@ error: `extern` block uses type `CStr`, which is not FFI-safe
44
LL | fn take_cstr(s: CStr);
55
| ^^^^ not FFI-safe
66
|
7-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
7+
= help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CStr` as needed
88
= note: `CStr`/`CString` do not have a guaranteed layout
99
note: the lint level is defined here
1010
--> $DIR/lint-cstr.rs:2:9
1111
|
1212
LL | #![deny(improper_ctypes, improper_c_fn_definitions, improper_c_callbacks)]
1313
| ^^^^^^^^^^^^^^^
1414

15-
error: `extern` block uses type `CStr`, which is not FFI-safe
15+
error: `extern` block uses type `&CStr`, which is not FFI-safe
1616
--> $DIR/lint-cstr.rs:10:25
1717
|
1818
LL | fn take_cstr_ref(s: &CStr);
1919
| ^^^^^ not FFI-safe
2020
|
21-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
21+
= note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer
22+
= help: consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed
2223
= note: `CStr`/`CString` do not have a guaranteed layout
2324

2425
error: `extern` block uses type `CString`, which is not FFI-safe
@@ -27,7 +28,7 @@ error: `extern` block uses type `CString`, which is not FFI-safe
2728
LL | fn take_cstring(s: CString);
2829
| ^^^^^^^ not FFI-safe
2930
|
30-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
31+
= help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed
3132
= note: `CStr`/`CString` do not have a guaranteed layout
3233

3334
error: `extern` block uses type `CString`, which is not FFI-safe
@@ -36,34 +37,35 @@ error: `extern` block uses type `CString`, which is not FFI-safe
3637
LL | fn take_cstring_ref(s: &CString);
3738
| ^^^^^^^^ not FFI-safe
3839
|
39-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
40+
= help: consider passing a `*const std::ffi::c_char` instead, converting to/from `CString` as needed
4041
= note: `CStr`/`CString` do not have a guaranteed layout
4142

4243
error: `extern` block uses type `CString`, which is not FFI-safe
43-
--> $DIR/lint-cstr.rs:20:43
44+
--> $DIR/lint-cstr.rs:20:32
4445
|
45-
LL | fn no_special_help_for_mut_cstring(s: *mut CString);
46-
| ^^^^^^^^^^^^ not FFI-safe
46+
LL | fn take_cstring_ptr_mut(s: *mut CString);
47+
| ^^^^^^^^^^^^ not FFI-safe
4748
|
48-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
49-
= note: this struct has unspecified layout
49+
= help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed
50+
= note: `CStr`/`CString` do not have a guaranteed layout
5051

5152
error: `extern` block uses type `CString`, which is not FFI-safe
52-
--> $DIR/lint-cstr.rs:24:47
53+
--> $DIR/lint-cstr.rs:24:32
5354
|
54-
LL | fn no_special_help_for_mut_cstring_ref(s: &mut CString);
55-
| ^^^^^^^^^^^^ not FFI-safe
55+
LL | fn take_cstring_ref_mut(s: &mut CString);
56+
| ^^^^^^^^^^^^ not FFI-safe
5657
|
57-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
58-
= note: this struct has unspecified layout
58+
= help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed
59+
= note: `CStr`/`CString` do not have a guaranteed layout
5960

60-
error: `extern` fn uses type `CStr`, which is not FFI-safe
61+
error: `extern` fn uses type `&CStr`, which is not FFI-safe
6162
--> $DIR/lint-cstr.rs:29:37
6263
|
6364
LL | extern "C" fn rust_take_cstr_ref(s: &CStr) {}
6465
| ^^^^^ not FFI-safe
6566
|
66-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
67+
= note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer
68+
= help: consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed
6769
= note: `CStr`/`CString` do not have a guaranteed layout
6870
note: the lint level is defined here
6971
--> $DIR/lint-cstr.rs:2:26
@@ -77,8 +79,26 @@ error: `extern` fn uses type `CString`, which is not FFI-safe
7779
LL | extern "C" fn rust_take_cstring(s: CString) {}
7880
| ^^^^^^^ not FFI-safe
7981
|
80-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
82+
= help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed
83+
= note: `CStr`/`CString` do not have a guaranteed layout
84+
85+
error: `extern` fn uses type `CString`, which is not FFI-safe
86+
--> $DIR/lint-cstr.rs:35:44
87+
|
88+
LL | extern "C" fn rust_take_cstring_ptr_mut(s: *mut CString) {}
89+
| ^^^^^^^^^^^^ not FFI-safe
90+
|
91+
= help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed
92+
= note: `CStr`/`CString` do not have a guaranteed layout
93+
94+
error: `extern` fn uses type `CString`, which is not FFI-safe
95+
--> $DIR/lint-cstr.rs:38:44
96+
|
97+
LL | extern "C" fn rust_take_cstring_ref_mut(s: &mut CString) {}
98+
| ^^^^^^^^^^^^ not FFI-safe
99+
|
100+
= help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed
81101
= note: `CStr`/`CString` do not have a guaranteed layout
82102

83-
error: aborting due to 8 previous errors
103+
error: aborting due to 10 previous errors
84104

0 commit comments

Comments
 (0)