Skip to content

Commit a319188

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

File tree

5 files changed

+136
-56
lines changed

5 files changed

+136
-56
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,10 @@ lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead
349349
350350
lint_improper_ctypes_char_reason = the `char` type has no C equivalent
351351
352-
lint_improper_ctypes_cstr_help =
353-
consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
352+
lint_improper_ctypes_cstr_help_const =
353+
consider passing a `*const std::ffi::c_char` instead, converting to/from `{$ty}` as needed
354+
lint_improper_ctypes_cstr_help_mut =
355+
consider passing a `*mut std::ffi::c_char` instead, converting to/from `{$ty}` as needed
354356
lint_improper_ctypes_cstr_reason = `CStr`/`CString` do not have a guaranteed layout
355357
356358
lint_improper_ctypes_dyn = trait objects have no C equivalent
@@ -373,8 +375,8 @@ lint_improper_ctypes_opaque = opaque types have no C equivalent
373375
lint_improper_ctypes_slice_help = consider using a raw pointer instead
374376
375377
lint_improper_ctypes_slice_reason = slices have no C equivalent
376-
lint_improper_ctypes_str_help = consider using `*const u8` and a length instead
377378
379+
lint_improper_ctypes_str_help = consider using `*const u8` and a length instead
378380
lint_improper_ctypes_str_reason = string slices have no C equivalent
379381
lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct
380382
@@ -396,6 +398,10 @@ lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[re
396398
lint_improper_ctypes_union_layout_reason = this union has unspecified layout
397399
lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive
398400
401+
lint_improper_ctypes_unsized_box = this box for an unsized type contains metadata, which makes it incompatible with a C pointer
402+
lint_improper_ctypes_unsized_ptr = this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer
403+
lint_improper_ctypes_unsized_ref = this reference to an unsized type contains metadata, which makes it incompatible with a C pointer
404+
399405
lint_int_to_ptr_transmutes = transmuting an integer to a pointer creates a pointer without provenance
400406
.note = this is dangerous because dereferencing the resulting pointer is undefined behavior
401407
.note_exposed_provenance = exposed provenance semantics can be used to create a pointer based on some previously exposed provenance

compiler/rustc_lint/src/types/improper_ctypes.rs

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,6 @@ impl<'tcx> FfiResult<'tcx> {
356356
/// If the FfiUnsafe variant, 'wraps' all reasons,
357357
/// creating new `FfiUnsafeReason`s, putting the originals as their `inner` fields.
358358
/// Otherwise, keep unchanged.
359-
#[expect(unused)]
360359
fn wrap_all(self, ty: Ty<'tcx>, note: DiagMessage, help: Option<DiagMessage>) -> Self {
361360
match self {
362361
Self::FfiUnsafe(this) => {
@@ -525,6 +524,12 @@ bitflags! {
525524
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
526525
enum OuterTyKind {
527526
None,
527+
/// Pointee through ref, raw pointer or Box
528+
/// (we don't need to distinguish the ownership of Box specifically)
529+
Pointee {
530+
mutable: hir::Mutability,
531+
raw: bool,
532+
},
528533
/// For struct/enum/union fields
529534
AdtField,
530535
/// Placeholder for properties that will be used eventually
@@ -536,16 +541,17 @@ impl OuterTyKind {
536541
fn from_outer_ty<'tcx>(ty: Ty<'tcx>) -> Self {
537542
match ty.kind() {
538543
ty::FnPtr(..) => Self::None,
544+
k @ (ty::Ref(_, _, mutable) | ty::RawPtr(_, mutable)) => {
545+
Self::Pointee { raw: matches!(k, ty::RawPtr(..)), mutable: *mutable }
546+
}
539547
ty::Adt(..) => {
540548
if ty.boxed_ty().is_some() {
541-
Self::UnusedVariant
549+
Self::Pointee { raw: false, mutable: hir::Mutability::Mut }
542550
} else {
543551
Self::AdtField
544552
}
545553
}
546-
ty::RawPtr(..) | ty::Ref(..) | ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => {
547-
Self::UnusedVariant
548-
}
554+
ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => Self::UnusedVariant,
549555
k @ _ => bug!("Unexpected outer type {:?} of kind {:?}", ty, k),
550556
}
551557
}
@@ -666,6 +672,11 @@ impl VisitorState {
666672
fn is_field(&self) -> bool {
667673
matches!(self.outer_ty_kind, OuterTyKind::AdtField)
668674
}
675+
676+
/// Whether the current type is behind a pointer that doesn't allow mutating this
677+
fn is_nonmut_pointee(&self) -> bool {
678+
matches!(self.outer_ty_kind, OuterTyKind::Pointee { mutable: hir::Mutability::Not, .. })
679+
}
669680
}
670681

671682
/// Visitor used to recursively traverse MIR types and evaluate FFI-safety.
@@ -676,14 +687,29 @@ struct ImproperCTypesVisitor<'a, 'tcx> {
676687
/// To prevent problems with recursive types,
677688
/// add a types-in-check cache.
678689
cache: FxHashSet<Ty<'tcx>>,
679-
/// The original type being checked, before we recursed
680-
/// to any other types it contains.
681-
base_ty: Ty<'tcx>,
682690
}
683691

684692
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
685-
fn new(cx: &'a LateContext<'tcx>, base_ty: Ty<'tcx>) -> Self {
686-
Self { cx, base_ty, cache: FxHashSet::default() }
693+
fn new(cx: &'a LateContext<'tcx>) -> Self {
694+
Self { cx, cache: FxHashSet::default() }
695+
}
696+
697+
/// Return the right help for Cstring and Cstr-linked unsafety.
698+
fn visit_cstr(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> {
699+
debug_assert!(matches!(ty.kind(), ty::Adt(def, _)
700+
if matches!(
701+
self.cx.tcx.get_diagnostic_name(def.did()),
702+
Some(sym::cstring_type | sym::cstr_type)
703+
)
704+
));
705+
706+
let help = if state.is_nonmut_pointee() {
707+
fluent::lint_improper_ctypes_cstr_help_const
708+
} else {
709+
fluent::lint_improper_ctypes_cstr_help_mut
710+
};
711+
712+
FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_cstr_reason, Some(help))
687713
}
688714

689715
/// Checks if the given indirection (box,ref,pointer) is "ffi-safe".
@@ -697,6 +723,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
697723
use FfiResult::*;
698724
let tcx = self.cx.tcx;
699725

726+
if let ty::Adt(def, _) = inner_ty.kind() {
727+
if let Some(diag_name @ (sym::cstring_type | sym::cstr_type)) =
728+
tcx.get_diagnostic_name(def.did())
729+
{
730+
// we have better error messages when checking for C-strings directly
731+
let mut cstr_res = self.visit_cstr(state.get_next(ty), inner_ty); // always unsafe with one depth-one reason.
732+
733+
// Cstr pointer have metadata, CString is Sized
734+
if diag_name == sym::cstr_type {
735+
// we need to override the "type" part of `cstr_res`'s only FfiResultReason
736+
// so it says that it's the use of the indirection that is unsafe
737+
match cstr_res {
738+
FfiResult::FfiUnsafe(ref mut reasons) => {
739+
reasons.first_mut().unwrap().reason.ty = ty;
740+
}
741+
_ => unreachable!(),
742+
}
743+
let note = match indirection_kind {
744+
IndirectionKind::RawPtr => fluent::lint_improper_ctypes_unsized_ptr,
745+
IndirectionKind::Ref => fluent::lint_improper_ctypes_unsized_ref,
746+
IndirectionKind::Box => fluent::lint_improper_ctypes_unsized_box,
747+
};
748+
return cstr_res.wrap_all(ty, note, None);
749+
} else {
750+
return cstr_res;
751+
}
752+
}
753+
}
754+
700755
match indirection_kind {
701756
IndirectionKind::Box => {
702757
// FIXME(ctypes): this logic is broken, but it still fits the current tests:
@@ -933,13 +988,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
933988
AdtKind::Struct | AdtKind::Union => {
934989
if let Some(sym::cstring_type | sym::cstr_type) =
935990
tcx.get_diagnostic_name(def.did())
936-
&& !self.base_ty.is_mutable_ptr()
937991
{
938-
return FfiResult::new_with_reason(
939-
ty,
940-
fluent::lint_improper_ctypes_cstr_reason,
941-
Some(fluent::lint_improper_ctypes_cstr_help),
942-
);
992+
return self.visit_cstr(state, ty);
943993
}
944994
self.visit_struct_or_union(state, ty, def, args)
945995
}
@@ -1220,7 +1270,7 @@ impl<'tcx> ImproperCTypesLint {
12201270
/// Check that an extern "ABI" static variable is of a ffi-safe type.
12211271
fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) {
12221272
let ty = cx.tcx.type_of(id).instantiate_identity();
1223-
let mut visitor = ImproperCTypesVisitor::new(cx, ty);
1273+
let mut visitor = ImproperCTypesVisitor::new(cx);
12241274
let ffi_res = visitor.check_type(VisitorState::static_var(), ty);
12251275
self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern);
12261276
}
@@ -1239,15 +1289,15 @@ impl<'tcx> ImproperCTypesLint {
12391289
for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
12401290
let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Arg);
12411291
state.depth = depth;
1242-
let mut visitor = ImproperCTypesVisitor::new(cx, *input_ty);
1292+
let mut visitor = ImproperCTypesVisitor::new(cx);
12431293
let ffi_res = visitor.check_type(state, *input_ty);
12441294
self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode);
12451295
}
12461296

12471297
if let hir::FnRetTy::Return(ret_hir) = decl.output {
12481298
let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Ret);
12491299
state.depth = depth;
1250-
let mut visitor = ImproperCTypesVisitor::new(cx, sig.output());
1300+
let mut visitor = ImproperCTypesVisitor::new(cx);
12511301
let ffi_res = visitor.check_type(state, sig.output());
12521302
self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode);
12531303
}

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

Lines changed: 1 addition & 1 deletion
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_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default
1010

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_ctypes_definitions)]
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)