Skip to content

Commit b5f34fc

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

File tree

4 files changed

+142
-52
lines changed

4 files changed

+142
-52
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,8 +363,17 @@ 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, and use `CStr::as_ptr()` or `CString::as_ptr()`
368+
lint_improper_ctypes_cstr_help_mut =
369+
consider passing a `*mut std::ffi::c_char` instead, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
370+
lint_improper_ctypes_cstr_help_owned =
371+
consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead,
372+
and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
373+
(note that `CString::into_raw()`'s output must not be `libc::free()`'d)
374+
lint_improper_ctypes_cstr_help_unknown =
375+
consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead,
376+
and use (depending on the use case) `CStr::as_ptr()`, `CString::into_raw()` then `CString::from_raw()`, or a dedicated buffer
368377
lint_improper_ctypes_cstr_reason = `CStr`/`CString` do not have a guaranteed layout
369378
370379
lint_improper_ctypes_dyn = trait objects have no C equivalent
@@ -386,8 +395,8 @@ lint_improper_ctypes_opaque = opaque types have no C equivalent
386395
lint_improper_ctypes_slice_help = consider using a raw pointer instead
387396
388397
lint_improper_ctypes_slice_reason = slices have no C equivalent
389-
lint_improper_ctypes_str_help = consider using `*const u8` and a length instead
390398
399+
lint_improper_ctypes_str_help = consider using `*const u8` and a length instead
391400
lint_improper_ctypes_str_reason = string slices have no C equivalent
392401
lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct
393402
@@ -409,6 +418,10 @@ lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[re
409418
lint_improper_ctypes_union_layout_reason = this union has unspecified layout
410419
lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive
411420
421+
lint_improper_ctypes_unsized_box = this box for an unsized type contains metadata, which makes it incompatible with a C pointer
422+
lint_improper_ctypes_unsized_ptr = this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer
423+
lint_improper_ctypes_unsized_ref = this reference to an unsized type contains metadata, which makes it incompatible with a C pointer
424+
412425
lint_incomplete_include =
413426
include macro expected single expression in source
414427

compiler/rustc_lint/src/types/improper_ctypes.rs

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,6 @@ impl<'tcx> FfiResult<'tcx> {
281281
/// If the FfiUnsafe variant, 'wraps' all reasons,
282282
/// creating new `FfiUnsafeReason`s, putting the originals as their `inner` fields.
283283
/// Otherwise, keep unchanged.
284-
#[expect(unused)]
285284
fn wrap_all(self, ty: Ty<'tcx>, note: DiagMessage, help: Option<DiagMessage>) -> Self {
286285
match self {
287286
Self::FfiUnsafe(this) => {
@@ -534,7 +533,6 @@ struct ImproperCTypesVisitor<'a, 'tcx> {
534533

535534
/// The original type being checked, before we recursed
536535
/// to any other types it contains.
537-
base_ty: Ty<'tcx>,
538536
base_fn_mode: CItemKind,
539537
}
540538

@@ -551,13 +549,8 @@ impl<'a, 'tcx, 'v> Drop for ImproperCTypesVisitorDepthGuard<'a, 'tcx, 'v> {
551549
}
552550

553551
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
554-
fn new(cx: &'a LateContext<'tcx>, base_ty: Ty<'tcx>, base_fn_mode: CItemKind) -> Self {
555-
Self {
556-
cx,
557-
base_ty,
558-
base_fn_mode,
559-
recursion_limiter: RefCell::new((FxHashSet::default(), 0)),
560-
}
552+
fn new(cx: &'a LateContext<'tcx>, base_fn_mode: CItemKind) -> Self {
553+
Self { cx, base_fn_mode, recursion_limiter: RefCell::new((FxHashSet::default(), 0)) }
561554
}
562555

563556
/// Protect against infinite recursion, for example
@@ -596,6 +589,36 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
596589
}
597590
}
598591

592+
/// Return the right help for Cstring and Cstr-linked unsafety.
593+
fn visit_cstr(&self, outer_ty: Option<Ty<'tcx>>, ty: Ty<'tcx>) -> FfiResult<'tcx> {
594+
debug_assert!(matches!(ty.kind(), ty::Adt(def, _)
595+
if matches!(
596+
self.cx.tcx.get_diagnostic_name(def.did()),
597+
Some(sym::cstring_type | sym::cstr_type)
598+
)
599+
));
600+
601+
let help = if let Some(outer_ty) = outer_ty {
602+
match outer_ty.kind() {
603+
ty::Ref(..) | ty::RawPtr(..) => {
604+
if outer_ty.is_mutable_ptr() {
605+
fluent::lint_improper_ctypes_cstr_help_mut
606+
} else {
607+
fluent::lint_improper_ctypes_cstr_help_const
608+
}
609+
}
610+
ty::Adt(..) if outer_ty.boxed_ty().is_some() => {
611+
fluent::lint_improper_ctypes_cstr_help_owned
612+
}
613+
_ => fluent::lint_improper_ctypes_cstr_help_unknown,
614+
}
615+
} else {
616+
fluent::lint_improper_ctypes_cstr_help_owned
617+
};
618+
619+
FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_cstr_reason, Some(help))
620+
}
621+
599622
/// Checks if the given indirection (box,ref,pointer) is "ffi-safe".
600623
fn visit_indirection(
601624
&self,
@@ -607,6 +630,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
607630
use FfiResult::*;
608631
let tcx = self.cx.tcx;
609632

633+
if let ty::Adt(def, _) = inner_ty.kind() {
634+
if let Some(diag_name @ (sym::cstring_type | sym::cstr_type)) =
635+
tcx.get_diagnostic_name(def.did())
636+
{
637+
// we have better error messages when checking for C-strings directly
638+
let mut cstr_res = self.visit_cstr(Some(ty), inner_ty); // always unsafe with one depth-one reason.
639+
640+
// Cstr pointer have metadata, CString is Sized
641+
if diag_name == sym::cstr_type {
642+
// we need to override the "type" part of `cstr_res`'s only FfiResultReason
643+
// so it says that it's the use of the indirection that is unsafe
644+
match cstr_res {
645+
FfiResult::FfiUnsafe(ref mut reasons) => {
646+
reasons.first_mut().unwrap().reason.ty = ty;
647+
}
648+
_ => unreachable!(),
649+
}
650+
let note = match indirection_type {
651+
IndirectionType::RawPtr => fluent::lint_improper_ctypes_unsized_ptr,
652+
IndirectionType::Ref => fluent::lint_improper_ctypes_unsized_ref,
653+
IndirectionType::Box => fluent::lint_improper_ctypes_unsized_box,
654+
};
655+
return cstr_res.wrap_all(ty, note, None);
656+
} else {
657+
return cstr_res;
658+
}
659+
}
660+
}
661+
610662
match indirection_type {
611663
IndirectionType::Box => {
612664
// FIXME(ctypes): this logic is broken, but it still fits the current tests
@@ -830,13 +882,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
830882
// but function *pointers* don't seem to have the same no-unsized-parameters requirement to compile
831883
if let Some(sym::cstring_type | sym::cstr_type) =
832884
tcx.get_diagnostic_name(def.did())
833-
&& !self.base_ty.is_mutable_ptr()
834885
{
835-
return FfiResult::new_with_reason(
836-
ty,
837-
fluent::lint_improper_ctypes_cstr_reason,
838-
Some(fluent::lint_improper_ctypes_cstr_help),
839-
);
886+
return self.visit_cstr(outer_ty, ty);
840887
}
841888
self.visit_struct_or_union(state, ty, def, args)
842889
}
@@ -1144,7 +1191,7 @@ impl<'tcx> ImproperCTypesLint {
11441191

11451192
let all_types = iter::zip(visitor.tys.drain(..), visitor.spans.drain(..));
11461193
for (fn_ptr_ty, span) in all_types {
1147-
let visitor = ImproperCTypesVisitor::new(cx, fn_ptr_ty, fn_mode);
1194+
let visitor = ImproperCTypesVisitor::new(cx, fn_mode);
11481195
// FIXME(ctypes): make a check_for_fnptr
11491196
let ffi_res = visitor.check_type(state, fn_ptr_ty);
11501197

@@ -1194,7 +1241,7 @@ impl<'tcx> ImproperCTypesLint {
11941241

11951242
fn check_foreign_static(&self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) {
11961243
let ty = cx.tcx.type_of(id).instantiate_identity();
1197-
let visitor = ImproperCTypesVisitor::new(cx, ty, CItemKind::ImportedExtern);
1244+
let visitor = ImproperCTypesVisitor::new(cx, CItemKind::ImportedExtern);
11981245
let ffi_res = visitor.check_type(VisitorState::STATIC_TY, ty);
11991246
self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern);
12001247
}
@@ -1212,14 +1259,14 @@ impl<'tcx> ImproperCTypesLint {
12121259

12131260
for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
12141261
let visit_state = VisitorState::argument_from_fnmode(fn_mode);
1215-
let visitor = ImproperCTypesVisitor::new(cx, *input_ty, fn_mode);
1262+
let visitor = ImproperCTypesVisitor::new(cx, fn_mode);
12161263
let ffi_res = visitor.check_type(visit_state, *input_ty);
12171264
self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode);
12181265
}
12191266

12201267
if let hir::FnRetTy::Return(ret_hir) = decl.output {
12211268
let visit_state = VisitorState::return_from_fnmode(fn_mode);
1222-
let visitor = ImproperCTypesVisitor::new(cx, sig.output(), fn_mode);
1269+
let visitor = ImproperCTypesVisitor::new(cx, fn_mode);
12231270
let ffi_res = visitor.check_type(visit_state, sig.output());
12241271
self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode);
12251272
}

tests/ui/lint/improper_ctypes/lint-cstr.rs

Lines changed: 15 additions & 11 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 `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead
1010
fn take_cstr_ref(s: &CStr);
11-
//~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe
11+
//~^ ERROR `extern` block uses type `&CStr`, which is not FFI-safe
1212
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
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 `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead
1616
fn take_cstring_ref(s: &CString);
1717
//~^ ERROR `extern` block uses type `CString`, which is not FFI-safe
1818
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
2727
}
2828

2929
extern "C" fn rust_take_cstr_ref(s: &CStr) {}
30-
//~^ ERROR `extern` fn uses type `CStr`, which is not FFI-safe
30+
//~^ ERROR `extern` fn uses type `&CStr`, which is not FFI-safe
3131
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
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 `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead
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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
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, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer

tests/ui/lint/improper_ctypes/lint-cstr.stderr

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,24 @@ 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 `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead,
8+
and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
9+
(note that `CString::into_raw()`'s output must not be `libc::free()`'d)
810
= note: `CStr`/`CString` do not have a guaranteed layout
911
note: the lint level is defined here
1012
--> $DIR/lint-cstr.rs:2:9
1113
|
1214
LL | #![deny(improper_ctypes, improper_c_fn_definitions, improper_c_callbacks)]
1315
| ^^^^^^^^^^^^^^^
1416

15-
error: `extern` block uses type `CStr`, which is not FFI-safe
17+
error: `extern` block uses type `&CStr`, which is not FFI-safe
1618
--> $DIR/lint-cstr.rs:10:25
1719
|
1820
LL | fn take_cstr_ref(s: &CStr);
1921
| ^^^^^ not FFI-safe
2022
|
21-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
23+
= note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer
24+
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` or `CString::as_ptr()`
2225
= note: `CStr`/`CString` do not have a guaranteed layout
2326

2427
error: `extern` block uses type `CString`, which is not FFI-safe
@@ -27,7 +30,9 @@ error: `extern` block uses type `CString`, which is not FFI-safe
2730
LL | fn take_cstring(s: CString);
2831
| ^^^^^^^ not FFI-safe
2932
|
30-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
33+
= help: consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead,
34+
and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
35+
(note that `CString::into_raw()`'s output must not be `libc::free()`'d)
3136
= note: `CStr`/`CString` do not have a guaranteed layout
3237

3338
error: `extern` block uses type `CString`, which is not FFI-safe
@@ -36,34 +41,35 @@ error: `extern` block uses type `CString`, which is not FFI-safe
3641
LL | fn take_cstring_ref(s: &CString);
3742
| ^^^^^^^^ not FFI-safe
3843
|
39-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
44+
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` or `CString::as_ptr()`
4045
= note: `CStr`/`CString` do not have a guaranteed layout
4146

4247
error: `extern` block uses type `CString`, which is not FFI-safe
43-
--> $DIR/lint-cstr.rs:20:43
48+
--> $DIR/lint-cstr.rs:20:32
4449
|
45-
LL | fn no_special_help_for_mut_cstring(s: *mut CString);
46-
| ^^^^^^^^^^^^ not FFI-safe
50+
LL | fn take_cstring_ptr_mut(s: *mut CString);
51+
| ^^^^^^^^^^^^ not FFI-safe
4752
|
48-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
49-
= note: this struct has unspecified layout
53+
= help: consider passing a `*mut std::ffi::c_char` instead, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
54+
= note: `CStr`/`CString` do not have a guaranteed layout
5055

5156
error: `extern` block uses type `CString`, which is not FFI-safe
52-
--> $DIR/lint-cstr.rs:24:47
57+
--> $DIR/lint-cstr.rs:24:32
5358
|
54-
LL | fn no_special_help_for_mut_cstring_ref(s: &mut CString);
55-
| ^^^^^^^^^^^^ not FFI-safe
59+
LL | fn take_cstring_ref_mut(s: &mut CString);
60+
| ^^^^^^^^^^^^ not FFI-safe
5661
|
57-
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
58-
= note: this struct has unspecified layout
62+
= help: consider passing a `*mut std::ffi::c_char` instead, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
63+
= note: `CStr`/`CString` do not have a guaranteed layout
5964

60-
error: `extern` fn uses type `CStr`, which is not FFI-safe
65+
error: `extern` fn uses type `&CStr`, which is not FFI-safe
6166
--> $DIR/lint-cstr.rs:29:37
6267
|
6368
LL | extern "C" fn rust_take_cstr_ref(s: &CStr) {}
6469
| ^^^^^ not FFI-safe
6570
|
66-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
71+
= note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer
72+
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` or `CString::as_ptr()`
6773
= note: `CStr`/`CString` do not have a guaranteed layout
6874
note: the lint level is defined here
6975
--> $DIR/lint-cstr.rs:2:26
@@ -77,8 +83,28 @@ error: `extern` fn uses type `CString`, which is not FFI-safe
7783
LL | extern "C" fn rust_take_cstring(s: CString) {}
7884
| ^^^^^^^ not FFI-safe
7985
|
80-
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
86+
= help: consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead,
87+
and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
88+
(note that `CString::into_raw()`'s output must not be `libc::free()`'d)
89+
= note: `CStr`/`CString` do not have a guaranteed layout
90+
91+
error: `extern` fn uses type `CString`, which is not FFI-safe
92+
--> $DIR/lint-cstr.rs:35:44
93+
|
94+
LL | extern "C" fn rust_take_cstring_ptr_mut(s: *mut CString) {}
95+
| ^^^^^^^^^^^^ not FFI-safe
96+
|
97+
= help: consider passing a `*mut std::ffi::c_char` instead, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
98+
= note: `CStr`/`CString` do not have a guaranteed layout
99+
100+
error: `extern` fn uses type `CString`, which is not FFI-safe
101+
--> $DIR/lint-cstr.rs:38:44
102+
|
103+
LL | extern "C" fn rust_take_cstring_ref_mut(s: &mut CString) {}
104+
| ^^^^^^^^^^^^ not FFI-safe
105+
|
106+
= help: consider passing a `*mut std::ffi::c_char` instead, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
81107
= note: `CStr`/`CString` do not have a guaranteed layout
82108

83-
error: aborting due to 8 previous errors
109+
error: aborting due to 10 previous errors
84110

0 commit comments

Comments
 (0)