Skip to content

Commit b80c5ae

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

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
@@ -134,7 +134,6 @@ impl<'tcx> FfiResult<'tcx> {
134134
/// If the FfiUnsafe variant, 'wraps' all reasons,
135135
/// creating new `FfiUnsafeReason`s, putting the originals as their `inner` fields.
136136
/// Otherwise, keep unchanged.
137-
#[expect(unused)]
138137
fn wrap_all(self, ty: Ty<'tcx>, note: DiagMessage, help: Option<DiagMessage>) -> Self {
139138
match self {
140139
Self::FfiUnsafe(this) => {
@@ -376,7 +375,6 @@ struct ImproperCTypesVisitor<'a, 'tcx> {
376375

377376
/// The original type being checked, before we recursed
378377
/// to any other types it contains.
379-
base_ty: Ty<'tcx>,
380378
base_fn_mode: CItemKind,
381379
}
382380

@@ -393,13 +391,8 @@ impl<'a, 'tcx, 'v> Drop for ImproperCTypesVisitorDepthGuard<'a, 'tcx, 'v> {
393391
}
394392

395393
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
396-
fn new(cx: &'a LateContext<'tcx>, base_ty: Ty<'tcx>, base_fn_mode: CItemKind) -> Self {
397-
Self {
398-
cx,
399-
base_ty,
400-
base_fn_mode,
401-
recursion_limiter: RefCell::new((FxHashSet::default(), 0)),
402-
}
394+
fn new(cx: &'a LateContext<'tcx>, base_fn_mode: CItemKind) -> Self {
395+
Self { cx, base_fn_mode, recursion_limiter: RefCell::new((FxHashSet::default(), 0)) }
403396
}
404397

405398
/// Protect against infinite recursion, for example
@@ -438,6 +431,36 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
438431
}
439432
}
440433

434+
/// Return the right help for Cstring and Cstr-linked unsafety.
435+
fn visit_cstr(&self, outer_ty: Option<Ty<'tcx>>, ty: Ty<'tcx>) -> FfiResult<'tcx> {
436+
debug_assert!(matches!(ty.kind(), ty::Adt(def, _)
437+
if matches!(
438+
self.cx.tcx.get_diagnostic_name(def.did()),
439+
Some(sym::cstring_type | sym::cstr_type)
440+
)
441+
));
442+
443+
let help = if let Some(outer_ty) = outer_ty {
444+
match outer_ty.kind() {
445+
ty::Ref(..) | ty::RawPtr(..) => {
446+
if outer_ty.is_mutable_ptr() {
447+
fluent::lint_improper_ctypes_cstr_help_mut
448+
} else {
449+
fluent::lint_improper_ctypes_cstr_help_const
450+
}
451+
}
452+
ty::Adt(..) if outer_ty.boxed_ty().is_some() => {
453+
fluent::lint_improper_ctypes_cstr_help_owned
454+
}
455+
_ => fluent::lint_improper_ctypes_cstr_help_unknown,
456+
}
457+
} else {
458+
fluent::lint_improper_ctypes_cstr_help_owned
459+
};
460+
461+
FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_cstr_reason, Some(help))
462+
}
463+
441464
/// Checks if the given indirection (box,ref,pointer) is "ffi-safe".
442465
fn visit_indirection(
443466
&self,
@@ -449,6 +472,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
449472
use FfiResult::*;
450473
let tcx = self.cx.tcx;
451474

475+
if let ty::Adt(def, _) = inner_ty.kind() {
476+
if let Some(diag_name @ (sym::cstring_type | sym::cstr_type)) =
477+
tcx.get_diagnostic_name(def.did())
478+
{
479+
// we have better error messages when checking for C-strings directly
480+
let mut cstr_res = self.visit_cstr(Some(ty), inner_ty); // always unsafe with one depth-one reason.
481+
482+
// Cstr pointer have metadata, CString is Sized
483+
if diag_name == sym::cstr_type {
484+
// we need to override the "type" part of `cstr_res`'s only FfiResultReason
485+
// so it says that it's the use of the indirection that is unsafe
486+
match cstr_res {
487+
FfiResult::FfiUnsafe(ref mut reasons) => {
488+
reasons.first_mut().unwrap().reason.ty = ty;
489+
}
490+
_ => unreachable!(),
491+
}
492+
let note = match indirection_type {
493+
IndirectionType::RawPtr => fluent::lint_improper_ctypes_unsized_ptr,
494+
IndirectionType::Ref => fluent::lint_improper_ctypes_unsized_ref,
495+
IndirectionType::Box => fluent::lint_improper_ctypes_unsized_box,
496+
};
497+
return cstr_res.wrap_all(ty, note, None);
498+
} else {
499+
return cstr_res;
500+
}
501+
}
502+
}
503+
452504
match indirection_type {
453505
IndirectionType::Box => {
454506
// TODO: this logic is broken, but it still fits the current tests
@@ -679,13 +731,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
679731
// but function *pointers* don't seem to have the same no-unsized-parameters requirement to compile
680732
if let Some(sym::cstring_type | sym::cstr_type) =
681733
tcx.get_diagnostic_name(def.did())
682-
&& !self.base_ty.is_mutable_ptr()
683734
{
684-
return FfiResult::new_with_reason(
685-
ty,
686-
fluent::lint_improper_ctypes_cstr_reason,
687-
Some(fluent::lint_improper_ctypes_cstr_help),
688-
);
735+
return self.visit_cstr(outer_ty, ty);
689736
}
690737
self.visit_struct_or_union(state, ty, def, args)
691738
}
@@ -991,7 +1038,7 @@ impl<'tcx> ImproperCTypesLint {
9911038

9921039
let all_types = iter::zip(visitor.tys.drain(..), visitor.spans.drain(..));
9931040
all_types.for_each(|(fn_ptr_ty, span)| {
994-
let visitor = ImproperCTypesVisitor::new(cx, fn_ptr_ty, fn_mode);
1041+
let visitor = ImproperCTypesVisitor::new(cx, fn_mode);
9951042
// TODO: make a check_for_fnptr
9961043
let ffi_res = visitor.check_for_type(state, fn_ptr_ty);
9971044

@@ -1041,7 +1088,7 @@ impl<'tcx> ImproperCTypesLint {
10411088

10421089
fn check_foreign_static(&self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) {
10431090
let ty = cx.tcx.type_of(id).instantiate_identity();
1044-
let visitor = ImproperCTypesVisitor::new(cx, ty, CItemKind::ImportedExtern);
1091+
let visitor = ImproperCTypesVisitor::new(cx, CItemKind::ImportedExtern);
10451092
let ffi_res = visitor.check_for_type(VisitorState::StaticTy, ty);
10461093
self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern);
10471094
}
@@ -1059,14 +1106,14 @@ impl<'tcx> ImproperCTypesLint {
10591106

10601107
for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
10611108
let visit_state = VisitorState::argument_from_fnmode(fn_mode);
1062-
let visitor = ImproperCTypesVisitor::new(cx, *input_ty, fn_mode);
1109+
let visitor = ImproperCTypesVisitor::new(cx, fn_mode);
10631110
let ffi_res = visitor.check_for_type(visit_state, *input_ty);
10641111
self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode);
10651112
}
10661113

10671114
if let hir::FnRetTy::Return(ret_hir) = decl.output {
10681115
let visit_state = VisitorState::return_from_fnmode(fn_mode);
1069-
let visitor = ImproperCTypesVisitor::new(cx, sig.output(), fn_mode);
1116+
let visitor = ImproperCTypesVisitor::new(cx, fn_mode);
10701117
let ffi_res = visitor.check_for_type(visit_state, sig.output());
10711118
self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode);
10721119
}

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)