Skip to content

Commit

Permalink
Add a special case for CStr/CString in the improper_ctypes lint
Browse files Browse the repository at this point in the history
Instead of saying to "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct",
we now tell users to "consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`"
when the type involved is a `CStr` or a `CString`.
  • Loading branch information
Flying-Toast committed Mar 23, 2024
1 parent c3b05c6 commit 2ef3825
Show file tree
Hide file tree
Showing 14 changed files with 172 additions and 21 deletions.
5 changes: 5 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,11 @@ lint_improper_ctypes_box = box cannot be represented as a single pointer
lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead
lint_improper_ctypes_char_reason = the `char` type has no C equivalent
lint_improper_ctypes_cstr_help =
consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
lint_improper_ctypes_cstr_reason = `CStr`/`CString` do not have a guaranteed layout
lint_improper_ctypes_dyn = trait objects have no C equivalent
lint_improper_ctypes_enum_repr_help =
Expand Down
50 changes: 37 additions & 13 deletions compiler/rustc_lint/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,14 @@ struct ImproperCTypesVisitor<'a, 'tcx> {
mode: CItemKind,
}

/// Accumulator for recursive ffi type checking
struct CTypesVisitorState<'tcx> {
cache: FxHashSet<Ty<'tcx>>,
/// The original type being checked, before we recursed
/// to any other types it contains.
base_ty: Ty<'tcx>,
}

enum FfiResult<'tcx> {
FfiSafe,
FfiPhantom(Ty<'tcx>),
Expand Down Expand Up @@ -1143,7 +1151,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
/// Checks if the given field's type is "ffi-safe".
fn check_field_type_for_ffi(
&self,
cache: &mut FxHashSet<Ty<'tcx>>,
acc: &mut CTypesVisitorState<'tcx>,
field: &ty::FieldDef,
args: GenericArgsRef<'tcx>,
) -> FfiResult<'tcx> {
Expand All @@ -1153,13 +1161,13 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
.tcx
.try_normalize_erasing_regions(self.cx.param_env, field_ty)
.unwrap_or(field_ty);
self.check_type_for_ffi(cache, field_ty)
self.check_type_for_ffi(acc, field_ty)
}

/// Checks if the given `VariantDef`'s field types are "ffi-safe".
fn check_variant_for_ffi(
&self,
cache: &mut FxHashSet<Ty<'tcx>>,
acc: &mut CTypesVisitorState<'tcx>,
ty: Ty<'tcx>,
def: ty::AdtDef<'tcx>,
variant: &ty::VariantDef,
Expand All @@ -1170,7 +1178,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
let transparent_with_all_zst_fields = if def.repr().transparent() {
if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) {
// Transparent newtypes have at most one non-ZST field which needs to be checked..
match self.check_field_type_for_ffi(cache, field, args) {
match self.check_field_type_for_ffi(acc, field, args) {
FfiUnsafe { ty, .. } if ty.is_unit() => (),
r => return r,
}
Expand All @@ -1188,7 +1196,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
// We can't completely trust `repr(C)` markings, so make sure the fields are actually safe.
let mut all_phantom = !variant.fields.is_empty();
for field in &variant.fields {
all_phantom &= match self.check_field_type_for_ffi(cache, field, args) {
all_phantom &= match self.check_field_type_for_ffi(acc, field, args) {
FfiSafe => false,
// `()` fields are FFI-safe!
FfiUnsafe { ty, .. } if ty.is_unit() => false,
Expand All @@ -1208,7 +1216,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {

/// Checks if the given type is "ffi-safe" (has a stable, well-defined
/// representation which can be exported to C code).
fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, ty: Ty<'tcx>) -> FfiResult<'tcx> {
fn check_type_for_ffi(
&self,
acc: &mut CTypesVisitorState<'tcx>,
ty: Ty<'tcx>,
) -> FfiResult<'tcx> {
use FfiResult::*;

let tcx = self.cx.tcx;
Expand All @@ -1217,7 +1229,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
// `struct S(*mut S);`.
// FIXME: A recursion limit is necessary as well, for irregular
// recursive types.
if !cache.insert(ty) {
if !acc.cache.insert(ty) {
return FfiSafe;
}

Expand All @@ -1239,6 +1251,17 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
}
match def.adt_kind() {
AdtKind::Struct | AdtKind::Union => {
if let Some(sym::cstring_type | sym::cstr_type) =
tcx.get_diagnostic_name(def.did())
&& !acc.base_ty.is_mutable_ptr()
{
return FfiUnsafe {
ty,
reason: fluent::lint_improper_ctypes_cstr_reason,
help: Some(fluent::lint_improper_ctypes_cstr_help),
};
}

if !def.repr().c() && !def.repr().transparent() {
return FfiUnsafe {
ty,
Expand Down Expand Up @@ -1285,7 +1308,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
};
}

self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), args)
self.check_variant_for_ffi(acc, ty, def, def.non_enum_variant(), args)
}
AdtKind::Enum => {
if def.variants().is_empty() {
Expand Down Expand Up @@ -1328,7 +1351,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
};
}

match self.check_variant_for_ffi(cache, ty, def, variant, args) {
match self.check_variant_for_ffi(acc, ty, def, variant, args) {
FfiSafe => (),
r => return r,
}
Expand Down Expand Up @@ -1394,7 +1417,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {

ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(cache, ty),

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

ty::FnPtr(sig) => {
if self.is_internal_abi(sig.abi()) {
Expand All @@ -1407,7 +1430,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {

let sig = tcx.instantiate_bound_regions_with_erased(sig);
for arg in sig.inputs() {
match self.check_type_for_ffi(cache, *arg) {
match self.check_type_for_ffi(acc, *arg) {
FfiSafe => {}
r => return r,
}
Expand All @@ -1418,7 +1441,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
return FfiSafe;
}

self.check_type_for_ffi(cache, ret_ty)
self.check_type_for_ffi(acc, ret_ty)
}

ty::Foreign(..) => FfiSafe,
Expand Down Expand Up @@ -1541,7 +1564,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
return;
}

match self.check_type_for_ffi(&mut FxHashSet::default(), ty) {
let mut acc = CTypesVisitorState { cache: FxHashSet::default(), base_ty: ty };
match self.check_type_for_ffi(&mut acc, ty) {
FfiResult::FfiSafe => {}
FfiResult::FfiPhantom(ty) => {
self.emit_ffi_unsafe_type_lint(
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ symbols! {
crate_visibility_modifier,
crt_dash_static: "crt-static",
csky_target_feature,
cstr_type,
cstring_type,
ctlz,
ctlz_nonzero,
Expand Down
1 change: 1 addition & 0 deletions library/core/src/ffi/c_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ use crate::str;
/// [str]: prim@str "str"
#[derive(Hash)]
#[stable(feature = "core_c_str", since = "1.64.0")]
#[rustc_diagnostic_item = "cstr_type"]
#[rustc_has_incoherent_inherent_impls]
#[lang = "CStr"]
// `fn from` in `impl From<&CStr> for Box<CStr>` current implementation relies
Expand Down
2 changes: 1 addition & 1 deletion library/stdarch
Submodule stdarch updated 139 files
2 changes: 1 addition & 1 deletion src/doc/book
Submodule book updated 95 files
+2 −2 .github/workflows/main.yml
+0 −1 ci/dictionary.txt
+8 −8 listings/ch02-guessing-game-tutorial/listing-02-02/Cargo.lock
+5 −5 listings/ch02-guessing-game-tutorial/listing-02-04/output.txt
+0 −4 listings/ch02-guessing-game-tutorial/no-listing-02-without-expect/output.txt
+1 −1 listings/ch03-common-programming-concepts/no-listing-01-variables-are-immutable/output.txt
+1 −7 listings/ch03-common-programming-concepts/no-listing-05-mut-cant-change-types/output.txt
+17 −2 listings/ch03-common-programming-concepts/no-listing-19-statements-vs-expressions/output.txt
+1 −1 listings/ch03-common-programming-concepts/no-listing-23-statements-dont-return-values/output.txt
+1 −1 listings/ch03-common-programming-concepts/no-listing-28-if-condition-must-be-bool/output.txt
+1 −1 listings/ch03-common-programming-concepts/no-listing-31-arms-must-return-same-type/output.txt
+6 −7 listings/ch03-common-programming-concepts/output-only-01-no-type-annotations/output.txt
+4 −7 listings/ch04-understanding-ownership/listing-04-06/output.txt
+1 −1 listings/ch04-understanding-ownership/no-listing-04-cant-use-after-move/output.txt
+1 −1 listings/ch04-understanding-ownership/no-listing-10-multiple-mut-not-allowed/output.txt
+1 −1 listings/ch04-understanding-ownership/no-listing-12-immutable-and-mutable-not-allowed/output.txt
+2 −7 listings/ch04-understanding-ownership/no-listing-14-dangling-reference/output.txt
+1 −1 listings/ch04-understanding-ownership/no-listing-19-slice-error/output.txt
+1 −1 listings/ch05-using-structs-to-structure-related-data/listing-05-11/output.txt
+1 −1 listings/ch05-using-structs-to-structure-related-data/no-listing-02-reference-in-struct/output.txt
+2 −2 listings/ch05-using-structs-to-structure-related-data/no-listing-05-dbg-macro/output.txt
+2 −3 listings/ch05-using-structs-to-structure-related-data/output-only-01-debug/output.txt
+3 −3 listings/ch06-enums-and-pattern-matching/no-listing-07-cant-use-option-directly/output.txt
+4 −4 listings/ch06-enums-and-pattern-matching/no-listing-10-non-exhaustive-match/output.txt
+3 −7 listings/ch07-managing-growing-projects/listing-07-03/output.txt
+1 −1 listings/ch07-managing-growing-projects/listing-07-05/output.txt
+7 −12 listings/ch07-managing-growing-projects/listing-07-12/output.txt
+2 −2 listings/ch08-common-collections/listing-08-06/output.txt
+6 −6 listings/ch08-common-collections/listing-08-19/output.txt
+1 −2 listings/ch08-common-collections/output-only-01-not-char-boundary/output.txt
+1 −2 listings/ch09-error-handling/listing-09-01/output.txt
+1 −2 listings/ch09-error-handling/listing-09-04/output.txt
+1 −1 listings/ch09-error-handling/listing-09-10/output.txt
+1 −2 listings/ch09-error-handling/no-listing-01-panic/output.txt
+1 −1 listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/output.txt
+1 −1 listings/ch10-generic-types-traits-and-lifetimes/listing-10-07/output.txt
+1 −3 listings/ch10-generic-types-traits-and-lifetimes/listing-10-16/output.txt
+1 −1 listings/ch10-generic-types-traits-and-lifetimes/listing-10-20/output.txt
+2 −4 listings/ch10-generic-types-traits-and-lifetimes/listing-10-23/output.txt
+3 −6 listings/ch10-generic-types-traits-and-lifetimes/no-listing-09-unrelated-lifetime/output.txt
+1 −7 listings/ch11-writing-automated-tests/listing-11-01/src/lib.rs
+1 −2 listings/ch11-writing-automated-tests/listing-11-03/output.txt
+3 −4 listings/ch11-writing-automated-tests/listing-11-10/output.txt
+1 −2 listings/ch11-writing-automated-tests/no-listing-03-introducing-a-bug/output.txt
+3 −4 listings/ch11-writing-automated-tests/no-listing-04-bug-in-add-two/output.txt
+1 −2 listings/ch11-writing-automated-tests/no-listing-06-greeter-with-bug/output.txt
+1 −2 listings/ch11-writing-automated-tests/no-listing-07-custom-failure-message/output.txt
+1 −2 listings/ch11-writing-automated-tests/no-listing-09-guess-with-panic-msg-bug/output.txt
+3 −4 listings/ch11-writing-automated-tests/output-only-01-show-output/output.txt
+1 −1 listings/ch12-an-io-project/listing-12-01/output.txt
+1 −2 listings/ch12-an-io-project/listing-12-07/output.txt
+1 −2 listings/ch12-an-io-project/listing-12-08/output.txt
+0 −4 listings/ch12-an-io-project/listing-12-12/output.txt
+3 −4 listings/ch12-an-io-project/listing-12-16/output.txt
+1 −1 listings/ch12-an-io-project/output-only-01-with-args/output.txt
+1 −1 listings/ch12-an-io-project/output-only-02-missing-lifetimes/output.txt
+2 −9 listings/ch13-functional-features/listing-13-03/output.txt
+1 −1 listings/ch13-functional-features/listing-13-08/output.txt
+0 −4 listings/ch13-functional-features/listing-13-14/output.txt
+1 −7 listings/ch14-more-about-cargo/output-only-02-add-one/add/add_one/src/lib.rs
+1 −1 listings/ch15-smart-pointers/listing-15-03/output.txt
+1 −1 listings/ch15-smart-pointers/listing-15-09/output.txt
+5 −7 listings/ch15-smart-pointers/listing-15-15/output.txt
+1 −1 listings/ch15-smart-pointers/listing-15-17/output.txt
+6 −7 listings/ch15-smart-pointers/listing-15-21/output.txt
+1 −2 listings/ch15-smart-pointers/listing-15-23/output.txt
+3 −6 listings/ch15-smart-pointers/no-listing-01-cant-borrow-immutable-as-mutable/output.txt
+6 −6 listings/ch15-smart-pointers/output-only-01-comparing-to-reference/output.txt
+1 −1 listings/ch16-fearless-concurrency/listing-16-03/output.txt
+1 −5 listings/ch16-fearless-concurrency/listing-16-09/output.txt
+6 −7 listings/ch16-fearless-concurrency/listing-16-13/output.txt
+6 −4 listings/ch16-fearless-concurrency/listing-16-14/output.txt
+1 −1 listings/ch16-fearless-concurrency/output-only-01-move-drop/output.txt
+2 −2 listings/ch17-oop/listing-17-10/output.txt
+1 −1 listings/ch18-patterns-and-matching/listing-18-05/output.txt
+12 −3 listings/ch18-patterns-and-matching/listing-18-08/output.txt
+1 −1 listings/ch18-patterns-and-matching/listing-18-25/output.txt
+1 −1 listings/ch19-advanced-features/listing-19-05/output.txt
+1 −1 listings/ch19-advanced-features/listing-19-20/output.txt
+3 −3 listings/ch19-advanced-features/no-listing-02-impl-outlineprint-for-point/output.txt
+4 −8 listings/ch19-advanced-features/no-listing-18-returns-closure/output.txt
+1 −1 listings/ch19-advanced-features/output-only-01-missing-unsafe/output.txt
+1 −1 listings/ch20-web-server/listing-20-12/output.txt
+1 −9 listings/ch20-web-server/listing-20-17/output.txt
+3 −3 listings/ch20-web-server/listing-20-22/output.txt
+1 −1 listings/ch20-web-server/no-listing-01-define-threadpool-struct/output.txt
+2 −2 listings/ch20-web-server/no-listing-02-impl-threadpool-new/output.txt
+3 −3 listings/ch20-web-server/no-listing-04-update-worker-definition/output.txt
+1 −1 rust-toolchain
+1 −2 src/ch03-02-data-types.md
+1 −1 src/ch05-01-defining-structs.md
+9 −10 src/ch09-01-unrecoverable-errors-with-panic.md
+6 −4 src/ch09-02-recoverable-errors-with-result.md
+1 −1 src/ch15-01-box.md
+1 −1 src/title-page.md
2 changes: 1 addition & 1 deletion src/doc/embedded-book
2 changes: 1 addition & 1 deletion src/tools/cargo
Submodule cargo updated 1881 files
36 changes: 36 additions & 0 deletions tests/ui/lint/lint-ctypes-cstr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#![crate_type = "lib"]
#![deny(improper_ctypes, improper_ctypes_definitions)]

use std::ffi::{CStr, CString};

extern "C" {
fn take_cstr(s: CStr);
//~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
fn take_cstr_ref(s: &CStr);
//~^ ERROR `extern` block uses type `CStr`, which is not FFI-safe
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
fn take_cstring(s: CString);
//~^ ERROR `extern` block uses type `CString`, which is not FFI-safe
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
fn take_cstring_ref(s: &CString);
//~^ ERROR `extern` block uses type `CString`, which is not FFI-safe
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`

fn no_special_help_for_mut_cstring(s: *mut CString);
//~^ ERROR `extern` block uses type `CString`, which is not FFI-safe
//~| HELP consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct

fn no_special_help_for_mut_cstring_ref(s: &mut CString);
//~^ ERROR `extern` block uses type `CString`, which is not FFI-safe
//~| HELP consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
}

extern "C" fn rust_take_cstr_ref(s: &CStr) {}
//~^ ERROR `extern` fn uses type `CStr`, which is not FFI-safe
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
extern "C" fn rust_take_cstring(s: CString) {}
//~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe
//~| HELP consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
extern "C" fn rust_no_special_help_for_mut_cstring(s: *mut CString) {}
extern "C" fn rust_no_special_help_for_mut_cstring_ref(s: &mut CString) {}
84 changes: 84 additions & 0 deletions tests/ui/lint/lint-ctypes-cstr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
error: `extern` block uses type `CStr`, which is not FFI-safe
--> $DIR/lint-ctypes-cstr.rs:7:21
|
LL | fn take_cstr(s: CStr);
| ^^^^ not FFI-safe
|
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
= note: `CStr`/`CString` do not have a guaranteed layout
note: the lint level is defined here
--> $DIR/lint-ctypes-cstr.rs:2:9
|
LL | #![deny(improper_ctypes, improper_ctypes_definitions)]
| ^^^^^^^^^^^^^^^

error: `extern` block uses type `CStr`, which is not FFI-safe
--> $DIR/lint-ctypes-cstr.rs:10:25
|
LL | fn take_cstr_ref(s: &CStr);
| ^^^^^ not FFI-safe
|
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
= note: `CStr`/`CString` do not have a guaranteed layout

error: `extern` block uses type `CString`, which is not FFI-safe
--> $DIR/lint-ctypes-cstr.rs:13:24
|
LL | fn take_cstring(s: CString);
| ^^^^^^^ not FFI-safe
|
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
= note: `CStr`/`CString` do not have a guaranteed layout

error: `extern` block uses type `CString`, which is not FFI-safe
--> $DIR/lint-ctypes-cstr.rs:16:28
|
LL | fn take_cstring_ref(s: &CString);
| ^^^^^^^^ not FFI-safe
|
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
= note: `CStr`/`CString` do not have a guaranteed layout

error: `extern` block uses type `CString`, which is not FFI-safe
--> $DIR/lint-ctypes-cstr.rs:20:43
|
LL | fn no_special_help_for_mut_cstring(s: *mut CString);
| ^^^^^^^^^^^^ not FFI-safe
|
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
= note: this struct has unspecified layout

error: `extern` block uses type `CString`, which is not FFI-safe
--> $DIR/lint-ctypes-cstr.rs:24:47
|
LL | fn no_special_help_for_mut_cstring_ref(s: &mut CString);
| ^^^^^^^^^^^^ not FFI-safe
|
= help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
= note: this struct has unspecified layout

error: `extern` fn uses type `CStr`, which is not FFI-safe
--> $DIR/lint-ctypes-cstr.rs:29:37
|
LL | extern "C" fn rust_take_cstr_ref(s: &CStr) {}
| ^^^^^ not FFI-safe
|
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
= note: `CStr`/`CString` do not have a guaranteed layout
note: the lint level is defined here
--> $DIR/lint-ctypes-cstr.rs:2:26
|
LL | #![deny(improper_ctypes, improper_ctypes_definitions)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: `extern` fn uses type `CString`, which is not FFI-safe
--> $DIR/lint-ctypes-cstr.rs:32:36
|
LL | extern "C" fn rust_take_cstring(s: CString) {}
| ^^^^^^^ not FFI-safe
|
= help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
= note: `CStr`/`CString` do not have a guaranteed layout

error: aborting due to 8 previous errors

0 comments on commit 2ef3825

Please sign in to comment.