From b335ec9ec8d1132efee4e900a1c173efc7f4a357 Mon Sep 17 00:00:00 2001 From: Flying-Toast <38232168+Flying-Toast@users.noreply.github.com> Date: Sat, 23 Mar 2024 10:49:05 -0400 Subject: [PATCH] Add a special case for CStr/CString in the improper_ctypes lint Instead of saying to "consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct", we now tell users to "Use `*const ffi::c_char` instead, and pass the value from `CStr::as_ptr()`" when the type involved is a `CStr` or a `CString`. Co-authored-by: Jieyou Xu --- compiler/rustc_lint/messages.ftl | 5 ++ compiler/rustc_lint/src/types.rs | 54 ++++++++---- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/ffi/c_str.rs | 1 + ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 12 +-- tests/ui/lint/lint-ctypes-cstr.rs | 36 ++++++++ tests/ui/lint/lint-ctypes-cstr.stderr | 84 +++++++++++++++++++ 7 files changed, 172 insertions(+), 21 deletions(-) create mode 100644 tests/ui/lint/lint-ctypes-cstr.rs create mode 100644 tests/ui/lint/lint-ctypes-cstr.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 7e4feb0a827..52a03772dc2 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -361,6 +361,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 = diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index f3196cfed53..d84d60ef5a7 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -984,6 +984,14 @@ struct ImproperCTypesVisitor<'a, 'tcx> { mode: CItemKind, } +/// Accumulator for recursive ffi type checking +struct CTypesVisitorState<'tcx> { + cache: FxHashSet>, + /// 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>), @@ -1212,7 +1220,7 @@ fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { /// Checks if the given field's type is "ffi-safe". fn check_field_type_for_ffi( &self, - cache: &mut FxHashSet>, + acc: &mut CTypesVisitorState<'tcx>, field: &ty::FieldDef, args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { @@ -1222,13 +1230,13 @@ fn check_field_type_for_ffi( .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>, + acc: &mut CTypesVisitorState<'tcx>, ty: Ty<'tcx>, def: ty::AdtDef<'tcx>, variant: &ty::VariantDef, @@ -1238,7 +1246,7 @@ fn check_variant_for_ffi( 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, } @@ -1256,7 +1264,7 @@ fn check_variant_for_ffi( // 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, @@ -1276,7 +1284,11 @@ fn check_variant_for_ffi( /// 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: 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; @@ -1285,7 +1297,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F // `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; } @@ -1307,6 +1319,17 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F } 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, @@ -1353,7 +1376,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F }; } - 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() { @@ -1377,7 +1400,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F if let Some(ty) = repr_nullable_ptr(self.cx.tcx, self.cx.param_env, ty, self.mode) { - return self.check_type_for_ffi(cache, ty); + return self.check_type_for_ffi(acc, ty); } return FfiUnsafe { @@ -1398,7 +1421,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F }; } - 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, } @@ -1468,9 +1491,9 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F FfiSafe } - ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(cache, ty), + ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.check_type_for_ffi(acc, 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()) { @@ -1483,7 +1506,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F 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, } @@ -1494,7 +1517,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F return FfiSafe; } - self.check_type_for_ffi(cache, ret_ty) + self.check_type_for_ffi(acc, ret_ty) } ty::Foreign(..) => FfiSafe, @@ -1617,7 +1640,8 @@ fn check_type_for_ffi_and_report_errors( 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( diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 94cf21da4ef..407f95a0f21 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -671,6 +671,7 @@ crate_visibility_modifier, crt_dash_static: "crt-static", csky_target_feature, + cstr_type, cstring_type, ctlz, ctlz_nonzero, diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index 22084dcff8f..7808d42ab5d 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -91,6 +91,7 @@ /// [str]: prim@str "str" #[derive(PartialEq, Eq, 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` current implementation relies diff --git a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr index 83492988479..044c1ae2dd4 100644 --- a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr +++ b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.stderr @@ -1,21 +1,21 @@ -warning: `extern` fn uses type `[i8 or u8 (arch dependant)]`, which is not FFI-safe +warning: `extern` fn uses type `CStr`, which is not FFI-safe --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:7:12 | LL | type Foo = extern "C" fn(::std::ffi::CStr); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead - = note: slices have no C equivalent + = 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: `#[warn(improper_ctypes_definitions)]` on by default -warning: `extern` block uses type `[i8 or u8 (arch dependant)]`, which is not FFI-safe +warning: `extern` block uses type `CStr`, which is not FFI-safe --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:10:18 | LL | fn meh(blah: Foo); | ^^^ not FFI-safe | - = help: consider using a raw pointer instead - = note: slices have no C equivalent + = 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: `#[warn(improper_ctypes)]` on by default warning: 2 warnings emitted diff --git a/tests/ui/lint/lint-ctypes-cstr.rs b/tests/ui/lint/lint-ctypes-cstr.rs new file mode 100644 index 00000000000..b04decd0bca --- /dev/null +++ b/tests/ui/lint/lint-ctypes-cstr.rs @@ -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) {} diff --git a/tests/ui/lint/lint-ctypes-cstr.stderr b/tests/ui/lint/lint-ctypes-cstr.stderr new file mode 100644 index 00000000000..8957758d577 --- /dev/null +++ b/tests/ui/lint/lint-ctypes-cstr.stderr @@ -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 +