From f5a441e2d30d1b851b2bfa1b8974f1c21f47e76f Mon Sep 17 00:00:00 2001 From: niacdoial Date: Sun, 24 Aug 2025 14:57:59 +0200 Subject: [PATCH 01/20] ImproperCTypes: split type visiting into subfunctions Another interal change that shouldn't impact rustc users. The goal is to break apart the gigantic visit_type function into more managable and easily-editable bits that focus on specific parts of FFI safety. --- .../rustc_lint/src/types/improper_ctypes.rs | 525 +++++++++++------- .../lint-non-recursion-limit.rs | 4 + 2 files changed, 335 insertions(+), 194 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 2c88397086699..a3391799b3467 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -137,6 +137,17 @@ declare_lint_pass!(ImproperCTypesLint => [ USES_POWER_ALIGNMENT ]); +/// Getting the (normalized) type out of a field (for, e.g., an enum variant or a tuple). +#[inline] +fn get_type_from_field<'tcx>( + cx: &LateContext<'tcx>, + field: &ty::FieldDef, + args: GenericArgsRef<'tcx>, +) -> Ty<'tcx> { + let field_ty = field.ty(cx.tcx, args); + cx.tcx.try_normalize_erasing_regions(cx.typing_env(), field_ty).unwrap_or(field_ty) +} + /// Check a variant of a non-exhaustive enum for improper ctypes /// /// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added". @@ -178,6 +189,7 @@ fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { fn check_arg_for_power_alignment<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { let tcx = cx.tcx; assert!(tcx.sess.target.os == "aix"); + // Structs (under repr(C)) follow the power alignment rule if: // - the first field of the struct is a floating-point type that // is greater than 4-bytes, or @@ -259,6 +271,17 @@ enum FfiResult<'tcx> { /// in the `FfiResult` is final. type PartialFfiResult<'tcx> = Option>; +/// What type indirection points to a given type. +#[derive(Clone, Copy)] +enum IndirectionKind { + /// Box (valid non-null pointer, owns pointee). + Box, + /// Ref (valid non-null pointer, borrows pointee). + Ref, + /// Raw pointer (not necessarily non-null or valid. no info on ownership). + RawPtr, +} + bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct VisitorState: u8 { @@ -345,6 +368,58 @@ impl VisitorState { } } +bitflags! { + /// Data that summarises how an "outer type" surrounds its inner type(s) + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + struct OuterTyData: u8 { + /// To annotate pointees (through Ref,RawPtr,Box). + const IN_PTR = 0b000001; + /// For pointees, show pointer mutability-or-ownership. + const PTR_MUT = 0b000010; + /// For pointees, show the pointer is a raw one. + const PTR_RAW = 0b000100; + /// For types "directly contained" in the parent type's memory layout + /// (tuple, ADT, array, etc.) + const MEMORY_INLINED = 0b001000; + /// Show that the type is contained in an ADT field. + const IN_ADT = 0b010000; + /// To show that there is no outer type, the current type is directly used by a `static` + /// variable or a function/FnPtr + const NO_OUTER_TY = 0b100000; + /// For NO_OUTER_TY cases, show that we are being directly used by a FnPtr specifically + /// FIXME(ctypes): this is only used for "bad bahaviour" reproduced for compatibility's sake + const NOOUT_FNPTR = 0b1000000; + } +} + +impl OuterTyData { + /// Get the proper data for a given outer type. + fn from_ty<'tcx>(ty: Ty<'tcx>) -> Self { + match ty.kind() { + ty::FnPtr(..) => Self::NO_OUTER_TY | Self::NOOUT_FNPTR, + k @ (ty::RawPtr(..) | ty::Ref(..)) => { + let mut ret = Self::IN_PTR; + if ty.is_mutable_ptr() { + ret |= Self::PTR_MUT; + } + if matches!(k, ty::RawPtr(..)) { + ret |= Self::PTR_RAW; + } + ret + } + ty::Adt(..) => { + if ty.boxed_ty().is_some() { + Self::IN_PTR | Self::PTR_MUT + } else { + Self::IN_ADT | Self::MEMORY_INLINED + } + } + ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => Self::MEMORY_INLINED, + k @ _ => bug!("Unexpected outer type {:?} of kind {:?}", ty, k), + } + } +} + /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. /// It uses ``check_*`` methods as entrypoints to be called elsewhere, /// and ``visit_*`` methods to recurse. @@ -364,36 +439,94 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { Self { cx, base_ty, base_fn_mode, cache: FxHashSet::default() } } - /// Checks if the given field's type is "ffi-safe". - fn check_field_type_for_ffi( + /// Checks if the given indirection (box,ref,pointer) is "ffi-safe". + fn visit_indirection( &mut self, state: VisitorState, - field: &ty::FieldDef, - args: GenericArgsRef<'tcx>, + ty: Ty<'tcx>, + inner_ty: Ty<'tcx>, + indirection_kind: IndirectionKind, ) -> FfiResult<'tcx> { - let field_ty = field.ty(self.cx.tcx, args); - let field_ty = self - .cx - .tcx - .try_normalize_erasing_regions(self.cx.typing_env(), field_ty) - .unwrap_or(field_ty); - self.visit_type(state, field_ty) + use FfiResult::*; + let tcx = self.cx.tcx; + + match indirection_kind { + IndirectionKind::Box => { + // FIXME(ctypes): this logic is broken, but it still fits the current tests: + // - for some reason `Box<_>`es in `extern "ABI" {}` blocks + // (including within FnPtr:s) + // are not treated as pointers but as FFI-unsafe structs + // - otherwise, treat the box itself correctly, and follow pointee safety logic + // as described in the other `indirection_type` match branch. + if state.is_in_defined_function() + || (state.is_in_fnptr() && matches!(self.base_fn_mode, CItemKind::Definition)) + { + if inner_ty.is_sized(tcx, self.cx.typing_env()) { + return FfiSafe; + } else { + return FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_box, + help: None, + }; + } + } else { + // (mid-retcon-commit-chain comment:) + // this is the original fallback behavior, which is wrong + if let ty::Adt(def, args) = ty.kind() { + self.visit_struct_or_union(state, ty, *def, args) + } else if cfg!(debug_assertions) { + bug!("ImproperCTypes: this retcon commit was badly written") + } else { + FfiSafe + } + } + } + IndirectionKind::Ref | IndirectionKind::RawPtr => { + // Weird behaviour for pointee safety. the big question here is + // "if you have a FFI-unsafe pointee behind a FFI-safe pointer type, is it ok?" + // The answer until now is: + // "It's OK for rust-defined functions and callbacks, we'll assume those are + // meant to be opaque types on the other side of the FFI boundary". + // + // Reasoning: + // For extern function declarations, the actual definition of the function is + // written somewhere else, meaning the declaration is free to express this + // opaqueness with an extern type (opaque caller-side) or a std::ffi::c_void + // (opaque callee-side). For extern function definitions, however, in the case + // where the type is opaque caller-side, it is not opaque callee-side, + // and having the full type information is necessary to compile the function. + // + // It might be better to rething this, or even ignore pointee safety for a first + // batch of behaviour changes. See the discussion that ends with + // https://github.com/rust-lang/rust/pull/134697#issuecomment-2692610258 + if (state.is_in_defined_function() || state.is_in_fnptr()) + && inner_ty.is_sized(self.cx.tcx, self.cx.typing_env()) + { + FfiSafe + } else { + self.visit_type(state, OuterTyData::from_ty(ty), inner_ty) + } + } + } } /// Checks if the given `VariantDef`'s field types are "ffi-safe". - fn check_variant_for_ffi( + fn visit_variant_fields( &mut self, state: VisitorState, ty: Ty<'tcx>, - def: ty::AdtDef<'tcx>, + def: AdtDef<'tcx>, variant: &ty::VariantDef, args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { use FfiResult::*; + let transparent_with_all_zst_fields = if def.repr().transparent() { if let Some(field) = super::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(state, field, args) { + let field_ty = get_type_from_field(self.cx, field, args); + match self.visit_type(state, OuterTyData::from_ty(ty), field_ty) { FfiUnsafe { ty, .. } if ty.is_unit() => (), r => return r, } @@ -411,7 +544,8 @@ 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(state, field, args) { + let field_ty = get_type_from_field(self.cx, field, args); + all_phantom &= match self.visit_type(state, OuterTyData::from_ty(ty), field_ty) { FfiSafe => false, // `()` fields are FFI-safe! FfiUnsafe { ty, .. } if ty.is_unit() => false, @@ -429,9 +563,119 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } + fn visit_struct_or_union( + &mut self, + state: VisitorState, + ty: Ty<'tcx>, + def: AdtDef<'tcx>, + args: GenericArgsRef<'tcx>, + ) -> FfiResult<'tcx> { + debug_assert!(matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union)); + use FfiResult::*; + + if !def.repr().c() && !def.repr().transparent() { + return FfiUnsafe { + ty, + reason: if def.is_struct() { + fluent::lint_improper_ctypes_struct_layout_reason + } else { + fluent::lint_improper_ctypes_union_layout_reason + }, + help: if def.is_struct() { + Some(fluent::lint_improper_ctypes_struct_layout_help) + } else { + // FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises + Some(fluent::lint_improper_ctypes_union_layout_help) + }, + }; + } + + if def.non_enum_variant().field_list_has_applicable_non_exhaustive() { + return FfiUnsafe { + ty, + reason: if def.is_struct() { + fluent::lint_improper_ctypes_struct_non_exhaustive + } else { + fluent::lint_improper_ctypes_union_non_exhaustive + }, + help: None, + }; + } + + if def.non_enum_variant().fields.is_empty() { + return FfiUnsafe { + ty, + reason: if def.is_struct() { + fluent::lint_improper_ctypes_struct_fieldless_reason + } else { + fluent::lint_improper_ctypes_union_fieldless_reason + }, + help: if def.is_struct() { + Some(fluent::lint_improper_ctypes_struct_fieldless_help) + } else { + Some(fluent::lint_improper_ctypes_union_fieldless_help) + }, + }; + } else { + self.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) + } + } + + fn visit_enum( + &mut self, + state: VisitorState, + ty: Ty<'tcx>, + def: AdtDef<'tcx>, + args: GenericArgsRef<'tcx>, + ) -> FfiResult<'tcx> { + debug_assert!(matches!(def.adt_kind(), AdtKind::Enum)); + use FfiResult::*; + + if def.variants().is_empty() { + // Empty enums are okay... although sort of useless. + return FfiSafe; + } + // Check for a repr() attribute to specify the size of the + // discriminant. + if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() { + // Special-case types like `Option` and `Result` + if let Some(inner_ty) = repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty) { + return self.visit_type(state, OuterTyData::from_ty(ty), inner_ty); + } + + return FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_enum_repr_reason, + help: Some(fluent::lint_improper_ctypes_enum_repr_help), + }; + } + + let non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); + // Check the contained variants. + let ret = def.variants().iter().try_for_each(|variant| { + check_non_exhaustive_variant(non_exhaustive, variant) + .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; + + match self.visit_variant_fields(state, ty, def, variant, args) { + FfiSafe => ControlFlow::Continue(()), + r => ControlFlow::Break(r), + } + }); + if let ControlFlow::Break(result) = ret { + return result; + } + + FfiSafe + } + /// Checks if the given type is "ffi-safe" (has a stable, well-defined /// representation which can be exported to C code). - fn visit_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { + fn visit_type( + &mut self, + state: VisitorState, + outer_ty: OuterTyData, + ty: Ty<'tcx>, + ) -> FfiResult<'tcx> { use FfiResult::*; let tcx = self.cx.tcx; @@ -446,23 +690,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { match *ty.kind() { ty::Adt(def, args) => { - if let Some(boxed) = ty.boxed_ty() - && ( - // FIXME(ctypes): this logic is broken, but it still fits the current tests - state.is_in_defined_function() - || (state.is_in_fnptr() - && matches!(self.base_fn_mode, CItemKind::Definition)) - ) - { - if boxed.is_sized(tcx, self.cx.typing_env()) { - return FfiSafe; - } else { - return FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_box, - help: None, - }; - } + if let Some(inner_ty) = ty.boxed_ty() { + return self.visit_indirection(state, ty, inner_ty, IndirectionKind::Box); } if def.is_phantom_data() { return FfiPhantom(ty); @@ -479,109 +708,29 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: Some(fluent::lint_improper_ctypes_cstr_help), }; } - - if !def.repr().c() && !def.repr().transparent() { - return FfiUnsafe { - ty, - reason: if def.is_struct() { - fluent::lint_improper_ctypes_struct_layout_reason - } else { - fluent::lint_improper_ctypes_union_layout_reason - }, - help: if def.is_struct() { - Some(fluent::lint_improper_ctypes_struct_layout_help) - } else { - Some(fluent::lint_improper_ctypes_union_layout_help) - }, - }; - } - - if def.non_enum_variant().field_list_has_applicable_non_exhaustive() { - return FfiUnsafe { - ty, - reason: if def.is_struct() { - fluent::lint_improper_ctypes_struct_non_exhaustive - } else { - fluent::lint_improper_ctypes_union_non_exhaustive - }, - help: None, - }; - } - - if def.non_enum_variant().fields.is_empty() { - return FfiUnsafe { - ty, - reason: if def.is_struct() { - fluent::lint_improper_ctypes_struct_fieldless_reason - } else { - fluent::lint_improper_ctypes_union_fieldless_reason - }, - help: if def.is_struct() { - Some(fluent::lint_improper_ctypes_struct_fieldless_help) - } else { - Some(fluent::lint_improper_ctypes_union_fieldless_help) - }, - }; - } - - self.check_variant_for_ffi(state, ty, def, def.non_enum_variant(), args) + self.visit_struct_or_union(state, ty, def, args) } - AdtKind::Enum => { - if def.variants().is_empty() { - // Empty enums are okay... although sort of useless. - return FfiSafe; - } - // Check for a repr() attribute to specify the size of the - // discriminant. - if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() - { - // Special-case types like `Option` and `Result` - if let Some(ty) = - repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty) - { - return self.visit_type(state, ty); - } + AdtKind::Enum => self.visit_enum(state, ty, def, args), + } + } - return FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_enum_repr_reason, - help: Some(fluent::lint_improper_ctypes_enum_repr_help), - }; - } + // Pattern types are just extra invariants on the type that you need to uphold, + // but only the base type is relevant for being representable in FFI. + // (note: this lint was written when pattern types could only be integers constrained to ranges) + ty::Pat(pat_ty, _) => self.visit_type(state, outer_ty, pat_ty), - let non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); - // Check the contained variants. - let ret = def.variants().iter().try_for_each(|variant| { - check_non_exhaustive_variant(non_exhaustive, variant) - .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; - - match self.check_variant_for_ffi(state, ty, def, variant, args) { - FfiSafe => ControlFlow::Continue(()), - r => ControlFlow::Break(r), - } - }); - if let ControlFlow::Break(result) = ret { - return result; - } + // types which likely have a stable representation, if the target architecture defines those + // note: before rust 1.77, 128-bit ints were not FFI-safe on x86_64 + ty::Int(..) | ty::Uint(..) | ty::Float(..) => FfiResult::FfiSafe, - FfiSafe - } - } - } + ty::Bool => FfiResult::FfiSafe, - ty::Char => FfiUnsafe { + ty::Char => FfiResult::FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_char_reason, help: Some(fluent::lint_improper_ctypes_char_help), }, - // It's just extra invariants on the type that you need to uphold, - // but only the base type is relevant for being representable in FFI. - ty::Pat(base, ..) => self.visit_type(state, base), - - // Primitive types with a stable representation. - ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe, - ty::Slice(_) => FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_slice_reason, @@ -598,19 +747,23 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: Some(fluent::lint_improper_ctypes_str_help), }, - ty::Tuple(..) => FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_tuple_reason, - help: Some(fluent::lint_improper_ctypes_tuple_help), - }, + ty::Tuple(tuple) => { + let empty_and_safe = if tuple.is_empty() { + // C functions can return void + outer_ty.contains(OuterTyData::NO_OUTER_TY) && state.is_in_function_return() + } else { + false + }; - ty::RawPtr(ty, _) | ty::Ref(_, ty, _) - if { - (state.is_in_defined_function() || state.is_in_fnptr()) - && ty.is_sized(self.cx.tcx, self.cx.typing_env()) - } => - { - FfiSafe + if empty_and_safe { + FfiSafe + } else { + FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_tuple_reason, + help: Some(fluent::lint_improper_ctypes_tuple_help), + } + } } ty::RawPtr(ty, _) @@ -622,9 +775,32 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { FfiSafe } - ty::RawPtr(ty, _) | ty::Ref(_, ty, _) => self.visit_type(state, ty), + ty::RawPtr(inner_ty, _) => { + return self.visit_indirection(state, ty, inner_ty, IndirectionKind::RawPtr); + } + ty::Ref(_, inner_ty, _) => { + return self.visit_indirection(state, ty, inner_ty, IndirectionKind::Ref); + } - ty::Array(inner_ty, _) => self.visit_type(state, inner_ty), + ty::Array(inner_ty, _) => { + if state.is_in_function() + && outer_ty.contains(OuterTyData::NO_OUTER_TY) + // FIXME(ctypes): VVV-this-VVV shouldn't be the case + && !outer_ty.contains(OuterTyData::NOOUT_FNPTR) + { + // C doesn't really support passing arrays by value - the only way to pass an array by value + // is through a struct. + FfiResult::FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_array_reason, + help: Some(fluent::lint_improper_ctypes_array_help), + } + } else { + // let's allow phantoms to go through, + // since an array of 1-ZSTs is also a 1-ZST + self.visit_type(state, OuterTyData::from_ty(ty), inner_ty) + } + } ty::FnPtr(sig_tys, hdr) => { let sig = sig_tys.with(hdr); @@ -638,22 +814,25 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let sig = tcx.instantiate_bound_regions_with_erased(sig); for arg in sig.inputs() { - match self.visit_type(VisitorState::ARGUMENT_TY_IN_FNPTR, *arg) { + match self.visit_type( + VisitorState::ARGUMENT_TY_IN_FNPTR, + OuterTyData::from_ty(ty), + *arg, + ) { FfiSafe => {} r => return r, } } let ret_ty = sig.output(); - if ret_ty.is_unit() { - return FfiSafe; - } - self.visit_type(VisitorState::RETURN_TY_IN_FNPTR, ret_ty) + self.visit_type(VisitorState::RETURN_TY_IN_FNPTR, OuterTyData::from_ty(ty), ret_ty) } ty::Foreign(..) => FfiSafe, + ty::Never => FfiSafe, + // While opaque types are checked for earlier, if a projection in a struct field // normalizes to an opaque type, then it will reach this branch. ty::Alias(ty::Opaque, ..) => { @@ -702,62 +881,20 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - if let Some(ty) = self - .cx - .tcx - .try_normalize_erasing_regions(self.cx.typing_env(), ty) - .unwrap_or(ty) - .visit_with(&mut ProhibitOpaqueTypes) - .break_value() - { - Some(FfiResult::FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_opaque, - help: None, - }) - } else { - None - } - } - - /// Check if the type is array and emit an unsafe type lint. - fn check_for_array_ty(&mut self, ty: Ty<'tcx>) -> PartialFfiResult<'tcx> { - if let ty::Array(..) = ty.kind() { - Some(FfiResult::FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_array_reason, - help: Some(fluent::lint_improper_ctypes_array_help), - }) - } else { - None - } + ty.visit_with(&mut ProhibitOpaqueTypes).break_value().map(|ty| FfiResult::FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_opaque, + help: None, + }) } - /// Determine the FFI-safety of a single (MIR) type, given the context of how it is used. fn check_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { + let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.typing_env(), ty).unwrap_or(ty); if let Some(res) = self.visit_for_opaque_ty(ty) { return res; } - let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.typing_env(), ty).unwrap_or(ty); - - // C doesn't really support passing arrays by value - the only way to pass an array by value - // is through a struct. So, first test that the top level isn't an array, and then - // recursively check the types inside. - if state.is_in_function() { - if let Some(res) = self.check_for_array_ty(ty) { - return res; - } - } - - // Don't report FFI errors for unit return types. This check exists here, and not in - // the caller (where it would make more sense) so that normalization has definitely - // happened. - if state.is_in_function_return() && ty.is_unit() { - return FfiResult::FfiSafe; - } - - self.visit_type(state, ty) + self.visit_type(state, OuterTyData::NO_OUTER_TY, ty) } } diff --git a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs index 61e95dc5a464c..1d75a7ca9ab9f 100644 --- a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs +++ b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs @@ -1,5 +1,9 @@ //@ check-pass +//! This test checks that the depth limit of the ImproperCTypes lints counts the depth +//! of a type properly. +//! Issue: https://github.com/rust-lang/rust/issues/130757 + #![recursion_limit = "5"] #![allow(unused)] #![deny(improper_ctypes)] From ee32bf773f000ea86369b8131eb4d1d6113cf7f3 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Wed, 10 Sep 2025 21:58:56 +0200 Subject: [PATCH 02/20] ImproperCTypes: merge outer_ty information into VisitorState Another user-transparent change, unifying outer-type information and the existing VisitorState flags. --- .../rustc_lint/src/types/improper_ctypes.rs | 257 ++++++++++-------- 1 file changed, 141 insertions(+), 116 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index a3391799b3467..23b997ddaebd6 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -254,12 +254,22 @@ fn check_struct_for_power_alignment<'tcx>( } } +/// Annotates whether we are in the context of an item *defined* in rust +/// and exposed to an FFI boundary, +/// or the context of an item from elsewhere, whose interface is re-*declared* in rust. #[derive(Clone, Copy)] enum CItemKind { Declaration, Definition, } +/// Annotates whether we are in the context of a function's argument types or return type. +#[derive(Clone, Copy)] +enum FnPos { + Arg, + Ret, +} + enum FfiResult<'tcx> { FfiSafe, FfiPhantom(Ty<'tcx>), @@ -283,8 +293,10 @@ enum IndirectionKind { } bitflags! { + /// VisitorState flags that are linked with the root type's use. + /// (These are the permanent part of the state, kept when visiting new mir::Ty.) #[derive(Clone, Copy, Debug, PartialEq, Eq)] - struct VisitorState: u8 { + struct RootUseFlags: u16 { /// For use in (externally-linked) static variables. const STATIC = 0b000001; /// For use in functions in general. @@ -299,7 +311,45 @@ bitflags! { } } -impl VisitorState { +/// Description of the relationship between current mir::Ty and +/// the type (or lack thereof) immediately containing it +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum OuterTyKind { + None, + /// A variant that should not exist, + /// but is needed because we don't change the lint's behavior yet + NoneThroughFnPtr, + /// Placeholder for properties that will be used eventually + UnusedVariant, +} + +impl OuterTyKind { + /// Computes the relationship by providing the containing mir::Ty itself + fn from_outer_ty<'tcx>(ty: Ty<'tcx>) -> Self { + match ty.kind() { + ty::FnPtr(..) => Self::NoneThroughFnPtr, + ty::RawPtr(..) + | ty::Ref(..) + | ty::Adt(..) + | ty::Tuple(..) + | ty::Array(..) + | ty::Slice(_) => Self::UnusedVariant, + k @ _ => bug!("Unexpected outer type {:?} of kind {:?}", ty, k), + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +struct VisitorState { + /// Flags describing both the overall context in which the current mir::Ty is, + /// linked to how the Visitor's original mir::Ty was used. + root_use_flags: RootUseFlags, + /// Flags describing both the immediate context in which the current mir::Ty is, + /// linked to how it relates to its parent mir::Ty (or lack thereof). + outer_ty_kind: OuterTyKind, +} + +impl RootUseFlags { // The values that can be set. const STATIC_TY: Self = Self::STATIC; const ARGUMENT_TY_IN_DEFINITION: Self = @@ -314,109 +364,100 @@ impl VisitorState { const RETURN_TY_IN_FNPTR: Self = Self::from_bits(Self::FUNC.bits() | Self::THEORETICAL.bits() | Self::FN_RETURN.bits()) .unwrap(); +} - /// Get the proper visitor state for a given function's arguments. - fn argument_from_fnmode(fn_mode: CItemKind) -> Self { - match fn_mode { - CItemKind::Definition => VisitorState::ARGUMENT_TY_IN_DEFINITION, - CItemKind::Declaration => VisitorState::ARGUMENT_TY_IN_DECLARATION, +impl VisitorState { + /// From an existing state, compute the state of any subtype of the current type. + /// (General case.) + fn get_next<'tcx>(&self, current_ty: Ty<'tcx>) -> Self { + assert!(!matches!(current_ty.kind(), ty::FnPtr(..))); + Self { + root_use_flags: self.root_use_flags, + outer_ty_kind: OuterTyKind::from_outer_ty(current_ty), } } - - /// Get the proper visitor state for a given function's return type. - fn return_from_fnmode(fn_mode: CItemKind) -> Self { - match fn_mode { - CItemKind::Definition => VisitorState::RETURN_TY_IN_DEFINITION, - CItemKind::Declaration => VisitorState::RETURN_TY_IN_DECLARATION, + /// From an existing state, compute the state of any subtype of the current type. + /// (Case where the current type is a function pointer, + /// meaning we need to specify if the subtype is an argument or the return.) + fn get_next_in_fnptr<'tcx>(&self, current_ty: Ty<'tcx>, fn_pos: FnPos) -> Self { + assert!(matches!(current_ty.kind(), ty::FnPtr(..))); + Self { + root_use_flags: match fn_pos { + FnPos::Ret => RootUseFlags::RETURN_TY_IN_FNPTR, + FnPos::Arg => RootUseFlags::ARGUMENT_TY_IN_FNPTR, + }, + outer_ty_kind: OuterTyKind::from_outer_ty(current_ty), } } + /// Generate the state for an "outermost" type that needs to be checked + fn entry_point(root_use_flags: RootUseFlags) -> Self { + Self { root_use_flags, outer_ty_kind: OuterTyKind::None } + } + + /// Get the proper visitor state for a given function's arguments or return type. + fn entry_point_from_fnmode(fn_mode: CItemKind, fn_pos: FnPos) -> Self { + let p_flags = match (fn_mode, fn_pos) { + (CItemKind::Definition, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DEFINITION, + (CItemKind::Declaration, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DECLARATION, + (CItemKind::Definition, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DEFINITION, + (CItemKind::Declaration, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DECLARATION, + }; + Self::entry_point(p_flags) + } + + /// Get the proper visitor state for a static variable's type + fn static_var() -> Self { + Self::entry_point(RootUseFlags::STATIC_TY) + } + /// Whether the type is used in a function. - fn is_in_function(self) -> bool { - let ret = self.contains(Self::FUNC); + fn is_in_function(&self) -> bool { + let ret = self.root_use_flags.contains(RootUseFlags::FUNC); if ret { - debug_assert!(!self.contains(Self::STATIC)); + debug_assert!(!self.root_use_flags.contains(RootUseFlags::STATIC)); } ret } /// Whether the type is used (directly or not) in a function, in return position. - fn is_in_function_return(self) -> bool { - let ret = self.contains(Self::FN_RETURN); + fn is_in_function_return(&self) -> bool { + let ret = self.root_use_flags.contains(RootUseFlags::FN_RETURN); if ret { debug_assert!(self.is_in_function()); } ret } + + /// Whether the type is directly used in a function, in return position. + fn is_direct_function_return(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::None | OuterTyKind::NoneThroughFnPtr) + && self.is_in_function_return() + } + + /// Whether the type itself is the type of a function argument or return type. + fn is_direct_in_function(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::None | OuterTyKind::NoneThroughFnPtr) + && self.is_in_function() + } + /// Whether the type is used (directly or not) in a defined function. /// In other words, whether or not we allow non-FFI-safe types behind a C pointer, /// to be treated as an opaque type on the other side of the FFI boundary. - fn is_in_defined_function(self) -> bool { - self.contains(Self::DEFINED) && self.is_in_function() + fn is_in_defined_function(&self) -> bool { + self.root_use_flags.contains(RootUseFlags::DEFINED) && self.is_in_function() } /// Whether the type is used (directly or not) in a function pointer type. /// Here, we also allow non-FFI-safe types behind a C pointer, /// to be treated as an opaque type on the other side of the FFI boundary. - fn is_in_fnptr(self) -> bool { - self.contains(Self::THEORETICAL) && self.is_in_function() + fn is_in_fnptr(&self) -> bool { + self.root_use_flags.contains(RootUseFlags::THEORETICAL) && self.is_in_function() } /// Whether we can expect type parameters and co in a given type. - fn can_expect_ty_params(self) -> bool { + fn can_expect_ty_params(&self) -> bool { // rust-defined functions, as well as FnPtrs - self.contains(Self::THEORETICAL) || self.is_in_defined_function() - } -} - -bitflags! { - /// Data that summarises how an "outer type" surrounds its inner type(s) - #[derive(Clone, Copy, Debug, PartialEq, Eq)] - struct OuterTyData: u8 { - /// To annotate pointees (through Ref,RawPtr,Box). - const IN_PTR = 0b000001; - /// For pointees, show pointer mutability-or-ownership. - const PTR_MUT = 0b000010; - /// For pointees, show the pointer is a raw one. - const PTR_RAW = 0b000100; - /// For types "directly contained" in the parent type's memory layout - /// (tuple, ADT, array, etc.) - const MEMORY_INLINED = 0b001000; - /// Show that the type is contained in an ADT field. - const IN_ADT = 0b010000; - /// To show that there is no outer type, the current type is directly used by a `static` - /// variable or a function/FnPtr - const NO_OUTER_TY = 0b100000; - /// For NO_OUTER_TY cases, show that we are being directly used by a FnPtr specifically - /// FIXME(ctypes): this is only used for "bad bahaviour" reproduced for compatibility's sake - const NOOUT_FNPTR = 0b1000000; - } -} - -impl OuterTyData { - /// Get the proper data for a given outer type. - fn from_ty<'tcx>(ty: Ty<'tcx>) -> Self { - match ty.kind() { - ty::FnPtr(..) => Self::NO_OUTER_TY | Self::NOOUT_FNPTR, - k @ (ty::RawPtr(..) | ty::Ref(..)) => { - let mut ret = Self::IN_PTR; - if ty.is_mutable_ptr() { - ret |= Self::PTR_MUT; - } - if matches!(k, ty::RawPtr(..)) { - ret |= Self::PTR_RAW; - } - ret - } - ty::Adt(..) => { - if ty.boxed_ty().is_some() { - Self::IN_PTR | Self::PTR_MUT - } else { - Self::IN_ADT | Self::MEMORY_INLINED - } - } - ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => Self::MEMORY_INLINED, - k @ _ => bug!("Unexpected outer type {:?} of kind {:?}", ty, k), - } + self.root_use_flags.contains(RootUseFlags::THEORETICAL) || self.is_in_defined_function() } } @@ -505,7 +546,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { { FfiSafe } else { - self.visit_type(state, OuterTyData::from_ty(ty), inner_ty) + self.visit_type(state.get_next(ty), inner_ty) } } } @@ -526,7 +567,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { // Transparent newtypes have at most one non-ZST field which needs to be checked.. let field_ty = get_type_from_field(self.cx, field, args); - match self.visit_type(state, OuterTyData::from_ty(ty), field_ty) { + match self.visit_type(state.get_next(ty), field_ty) { FfiUnsafe { ty, .. } if ty.is_unit() => (), r => return r, } @@ -545,7 +586,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let mut all_phantom = !variant.fields.is_empty(); for field in &variant.fields { let field_ty = get_type_from_field(self.cx, field, args); - all_phantom &= match self.visit_type(state, OuterTyData::from_ty(ty), field_ty) { + all_phantom &= match self.visit_type(state.get_next(ty), field_ty) { FfiSafe => false, // `()` fields are FFI-safe! FfiUnsafe { ty, .. } if ty.is_unit() => false, @@ -584,7 +625,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: if def.is_struct() { Some(fluent::lint_improper_ctypes_struct_layout_help) } else { - // FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises + // FIXME(#60405): confirm that this makes sense for unions once #60405 / RFC2645 stabilises Some(fluent::lint_improper_ctypes_union_layout_help) }, }; @@ -640,7 +681,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() { // Special-case types like `Option` and `Result` if let Some(inner_ty) = repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty) { - return self.visit_type(state, OuterTyData::from_ty(ty), inner_ty); + return self.visit_type(state.get_next(ty), inner_ty); } return FfiUnsafe { @@ -670,12 +711,7 @@ 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 visit_type( - &mut self, - state: VisitorState, - outer_ty: OuterTyData, - ty: Ty<'tcx>, - ) -> FfiResult<'tcx> { + fn visit_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { use FfiResult::*; let tcx = self.cx.tcx; @@ -717,7 +753,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // Pattern types are just extra invariants on the type that you need to uphold, // but only the base type is relevant for being representable in FFI. // (note: this lint was written when pattern types could only be integers constrained to ranges) - ty::Pat(pat_ty, _) => self.visit_type(state, outer_ty, pat_ty), + // (also note: the lack of ".get_next(ty)" on the state is on purpose) + ty::Pat(pat_ty, _) => self.visit_type(state, pat_ty), // types which likely have a stable representation, if the target architecture defines those // note: before rust 1.77, 128-bit ints were not FFI-safe on x86_64 @@ -748,14 +785,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }, ty::Tuple(tuple) => { - let empty_and_safe = if tuple.is_empty() { + if tuple.is_empty() && state.is_direct_function_return() { // C functions can return void - outer_ty.contains(OuterTyData::NO_OUTER_TY) && state.is_in_function_return() - } else { - false - }; - - if empty_and_safe { FfiSafe } else { FfiUnsafe { @@ -783,10 +814,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } ty::Array(inner_ty, _) => { - if state.is_in_function() - && outer_ty.contains(OuterTyData::NO_OUTER_TY) - // FIXME(ctypes): VVV-this-VVV shouldn't be the case - && !outer_ty.contains(OuterTyData::NOOUT_FNPTR) + if state.is_direct_in_function() + // FIXME(ctypes): VVV-this-VVV shouldn't be part of the check + && !matches!(state.outer_ty_kind, OuterTyKind::NoneThroughFnPtr) { // C doesn't really support passing arrays by value - the only way to pass an array by value // is through a struct. @@ -798,7 +828,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } else { // let's allow phantoms to go through, // since an array of 1-ZSTs is also a 1-ZST - self.visit_type(state, OuterTyData::from_ty(ty), inner_ty) + self.visit_type(state.get_next(ty), inner_ty) } } @@ -814,19 +844,14 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let sig = tcx.instantiate_bound_regions_with_erased(sig); for arg in sig.inputs() { - match self.visit_type( - VisitorState::ARGUMENT_TY_IN_FNPTR, - OuterTyData::from_ty(ty), - *arg, - ) { + match self.visit_type(state.get_next_in_fnptr(ty, FnPos::Arg), *arg) { FfiSafe => {} r => return r, } } let ret_ty = sig.output(); - - self.visit_type(VisitorState::RETURN_TY_IN_FNPTR, OuterTyData::from_ty(ty), ret_ty) + self.visit_type(state.get_next_in_fnptr(ty, FnPos::Ret), ret_ty) } ty::Foreign(..) => FfiSafe, @@ -894,7 +919,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { return res; } - self.visit_type(state, OuterTyData::NO_OUTER_TY, ty) + self.visit_type(state, ty) } } @@ -923,7 +948,7 @@ impl<'tcx> ImproperCTypesLint { self.spans.push(ty.span); } - hir::intravisit::walk_ty(self, ty) + hir::intravisit::walk_ty(self, ty); } } @@ -968,12 +993,12 @@ impl<'tcx> ImproperCTypesLint { let sig = cx.tcx.instantiate_bound_regions_with_erased(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - let state = VisitorState::argument_from_fnmode(fn_mode); + let state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Arg); self.check_type_for_external_abi_fnptr(cx, state, input_hir, *input_ty, fn_mode); } if let hir::FnRetTy::Return(ret_hir) = decl.output { - let state = VisitorState::return_from_fnmode(fn_mode); + let state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Ret); self.check_type_for_external_abi_fnptr(cx, state, ret_hir, sig.output(), fn_mode); } } @@ -998,7 +1023,7 @@ impl<'tcx> ImproperCTypesLint { fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) { let ty = cx.tcx.type_of(id).instantiate_identity(); let mut visitor = ImproperCTypesVisitor::new(cx, ty, CItemKind::Declaration); - let ffi_res = visitor.check_type(VisitorState::STATIC_TY, ty); + let ffi_res = visitor.check_type(VisitorState::static_var(), ty); self.process_ffi_result(cx, span, ffi_res, CItemKind::Declaration); } @@ -1014,14 +1039,14 @@ impl<'tcx> ImproperCTypesLint { let sig = cx.tcx.instantiate_bound_regions_with_erased(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - let state = VisitorState::argument_from_fnmode(fn_mode); + let state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Arg); let mut visitor = ImproperCTypesVisitor::new(cx, *input_ty, fn_mode); let ffi_res = visitor.check_type(state, *input_ty); self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode); } if let hir::FnRetTy::Return(ret_hir) = decl.output { - let state = VisitorState::return_from_fnmode(fn_mode); + let state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Ret); let mut visitor = ImproperCTypesVisitor::new(cx, sig.output(), fn_mode); let ffi_res = visitor.check_type(state, sig.output()); self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode); @@ -1122,7 +1147,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { | hir::ItemKind::TyAlias(_, _, ty) => { self.check_type_for_external_abi_fnptr( cx, - VisitorState::STATIC_TY, + VisitorState::static_var(), ty, cx.tcx.type_of(item.owner_id).instantiate_identity(), CItemKind::Definition, @@ -1156,7 +1181,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) { self.check_type_for_external_abi_fnptr( cx, - VisitorState::STATIC_TY, + VisitorState::static_var(), field.ty, cx.tcx.type_of(field.def_id).instantiate_identity(), CItemKind::Definition, From 38bbcde7678dd72087d0b28d2a535b3f9119e514 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 11 Sep 2025 00:51:56 +0200 Subject: [PATCH 03/20] ImproperCTypes: add recursion limit Simple change to stop irregular recursive types from causing infinitely-deep recursion in type checking. --- .../rustc_lint/src/types/improper_ctypes.rs | 33 ++++++++++++++----- tests/crashes/130310.rs | 15 --------- .../ice-irregular-recursive-types.rs | 21 ++++++++++++ .../lint-non-recursion-limit.rs | 9 +++-- .../lint-non-recursion-limit.stderr | 16 +++++++++ 5 files changed, 66 insertions(+), 28 deletions(-) delete mode 100644 tests/crashes/130310.rs create mode 100644 tests/ui/lint/improper-ctypes/ice-irregular-recursive-types.rs create mode 100644 tests/ui/lint/improper-ctypes/lint-non-recursion-limit.stderr diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 23b997ddaebd6..3db9d6495f7af 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -347,6 +347,8 @@ struct VisitorState { /// Flags describing both the immediate context in which the current mir::Ty is, /// linked to how it relates to its parent mir::Ty (or lack thereof). outer_ty_kind: OuterTyKind, + /// Type recursion depth, to prevent infinite recursion + depth: usize, } impl RootUseFlags { @@ -374,6 +376,7 @@ impl VisitorState { Self { root_use_flags: self.root_use_flags, outer_ty_kind: OuterTyKind::from_outer_ty(current_ty), + depth: self.depth + 1, } } /// From an existing state, compute the state of any subtype of the current type. @@ -387,12 +390,13 @@ impl VisitorState { FnPos::Arg => RootUseFlags::ARGUMENT_TY_IN_FNPTR, }, outer_ty_kind: OuterTyKind::from_outer_ty(current_ty), + depth: self.depth + 1, } } /// Generate the state for an "outermost" type that needs to be checked fn entry_point(root_use_flags: RootUseFlags) -> Self { - Self { root_use_flags, outer_ty_kind: OuterTyKind::None } + Self { root_use_flags, outer_ty_kind: OuterTyKind::None, depth: 0 } } /// Get the proper visitor state for a given function's arguments or return type. @@ -718,9 +722,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // Protect against infinite recursion, for example // `struct S(*mut S);`. - // FIXME: A recursion limit is necessary as well, for irregular - // recursive types. - if !self.cache.insert(ty) { + if !(self.cache.insert(ty) && self.cx.tcx.recursion_limit().value_within_limit(state.depth)) + { return FfiSafe; } @@ -935,6 +938,8 @@ impl<'tcx> ImproperCTypesLint { fn_mode: CItemKind, ) { struct FnPtrFinder<'tcx> { + current_depth: usize, + depths: Vec, spans: Vec, tys: Vec>, } @@ -942,13 +947,16 @@ impl<'tcx> ImproperCTypesLint { impl<'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'tcx> { fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) { debug!(?ty); + self.current_depth += 1; if let hir::TyKind::FnPtr(hir::FnPtrTy { abi, .. }) = ty.kind && !abi.is_rustic_abi() { + self.depths.push(self.current_depth); self.spans.push(ty.span); } hir::intravisit::walk_ty(self, ty); + self.current_depth -= 1; } } @@ -966,15 +974,24 @@ impl<'tcx> ImproperCTypesLint { } } - let mut visitor = FnPtrFinder { spans: Vec::new(), tys: Vec::new() }; + let mut visitor = FnPtrFinder { + spans: Vec::new(), + tys: Vec::new(), + depths: Vec::new(), + current_depth: 0, + }; ty.visit_with(&mut visitor); visitor.visit_ty_unambig(hir_ty); - let all_types = iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)); - for (fn_ptr_ty, span) in all_types { + let all_types = iter::zip( + visitor.depths.drain(..), + iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)), + ); + for (depth, (fn_ptr_ty, span)) in all_types { let mut visitor = ImproperCTypesVisitor::new(cx, fn_ptr_ty, fn_mode); + let bridge_state = VisitorState { depth, ..state }; // FIXME(ctypes): make a check_for_fnptr - let ffi_res = visitor.check_type(state, fn_ptr_ty); + let ffi_res = visitor.check_type(bridge_state, fn_ptr_ty); self.process_ffi_result(cx, span, ffi_res, fn_mode); } diff --git a/tests/crashes/130310.rs b/tests/crashes/130310.rs deleted file mode 100644 index d59dd39983c78..0000000000000 --- a/tests/crashes/130310.rs +++ /dev/null @@ -1,15 +0,0 @@ -//@ known-bug: rust-lang/rust#130310 - -use std::marker::PhantomData; - -#[repr(C)] -struct A { - a: *const A>, - p: PhantomData, -} - -extern "C" { - fn f(a: *const A<()>); -} - -fn main() {} diff --git a/tests/ui/lint/improper-ctypes/ice-irregular-recursive-types.rs b/tests/ui/lint/improper-ctypes/ice-irregular-recursive-types.rs new file mode 100644 index 0000000000000..22975b45a3b4d --- /dev/null +++ b/tests/ui/lint/improper-ctypes/ice-irregular-recursive-types.rs @@ -0,0 +1,21 @@ +//@ check-pass + +//! this test checks that irregular recursive types do not cause stack overflow in ImproperCTypes +//! Issue: https://github.com/rust-lang/rust/issues/94223 + +#![deny(improper_ctypes, improper_ctypes_definitions)] + +use std::marker::PhantomData; + +#[repr(C)] +struct A { + a: *const A>, // without a recursion limit, checking this ends up creating checks for + // infinitely deep types the likes of `A>>>>>` + p: PhantomData, +} + +extern "C" { + fn f(a: *const A<()>); +} + +fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs index 1d75a7ca9ab9f..de731171b9a66 100644 --- a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs +++ b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.rs @@ -1,12 +1,10 @@ -//@ check-pass - //! This test checks that the depth limit of the ImproperCTypes lints counts the depth //! of a type properly. //! Issue: https://github.com/rust-lang/rust/issues/130757 #![recursion_limit = "5"] #![allow(unused)] -#![deny(improper_ctypes)] +#![deny(improper_ctypes_definitions)] #[repr(C)] struct F1(*const ()); @@ -19,7 +17,7 @@ struct F4(*const ()); #[repr(C)] struct F5(*const ()); #[repr(C)] -struct F6(*const ()); +struct F6([char;8]); //oops! #[repr(C)] struct B { @@ -28,9 +26,10 @@ struct B { f3: F3, f4: F4, f5: F5, - f6: F6, + f6: F6, // when the recursion limit hits, things are assumed safe, so this should error } extern "C" fn foo(_: B) {} +//~^ ERROR: uses type `char` fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.stderr b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.stderr new file mode 100644 index 0000000000000..ce565c542c94a --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-non-recursion-limit.stderr @@ -0,0 +1,16 @@ +error: `extern` fn uses type `char`, which is not FFI-safe + --> $DIR/lint-non-recursion-limit.rs:32:22 + | +LL | extern "C" fn foo(_: B) {} + | ^ not FFI-safe + | + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent +note: the lint level is defined here + --> $DIR/lint-non-recursion-limit.rs:7:9 + | +LL | #![deny(improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 6540f12351a7f0b3b976b350d318962ae7e77aab Mon Sep 17 00:00:00 2001 From: niacdoial Date: Sun, 24 Aug 2025 18:09:02 +0200 Subject: [PATCH 04/20] ImproperCTypes: add architecture for layered reasoning in lints Another change that only impacts rustc developers: Added the necessary changes so that lints are able to specify in detail "A in unsafe because of its B field, which in turn is unsafe because of C, etc", and possibly specify multiple help messages (multiple ways to reach FFI-safety) --- compiler/rustc_lint/src/lints.rs | 48 ++- .../rustc_lint/src/types/improper_ctypes.rs | 406 ++++++++++++++---- 2 files changed, 352 insertions(+), 102 deletions(-) diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index c55f2b9dd6f24..c51d2eb2e16ba 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1950,29 +1950,55 @@ pub(crate) enum UnpredictableFunctionPointerComparisonsSuggestion<'a, 'tcx> { }, } +pub(crate) struct ImproperCTypesLayer<'a> { + pub ty: Ty<'a>, + pub inner_ty: Option>, + pub note: DiagMessage, + pub span_note: Option, + pub help: Option, +} + +impl<'a> Subdiagnostic for ImproperCTypesLayer<'a> { + fn add_to_diag(self, diag: &mut Diag<'_, G>) { + diag.arg("ty", self.ty); + if let Some(ty) = self.inner_ty { + diag.arg("inner_ty", ty); + } + + if let Some(help) = self.help { + diag.help(diag.eagerly_translate(help)); + } + + diag.note(diag.eagerly_translate(self.note)); + if let Some(note) = self.span_note { + diag.span_note(note, fluent::lint_note); + }; + + diag.remove_arg("ty"); + if self.inner_ty.is_some() { + diag.remove_arg("inner_ty"); + } + } +} + pub(crate) struct ImproperCTypes<'a> { pub ty: Ty<'a>, pub desc: &'a str, pub label: Span, - pub help: Option, - pub note: DiagMessage, - pub span_note: Option, + pub reasons: Vec>, } // Used because of the complexity of Option, DiagMessage, and Option impl<'a> LintDiagnostic<'a, ()> for ImproperCTypes<'_> { fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { diag.primary_message(fluent::lint_improper_ctypes); - diag.arg("ty", self.ty); - diag.arg("desc", self.desc); diag.span_label(self.label, fluent::lint_label); - if let Some(help) = self.help { - diag.help(help); - } - diag.note(self.note); - if let Some(note) = self.span_note { - diag.span_note(note, fluent::lint_note); + for reason in self.reasons.into_iter() { + diag.subdiagnostic(reason); } + // declare the arguments at the end to avoid them being clobbered in the subdiagnostics + diag.arg("ty", self.ty); + diag.arg("desc", self.desc); } } diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 3db9d6495f7af..c53c8ec42c0b2 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -19,7 +19,7 @@ use rustc_span::{Span, sym}; use tracing::debug; use super::repr_nullable_ptr; -use crate::lints::{ImproperCTypes, UsesPowerAlignment}; +use crate::lints::{ImproperCTypes, ImproperCTypesLayer, UsesPowerAlignment}; use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent}; declare_lint! { @@ -270,10 +270,184 @@ enum FnPos { Ret, } +#[derive(Clone, Debug)] +struct FfiUnsafeReason<'tcx> { + ty: Ty<'tcx>, + note: DiagMessage, + help: Option, + inner: Option>>, +} + +/// A single explanation (out of possibly multiple) +/// telling why a given element is rendered FFI-unsafe. +/// This goes as deep as the 'core cause', but it might be located elsewhere, possibly in a different crate. +/// So, we also track the 'smallest' type in the explanation that appears in the span of the unsafe element. +/// (we call this the 'cause' or the 'local cause' of the unsafety) +#[derive(Clone, Debug)] +struct FfiUnsafeExplanation<'tcx> { + /// A stack of incrementally "smaller" types, justifications and help messages, + /// ending with the 'core reason' why something is FFI-unsafe, making everything around it also unsafe. + reason: Box>, + /// Override the type considered the local cause of the FFI-unsafety. + /// (e.g.: even if the lint goes into detail as to why a struct used as a function argument + /// is unsafe, have the first lint line say that the fault lies in the use of said struct.) + override_cause_ty: Option>, +} + +/// The result describing the safety (or lack thereof) of a given type. +#[derive(Clone, Debug)] enum FfiResult<'tcx> { + /// The type is known to be safe. FfiSafe, + /// The type is only a phantom annotation. + /// (Safe in some contexts, unsafe in others.) FfiPhantom(Ty<'tcx>), - FfiUnsafe { ty: Ty<'tcx>, reason: DiagMessage, help: Option }, + /// The type is not safe. + /// there might be any number of "explanations" as to why, + /// each being a stack of "reasons" going from the type + /// to a core cause of FFI-unsafety. + FfiUnsafe(Vec>), +} + +impl<'tcx> FfiResult<'tcx> { + /// Simplified creation of the FfiUnsafe variant for a single unsafety reason. + fn new_with_reason(ty: Ty<'tcx>, note: DiagMessage, help: Option) -> Self { + Self::FfiUnsafe(vec![FfiUnsafeExplanation { + override_cause_ty: None, + reason: Box::new(FfiUnsafeReason { ty, help, note, inner: None }), + }]) + } + + /// If the FfiUnsafe variant, 'wraps' all reasons, + /// creating new `FfiUnsafeReason`s, putting the originals as their `inner` fields. + /// Otherwise, keep unchanged. + #[expect(unused)] + fn wrap_all(self, ty: Ty<'tcx>, note: DiagMessage, help: Option) -> Self { + match self { + Self::FfiUnsafe(this) => { + let unsafeties = this + .into_iter() + .map(|FfiUnsafeExplanation { reason, override_cause_ty }| { + let reason = Box::new(FfiUnsafeReason { + ty, + help: help.clone(), + note: note.clone(), + inner: Some(reason), + }); + FfiUnsafeExplanation { reason, override_cause_ty } + }) + .collect::>(); + Self::FfiUnsafe(unsafeties) + } + r @ _ => r, + } + } + /// If the FfiPhantom variant, turns it into a FfiUnsafe version. + /// Otherwise, keep unchanged. + #[expect(unused)] + fn forbid_phantom(self) -> Self { + match self { + Self::FfiPhantom(ty) => { + Self::new_with_reason(ty, fluent::lint_improper_ctypes_only_phantomdata, None) + } + _ => self, + } + } + + /// Selectively "pluck" some explanations out of a FfiResult::FfiUnsafe, + /// if the note at their core reason is one in a provided list. + /// If the FfiResult is not FfiUnsafe, or if no reasons are plucked, + /// then return FfiSafe. + #[expect(unused)] + fn take_with_core_note(&mut self, notes: &[DiagMessage]) -> Self { + match self { + Self::FfiUnsafe(this) => { + let mut remaining_explanations = vec![]; + std::mem::swap(this, &mut remaining_explanations); + let mut filtered_explanations = vec![]; + let mut remaining_explanations = remaining_explanations + .into_iter() + .filter_map(|explanation| { + let mut reason = explanation.reason.as_ref(); + while let Some(ref inner) = reason.inner { + reason = inner.as_ref(); + } + let mut does_remain = true; + for note_match in notes { + if note_match == &reason.note { + does_remain = false; + break; + } + } + if does_remain { + Some(explanation) + } else { + filtered_explanations.push(explanation); + None + } + }) + .collect::>(); + std::mem::swap(this, &mut remaining_explanations); + if filtered_explanations.len() > 0 { + Self::FfiUnsafe(filtered_explanations) + } else { + Self::FfiSafe + } + } + _ => Self::FfiSafe, + } + } + + /// Wrap around code that generates FfiResults "from a different cause". + /// For instance, if we have a repr(C) struct in a function's argument, FFI unsafeties inside the struct + /// are to be blamed on the struct and not the members. + /// This is where we use this wrapper, to tell "all FFI-unsafeties in there are caused by this `ty`" + #[expect(unused)] + fn with_overrides(mut self, override_cause_ty: Option>) -> FfiResult<'tcx> { + use FfiResult::*; + + if let FfiUnsafe(ref mut explanations) = self { + explanations.iter_mut().for_each(|explanation| { + explanation.override_cause_ty = override_cause_ty; + }); + } + self + } +} + +impl<'tcx> std::ops::AddAssign> for FfiResult<'tcx> { + fn add_assign(&mut self, other: Self) { + // note: we shouldn't really encounter FfiPhantoms here, they should be dealt with beforehand + // still, this function deals with them in a reasonable way, I think + + match (self, other) { + (Self::FfiUnsafe(myself), Self::FfiUnsafe(mut other_reasons)) => { + myself.append(&mut other_reasons); + } + (Self::FfiUnsafe(_), _) => { + // nothing to do + } + (myself, other @ Self::FfiUnsafe(_)) => { + *myself = other; + } + (Self::FfiPhantom(ty1), Self::FfiPhantom(ty2)) => { + debug!("whoops, both FfiPhantom: self({:?}) += other({:?})", ty1, ty2); + } + (myself @ Self::FfiSafe, other @ Self::FfiPhantom(_)) => { + *myself = other; + } + (_, Self::FfiSafe) => { + // nothing to do + } + } + } +} +impl<'tcx> std::ops::Add> for FfiResult<'tcx> { + type Output = FfiResult<'tcx>; + fn add(mut self, other: Self) -> Self::Output { + self += other; + self + } } /// The result when a type has been checked but perhaps not completely. `None` indicates that @@ -319,6 +493,8 @@ enum OuterTyKind { /// A variant that should not exist, /// but is needed because we don't change the lint's behavior yet NoneThroughFnPtr, + /// For struct/enum/union fields + AdtField, /// Placeholder for properties that will be used eventually UnusedVariant, } @@ -328,12 +504,16 @@ impl OuterTyKind { fn from_outer_ty<'tcx>(ty: Ty<'tcx>) -> Self { match ty.kind() { ty::FnPtr(..) => Self::NoneThroughFnPtr, - ty::RawPtr(..) - | ty::Ref(..) - | ty::Adt(..) - | ty::Tuple(..) - | ty::Array(..) - | ty::Slice(_) => Self::UnusedVariant, + ty::Adt(..) => { + if ty.boxed_ty().is_some() { + Self::UnusedVarient + } else { + Self::AdtField + } + } + ty::RawPtr(..) | ty::Ref(..) | ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => { + Self::UnusedVariant + } k @ _ => bug!("Unexpected outer type {:?} of kind {:?}", ty, k), } } @@ -463,6 +643,11 @@ impl VisitorState { // rust-defined functions, as well as FnPtrs self.root_use_flags.contains(RootUseFlags::THEORETICAL) || self.is_in_defined_function() } + + /// Whether the current type is an ADT field + fn is_field(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::AdtField) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -509,11 +694,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if inner_ty.is_sized(tcx, self.cx.typing_env()) { return FfiSafe; } else { - return FfiUnsafe { + return FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_box, - help: None, - }; + fluent::lint_improper_ctypes_box, + None, + ); } } else { // (mid-retcon-commit-chain comment:) @@ -571,12 +756,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { // Transparent newtypes have at most one non-ZST field which needs to be checked.. let field_ty = get_type_from_field(self.cx, field, args); - match self.visit_type(state.get_next(ty), field_ty) { - FfiUnsafe { ty, .. } if ty.is_unit() => (), - r => return r, - } - - false + return self.visit_type(state.get_next(ty), field_ty); } else { // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all // `PhantomData`). @@ -592,8 +772,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { let field_ty = get_type_from_field(self.cx, field, args); all_phantom &= match self.visit_type(state.get_next(ty), field_ty) { FfiSafe => false, - // `()` fields are FFI-safe! - FfiUnsafe { ty, .. } if ty.is_unit() => false, FfiPhantom(..) => true, r @ FfiUnsafe { .. } => return r, } @@ -602,7 +780,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if all_phantom { FfiPhantom(ty) } else if transparent_with_all_zst_fields { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None } + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_struct_zst, None) } else { FfiSafe } @@ -616,51 +794,50 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { debug_assert!(matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union)); - use FfiResult::*; if !def.repr().c() && !def.repr().transparent() { - return FfiUnsafe { + return FfiResult::new_with_reason( ty, - reason: if def.is_struct() { + if def.is_struct() { fluent::lint_improper_ctypes_struct_layout_reason } else { fluent::lint_improper_ctypes_union_layout_reason }, - help: if def.is_struct() { + if def.is_struct() { Some(fluent::lint_improper_ctypes_struct_layout_help) } else { // FIXME(#60405): confirm that this makes sense for unions once #60405 / RFC2645 stabilises Some(fluent::lint_improper_ctypes_union_layout_help) }, - }; + ); } if def.non_enum_variant().field_list_has_applicable_non_exhaustive() { - return FfiUnsafe { + return FfiResult::new_with_reason( ty, - reason: if def.is_struct() { + if def.is_struct() { fluent::lint_improper_ctypes_struct_non_exhaustive } else { fluent::lint_improper_ctypes_union_non_exhaustive }, - help: None, - }; + None, + ); } if def.non_enum_variant().fields.is_empty() { - return FfiUnsafe { + FfiResult::new_with_reason( ty, - reason: if def.is_struct() { + if def.is_struct() { fluent::lint_improper_ctypes_struct_fieldless_reason } else { fluent::lint_improper_ctypes_union_fieldless_reason }, - help: if def.is_struct() { + if def.is_struct() { Some(fluent::lint_improper_ctypes_struct_fieldless_help) } else { Some(fluent::lint_improper_ctypes_union_fieldless_help) }, - }; + ) } else { self.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) } @@ -688,18 +865,21 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { return self.visit_type(state.get_next(ty), inner_ty); } - return FfiUnsafe { + return FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_enum_repr_reason, - help: Some(fluent::lint_improper_ctypes_enum_repr_help), - }; + fluent::lint_improper_ctypes_enum_repr_reason, + Some(fluent::lint_improper_ctypes_enum_repr_help), + ); } + // FIXME(ctypes): connect `def.repr().int` to visit_numeric + // (for now it's OK, `repr(char)` doesn't exist and visit_numeric doesn't warn on anything else) + let non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); // Check the contained variants. let ret = def.variants().iter().try_for_each(|variant| { check_non_exhaustive_variant(non_exhaustive, variant) - .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; + .map_break(|reason| FfiResult::new_with_reason(ty, reason, None))?; match self.visit_variant_fields(state, ty, def, variant, args) { FfiSafe => ControlFlow::Continue(()), @@ -741,11 +921,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { tcx.get_diagnostic_name(def.did()) && !self.base_ty.is_mutable_ptr() { - return FfiUnsafe { + return FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_cstr_reason, - help: Some(fluent::lint_improper_ctypes_cstr_help), - }; + fluent::lint_improper_ctypes_cstr_reason, + Some(fluent::lint_improper_ctypes_cstr_help), + ); } self.visit_struct_or_union(state, ty, def, args) } @@ -765,38 +945,44 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty::Bool => FfiResult::FfiSafe, - ty::Char => FfiResult::FfiUnsafe { + ty::Char => FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_char_reason, - help: Some(fluent::lint_improper_ctypes_char_help), - }, + fluent::lint_improper_ctypes_char_reason, + Some(fluent::lint_improper_ctypes_char_help), + ), - ty::Slice(_) => FfiUnsafe { + ty::Slice(_) => FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_slice_reason, - help: Some(fluent::lint_improper_ctypes_slice_help), - }, + fluent::lint_improper_ctypes_slice_reason, + Some(fluent::lint_improper_ctypes_slice_help), + ), ty::Dynamic(..) => { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_dyn, help: None } + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_dyn, None) } - ty::Str => FfiUnsafe { + ty::Str => FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_str_reason, - help: Some(fluent::lint_improper_ctypes_str_help), - }, + fluent::lint_improper_ctypes_str_reason, + Some(fluent::lint_improper_ctypes_str_help), + ), ty::Tuple(tuple) => { - if tuple.is_empty() && state.is_direct_function_return() { - // C functions can return void + if tuple.is_empty() + && ( + // C functions can return void + state.is_direct_function_return() + // `()` fields are safe + || state.is_field() + ) + { FfiSafe } else { - FfiUnsafe { + FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_tuple_reason, - help: Some(fluent::lint_improper_ctypes_tuple_help), - } + fluent::lint_improper_ctypes_tuple_reason, + Some(fluent::lint_improper_ctypes_tuple_help), + ) } } @@ -823,11 +1009,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { { // C doesn't really support passing arrays by value - the only way to pass an array by value // is through a struct. - FfiResult::FfiUnsafe { + FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_array_reason, - help: Some(fluent::lint_improper_ctypes_array_help), - } + fluent::lint_improper_ctypes_array_reason, + Some(fluent::lint_improper_ctypes_array_help), + ) } else { // let's allow phantoms to go through, // since an array of 1-ZSTs is also a 1-ZST @@ -838,11 +1024,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty::FnPtr(sig_tys, hdr) => { let sig = sig_tys.with(hdr); if sig.abi().is_rustic_abi() { - return FfiUnsafe { + return FfiResult::new_with_reason( ty, - reason: fluent::lint_improper_ctypes_fnptr_reason, - help: Some(fluent::lint_improper_ctypes_fnptr_help), - }; + fluent::lint_improper_ctypes_fnptr_reason, + Some(fluent::lint_improper_ctypes_fnptr_help), + ); } let sig = tcx.instantiate_bound_regions_with_erased(sig); @@ -864,7 +1050,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // While opaque types are checked for earlier, if a projection in a struct field // normalizes to an opaque type, then it will reach this branch. ty::Alias(ty::Opaque, ..) => { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_opaque, help: None } + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_opaque, None) } // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe, @@ -909,11 +1095,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - ty.visit_with(&mut ProhibitOpaqueTypes).break_value().map(|ty| FfiResult::FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_opaque, - help: None, - }) + ty.visit_with(&mut ProhibitOpaqueTypes) + .break_value() + .map(|ty| FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_opaque, None)) } fn check_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { @@ -1082,15 +1266,53 @@ impl<'tcx> ImproperCTypesLint { FfiResult::FfiPhantom(ty) => { self.emit_ffi_unsafe_type_lint( cx, - ty, + ty.clone(), sp, - fluent::lint_improper_ctypes_only_phantomdata, - None, + vec![ImproperCTypesLayer { + ty, + note: fluent::lint_improper_ctypes_only_phantomdata, + span_note: None, // filled later + help: None, + inner_ty: None, + }], fn_mode, ); } - FfiResult::FfiUnsafe { ty, reason, help } => { - self.emit_ffi_unsafe_type_lint(cx, ty, sp, reason, help, fn_mode); + FfiResult::FfiUnsafe(explanations) => { + for explanation in explanations { + let mut ffiresult_recursor = ControlFlow::Continue(explanation.reason.as_ref()); + let mut cimproper_layers: Vec> = vec![]; + + // this whole while block converts the arbitrarily-deep + // FfiResult stack to an ImproperCTypesLayer Vec + while let ControlFlow::Continue(FfiUnsafeReason { ty, note, help, inner }) = + ffiresult_recursor + { + if let Some(layer) = cimproper_layers.last_mut() { + layer.inner_ty = Some(ty.clone()); + } + cimproper_layers.push(ImproperCTypesLayer { + ty: ty.clone(), + inner_ty: None, + help: help.clone(), + note: note.clone(), + span_note: None, // filled later + }); + + if let Some(inner) = inner { + ffiresult_recursor = ControlFlow::Continue(inner.as_ref()); + } else { + ffiresult_recursor = ControlFlow::Break(()); + } + } + let cause_ty = if let Some(cause_ty) = explanation.override_cause_ty { + cause_ty + } else { + // should always have at least one type + cimproper_layers.last().unwrap().ty.clone() + }; + self.emit_ffi_unsafe_type_lint(cx, cause_ty, sp, cimproper_layers, fn_mode); + } } } } @@ -1100,8 +1322,7 @@ impl<'tcx> ImproperCTypesLint { cx: &LateContext<'tcx>, ty: Ty<'tcx>, sp: Span, - note: DiagMessage, - help: Option, + mut reasons: Vec>, fn_mode: CItemKind, ) { let lint = match fn_mode { @@ -1112,14 +1333,17 @@ impl<'tcx> ImproperCTypesLint { CItemKind::Declaration => "block", CItemKind::Definition => "fn", }; - let span_note = if let ty::Adt(def, _) = ty.kind() - && let Some(sp) = cx.tcx.hir_span_if_local(def.did()) - { - Some(sp) - } else { - None - }; - cx.emit_span_lint(lint, sp, ImproperCTypes { ty, desc, label: sp, help, note, span_note }); + for reason in reasons.iter_mut() { + reason.span_note = if let ty::Adt(def, _) = reason.ty.kind() + && let Some(sp) = cx.tcx.hir_span_if_local(def.did()) + { + Some(sp) + } else { + None + }; + } + + cx.emit_span_lint(lint, sp, ImproperCTypes { ty, desc, label: sp, reasons }); } } From 7f0d3468002d5dd4c629eb11015ab924c9b6869f Mon Sep 17 00:00:00 2001 From: niacdoial Date: Tue, 26 Aug 2025 00:35:22 +0200 Subject: [PATCH 05/20] ImproperCTypes: splitting definitions lint into two First retconned commit in this change to impact rustc users: The improper_ctypes_definitions has been split into improper_c_fn_definitions and improper_c_callbacks, with the former lint name being turned into a lint group, so that users aren't forced to immediately change their code. Deprecating this old name will be left as an exercise to whichever team is in charge of breaking changes. Another lint group has been created to deal with all `improper_c*` lints at once. --- compiler/rustc_lint/messages.ftl | 2 +- compiler/rustc_lint/src/lib.rs | 2 + compiler/rustc_lint/src/types.rs | 4 +- .../rustc_lint/src/types/improper_ctypes.rs | 99 ++++++++++++------- src/tools/lint-docs/src/groups.rs | 1 + tests/assembly-llvm/naked-functions/wasm32.rs | 1 - tests/ui/abi/compatibility.rs | 3 +- tests/ui/abi/extern/extern-pass-empty.rs | 3 +- tests/ui/abi/foreign/foreign-fn-with-byval.rs | 2 +- ...sized-args-in-c-abi-issues-94223-115845.rs | 2 +- tests/ui/asm/naked-functions-ffi.stderr | 2 +- .../cmse-nonsecure-call/return-via-stack.rs | 2 +- .../cmse-nonsecure-call/via-registers.rs | 3 +- .../extern-C-non-FFI-safe-arg-ice-52334.rs | 2 +- ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 5 +- tests/ui/extern/extern-C-str-arg-ice-80125.rs | 2 +- .../extern/extern-C-str-arg-ice-80125.stderr | 5 +- tests/ui/hashmap/hashmap-memory.rs | 2 +- tests/ui/issues/issue-51907.rs | 2 + tests/ui/lint/clashing-extern-fn.stderr | 2 +- tests/ui/lint/extern-C-fnptr-lints-slices.rs | 4 +- .../lint/extern-C-fnptr-lints-slices.stderr | 6 +- tests/ui/lint/improper-ctypes/lint-94223.rs | 26 ++--- .../ui/lint/improper-ctypes/lint-94223.stderr | 30 +++--- tests/ui/lint/improper-ctypes/lint-fn.rs | 2 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 2 +- .../lint/improper-ctypes/mustpass-113436.rs | 2 +- .../improper-ctypes/mustpass-134060.stderr | 2 +- .../repr/repr-transparent-issue-87496.stderr | 2 +- 29 files changed, 132 insertions(+), 90 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 1f6a382175b7c..14b5b7290b4f8 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -336,7 +336,7 @@ lint_implicit_unsafe_autorefs = implicit autoref creates a reference to the dere .method_def = method calls to `{$method_name}` require a reference .suggestion = try using a raw pointer method instead; or if this reference is intentional, make it explicit -lint_improper_ctypes = `extern` {$desc} uses type `{$ty}`, which is not FFI-safe +lint_improper_ctypes = {$desc} uses type `{$ty}`, which is not FFI-safe .label = not FFI-safe .note = the type is defined here diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index b4c18483a923b..9242a7ab706f2 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -335,6 +335,8 @@ fn register_builtins(store: &mut LintStore) { REFINING_IMPL_TRAIT_INTERNAL ); + add_lint_group!("improper_c_boundaries", IMPROPER_CTYPES_DEFINITIONS, IMPROPER_CTYPES); + add_lint_group!("deprecated_safe", DEPRECATED_SAFE_2024); add_lint_group!( diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 8ce74ff76effc..2a75ae0f87f02 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -11,7 +11,9 @@ use tracing::debug; use {rustc_ast as ast, rustc_hir as hir}; mod improper_ctypes; // these filed do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations -pub(crate) use improper_ctypes::ImproperCTypesLint; +pub(crate) use improper_ctypes::{ + IMPROPER_CTYPES, IMPROPER_CTYPES_DEFINITIONS, ImproperCTypesLint, +}; use crate::lints::{ AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion, diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index c53c8ec42c0b2..9622babd5596a 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -25,13 +25,19 @@ use crate::{LateContext, LateLintPass, LintContext, fluent_generated as fluent}; declare_lint! { /// The `improper_ctypes` lint detects incorrect use of types in foreign /// modules. + /// (In other words, declarations of items defined in foreign code.) + /// This also includes all [`extern` function] pointers. + /// + /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier /// /// ### Example /// /// ```rust /// unsafe extern "C" { /// static STATIC: String; + /// fn some_func(a:String); /// } + /// extern "C" fn register_callback(a: i32, call: extern "C" fn(char)) { /* ... */ } /// ``` /// /// {{produces}} @@ -44,7 +50,7 @@ declare_lint! { /// detects a probable mistake in a definition. The lint usually should /// provide a description of the issue, along with possibly a hint on how /// to resolve it. - IMPROPER_CTYPES, + pub(crate) IMPROPER_CTYPES, Warn, "proper use of libc types in foreign modules" } @@ -52,6 +58,7 @@ declare_lint! { declare_lint! { /// The `improper_ctypes_definitions` lint detects incorrect use of /// [`extern` function] definitions. + /// (In other words, functions to be used by foreign code.) /// /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier /// @@ -71,7 +78,7 @@ declare_lint! { /// lint is an alert that these types should not be used. The lint usually /// should provide a description of the issue, along with possibly a hint /// on how to resolve it. - IMPROPER_CTYPES_DEFINITIONS, + pub(crate) IMPROPER_CTYPES_DEFINITIONS, Warn, "proper use of libc types in foreign item definitions" } @@ -134,7 +141,7 @@ declare_lint! { declare_lint_pass!(ImproperCTypesLint => [ IMPROPER_CTYPES, IMPROPER_CTYPES_DEFINITIONS, - USES_POWER_ALIGNMENT + USES_POWER_ALIGNMENT, ]); /// Getting the (normalized) type out of a field (for, e.g., an enum variant or a tuple). @@ -254,13 +261,19 @@ fn check_struct_for_power_alignment<'tcx>( } } -/// Annotates whether we are in the context of an item *defined* in rust -/// and exposed to an FFI boundary, -/// or the context of an item from elsewhere, whose interface is re-*declared* in rust. -#[derive(Clone, Copy)] +/// Annotates the nature of the "original item" being checked, and its relation +/// to FFI boundaries. +/// Mainly, whether is is something defined in rust and exported through the FFI boundary, +/// or something rust imports through the same boundary. +/// Callbacks are ultimately treated as imported items, in terms of denying/warning/ignoring FFI-unsafety +#[derive(Clone, Copy, Debug)] enum CItemKind { - Declaration, - Definition, + /// Imported items in an `extern "C"` block (function declarations, static variables) -> IMPROPER_CTYPES + ImportedExtern, + /// `extern "C"` function definitions, to be used elsewhere -> IMPROPER_CTYPES_DEFINITIONS, + ExportedFunction, + /// `extern "C"` function pointers -> also IMPROPER_CTYPES, + Callback, } /// Annotates whether we are in the context of a function's argument types or return type. @@ -582,10 +595,13 @@ impl VisitorState { /// Get the proper visitor state for a given function's arguments or return type. fn entry_point_from_fnmode(fn_mode: CItemKind, fn_pos: FnPos) -> Self { let p_flags = match (fn_mode, fn_pos) { - (CItemKind::Definition, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DEFINITION, - (CItemKind::Declaration, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DECLARATION, - (CItemKind::Definition, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DEFINITION, - (CItemKind::Declaration, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DECLARATION, + (CItemKind::ExportedFunction, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DEFINITION, + (CItemKind::ImportedExtern, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DECLARATION, + (CItemKind::ExportedFunction, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DEFINITION, + (CItemKind::ImportedExtern, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DECLARATION, + // we could also deal with CItemKind::Callback, + // but we bake an assumption from this function's call sites here. + _ => bug!("cannot be called with CItemKind::{:?}", fn_mode), }; Self::entry_point(p_flags) } @@ -689,7 +705,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // - otherwise, treat the box itself correctly, and follow pointee safety logic // as described in the other `indirection_type` match branch. if state.is_in_defined_function() - || (state.is_in_fnptr() && matches!(self.base_fn_mode, CItemKind::Definition)) + || (state.is_in_fnptr() + && matches!(self.base_fn_mode, CItemKind::ExportedFunction)) { if inner_ty.is_sized(tcx, self.cx.typing_env()) { return FfiSafe; @@ -1177,7 +1194,7 @@ impl<'tcx> ImproperCTypesLint { // FIXME(ctypes): make a check_for_fnptr let ffi_res = visitor.check_type(bridge_state, fn_ptr_ty); - self.process_ffi_result(cx, span, ffi_res, fn_mode); + self.process_ffi_result(cx, span, ffi_res, CItemKind::Callback); } } @@ -1223,9 +1240,9 @@ impl<'tcx> ImproperCTypesLint { fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) { let ty = cx.tcx.type_of(id).instantiate_identity(); - let mut visitor = ImproperCTypesVisitor::new(cx, ty, CItemKind::Declaration); + let mut visitor = ImproperCTypesVisitor::new(cx, ty, CItemKind::ImportedExtern); let ffi_res = visitor.check_type(VisitorState::static_var(), ty); - self.process_ffi_result(cx, span, ffi_res, CItemKind::Declaration); + self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern); } /// Check if a function's argument types and result type are "ffi-safe". @@ -1326,12 +1343,16 @@ impl<'tcx> ImproperCTypesLint { fn_mode: CItemKind, ) { let lint = match fn_mode { - CItemKind::Declaration => IMPROPER_CTYPES, - CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS, + CItemKind::ImportedExtern => IMPROPER_CTYPES, + CItemKind::ExportedFunction => IMPROPER_CTYPES_DEFINITIONS, + // Internally, we treat this differently, but at the end of the day + // their linting needs to be enabled/disabled alongside that of "FFI-imported" items. + CItemKind::Callback => IMPROPER_CTYPES, }; let desc = match fn_mode { - CItemKind::Declaration => "block", - CItemKind::Definition => "fn", + CItemKind::ImportedExtern => "`extern` block", + CItemKind::ExportedFunction => "`extern` fn", + CItemKind::Callback => "`extern` callback", }; for reason in reasons.iter_mut() { reason.span_note = if let ty::Adt(def, _) = reason.ty.kind() @@ -1347,13 +1368,20 @@ impl<'tcx> ImproperCTypesLint { } } -/// `ImproperCTypesDefinitions` checks items outside of foreign items (e.g. stuff that isn't in -/// `extern "C" { }` blocks): +/// IMPROPER_CTYPES checks items that are part of a header to a non-rust library +/// Namely, functions and static variables in `extern "" { }`, +/// if `` is external (e.g. "C"). +/// it also checks for function pointers marked with an external ABI. +/// (fields of type `extern "" fn`, where e.g. `` is `C`) +/// These pointers are searched in all other items which contain types +/// (e.g.functions, struct definitions, etc) /// -/// - `extern "" fn` definitions are checked in the same way as the -/// `ImproperCtypesDeclarations` visitor checks functions if `` is external (e.g. "C"). -/// - All other items which contain types (e.g. other functions, struct definitions, etc) are -/// checked for extern fn-ptrs with external ABIs. +/// `IMPROPER_CTYPES_DEFINITIONS` checks rust-defined functions that are marked +/// to be used from the other side of a FFI boundary. +/// In other words, `extern "" fn` definitions and trait-method declarations. +/// This only matters if `` is external (e.g. `C`). +/// +/// maybe later: specialised lints for pointees impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) { let abi = cx.tcx.hir_get_foreign_abi(it.hir_id()); @@ -1364,11 +1392,16 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // "the element rendered unsafe" because their unsafety doesn't affect // their surroundings, and their type is often declared inline if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::Declaration, it.owner_id.def_id, sig.decl); + self.check_foreign_fn( + cx, + CItemKind::ImportedExtern, + it.owner_id.def_id, + sig.decl, + ); } else { self.check_fn_for_external_abi_fnptr( cx, - CItemKind::Declaration, + CItemKind::ImportedExtern, it.owner_id.def_id, sig.decl, ); @@ -1391,7 +1424,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { VisitorState::static_var(), ty, cx.tcx.type_of(item.owner_id).instantiate_identity(), - CItemKind::Definition, + CItemKind::ExportedFunction, // TODO: for some reason, this is the value that reproduces old behaviour ); } // See `check_fn` for declarations, `check_foreign_items` for definitions in extern blocks @@ -1425,7 +1458,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { VisitorState::static_var(), field.ty, cx.tcx.type_of(field.def_id).instantiate_identity(), - CItemKind::Definition, + CItemKind::ImportedExtern, ); } @@ -1450,9 +1483,9 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // "the element rendered unsafe" because their unsafety doesn't affect // their surroundings, and their type is often declared inline if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::Definition, id, decl); + self.check_foreign_fn(cx, CItemKind::ExportedFunction, id, decl); } else { - self.check_fn_for_external_abi_fnptr(cx, CItemKind::Definition, id, decl); + self.check_fn_for_external_abi_fnptr(cx, CItemKind::ExportedFunction, id, decl); } } } diff --git a/src/tools/lint-docs/src/groups.rs b/src/tools/lint-docs/src/groups.rs index a24fbbc0ceab3..10ae9e6421b19 100644 --- a/src/tools/lint-docs/src/groups.rs +++ b/src/tools/lint-docs/src/groups.rs @@ -30,6 +30,7 @@ static GROUP_DESCRIPTIONS: &[(&str, &str)] = &[ "unknown-or-malformed-diagnostic-attributes", "detects unknown or malformed diagnostic attributes", ), + ("improper-c-boundaries", "Lints for points where rust code interacts with non-rust code"), ]; type LintGroups = BTreeMap>; diff --git a/tests/assembly-llvm/naked-functions/wasm32.rs b/tests/assembly-llvm/naked-functions/wasm32.rs index 4bf04dd392341..7d92ec474179a 100644 --- a/tests/assembly-llvm/naked-functions/wasm32.rs +++ b/tests/assembly-llvm/naked-functions/wasm32.rs @@ -99,7 +99,6 @@ extern "C" fn fn_i64_i64(num: i64) -> i64 { // wasm32-unknown: .functype fn_i128_i128 (i32, i64, i64) -> () // wasm32-wasip1: .functype fn_i128_i128 (i32, i64, i64) -> () // wasm64-unknown: .functype fn_i128_i128 (i64, i64, i64) -> () -#[allow(improper_ctypes_definitions)] #[no_mangle] #[unsafe(naked)] extern "C" fn fn_i128_i128(num: i128) -> i128 { diff --git a/tests/ui/abi/compatibility.rs b/tests/ui/abi/compatibility.rs index 4ffc81eb5f6f5..029de4a2676ef 100644 --- a/tests/ui/abi/compatibility.rs +++ b/tests/ui/abi/compatibility.rs @@ -63,7 +63,8 @@ #![feature(no_core, rustc_attrs, lang_items)] #![feature(unsized_fn_params, transparent_unions)] #![no_core] -#![allow(unused, improper_ctypes_definitions, internal_features)] +#![allow(unused, internal_features)] +#![allow(improper_ctypes_definitions, improper_ctypes)] // FIXME: some targets are broken in various ways. // Hence there are `cfg` throughout this test to disable parts of it on those targets. diff --git a/tests/ui/abi/extern/extern-pass-empty.rs b/tests/ui/abi/extern/extern-pass-empty.rs index 1ad52b128ad93..f38f76166bf27 100644 --- a/tests/ui/abi/extern/extern-pass-empty.rs +++ b/tests/ui/abi/extern/extern-pass-empty.rs @@ -1,5 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] // FIXME: this test is inherently not FFI-safe. +#![allow(improper_ctypes)] +// FIXME: this test is inherently not FFI-safe. // Test a foreign function that accepts empty struct. diff --git a/tests/ui/abi/foreign/foreign-fn-with-byval.rs b/tests/ui/abi/foreign/foreign-fn-with-byval.rs index 9908ec2d2c01a..dbf80385e15f7 100644 --- a/tests/ui/abi/foreign/foreign-fn-with-byval.rs +++ b/tests/ui/abi/foreign/foreign-fn-with-byval.rs @@ -1,5 +1,5 @@ //@ run-pass -#![allow(improper_ctypes, improper_ctypes_definitions)] +#![allow(improper_ctypes)] #[derive(Copy, Clone)] pub struct S { diff --git a/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs b/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs index 7d21307e1b2d9..0dfe91b95dd68 100644 --- a/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs +++ b/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs @@ -1,5 +1,5 @@ //@ check-pass -#![allow(improper_ctypes_definitions)] +#![allow(improper_ctypes_definitions, improper_ctypes)] #![feature(unsized_fn_params)] #![crate_type = "lib"] diff --git a/tests/ui/asm/naked-functions-ffi.stderr b/tests/ui/asm/naked-functions-ffi.stderr index f7893a3b8de98..ed31959e18565 100644 --- a/tests/ui/asm/naked-functions-ffi.stderr +++ b/tests/ui/asm/naked-functions-ffi.stderr @@ -6,7 +6,7 @@ LL | pub extern "C" fn naked(p: char) -> u128 { | = help: consider using `u32` or `libc::wchar_t` instead = note: the `char` type has no C equivalent - = note: `#[warn(improper_ctypes_definitions)]` on by default + = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: 1 warning emitted diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs index 77347b04ede83..b5824243215c4 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs @@ -31,7 +31,7 @@ pub fn test( ) { } -#[allow(improper_ctypes_definitions)] +#[allow(improper_ctypes)] struct Test { u128: extern "cmse-nonsecure-call" fn() -> u128, //~ ERROR [E0798] i128: extern "cmse-nonsecure-call" fn() -> i128, //~ ERROR [E0798] diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs index 419d26875bcd9..1a261f5df2af7 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs @@ -25,7 +25,7 @@ pub enum ReprTransparentEnumU64 { pub struct U32Compound(u16, u16); #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_ctypes)] pub fn params( f1: extern "cmse-nonsecure-call" fn(), f2: extern "cmse-nonsecure-call" fn(u32, u32, u32, u32), @@ -38,6 +38,7 @@ pub fn params( } #[no_mangle] +#[allow(improper_ctypes)] pub fn returns( f1: extern "cmse-nonsecure-call" fn() -> u32, f2: extern "cmse-nonsecure-call" fn() -> u64, diff --git a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs index 33d295f7ebe19..2ebdbf0e14d19 100644 --- a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs +++ b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs @@ -5,7 +5,7 @@ //@ normalize-stderr: "\[u8\]" -> "[i8 or u8 (arch dependant)]" type Foo = extern "C" fn(::std::ffi::CStr); -//~^ WARN `extern` fn uses type +//~^ WARN `extern` callback uses type extern "C" { fn meh(blah: Foo); //~^ WARN `extern` block uses type 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 044c1ae2dd42f..6dafe76292cac 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,4 +1,4 @@ -warning: `extern` fn uses type `CStr`, which is not FFI-safe +warning: `extern` callback 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); @@ -6,7 +6,7 @@ LL | type Foo = extern "C" fn(::std::ffi::CStr); | = 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 + = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: `extern` block uses type `CStr`, which is not FFI-safe --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:10:18 @@ -16,7 +16,6 @@ LL | fn meh(blah: Foo); | = 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/extern/extern-C-str-arg-ice-80125.rs b/tests/ui/extern/extern-C-str-arg-ice-80125.rs index 0908d6199efb8..571652b87369d 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.rs +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.rs @@ -1,7 +1,7 @@ // issue: rust-lang/rust#80125 //@ check-pass type ExternCallback = extern "C" fn(*const u8, u32, str); -//~^ WARN `extern` fn uses type `str`, which is not FFI-safe +//~^ WARN `extern` callback uses type `str`, which is not FFI-safe pub struct Struct(ExternCallback); diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr index ebd6cec6ecd3f..372d4ba2e1937 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr @@ -1,4 +1,4 @@ -warning: `extern` fn uses type `str`, which is not FFI-safe +warning: `extern` callback uses type `str`, which is not FFI-safe --> $DIR/extern-C-str-arg-ice-80125.rs:3:23 | LL | type ExternCallback = extern "C" fn(*const u8, u32, str); @@ -6,7 +6,7 @@ LL | type ExternCallback = extern "C" fn(*const u8, u32, str); | = help: consider using `*const u8` and a length instead = note: string slices have no C equivalent - = note: `#[warn(improper_ctypes_definitions)]` on by default + = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: `extern` fn uses type `str`, which is not FFI-safe --> $DIR/extern-C-str-arg-ice-80125.rs:9:44 @@ -16,6 +16,7 @@ LL | pub extern "C" fn register_something(bind: ExternCallback) -> Struct { | = help: consider using `*const u8` and a length instead = note: string slices have no C equivalent + = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: `extern` fn uses type `Struct`, which is not FFI-safe --> $DIR/extern-C-str-arg-ice-80125.rs:9:63 diff --git a/tests/ui/hashmap/hashmap-memory.rs b/tests/ui/hashmap/hashmap-memory.rs index 6db5d2e7bef35..7084e4bab3ff3 100644 --- a/tests/ui/hashmap/hashmap-memory.rs +++ b/tests/ui/hashmap/hashmap-memory.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes_definitions)] +#![allow(improper_ctypes)] #![allow(non_camel_case_types)] #![allow(dead_code)] #![allow(unused_mut)] diff --git a/tests/ui/issues/issue-51907.rs b/tests/ui/issues/issue-51907.rs index bf3f629df4970..808064fa02240 100644 --- a/tests/ui/issues/issue-51907.rs +++ b/tests/ui/issues/issue-51907.rs @@ -1,6 +1,8 @@ //@ run-pass trait Foo { + #[allow(improper_ctypes_definitions)] extern "C" fn borrow(&self); + #[allow(improper_ctypes_definitions)] extern "C" fn take(self: Box); } diff --git a/tests/ui/lint/clashing-extern-fn.stderr b/tests/ui/lint/clashing-extern-fn.stderr index 0c27547a6ed8f..e09ac4f71fbc8 100644 --- a/tests/ui/lint/clashing-extern-fn.stderr +++ b/tests/ui/lint/clashing-extern-fn.stderr @@ -6,7 +6,7 @@ LL | fn hidden_niche_transparent_no_niche() -> Option>>`, which is not FFI-safe --> $DIR/clashing-extern-fn.rs:487:46 diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.rs b/tests/ui/lint/extern-C-fnptr-lints-slices.rs index 0c35eb37a4890..08db0539ab4e7 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.rs +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.rs @@ -1,9 +1,9 @@ -#[deny(improper_ctypes_definitions)] +#[deny(improper_ctypes)] // It's an improper ctype (a slice) arg in an extern "C" fnptr. pub type F = extern "C" fn(&[u8]); -//~^ ERROR: `extern` fn uses type `[u8]`, which is not FFI-safe +//~^ ERROR: `extern` callback uses type `[u8]`, which is not FFI-safe fn main() {} diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr index d13f93ca96f22..2ac80150d7b15 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -1,4 +1,4 @@ -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/extern-C-fnptr-lints-slices.rs:5:14 | LL | pub type F = extern "C" fn(&[u8]); @@ -9,8 +9,8 @@ LL | pub type F = extern "C" fn(&[u8]); note: the lint level is defined here --> $DIR/extern-C-fnptr-lints-slices.rs:1:8 | -LL | #[deny(improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[deny(improper_ctypes)] + | ^^^^^^^^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/lint/improper-ctypes/lint-94223.rs b/tests/ui/lint/improper-ctypes/lint-94223.rs index ac24f61b0ac7a..0c8d531f69247 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.rs +++ b/tests/ui/lint/improper-ctypes/lint-94223.rs @@ -1,35 +1,35 @@ #![crate_type = "lib"] -#![deny(improper_ctypes_definitions)] +#![deny(improper_ctypes_definitions, improper_ctypes)] pub fn bad(f: extern "C" fn([u8])) {} -//~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe pub fn bad_twice(f: Result) {} -//~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe -//~^^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe +//~^^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe struct BadStruct(extern "C" fn([u8])); -//~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe enum BadEnum { A(extern "C" fn([u8])), - //~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe + //~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe } enum BadUnion { A(extern "C" fn([u8])), - //~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe + //~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe } type Foo = extern "C" fn([u8]); -//~^ ERROR `extern` fn uses type `[u8]`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe pub trait FooTrait { type FooType; } pub type Foo2 = extern "C" fn(Option<&::FooType>); -//~^ ERROR `extern` fn uses type `Option<&::FooType>`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `Option<&::FooType>`, which is not FFI-safe pub struct FfiUnsafe; @@ -39,11 +39,11 @@ extern "C" fn f(_: FfiUnsafe) { } pub static BAD: extern "C" fn(FfiUnsafe) = f; -//~^ ERROR `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `FfiUnsafe`, which is not FFI-safe pub static BAD_TWICE: Result = Ok(f); -//~^ ERROR `extern` fn uses type `FfiUnsafe`, which is not FFI-safe -//~^^ ERROR `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `FfiUnsafe`, which is not FFI-safe +//~^^ ERROR `extern` callback uses type `FfiUnsafe`, which is not FFI-safe pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; -//~^ ERROR `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +//~^ ERROR `extern` callback uses type `FfiUnsafe`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/lint-94223.stderr b/tests/ui/lint/improper-ctypes/lint-94223.stderr index 008debf8f010a..db05f545f106e 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/lint-94223.stderr @@ -1,4 +1,4 @@ -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:4:15 | LL | pub fn bad(f: extern "C" fn([u8])) {} @@ -7,12 +7,12 @@ LL | pub fn bad(f: extern "C" fn([u8])) {} = help: consider using a raw pointer instead = note: slices have no C equivalent note: the lint level is defined here - --> $DIR/lint-94223.rs:2:9 + --> $DIR/lint-94223.rs:2:38 | -LL | #![deny(improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #![deny(improper_ctypes_definitions, improper_ctypes)] + | ^^^^^^^^^^^^^^^ -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:7:28 | LL | pub fn bad_twice(f: Result) {} @@ -21,7 +21,7 @@ LL | pub fn bad_twice(f: Result) {} = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:7:49 | LL | pub fn bad_twice(f: Result) {} @@ -30,7 +30,7 @@ LL | pub fn bad_twice(f: Result) {} = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:11:18 | LL | struct BadStruct(extern "C" fn([u8])); @@ -39,7 +39,7 @@ LL | struct BadStruct(extern "C" fn([u8])); = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:15:7 | LL | A(extern "C" fn([u8])), @@ -48,7 +48,7 @@ LL | A(extern "C" fn([u8])), = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:20:7 | LL | A(extern "C" fn([u8])), @@ -57,7 +57,7 @@ LL | A(extern "C" fn([u8])), = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `[u8]`, which is not FFI-safe --> $DIR/lint-94223.rs:24:12 | LL | type Foo = extern "C" fn([u8]); @@ -66,7 +66,7 @@ LL | type Foo = extern "C" fn([u8]); = help: consider using a raw pointer instead = note: slices have no C equivalent -error: `extern` fn uses type `Option<&::FooType>`, which is not FFI-safe +error: `extern` callback uses type `Option<&::FooType>`, which is not FFI-safe --> $DIR/lint-94223.rs:31:20 | LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); @@ -75,7 +75,7 @@ LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe --> $DIR/lint-94223.rs:41:17 | LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; @@ -89,7 +89,7 @@ note: the type is defined here LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ -error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe --> $DIR/lint-94223.rs:44:30 | LL | pub static BAD_TWICE: Result = Ok(f); @@ -103,7 +103,7 @@ note: the type is defined here LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ -error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe --> $DIR/lint-94223.rs:44:56 | LL | pub static BAD_TWICE: Result = Ok(f); @@ -117,7 +117,7 @@ note: the type is defined here LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ -error: `extern` fn uses type `FfiUnsafe`, which is not FFI-safe +error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe --> $DIR/lint-94223.rs:48:22 | LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; diff --git a/tests/ui/lint/improper-ctypes/lint-fn.rs b/tests/ui/lint/improper-ctypes/lint-fn.rs index 0b84098e39067..de2ea99e36648 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.rs +++ b/tests/ui/lint/improper-ctypes/lint-fn.rs @@ -1,5 +1,5 @@ #![allow(private_interfaces)] -#![deny(improper_ctypes_definitions)] +#![deny(improper_ctypes_definitions, improper_ctypes)] use std::default::Default; use std::marker::PhantomData; diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index 34e3bd021b92d..a78b86ad3a4ba 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -9,7 +9,7 @@ LL | pub extern "C" fn slice_type(p: &[u32]) { } note: the lint level is defined here --> $DIR/lint-fn.rs:2:9 | -LL | #![deny(improper_ctypes_definitions)] +LL | #![deny(improper_ctypes_definitions, improper_ctypes)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `str`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/mustpass-113436.rs b/tests/ui/lint/improper-ctypes/mustpass-113436.rs index d5acdc45f92e5..83afaa24d2ef9 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-113436.rs +++ b/tests/ui/lint/improper-ctypes/mustpass-113436.rs @@ -1,5 +1,5 @@ //@ check-pass -#![deny(improper_ctypes_definitions)] +#![deny(improper_ctypes_definitions, improper_ctypes)] #[repr(C)] pub struct Wrap(T); diff --git a/tests/ui/lint/improper-ctypes/mustpass-134060.stderr b/tests/ui/lint/improper-ctypes/mustpass-134060.stderr index 791b2f7370983..9b2de49a7eb51 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-134060.stderr +++ b/tests/ui/lint/improper-ctypes/mustpass-134060.stderr @@ -6,7 +6,7 @@ LL | extern "C" fn foo_(&self, _: ()) -> i64 { | = help: consider using a struct instead = note: tuples have unspecified layout - = note: `#[warn(improper_ctypes_definitions)]` on by default + = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: 1 warning emitted diff --git a/tests/ui/repr/repr-transparent-issue-87496.stderr b/tests/ui/repr/repr-transparent-issue-87496.stderr index aee31212b4ed2..f55024749a688 100644 --- a/tests/ui/repr/repr-transparent-issue-87496.stderr +++ b/tests/ui/repr/repr-transparent-issue-87496.stderr @@ -10,7 +10,7 @@ note: the type is defined here | LL | struct TransparentCustomZst(()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: `#[warn(improper_ctypes)]` on by default + = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: 1 warning emitted From 8c9ac2bbd17f75f8868cd5d7418d429f3cbf4c10 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Wed, 27 Aug 2025 21:13:09 +0200 Subject: [PATCH 06/20] ImproperCTypes: change handling of FnPtrs Notably, those FnPtrs are treated as "the item impacted by the error", instead of the functions/structs making use of them. --- compiler/rustc_lint/messages.ftl | 1 + .../rustc_lint/src/types/improper_ctypes.rs | 174 +++++++----------- .../extern-C-non-FFI-safe-arg-ice-52334.rs | 1 - ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 15 +- tests/ui/extern/extern-C-str-arg-ice-80125.rs | 3 +- .../extern/extern-C-str-arg-ice-80125.stderr | 17 +- .../lint/extern-C-fnptr-lints-slices.stderr | 4 +- .../ui/lint/improper-ctypes/lint-94223.stderr | 48 ++--- tests/ui/lint/improper-ctypes/lint-ctypes.rs | 4 +- .../lint/improper-ctypes/lint-ctypes.stderr | 20 +- tests/ui/lint/improper-ctypes/lint-fn.rs | 11 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 14 +- 12 files changed, 123 insertions(+), 189 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 14b5b7290b4f8..4451ce7e5ee19 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -362,6 +362,7 @@ lint_improper_ctypes_enum_repr_reason = enum has no representation hint lint_improper_ctypes_fnptr_help = consider using an `extern fn(...) -> ...` function pointer instead lint_improper_ctypes_fnptr_reason = this function pointer has Rust-specific calling convention + lint_improper_ctypes_non_exhaustive = this enum is non-exhaustive lint_improper_ctypes_non_exhaustive_variant = this enum has non-exhaustive variants diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 9622babd5596a..fd672b4a3f68d 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -10,8 +10,8 @@ use rustc_hir::intravisit::VisitorExt; use rustc_hir::{self as hir, AmbigArg}; use rustc_middle::bug; use rustc_middle::ty::{ - self, Adt, AdtDef, AdtKind, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, - TypeVisitableExt, + self, Adt, AdtDef, AdtKind, Binder, FnSig, GenericArgsRef, Ty, TyCtxt, TypeSuperVisitable, + TypeVisitable, TypeVisitableExt, }; use rustc_session::{declare_lint, declare_lint_pass}; use rustc_span::def_id::LocalDefId; @@ -144,6 +144,28 @@ declare_lint_pass!(ImproperCTypesLint => [ USES_POWER_ALIGNMENT, ]); +type Sig<'tcx> = Binder<'tcx, FnSig<'tcx>>; + +/// Extract (binder-wrapped) FnSig object from a FnPtr's mir::Ty +fn get_fn_sig_from_mir_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Sig<'tcx> { + let ty = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty).unwrap_or(ty); + match *ty.kind() { + ty::FnPtr(sig_tys, hdr) => { + let sig = sig_tys.with(hdr); + if sig.abi().is_rustic_abi() { + bug!( + "expected to inspect the type of an `extern \"ABI\"` FnPtr, not an internal-ABI one" + ) + } else { + sig + } + } + r @ _ => { + bug!("expected to inspect the type of an `extern \"ABI\"` FnPtr, not {:?}", r,) + } + } +} + /// Getting the (normalized) type out of a field (for, e.g., an enum variant or a tuple). #[inline] fn get_type_from_field<'tcx>( @@ -503,9 +525,6 @@ bitflags! { #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum OuterTyKind { None, - /// A variant that should not exist, - /// but is needed because we don't change the lint's behavior yet - NoneThroughFnPtr, /// For struct/enum/union fields AdtField, /// Placeholder for properties that will be used eventually @@ -516,10 +535,10 @@ impl OuterTyKind { /// Computes the relationship by providing the containing mir::Ty itself fn from_outer_ty<'tcx>(ty: Ty<'tcx>) -> Self { match ty.kind() { - ty::FnPtr(..) => Self::NoneThroughFnPtr, + ty::FnPtr(..) => Self::None, ty::Adt(..) => { if ty.boxed_ty().is_some() { - Self::UnusedVarient + Self::UnusedVariant } else { Self::AdtField } @@ -572,20 +591,6 @@ impl VisitorState { depth: self.depth + 1, } } - /// From an existing state, compute the state of any subtype of the current type. - /// (Case where the current type is a function pointer, - /// meaning we need to specify if the subtype is an argument or the return.) - fn get_next_in_fnptr<'tcx>(&self, current_ty: Ty<'tcx>, fn_pos: FnPos) -> Self { - assert!(matches!(current_ty.kind(), ty::FnPtr(..))); - Self { - root_use_flags: match fn_pos { - FnPos::Ret => RootUseFlags::RETURN_TY_IN_FNPTR, - FnPos::Arg => RootUseFlags::ARGUMENT_TY_IN_FNPTR, - }, - outer_ty_kind: OuterTyKind::from_outer_ty(current_ty), - depth: self.depth + 1, - } - } /// Generate the state for an "outermost" type that needs to be checked fn entry_point(root_use_flags: RootUseFlags) -> Self { @@ -597,11 +602,10 @@ impl VisitorState { let p_flags = match (fn_mode, fn_pos) { (CItemKind::ExportedFunction, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DEFINITION, (CItemKind::ImportedExtern, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_DECLARATION, + (CItemKind::Callback, FnPos::Ret) => RootUseFlags::RETURN_TY_IN_FNPTR, (CItemKind::ExportedFunction, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DEFINITION, (CItemKind::ImportedExtern, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DECLARATION, - // we could also deal with CItemKind::Callback, - // but we bake an assumption from this function's call sites here. - _ => bug!("cannot be called with CItemKind::{:?}", fn_mode), + (CItemKind::Callback, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_FNPTR, }; Self::entry_point(p_flags) } @@ -630,14 +634,12 @@ impl VisitorState { /// Whether the type is directly used in a function, in return position. fn is_direct_function_return(&self) -> bool { - matches!(self.outer_ty_kind, OuterTyKind::None | OuterTyKind::NoneThroughFnPtr) - && self.is_in_function_return() + matches!(self.outer_ty_kind, OuterTyKind::None) && self.is_in_function_return() } /// Whether the type itself is the type of a function argument or return type. fn is_direct_in_function(&self) -> bool { - matches!(self.outer_ty_kind, OuterTyKind::None | OuterTyKind::NoneThroughFnPtr) - && self.is_in_function() + matches!(self.outer_ty_kind, OuterTyKind::None) && self.is_in_function() } /// Whether the type is used (directly or not) in a defined function. @@ -677,12 +679,11 @@ struct ImproperCTypesVisitor<'a, 'tcx> { /// The original type being checked, before we recursed /// to any other types it contains. base_ty: Ty<'tcx>, - base_fn_mode: CItemKind, } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>, base_ty: Ty<'tcx>, base_fn_mode: CItemKind) -> Self { - Self { cx, base_ty, base_fn_mode, cache: FxHashSet::default() } + fn new(cx: &'a LateContext<'tcx>, base_ty: Ty<'tcx>) -> Self { + Self { cx, base_ty, cache: FxHashSet::default() } } /// Checks if the given indirection (box,ref,pointer) is "ffi-safe". @@ -700,14 +701,10 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { IndirectionKind::Box => { // FIXME(ctypes): this logic is broken, but it still fits the current tests: // - for some reason `Box<_>`es in `extern "ABI" {}` blocks - // (including within FnPtr:s) // are not treated as pointers but as FFI-unsafe structs // - otherwise, treat the box itself correctly, and follow pointee safety logic // as described in the other `indirection_type` match branch. - if state.is_in_defined_function() - || (state.is_in_fnptr() - && matches!(self.base_fn_mode, CItemKind::ExportedFunction)) - { + if state.is_in_defined_function() || state.is_in_fnptr() { if inner_ty.is_sized(tcx, self.cx.typing_env()) { return FfiSafe; } else { @@ -1020,10 +1017,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } ty::Array(inner_ty, _) => { - if state.is_direct_in_function() - // FIXME(ctypes): VVV-this-VVV shouldn't be part of the check - && !matches!(state.outer_ty_kind, OuterTyKind::NoneThroughFnPtr) - { + if state.is_direct_in_function() { // C doesn't really support passing arrays by value - the only way to pass an array by value // is through a struct. FfiResult::new_with_reason( @@ -1038,26 +1032,21 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } + // fnptrs are a special case, they always need to be treated as + // "the element rendered unsafe" because their unsafety doesn't affect + // their surroundings, and their type is often declared inline + // as a result, don't go into them when scanning for the safety of something else ty::FnPtr(sig_tys, hdr) => { let sig = sig_tys.with(hdr); if sig.abi().is_rustic_abi() { - return FfiResult::new_with_reason( + FfiResult::new_with_reason( ty, fluent::lint_improper_ctypes_fnptr_reason, Some(fluent::lint_improper_ctypes_fnptr_help), - ); - } - - let sig = tcx.instantiate_bound_regions_with_erased(sig); - for arg in sig.inputs() { - match self.visit_type(state.get_next_in_fnptr(ty, FnPos::Arg), *arg) { - FfiSafe => {} - r => return r, - } + ) + } else { + FfiSafe } - - let ret_ty = sig.output(); - self.visit_type(state.get_next_in_fnptr(ty, FnPos::Ret), ret_ty) } ty::Foreign(..) => FfiSafe, @@ -1122,7 +1111,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if let Some(res) = self.visit_for_opaque_ty(ty) { return res; } - self.visit_type(state, ty) } } @@ -1133,27 +1121,25 @@ impl<'tcx> ImproperCTypesLint { fn check_type_for_external_abi_fnptr( &mut self, cx: &LateContext<'tcx>, - state: VisitorState, - hir_ty: &hir::Ty<'tcx>, + hir_ty: &'tcx hir::Ty<'tcx>, ty: Ty<'tcx>, - fn_mode: CItemKind, ) { struct FnPtrFinder<'tcx> { current_depth: usize, depths: Vec, - spans: Vec, + decls: Vec<&'tcx hir::FnDecl<'tcx>>, tys: Vec>, } - impl<'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'tcx> { - fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) { + impl<'tcx> hir::intravisit::Visitor<'tcx> for FnPtrFinder<'tcx> { + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, AmbigArg>) { debug!(?ty); self.current_depth += 1; - if let hir::TyKind::FnPtr(hir::FnPtrTy { abi, .. }) = ty.kind + if let hir::TyKind::FnPtr(hir::FnPtrTy { abi, decl, .. }) = ty.kind && !abi.is_rustic_abi() { + self.decls.push(*decl); self.depths.push(self.current_depth); - self.spans.push(ty.span); } hir::intravisit::walk_ty(self, ty); @@ -1176,8 +1162,8 @@ impl<'tcx> ImproperCTypesLint { } let mut visitor = FnPtrFinder { - spans: Vec::new(), tys: Vec::new(), + decls: Vec::new(), depths: Vec::new(), current_depth: 0, }; @@ -1186,15 +1172,11 @@ impl<'tcx> ImproperCTypesLint { let all_types = iter::zip( visitor.depths.drain(..), - iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)), + iter::zip(visitor.tys.drain(..), visitor.decls.drain(..)), ); - for (depth, (fn_ptr_ty, span)) in all_types { - let mut visitor = ImproperCTypesVisitor::new(cx, fn_ptr_ty, fn_mode); - let bridge_state = VisitorState { depth, ..state }; - // FIXME(ctypes): make a check_for_fnptr - let ffi_res = visitor.check_type(bridge_state, fn_ptr_ty); - - self.process_ffi_result(cx, span, ffi_res, CItemKind::Callback); + for (depth, (fn_ptr_ty, decl)) in all_types { + let mir_sig = get_fn_sig_from_mir_ty(cx, fn_ptr_ty); + self.check_foreign_fn(cx, CItemKind::Callback, mir_sig, decl, depth); } } @@ -1203,7 +1185,6 @@ impl<'tcx> ImproperCTypesLint { fn check_fn_for_external_abi_fnptr( &mut self, cx: &LateContext<'tcx>, - fn_mode: CItemKind, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>, ) { @@ -1211,13 +1192,11 @@ impl<'tcx> ImproperCTypesLint { let sig = cx.tcx.instantiate_bound_regions_with_erased(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - let state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Arg); - self.check_type_for_external_abi_fnptr(cx, state, input_hir, *input_ty, fn_mode); + self.check_type_for_external_abi_fnptr(cx, input_hir, *input_ty); } if let hir::FnRetTy::Return(ret_hir) = decl.output { - let state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Ret); - self.check_type_for_external_abi_fnptr(cx, state, ret_hir, sig.output(), fn_mode); + self.check_type_for_external_abi_fnptr(cx, ret_hir, sig.output()); } } @@ -1238,9 +1217,10 @@ impl<'tcx> ImproperCTypesLint { check_struct_for_power_alignment(cx, item, adt_def); } + /// Check that an extern "ABI" static variable is of a ffi-safe type. fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) { let ty = cx.tcx.type_of(id).instantiate_identity(); - let mut visitor = ImproperCTypesVisitor::new(cx, ty, CItemKind::ImportedExtern); + let mut visitor = ImproperCTypesVisitor::new(cx, ty); let ffi_res = visitor.check_type(VisitorState::static_var(), ty); self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern); } @@ -1250,22 +1230,24 @@ impl<'tcx> ImproperCTypesLint { &mut self, cx: &LateContext<'tcx>, fn_mode: CItemKind, - def_id: LocalDefId, + sig: Sig<'tcx>, decl: &'tcx hir::FnDecl<'_>, + depth: usize, ) { - let sig = cx.tcx.fn_sig(def_id).instantiate_identity(); let sig = cx.tcx.instantiate_bound_regions_with_erased(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { - let state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Arg); - let mut visitor = ImproperCTypesVisitor::new(cx, *input_ty, fn_mode); + let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Arg); + state.depth = depth; + let mut visitor = ImproperCTypesVisitor::new(cx, *input_ty); let ffi_res = visitor.check_type(state, *input_ty); self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode); } if let hir::FnRetTy::Return(ret_hir) = decl.output { - let state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Ret); - let mut visitor = ImproperCTypesVisitor::new(cx, sig.output(), fn_mode); + let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Ret); + state.depth = depth; + let mut visitor = ImproperCTypesVisitor::new(cx, sig.output()); let ffi_res = visitor.check_type(state, sig.output()); self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode); } @@ -1391,20 +1373,10 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // fnptrs are a special case, they always need to be treated as // "the element rendered unsafe" because their unsafety doesn't affect // their surroundings, and their type is often declared inline + self.check_fn_for_external_abi_fnptr(cx, it.owner_id.def_id, sig.decl); + let mir_sig = cx.tcx.fn_sig(it.owner_id.def_id).instantiate_identity(); if !abi.is_rustic_abi() { - self.check_foreign_fn( - cx, - CItemKind::ImportedExtern, - it.owner_id.def_id, - sig.decl, - ); - } else { - self.check_fn_for_external_abi_fnptr( - cx, - CItemKind::ImportedExtern, - it.owner_id.def_id, - sig.decl, - ); + self.check_foreign_fn(cx, CItemKind::ImportedExtern, mir_sig, sig.decl, 0); } } hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => { @@ -1421,10 +1393,8 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { | hir::ItemKind::TyAlias(_, _, ty) => { self.check_type_for_external_abi_fnptr( cx, - VisitorState::static_var(), ty, cx.tcx.type_of(item.owner_id).instantiate_identity(), - CItemKind::ExportedFunction, // TODO: for some reason, this is the value that reproduces old behaviour ); } // See `check_fn` for declarations, `check_foreign_items` for definitions in extern blocks @@ -1455,10 +1425,8 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) { self.check_type_for_external_abi_fnptr( cx, - VisitorState::static_var(), field.ty, cx.tcx.type_of(field.def_id).instantiate_identity(), - CItemKind::ImportedExtern, ); } @@ -1482,10 +1450,10 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // fnptrs are a special case, they always need to be treated as // "the element rendered unsafe" because their unsafety doesn't affect // their surroundings, and their type is often declared inline + self.check_fn_for_external_abi_fnptr(cx, id, decl); + let mir_sig = cx.tcx.fn_sig(id).instantiate_identity(); if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::ExportedFunction, id, decl); - } else { - self.check_fn_for_external_abi_fnptr(cx, CItemKind::ExportedFunction, id, decl); + self.check_foreign_fn(cx, CItemKind::ExportedFunction, mir_sig, decl, 0); } } } diff --git a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs index 2ebdbf0e14d19..7dc2421bb9e43 100644 --- a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs +++ b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs @@ -8,7 +8,6 @@ type Foo = extern "C" fn(::std::ffi::CStr); //~^ WARN `extern` callback uses type extern "C" { fn meh(blah: Foo); - //~^ WARN `extern` block uses type } fn main() { 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 6dafe76292cac..0020ae2b8d80b 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,12 @@ warning: `extern` callback uses type `CStr`, which is not FFI-safe - --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:7:12 + --> $DIR/extern-C-non-FFI-safe-arg-ice-52334.rs:7:26 | LL | type Foo = extern "C" fn(::std::ffi::CStr); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^^^^^^^^ 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: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default -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 passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` - = note: `CStr`/`CString` do not have a guaranteed layout - -warning: 2 warnings emitted +warning: 1 warning emitted diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.rs b/tests/ui/extern/extern-C-str-arg-ice-80125.rs index 571652b87369d..1c1abbe996839 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.rs +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.rs @@ -7,8 +7,7 @@ pub struct Struct(ExternCallback); #[no_mangle] pub extern "C" fn register_something(bind: ExternCallback) -> Struct { -//~^ WARN `extern` fn uses type `str`, which is not FFI-safe -//~^^ WARN `extern` fn uses type `Struct`, which is not FFI-safe +//~^ WARN `extern` fn uses type `Struct`, which is not FFI-safe Struct(bind) } diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr index 372d4ba2e1937..6eded6a78cb74 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr @@ -1,23 +1,13 @@ warning: `extern` callback uses type `str`, which is not FFI-safe - --> $DIR/extern-C-str-arg-ice-80125.rs:3:23 + --> $DIR/extern-C-str-arg-ice-80125.rs:3:53 | LL | type ExternCallback = extern "C" fn(*const u8, u32, str); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^ not FFI-safe | = help: consider using `*const u8` and a length instead = note: string slices have no C equivalent = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default -warning: `extern` fn uses type `str`, which is not FFI-safe - --> $DIR/extern-C-str-arg-ice-80125.rs:9:44 - | -LL | pub extern "C" fn register_something(bind: ExternCallback) -> Struct { - | ^^^^^^^^^^^^^^ not FFI-safe - | - = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent - = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default - warning: `extern` fn uses type `Struct`, which is not FFI-safe --> $DIR/extern-C-str-arg-ice-80125.rs:9:63 | @@ -31,6 +21,7 @@ note: the type is defined here | LL | pub struct Struct(ExternCallback); | ^^^^^^^^^^^^^^^^^ + = note: `#[warn(improper_ctypes_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default -warning: 3 warnings emitted +warning: 2 warnings emitted diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr index 2ac80150d7b15..6a36ba6c28d6f 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -1,8 +1,8 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/extern-C-fnptr-lints-slices.rs:5:14 + --> $DIR/extern-C-fnptr-lints-slices.rs:5:28 | LL | pub type F = extern "C" fn(&[u8]); - | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent diff --git a/tests/ui/lint/improper-ctypes/lint-94223.stderr b/tests/ui/lint/improper-ctypes/lint-94223.stderr index db05f545f106e..f079c2705e7cd 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/lint-94223.stderr @@ -1,8 +1,8 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:4:15 + --> $DIR/lint-94223.rs:4:29 | LL | pub fn bad(f: extern "C" fn([u8])) {} - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent @@ -13,73 +13,73 @@ LL | #![deny(improper_ctypes_definitions, improper_ctypes)] | ^^^^^^^^^^^^^^^ error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:7:28 + --> $DIR/lint-94223.rs:7:42 | LL | pub fn bad_twice(f: Result) {} - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:7:49 + --> $DIR/lint-94223.rs:7:63 | LL | pub fn bad_twice(f: Result) {} - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:11:18 + --> $DIR/lint-94223.rs:11:32 | LL | struct BadStruct(extern "C" fn([u8])); - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:15:7 + --> $DIR/lint-94223.rs:15:21 | LL | A(extern "C" fn([u8])), - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:20:7 + --> $DIR/lint-94223.rs:20:21 | LL | A(extern "C" fn([u8])), - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:24:12 + --> $DIR/lint-94223.rs:24:26 | LL | type Foo = extern "C" fn([u8]); - | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | = help: consider using a raw pointer instead = note: slices have no C equivalent error: `extern` callback uses type `Option<&::FooType>`, which is not FFI-safe - --> $DIR/lint-94223.rs:31:20 + --> $DIR/lint-94223.rs:31:34 | LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:41:17 + --> $DIR/lint-94223.rs:41:31 | LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; - | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^ not FFI-safe | = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout @@ -90,10 +90,10 @@ LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:44:30 + --> $DIR/lint-94223.rs:44:44 | LL | pub static BAD_TWICE: Result = Ok(f); - | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^ not FFI-safe | = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout @@ -104,10 +104,10 @@ LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:44:56 + --> $DIR/lint-94223.rs:44:70 | LL | pub static BAD_TWICE: Result = Ok(f); - | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^ not FFI-safe | = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout @@ -118,10 +118,10 @@ LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:48:22 + --> $DIR/lint-94223.rs:48:36 | LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; - | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^ not FFI-safe | = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 7dc06079fa32c..8e4de47d0c8e5 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -63,9 +63,9 @@ extern "C" { -> ::std::marker::PhantomData; //~ ERROR uses type `PhantomData` pub fn fn_type(p: RustFn); //~ ERROR uses type `fn()` pub fn fn_type2(p: fn()); //~ ERROR uses type `fn()` - pub fn fn_contained(p: RustBadRet); //~ ERROR: uses type `Box` + pub fn fn_contained(p: RustBadRet); pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `str` - pub fn transparent_fn(p: TransparentBadFn); //~ ERROR: uses type `Box` + pub fn transparent_fn(p: TransparentBadFn); pub fn raw_array(arr: [u8; 8]); //~ ERROR: uses type `[u8; 8]` pub fn no_niche_a(a: Option>); diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index 6f8b951c53d70..b11db19a0c1d5 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -155,15 +155,6 @@ LL | pub fn fn_type2(p: fn()); = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has Rust-specific calling convention -error: `extern` block uses type `Box`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:66:28 - | -LL | pub fn fn_contained(p: RustBadRet); - | ^^^^^^^^^^ 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 `str`, which is not FFI-safe --> $DIR/lint-ctypes.rs:67:31 | @@ -173,15 +164,6 @@ LL | pub fn transparent_str(p: TransparentStr); = help: consider using `*const u8` and a length instead = note: string slices have no C equivalent -error: `extern` block uses type `Box`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:68:30 - | -LL | pub fn transparent_fn(p: TransparentBadFn); - | ^^^^^^^^^^^^^^^^ 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 `[u8; 8]`, which is not FFI-safe --> $DIR/lint-ctypes.rs:69:27 | @@ -209,5 +191,5 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 21 previous errors +error: aborting due to 19 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint-fn.rs b/tests/ui/lint/improper-ctypes/lint-fn.rs index de2ea99e36648..8fca9fd927f98 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.rs +++ b/tests/ui/lint/improper-ctypes/lint-fn.rs @@ -1,5 +1,5 @@ #![allow(private_interfaces)] -#![deny(improper_ctypes_definitions, improper_ctypes)] +#![deny(improper_ctypes, improper_ctypes_definitions)] use std::default::Default; use std::marker::PhantomData; @@ -113,19 +113,22 @@ pub extern "C" fn fn_type2(p: fn()) { } //~^ ERROR uses type `fn()` pub extern "C" fn fn_contained(p: RustBadRet) { } +// ^ FIXME it doesn't see the error... but at least it reports it elsewhere? pub extern "C" fn transparent_str(p: TransparentStr) { } //~^ ERROR: uses type `str` pub extern "C" fn transparent_fn(p: TransparentBadFn) { } +// ^ possible FIXME: it doesn't see the actual FnPtr's error... +// but at least it reports it elsewhere? pub extern "C" fn good3(fptr: Option) { } -pub extern "C" fn good4(aptr: &[u8; 4 as usize]) { } +pub extern "C" fn argument_with_assumptions_4(aptr: &[u8; 4 as usize]) { } pub extern "C" fn good5(s: StructWithProjection) { } -pub extern "C" fn good6(s: StructWithProjectionAndLifetime) { } +pub extern "C" fn argument_with_assumptions_6(s: StructWithProjectionAndLifetime) { } pub extern "C" fn good7(fptr: extern "C" fn() -> ()) { } @@ -141,7 +144,7 @@ pub extern "C" fn good12(size: usize) { } pub extern "C" fn good13(n: TransparentInt) { } -pub extern "C" fn good14(p: TransparentRef) { } +pub extern "C" fn argument_with_assumptions_14(p: TransparentRef) { } pub extern "C" fn good15(p: TransparentLifetime) { } diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index a78b86ad3a4ba..8287cb44a80ff 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -7,10 +7,10 @@ LL | pub extern "C" fn slice_type(p: &[u32]) { } = help: consider using a raw pointer instead = note: slices have no C equivalent note: the lint level is defined here - --> $DIR/lint-fn.rs:2:9 + --> $DIR/lint-fn.rs:2:26 | -LL | #![deny(improper_ctypes_definitions, improper_ctypes)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `str`, which is not FFI-safe --> $DIR/lint-fn.rs:73:31 @@ -126,7 +126,7 @@ LL | pub extern "C" fn fn_type2(p: fn()) { } = note: this function pointer has Rust-specific calling convention error: `extern` fn uses type `str`, which is not FFI-safe - --> $DIR/lint-fn.rs:117:38 + --> $DIR/lint-fn.rs:118:38 | LL | pub extern "C" fn transparent_str(p: TransparentStr) { } | ^^^^^^^^^^^^^^ not FFI-safe @@ -135,7 +135,7 @@ LL | pub extern "C" fn transparent_str(p: TransparentStr) { } = note: string slices have no C equivalent error: `extern` fn uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-fn.rs:169:43 + --> $DIR/lint-fn.rs:172:43 | LL | pub extern "C" fn unused_generic2() -> PhantomData { | ^^^^^^^^^^^^^^^^^ not FFI-safe @@ -143,7 +143,7 @@ LL | pub extern "C" fn unused_generic2() -> PhantomData { = note: composed only of `PhantomData` error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-fn.rs:182:39 + --> $DIR/lint-fn.rs:185:39 | LL | pub extern "C" fn used_generic4(x: Vec) { } | ^^^^^^ not FFI-safe @@ -152,7 +152,7 @@ LL | pub extern "C" fn used_generic4(x: Vec) { } = note: this struct has unspecified layout error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-fn.rs:185:41 + --> $DIR/lint-fn.rs:188:41 | LL | pub extern "C" fn used_generic5() -> Vec { | ^^^^^^ not FFI-safe From 7c8cc228a8333fef262e53b155f4b449f92b3ed1 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Tue, 26 Aug 2025 23:41:01 +0200 Subject: [PATCH 07/20] ImproperCTypes: change cstr linting another user-visible change: change the messaging and help around CStr/CString lints --- compiler/rustc_lint/messages.ftl | 12 ++- .../rustc_lint/src/types/improper_ctypes.rs | 88 +++++++++++++++---- ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 2 +- tests/ui/lint/improper-ctypes/lint-cstr.rs | 32 ++++--- .../ui/lint/improper-ctypes/lint-cstr.stderr | 58 ++++++++---- 5 files changed, 136 insertions(+), 56 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 4451ce7e5ee19..a8398b69b792f 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -349,8 +349,10 @@ 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_help_const = + consider passing a `*const std::ffi::c_char` instead, converting to/from `{$ty}` as needed +lint_improper_ctypes_cstr_help_mut = + consider passing a `*mut std::ffi::c_char` instead, converting to/from `{$ty}` as needed lint_improper_ctypes_cstr_reason = `CStr`/`CString` do not have a guaranteed layout lint_improper_ctypes_dyn = trait objects have no C equivalent @@ -373,8 +375,8 @@ lint_improper_ctypes_opaque = opaque types have no C equivalent lint_improper_ctypes_slice_help = consider using a raw pointer instead lint_improper_ctypes_slice_reason = slices have no C equivalent -lint_improper_ctypes_str_help = consider using `*const u8` and a length instead +lint_improper_ctypes_str_help = consider using `*const u8` and a length instead lint_improper_ctypes_str_reason = string slices have no C equivalent lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct @@ -396,6 +398,10 @@ lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[re lint_improper_ctypes_union_layout_reason = this union has unspecified layout lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive +lint_improper_ctypes_unsized_box = this box for an unsized type contains metadata, which makes it incompatible with a C pointer +lint_improper_ctypes_unsized_ptr = this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer +lint_improper_ctypes_unsized_ref = this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + lint_int_to_ptr_transmutes = transmuting an integer to a pointer creates a pointer without provenance .note = this is dangerous because dereferencing the resulting pointer is undefined behavior .note_exposed_provenance = exposed provenance semantics can be used to create a pointer based on some previously exposed provenance diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index fd672b4a3f68d..acc10d19b60b4 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -356,7 +356,6 @@ impl<'tcx> FfiResult<'tcx> { /// If the FfiUnsafe variant, 'wraps' all reasons, /// creating new `FfiUnsafeReason`s, putting the originals as their `inner` fields. /// Otherwise, keep unchanged. - #[expect(unused)] fn wrap_all(self, ty: Ty<'tcx>, note: DiagMessage, help: Option) -> Self { match self { Self::FfiUnsafe(this) => { @@ -525,6 +524,12 @@ bitflags! { #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum OuterTyKind { None, + /// Pointee through ref, raw pointer or Box + /// (we don't need to distinguish the ownership of Box specifically) + Pointee { + mutable: hir::Mutability, + raw: bool, + }, /// For struct/enum/union fields AdtField, /// Placeholder for properties that will be used eventually @@ -536,16 +541,17 @@ impl OuterTyKind { fn from_outer_ty<'tcx>(ty: Ty<'tcx>) -> Self { match ty.kind() { ty::FnPtr(..) => Self::None, + k @ (ty::Ref(_, _, mutable) | ty::RawPtr(_, mutable)) => { + Self::Pointee { raw: matches!(k, ty::RawPtr(..)), mutable: *mutable } + } ty::Adt(..) => { if ty.boxed_ty().is_some() { - Self::UnusedVariant + Self::Pointee { raw: false, mutable: hir::Mutability::Mut } } else { Self::AdtField } } - ty::RawPtr(..) | ty::Ref(..) | ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => { - Self::UnusedVariant - } + ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => Self::UnusedVariant, k @ _ => bug!("Unexpected outer type {:?} of kind {:?}", ty, k), } } @@ -666,6 +672,11 @@ impl VisitorState { fn is_field(&self) -> bool { matches!(self.outer_ty_kind, OuterTyKind::AdtField) } + + /// Whether the current type is behind a pointer that doesn't allow mutating this + fn is_nonmut_pointee(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::Pointee { mutable: hir::Mutability::Not, .. }) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -676,14 +687,29 @@ struct ImproperCTypesVisitor<'a, 'tcx> { /// To prevent problems with recursive types, /// add a types-in-check cache. cache: FxHashSet>, - /// The original type being checked, before we recursed - /// to any other types it contains. - base_ty: Ty<'tcx>, } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>, base_ty: Ty<'tcx>) -> Self { - Self { cx, base_ty, cache: FxHashSet::default() } + fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { cx, cache: FxHashSet::default() } + } + + /// Return the right help for Cstring and Cstr-linked unsafety. + fn visit_cstr(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { + debug_assert!(matches!(ty.kind(), ty::Adt(def, _) + if matches!( + self.cx.tcx.get_diagnostic_name(def.did()), + Some(sym::cstring_type | sym::cstr_type) + ) + )); + + let help = if state.is_nonmut_pointee() { + fluent::lint_improper_ctypes_cstr_help_const + } else { + fluent::lint_improper_ctypes_cstr_help_mut + }; + + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_cstr_reason, Some(help)) } /// Checks if the given indirection (box,ref,pointer) is "ffi-safe". @@ -697,6 +723,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { use FfiResult::*; let tcx = self.cx.tcx; + if let ty::Adt(def, _) = inner_ty.kind() { + if let Some(diag_name @ (sym::cstring_type | sym::cstr_type)) = + tcx.get_diagnostic_name(def.did()) + { + // we have better error messages when checking for C-strings directly + let mut cstr_res = self.visit_cstr(state.get_next(ty), inner_ty); // always unsafe with one depth-one reason. + + // Cstr pointer have metadata, CString is Sized + if diag_name == sym::cstr_type { + // we need to override the "type" part of `cstr_res`'s only FfiResultReason + // so it says that it's the use of the indirection that is unsafe + match cstr_res { + FfiResult::FfiUnsafe(ref mut reasons) => { + reasons.first_mut().unwrap().reason.ty = ty; + } + _ => unreachable!(), + } + let note = match indirection_kind { + IndirectionKind::RawPtr => fluent::lint_improper_ctypes_unsized_ptr, + IndirectionKind::Ref => fluent::lint_improper_ctypes_unsized_ref, + IndirectionKind::Box => fluent::lint_improper_ctypes_unsized_box, + }; + return cstr_res.wrap_all(ty, note, None); + } else { + return cstr_res; + } + } + } + match indirection_kind { IndirectionKind::Box => { // FIXME(ctypes): this logic is broken, but it still fits the current tests: @@ -933,13 +988,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { AdtKind::Struct | AdtKind::Union => { if let Some(sym::cstring_type | sym::cstr_type) = tcx.get_diagnostic_name(def.did()) - && !self.base_ty.is_mutable_ptr() { - return FfiResult::new_with_reason( - ty, - fluent::lint_improper_ctypes_cstr_reason, - Some(fluent::lint_improper_ctypes_cstr_help), - ); + return self.visit_cstr(state, ty); } self.visit_struct_or_union(state, ty, def, args) } @@ -1220,7 +1270,7 @@ impl<'tcx> ImproperCTypesLint { /// Check that an extern "ABI" static variable is of a ffi-safe type. fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) { let ty = cx.tcx.type_of(id).instantiate_identity(); - let mut visitor = ImproperCTypesVisitor::new(cx, ty); + let mut visitor = ImproperCTypesVisitor::new(cx); let ffi_res = visitor.check_type(VisitorState::static_var(), ty); self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern); } @@ -1239,7 +1289,7 @@ impl<'tcx> ImproperCTypesLint { for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Arg); state.depth = depth; - let mut visitor = ImproperCTypesVisitor::new(cx, *input_ty); + let mut visitor = ImproperCTypesVisitor::new(cx); let ffi_res = visitor.check_type(state, *input_ty); self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode); } @@ -1247,7 +1297,7 @@ impl<'tcx> ImproperCTypesLint { if let hir::FnRetTy::Return(ret_hir) = decl.output { let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Ret); state.depth = depth; - let mut visitor = ImproperCTypesVisitor::new(cx, sig.output()); + let mut visitor = ImproperCTypesVisitor::new(cx); let ffi_res = visitor.check_type(state, sig.output()); self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode); } 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 0020ae2b8d80b..1cb2ec1884080 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 @@ -4,7 +4,7 @@ warning: `extern` callback uses type `CStr`, which is not FFI-safe LL | type Foo = extern "C" fn(::std::ffi::CStr); | ^^^^^^^^^^^^^^^^ not FFI-safe | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CStr` as needed = note: `CStr`/`CString` do not have a guaranteed layout = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) on by default diff --git a/tests/ui/lint/improper-ctypes/lint-cstr.rs b/tests/ui/lint/improper-ctypes/lint-cstr.rs index b04decd0bcacc..4fb660cb9e13c 100644 --- a/tests/ui/lint/improper-ctypes/lint-cstr.rs +++ b/tests/ui/lint/improper-ctypes/lint-cstr.rs @@ -6,31 +6,35 @@ 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()` + //~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CStr` as needed 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()` + //~^ ERROR `extern` block uses type `&CStr`, which is not FFI-safe + //~| HELP consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed 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()` + //~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed 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()` + //~| HELP consider passing a `*const std::ffi::c_char` instead, converting to/from `CString` as needed - fn no_special_help_for_mut_cstring(s: *mut CString); + fn take_cstring_ptr_mut(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 + //~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed - fn no_special_help_for_mut_cstring_ref(s: &mut CString); + fn take_cstring_ref_mut(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 + //~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed } 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()` +//~^ ERROR `extern` fn uses type `&CStr`, which is not FFI-safe +//~| HELP consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed 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) {} +//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed +extern "C" fn rust_take_cstring_ptr_mut(s: *mut CString) {} +//~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe +//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed +extern "C" fn rust_take_cstring_ref_mut(s: &mut CString) {} +//~^ ERROR `extern` fn uses type `CString`, which is not FFI-safe +//~| HELP consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed diff --git a/tests/ui/lint/improper-ctypes/lint-cstr.stderr b/tests/ui/lint/improper-ctypes/lint-cstr.stderr index da26306584311..1907d41c858a8 100644 --- a/tests/ui/lint/improper-ctypes/lint-cstr.stderr +++ b/tests/ui/lint/improper-ctypes/lint-cstr.stderr @@ -4,7 +4,7 @@ error: `extern` block uses type `CStr`, which is not FFI-safe LL | fn take_cstr(s: CStr); | ^^^^ not FFI-safe | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CStr` as needed = note: `CStr`/`CString` do not have a guaranteed layout note: the lint level is defined here --> $DIR/lint-cstr.rs:2:9 @@ -12,13 +12,14 @@ note: the lint level is defined here LL | #![deny(improper_ctypes, improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^ -error: `extern` block uses type `CStr`, which is not FFI-safe +error: `extern` block uses type `&CStr`, which is not FFI-safe --> $DIR/lint-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: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + = help: consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed = note: `CStr`/`CString` do not have a guaranteed layout 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 LL | fn take_cstring(s: CString); | ^^^^^^^ not FFI-safe | - = help: consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed = note: `CStr`/`CString` do not have a guaranteed layout 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 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()` + = help: consider passing a `*const std::ffi::c_char` instead, converting to/from `CString` as needed = note: `CStr`/`CString` do not have a guaranteed layout error: `extern` block uses type `CString`, which is not FFI-safe - --> $DIR/lint-cstr.rs:20:43 + --> $DIR/lint-cstr.rs:20:32 | -LL | fn no_special_help_for_mut_cstring(s: *mut CString); - | ^^^^^^^^^^^^ not FFI-safe +LL | fn take_cstring_ptr_mut(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 + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed + = note: `CStr`/`CString` do not have a guaranteed layout error: `extern` block uses type `CString`, which is not FFI-safe - --> $DIR/lint-cstr.rs:24:47 + --> $DIR/lint-cstr.rs:24:32 | -LL | fn no_special_help_for_mut_cstring_ref(s: &mut CString); - | ^^^^^^^^^^^^ not FFI-safe +LL | fn take_cstring_ref_mut(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 + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed + = note: `CStr`/`CString` do not have a guaranteed layout -error: `extern` fn uses type `CStr`, which is not FFI-safe +error: `extern` fn uses type `&CStr`, which is not FFI-safe --> $DIR/lint-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: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + = help: consider passing a `*const std::ffi::c_char` instead, converting to/from `&CStr` as needed = note: `CStr`/`CString` do not have a guaranteed layout note: the lint level is defined here --> $DIR/lint-cstr.rs:2:26 @@ -77,8 +79,26 @@ error: `extern` fn uses type `CString`, which is not FFI-safe 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()` + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` fn uses type `CString`, which is not FFI-safe + --> $DIR/lint-cstr.rs:35:44 + | +LL | extern "C" fn rust_take_cstring_ptr_mut(s: *mut CString) {} + | ^^^^^^^^^^^^ not FFI-safe + | + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed + = note: `CStr`/`CString` do not have a guaranteed layout + +error: `extern` fn uses type `CString`, which is not FFI-safe + --> $DIR/lint-cstr.rs:38:44 + | +LL | extern "C" fn rust_take_cstring_ref_mut(s: &mut CString) {} + | ^^^^^^^^^^^^ not FFI-safe + | + = help: consider passing a `*mut std::ffi::c_char` instead, converting to/from `CString` as needed = note: `CStr`/`CString` do not have a guaranteed layout -error: aborting due to 8 previous errors +error: aborting due to 10 previous errors From e2d810620ce122779bc35ca1ce0aaf4e0b53a84f Mon Sep 17 00:00:00 2001 From: niacdoial Date: Tue, 26 Aug 2025 23:57:30 +0200 Subject: [PATCH 08/20] ImproperCTypes: change handling of indirections - Uniformise how indirections (references, Boxes, raw pointers) are handled. (more specific indirection types with specific guarantees are not handled yet) - Indirections that are compiled to a "thick pointer" (indirections to slices, dyn objects, *not* foreign !Sized types) have better messaging around them. - Now, the pointee of a FFI-safe indirection is always considered safe. This might be a regression, if we consider that an extern function's API should describe how the function can be used by the non-defining side of the FFI boundary. However, enforcing this everywhere would force the user to perform an unreasonable amount of typecasts to/from opaque pointers. There is something better to do here, but it will be left to another PR. --- compiler/rustc_lint/messages.ftl | 1 - .../rustc_lint/src/types/improper_ctypes.rs | 276 ++++++++++++++---- tests/ui/lint/extern-C-fnptr-lints-slices.rs | 2 +- .../lint/extern-C-fnptr-lints-slices.stderr | 4 +- tests/ui/lint/improper-ctypes/lint-73249-2.rs | 5 +- .../lint/improper-ctypes/lint-73249-2.stderr | 15 - tests/ui/lint/improper-ctypes/lint-ctypes.rs | 46 ++- .../lint/improper-ctypes/lint-ctypes.stderr | 114 +++----- tests/ui/lint/improper-ctypes/lint-fn.rs | 12 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 64 ++-- 10 files changed, 336 insertions(+), 203 deletions(-) delete mode 100644 tests/ui/lint/improper-ctypes/lint-73249-2.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index a8398b69b792f..2bf12d5044ac0 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -343,7 +343,6 @@ lint_improper_ctypes = {$desc} uses type `{$ty}`, which is not FFI-safe lint_improper_ctypes_array_help = consider passing a pointer to the array lint_improper_ctypes_array_reason = passing raw arrays by value is not FFI-safe -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 diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index acc10d19b60b4..6eec85df5fe69 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -489,7 +489,7 @@ impl<'tcx> std::ops::Add> for FfiResult<'tcx> { /// in the `FfiResult` is final. type PartialFfiResult<'tcx> = Option>; -/// What type indirection points to a given type. +/// The type of an indirection (the way in which it points to its pointee). #[derive(Clone, Copy)] enum IndirectionKind { /// Box (valid non-null pointer, owns pointee). @@ -500,6 +500,148 @@ enum IndirectionKind { RawPtr, } +/// The different ways a given type can have/not have a fixed size. +/// Relies on the vocabulary of the Hierarchy of Sized Traits change (`#![feature(sized_hierarchy)]`) +#[derive(Clone, Copy)] +enum TypeSizedness { + /// Type of definite size (pointers are C-compatible). + Sized, + /// Unsized type because it includes an opaque/foreign type (pointers are C-compatible). + /// (Relies on all Unsized types being `extern` types, and unable to be used in an array/slice) + Unsized, + /// MetaSized types are types whose size can be computed from pointer metadata (slice, string, dyn Trait, closure, ...) + /// (pointers are not C-compatible). + MetaSized, + /// Not known, usually for placeholder types (Self in non-impl trait functions, type parameters, aliases, the like). + NotYetKnown, +} + +/// Determine if a type is sized or not, and whether it affects references/pointers/boxes to it. +fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> TypeSizedness { + let tcx = cx.tcx; + + // note that sizedness is unrelated to inhabitedness + if ty.is_sized(tcx, cx.typing_env()) { + TypeSizedness::Sized + } else { + // the overall type is !Sized or ?Sized + match ty.kind() { + ty::Slice(_) | ty::Str | ty::Dynamic(..) => TypeSizedness::MetaSized, + ty::Foreign(..) => TypeSizedness::Unsized, + ty::Adt(def, args) => { + // for now assume: boxes and phantoms don't mess with this + match def.adt_kind() { + AdtKind::Union | AdtKind::Enum => { + bug!("unions and enums are necessarily sized") + } + AdtKind::Struct => { + if let Some(intermediate) = + def.sizedness_constraint(tcx, ty::SizedTraitKind::MetaSized) + { + let ty = intermediate.instantiate(tcx, args); + get_type_sizedness(cx, ty) + } else { + debug_assert!( + def.sizedness_constraint(tcx, ty::SizedTraitKind::Sized).is_some() + ); + TypeSizedness::MetaSized + } + + // if let Some(sym::cstring_type | sym::cstr_type) = + // tcx.get_diagnostic_name(def.did()) + // { + // return TypeSizedness::MetaSized; + // } + + // // note: non-exhaustive structs from other crates are not assumed to be ?Sized + // // for the purpose of sizedness, it seems we are allowed to look at its current contents. + + // if def.non_enum_variant().fields.is_empty() { + // bug!("an empty struct is necessarily sized"); + // } + + // let variant = def.non_enum_variant(); + + // // only the last field may be !Sized (or ?Sized in the case of type params) + // let last_field = match (&variant.fields).iter().last() { + // Some(last_field) => last_field, + // // even nonexhaustive-empty structs from another crate are considered Sized + // // (eventhough one could add a !Sized field to them) + // None => bug!("Empty struct should be Sized, right?"), // + // }; + // let field_ty = get_type_from_field(cx, last_field, args); + // match get_type_sizedness(cx, field_ty) { + // s @ (TypeSizedness::MetaSized + // | TypeSizedness::Unsized + // | TypeSizedness::NotYetKnown) => s, + // TypeSizedness::Sized => { + // bug!("failed to find the reason why struct `{:?}` is unsized", ty) + // } + // } + } + } + } + ty::Tuple(tuple) => { + // only the last field may be !Sized (or ?Sized in the case of type params) + let item_ty: Ty<'tcx> = match tuple.last() { + Some(item_ty) => *item_ty, + None => bug!("Empty tuple (AKA unit type) should be Sized, right?"), + }; + let item_ty = cx + .tcx + .try_normalize_erasing_regions(cx.typing_env(), item_ty) + .unwrap_or(item_ty); + match get_type_sizedness(cx, item_ty) { + s @ (TypeSizedness::MetaSized + | TypeSizedness::Unsized + | TypeSizedness::NotYetKnown) => s, + TypeSizedness::Sized => { + bug!("failed to find the reason why tuple `{:?}` is unsized", ty) + } + } + } + + ty::Pat(base, _) => get_type_sizedness(cx, *base), + + ty_kind @ (ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Array(..) + | ty::RawPtr(..) + | ty::Ref(..) + | ty::FnPtr(..) + | ty::Never) => { + // those types are all sized, right? + bug!( + "This ty_kind (`{:?}`) should be sized, yet we are in a branch of code that deals with unsized types.", + ty_kind, + ) + } + + // While opaque types are checked for earlier, if a projection in a struct field + // normalizes to an opaque type, then it will reach ty::Alias(ty::Opaque) here. + ty::Param(..) | ty::Alias(ty::Opaque | ty::Projection | ty::Inherent, ..) => { + return TypeSizedness::NotYetKnown; + } + + ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"), + + ty::Alias(ty::Free, ..) + | ty::Infer(..) + | ty::Bound(..) + | ty::Error(_) + | ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::CoroutineWitness(..) + | ty::Placeholder(..) + | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty), + } + } +} + bitflags! { /// VisitorState flags that are linked with the root type's use. /// (These are the permanent part of the state, kept when visiting new mir::Ty.) @@ -655,13 +797,6 @@ impl VisitorState { self.root_use_flags.contains(RootUseFlags::DEFINED) && self.is_in_function() } - /// Whether the type is used (directly or not) in a function pointer type. - /// Here, we also allow non-FFI-safe types behind a C pointer, - /// to be treated as an opaque type on the other side of the FFI boundary. - fn is_in_fnptr(&self) -> bool { - self.root_use_flags.contains(RootUseFlags::THEORETICAL) && self.is_in_function() - } - /// Whether we can expect type parameters and co in a given type. fn can_expect_ty_params(&self) -> bool { // rust-defined functions, as well as FnPtrs @@ -677,6 +812,11 @@ impl VisitorState { fn is_nonmut_pointee(&self) -> bool { matches!(self.outer_ty_kind, OuterTyKind::Pointee { mutable: hir::Mutability::Not, .. }) } + + /// Whether the current type is behind a raw pointer + fn is_raw_pointee(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::Pointee { raw: true, .. }) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -686,12 +826,12 @@ struct ImproperCTypesVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, /// To prevent problems with recursive types, /// add a types-in-check cache. - cache: FxHashSet>, + ty_cache: FxHashSet>, } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { fn new(cx: &'a LateContext<'tcx>) -> Self { - Self { cx, cache: FxHashSet::default() } + Self { cx, ty_cache: FxHashSet::default() } } /// Return the right help for Cstring and Cstr-linked unsafety. @@ -720,7 +860,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { inner_ty: Ty<'tcx>, indirection_kind: IndirectionKind, ) -> FfiResult<'tcx> { - use FfiResult::*; let tcx = self.cx.tcx; if let ty::Adt(def, _) = inner_ty.kind() { @@ -752,60 +891,61 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - match indirection_kind { - IndirectionKind::Box => { - // FIXME(ctypes): this logic is broken, but it still fits the current tests: - // - for some reason `Box<_>`es in `extern "ABI" {}` blocks - // are not treated as pointers but as FFI-unsafe structs - // - otherwise, treat the box itself correctly, and follow pointee safety logic - // as described in the other `indirection_type` match branch. - if state.is_in_defined_function() || state.is_in_fnptr() { - if inner_ty.is_sized(tcx, self.cx.typing_env()) { - return FfiSafe; - } else { - return FfiResult::new_with_reason( - ty, - fluent::lint_improper_ctypes_box, - None, - ); - } - } else { - // (mid-retcon-commit-chain comment:) - // this is the original fallback behavior, which is wrong - if let ty::Adt(def, args) = ty.kind() { - self.visit_struct_or_union(state, ty, *def, args) - } else if cfg!(debug_assertions) { - bug!("ImproperCTypes: this retcon commit was badly written") - } else { - FfiSafe - } + // there are three remaining concerns with the pointer: + // - is the pointer compatible with a C pointer in the first place? (if not, only send that error message) + // - is the pointee FFI-safe? (it might not matter, see mere lines below) + // - does the pointer type contain a non-zero assumption, but has a value given by non-rust code? + // this block deals with the first two. + let type_sizedness = get_type_sizedness(self.cx, inner_ty); + match type_sizedness { + TypeSizedness::Unsized | TypeSizedness::Sized => { + if matches!( + (type_sizedness, indirection_kind), + (TypeSizedness::Unsized, IndirectionKind::Box) + ) { + // Box<_> means rust is capable of drop()'ing the pointee, + // which is impossible for `extern` types (foreign opaque types). + bug!( + "FFI-unsafeties similar to `Box` currently cause " + "compilation errors that should prevent ImproperCTypes from running. " + "If you see this, it is likely this behaviour has changed." + ); } + // FIXME(ctypes): + // for now, we consider this to be safe even in the case of a FFI-unsafe pointee + // this is technically only safe if the pointer is never dereferenced on the non-rust + // side of the FFI boundary, i.e. if the type is to be treated as opaque + // there are techniques to flag those pointees as opaque, but not always, so we can only enforce this + // in some cases. + FfiResult::FfiSafe } - IndirectionKind::Ref | IndirectionKind::RawPtr => { - // Weird behaviour for pointee safety. the big question here is - // "if you have a FFI-unsafe pointee behind a FFI-safe pointer type, is it ok?" - // The answer until now is: - // "It's OK for rust-defined functions and callbacks, we'll assume those are - // meant to be opaque types on the other side of the FFI boundary". - // - // Reasoning: - // For extern function declarations, the actual definition of the function is - // written somewhere else, meaning the declaration is free to express this - // opaqueness with an extern type (opaque caller-side) or a std::ffi::c_void - // (opaque callee-side). For extern function definitions, however, in the case - // where the type is opaque caller-side, it is not opaque callee-side, - // and having the full type information is necessary to compile the function. - // - // It might be better to rething this, or even ignore pointee safety for a first - // batch of behaviour changes. See the discussion that ends with - // https://github.com/rust-lang/rust/pull/134697#issuecomment-2692610258 - if (state.is_in_defined_function() || state.is_in_fnptr()) - && inner_ty.is_sized(self.cx.tcx, self.cx.typing_env()) - { - FfiSafe - } else { - self.visit_type(state.get_next(ty), inner_ty) - } + TypeSizedness::NotYetKnown => { + // types with sizedness NotYetKnown: + // - Type params (with `variable: impl Trait` shorthand or not) + // (function definitions only, let's see how this interacts with monomorphisation) + // - Self in trait functions/methods + // - Opaque return types + // (always FFI-unsafe) + // - non-exhaustive structs/enums/unions from other crates + // (always FFI-unsafe) + // (for the three first, this is unless there is a `+Sized` bound involved) + + // whether they are FFI-safe or not does not depend on the indirections involved (&Self, &T, Box), + // so let's not wrap the current context around a potential FfiUnsafe type param. + self.visit_type(state.get_next(ty), inner_ty) + } + TypeSizedness::MetaSized => { + let help = match inner_ty.kind() { + ty::Str => Some(fluent::lint_improper_ctypes_str_help), + ty::Slice(_) => Some(fluent::lint_improper_ctypes_slice_help), + _ => None, + }; + let reason = match indirection_kind { + IndirectionKind::RawPtr => fluent::lint_improper_ctypes_unsized_ptr, + IndirectionKind::Ref => fluent::lint_improper_ctypes_unsized_ref, + IndirectionKind::Box => fluent::lint_improper_ctypes_unsized_box, + }; + return FfiResult::new_with_reason(ty, reason, help); } } } @@ -971,7 +1111,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // Protect against infinite recursion, for example // `struct S(*mut S);`. - if !(self.cache.insert(ty) && self.cx.tcx.recursion_limit().value_within_limit(state.depth)) + if !(self.ty_cache.insert(ty) + && self.cx.tcx.recursion_limit().value_within_limit(state.depth)) { return FfiSafe; } @@ -986,6 +1127,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } match def.adt_kind() { AdtKind::Struct | AdtKind::Union => { + // There are two ways to encounter cstr here (since pointees are treated elsewhere): + // - Cstr used as an argument of a FnPtr (!Sized structs are in fact allowed there) + // - Cstr as the last field of a struct + // This excludes non-compiling code where a CStr is used where !Sized is not allowed + // (currently those mistakes prevent this lint from running) if let Some(sym::cstring_type | sym::cstr_type) = tcx.get_diagnostic_name(def.did()) { @@ -1038,6 +1184,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { state.is_direct_function_return() // `()` fields are safe || state.is_field() + // this serves as a "void*" + || state.is_raw_pointee() ) { FfiSafe diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.rs b/tests/ui/lint/extern-C-fnptr-lints-slices.rs index 08db0539ab4e7..bf0a754f81022 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.rs +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.rs @@ -3,7 +3,7 @@ // It's an improper ctype (a slice) arg in an extern "C" fnptr. pub type F = extern "C" fn(&[u8]); -//~^ ERROR: `extern` callback uses type `[u8]`, which is not FFI-safe +//~^ ERROR: `extern` callback uses type `&[u8]`, which is not FFI-safe fn main() {} diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr index 6a36ba6c28d6f..f0c0cc8167fd0 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -1,11 +1,11 @@ -error: `extern` callback uses type `[u8]`, which is not FFI-safe +error: `extern` callback uses type `&[u8]`, which is not FFI-safe --> $DIR/extern-C-fnptr-lints-slices.rs:5:28 | LL | pub type F = extern "C" fn(&[u8]); | ^^^^^ not FFI-safe | = help: consider using a raw pointer instead - = note: slices have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here --> $DIR/extern-C-fnptr-lints-slices.rs:1:8 | diff --git a/tests/ui/lint/improper-ctypes/lint-73249-2.rs b/tests/ui/lint/improper-ctypes/lint-73249-2.rs index 31af0e3d381ef..9286d822e22e3 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-2.rs +++ b/tests/ui/lint/improper-ctypes/lint-73249-2.rs @@ -1,3 +1,5 @@ +//@ check-pass // possible FIXME: see below + #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] @@ -24,7 +26,8 @@ struct A { } extern "C" { - fn lint_me() -> A<()>; //~ ERROR: uses type `Qux` + // possible FIXME(ctypes): the unsafety of Qux is unseen, as it is behing a FFI-safe indirection + fn lint_me() -> A<()>; } fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-2.stderr b/tests/ui/lint/improper-ctypes/lint-73249-2.stderr deleted file mode 100644 index d6c1cec2bd6c1..0000000000000 --- a/tests/ui/lint/improper-ctypes/lint-73249-2.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error: `extern` block uses type `Qux`, which is not FFI-safe - --> $DIR/lint-73249-2.rs:27:21 - | -LL | fn lint_me() -> A<()>; - | ^^^^^ not FFI-safe - | - = note: opaque types have no C equivalent -note: the lint level is defined here - --> $DIR/lint-73249-2.rs:2:9 - | -LL | #![deny(improper_ctypes)] - | ^^^^^^^^^^^^^^^ - -error: aborting due to 1 previous error - diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 8e4de47d0c8e5..23e6a22a2630d 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -1,4 +1,5 @@ #![feature(rustc_private)] +#![feature(extern_types)] #![allow(private_interfaces)] #![deny(improper_ctypes)] @@ -6,7 +7,9 @@ use std::cell::UnsafeCell; use std::marker::PhantomData; use std::ffi::{c_int, c_uint}; +use std::fmt::Debug; +unsafe extern "C" {type UnsizedOpaque;} trait Bar { } trait Mirror { type It: ?Sized; } impl Mirror for T { type It = Self; } @@ -20,7 +23,7 @@ pub type I32Pair = (i32, i32); #[repr(C)] pub struct ZeroSize; pub type RustFn = fn(); -pub type RustBadRet = extern "C" fn() -> Box; +pub type RustBoxRet = extern "C" fn() -> Box; pub type CVoidRet = (); pub struct Foo; #[repr(transparent)] @@ -28,7 +31,7 @@ pub struct TransparentI128(i128); #[repr(transparent)] pub struct TransparentStr(&'static str); #[repr(transparent)] -pub struct TransparentBadFn(RustBadRet); +pub struct TransparentBoxFn(RustBoxRet); #[repr(transparent)] pub struct TransparentInt(u32); #[repr(transparent)] @@ -39,21 +42,37 @@ pub struct TransparentLifetime<'a>(*const u8, PhantomData<&'a ()>); pub struct TransparentUnit(f32, PhantomData); #[repr(transparent)] pub struct TransparentCustomZst(i32, ZeroSize); +#[repr(C)] +pub struct UnsizedStructBecauseForeign { + sized: u32, + unszd: UnsizedOpaque, +} +#[repr(C)] +pub struct UnsizedStructBecauseDyn { + sized: u32, + unszd: dyn Debug, +} + +#[repr(C)] +pub struct TwoBadTypes<'a> { + non_c_type: char, + ref_with_mdata: &'a [u8], +} #[repr(C)] pub struct ZeroSizeWithPhantomData(::std::marker::PhantomData); extern "C" { - pub fn ptr_type1(size: *const Foo); //~ ERROR: uses type `Foo` - pub fn ptr_type2(size: *const Foo); //~ ERROR: uses type `Foo` + pub fn ptr_type1(size: *const Foo); + pub fn ptr_type2(size: *const Foo); pub fn ptr_unit(p: *const ()); - pub fn ptr_tuple(p: *const ((),)); //~ ERROR: uses type `((),)` - pub fn slice_type(p: &[u32]); //~ ERROR: uses type `[u32]` - pub fn str_type(p: &str); //~ ERROR: uses type `str` - pub fn box_type(p: Box); //~ ERROR uses type `Box` + pub fn ptr_tuple(p: *const ((),)); + pub fn slice_type(p: &[u32]); //~ ERROR: uses type `&[u32]` + pub fn str_type(p: &str); //~ ERROR: uses type `&str` + pub fn box_type(p: Box); pub fn opt_box_type(p: Option>); pub fn char_type(p: char); //~ ERROR uses type `char` - pub fn trait_type(p: &dyn Bar); //~ ERROR uses type `dyn Bar` + pub fn trait_type(p: &dyn Bar); //~ ERROR uses type `&dyn Bar` pub fn tuple_type(p: (i32, i32)); //~ ERROR uses type `(i32, i32)` pub fn tuple_type2(p: I32Pair); //~ ERROR uses type `(i32, i32)` pub fn zero_size(p: ZeroSize); //~ ERROR uses type `ZeroSize` @@ -63,11 +82,14 @@ extern "C" { -> ::std::marker::PhantomData; //~ ERROR uses type `PhantomData` pub fn fn_type(p: RustFn); //~ ERROR uses type `fn()` pub fn fn_type2(p: fn()); //~ ERROR uses type `fn()` - pub fn fn_contained(p: RustBadRet); - pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `str` - pub fn transparent_fn(p: TransparentBadFn); + pub fn fn_contained(p: RustBoxRet); + pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `&str` + pub fn transparent_fn(p: TransparentBoxFn); pub fn raw_array(arr: [u8; 8]); //~ ERROR: uses type `[u8; 8]` + pub fn struct_unsized_ptr_no_metadata(p: &UnsizedStructBecauseForeign); + pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); //~ ERROR uses type `&UnsizedStructBecauseDyn` + pub fn no_niche_a(a: Option>); //~^ ERROR: uses type `Option>` pub fn no_niche_b(b: Option>); diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index b11db19a0c1d5..be42b90dd4231 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -1,74 +1,28 @@ -error: `extern` block uses type `Foo`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:47:28 +error: `extern` block uses type `&[u32]`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:70:26 | -LL | pub fn ptr_type1(size: *const Foo); - | ^^^^^^^^^^ not FFI-safe - | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout -note: the type is defined here - --> $DIR/lint-ctypes.rs:25:1 +LL | pub fn slice_type(p: &[u32]); + | ^^^^^^ not FFI-safe | -LL | pub struct Foo; - | ^^^^^^^^^^^^^^ + = help: consider using a raw pointer instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here - --> $DIR/lint-ctypes.rs:4:9 + --> $DIR/lint-ctypes.rs:5:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ -error: `extern` block uses type `Foo`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:48:28 - | -LL | pub fn ptr_type2(size: *const Foo); - | ^^^^^^^^^^ not FFI-safe - | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout -note: the type is defined here - --> $DIR/lint-ctypes.rs:25:1 - | -LL | pub struct Foo; - | ^^^^^^^^^^^^^^ - -error: `extern` block uses type `((),)`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:50:25 - | -LL | pub fn ptr_tuple(p: *const ((),)); - | ^^^^^^^^^^^^ not FFI-safe - | - = help: consider using a struct instead - = note: tuples have unspecified layout - -error: `extern` block uses type `[u32]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:51:26 - | -LL | pub fn slice_type(p: &[u32]); - | ^^^^^^ not FFI-safe - | - = help: consider using a raw pointer instead - = note: slices have no C equivalent - -error: `extern` block uses type `str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:52:24 +error: `extern` block uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:71:24 | LL | pub fn str_type(p: &str); | ^^^^ not FFI-safe | = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent - -error: `extern` block uses type `Box`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:53:24 - | -LL | pub fn box_type(p: Box); - | ^^^^^^^^ not FFI-safe - | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `char`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:55:25 + --> $DIR/lint-ctypes.rs:74:25 | LL | pub fn char_type(p: char); | ^^^^ not FFI-safe @@ -76,16 +30,16 @@ LL | pub fn char_type(p: char); = help: consider using `u32` or `libc::wchar_t` instead = note: the `char` type has no C equivalent -error: `extern` block uses type `dyn Bar`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:56:26 +error: `extern` block uses type `&dyn Bar`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:75:26 | LL | pub fn trait_type(p: &dyn Bar); | ^^^^^^^^ not FFI-safe | - = note: trait objects have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `(i32, i32)`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:57:26 + --> $DIR/lint-ctypes.rs:76:26 | LL | pub fn tuple_type(p: (i32, i32)); | ^^^^^^^^^^ not FFI-safe @@ -94,7 +48,7 @@ LL | pub fn tuple_type(p: (i32, i32)); = note: tuples have unspecified layout error: `extern` block uses type `(i32, i32)`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:58:27 + --> $DIR/lint-ctypes.rs:77:27 | LL | pub fn tuple_type2(p: I32Pair); | ^^^^^^^ not FFI-safe @@ -103,7 +57,7 @@ LL | pub fn tuple_type2(p: I32Pair); = note: tuples have unspecified layout error: `extern` block uses type `ZeroSize`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:59:25 + --> $DIR/lint-ctypes.rs:78:25 | LL | pub fn zero_size(p: ZeroSize); | ^^^^^^^^ not FFI-safe @@ -111,26 +65,26 @@ LL | pub fn zero_size(p: ZeroSize); = help: consider adding a member to this struct = note: this struct has no fields note: the type is defined here - --> $DIR/lint-ctypes.rs:21:1 + --> $DIR/lint-ctypes.rs:24:1 | LL | pub struct ZeroSize; | ^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `ZeroSizeWithPhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:60:33 + --> $DIR/lint-ctypes.rs:79:33 | LL | pub fn zero_size_phantom(p: ZeroSizeWithPhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: composed only of `PhantomData` note: the type is defined here - --> $DIR/lint-ctypes.rs:44:1 + --> $DIR/lint-ctypes.rs:63:1 | LL | pub struct ZeroSizeWithPhantomData(::std::marker::PhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:63:12 + --> $DIR/lint-ctypes.rs:82:12 | LL | -> ::std::marker::PhantomData; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -138,7 +92,7 @@ LL | -> ::std::marker::PhantomData; = note: composed only of `PhantomData` error: `extern` block uses type `fn()`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:64:23 + --> $DIR/lint-ctypes.rs:83:23 | LL | pub fn fn_type(p: RustFn); | ^^^^^^ not FFI-safe @@ -147,7 +101,7 @@ LL | pub fn fn_type(p: RustFn); = note: this function pointer has Rust-specific calling convention error: `extern` block uses type `fn()`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:65:24 + --> $DIR/lint-ctypes.rs:84:24 | LL | pub fn fn_type2(p: fn()); | ^^^^ not FFI-safe @@ -155,17 +109,17 @@ LL | pub fn fn_type2(p: fn()); = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has Rust-specific calling convention -error: `extern` block uses type `str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:67:31 +error: `extern` block uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:86:31 | LL | pub fn transparent_str(p: TransparentStr); | ^^^^^^^^^^^^^^ not FFI-safe | = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `[u8; 8]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:69:27 + --> $DIR/lint-ctypes.rs:88:27 | LL | pub fn raw_array(arr: [u8; 8]); | ^^^^^^^ not FFI-safe @@ -173,8 +127,16 @@ LL | pub fn raw_array(arr: [u8; 8]); = help: consider passing a pointer to the array = note: passing raw arrays by value is not FFI-safe +error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:91:47 + | +LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); + | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:71:26 + --> $DIR/lint-ctypes.rs:93:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -183,7 +145,7 @@ LL | pub fn no_niche_a(a: Option>); = note: enum has no representation hint error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:73:26 + --> $DIR/lint-ctypes.rs:95:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -191,5 +153,5 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 19 previous errors +error: aborting due to 16 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint-fn.rs b/tests/ui/lint/improper-ctypes/lint-fn.rs index 8fca9fd927f98..9fdb6fa4db998 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.rs +++ b/tests/ui/lint/improper-ctypes/lint-fn.rs @@ -26,7 +26,7 @@ pub struct ZeroSize; pub type RustFn = fn(); -pub type RustBadRet = extern "C" fn() -> Box; +pub type RustBadRet = extern "C" fn() -> (u32,u64); //~ ERROR uses type `(u32, u64)` pub type CVoidRet = (); @@ -68,14 +68,15 @@ pub extern "C" fn ptr_unit(p: *const ()) { } pub extern "C" fn ptr_tuple(p: *const ((),)) { } pub extern "C" fn slice_type(p: &[u32]) { } -//~^ ERROR: uses type `[u32]` +//~^ ERROR: uses type `&[u32]` pub extern "C" fn str_type(p: &str) { } -//~^ ERROR: uses type `str` +//~^ ERROR: uses type `&str` pub extern "C" fn box_type(p: Box) { } pub extern "C" fn opt_box_type(p: Option>) { } +// no error here! pub extern "C" fn boxed_slice(p: Box<[u8]>) { } //~^ ERROR: uses type `Box<[u8]>` @@ -113,14 +114,11 @@ pub extern "C" fn fn_type2(p: fn()) { } //~^ ERROR uses type `fn()` pub extern "C" fn fn_contained(p: RustBadRet) { } -// ^ FIXME it doesn't see the error... but at least it reports it elsewhere? pub extern "C" fn transparent_str(p: TransparentStr) { } -//~^ ERROR: uses type `str` +//~^ ERROR: uses type `&str` pub extern "C" fn transparent_fn(p: TransparentBadFn) { } -// ^ possible FIXME: it doesn't see the actual FnPtr's error... -// but at least it reports it elsewhere? pub extern "C" fn good3(fptr: Option) { } diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index 8287cb44a80ff..2e0cc0fca487b 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -1,52 +1,68 @@ -error: `extern` fn uses type `[u32]`, which is not FFI-safe +error: `extern` callback uses type `(u32, u64)`, which is not FFI-safe + --> $DIR/lint-fn.rs:29:42 + | +LL | pub type RustBadRet = extern "C" fn() -> (u32,u64); + | ^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout +note: the lint level is defined here + --> $DIR/lint-fn.rs:2:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&[u32]`, which is not FFI-safe --> $DIR/lint-fn.rs:70:33 | LL | pub extern "C" fn slice_type(p: &[u32]) { } | ^^^^^^ not FFI-safe | = help: consider using a raw pointer instead - = note: slices have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here --> $DIR/lint-fn.rs:2:26 | LL | #![deny(improper_ctypes, improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `extern` fn uses type `str`, which is not FFI-safe +error: `extern` fn uses type `&str`, which is not FFI-safe --> $DIR/lint-fn.rs:73:31 | LL | pub extern "C" fn str_type(p: &str) { } | ^^^^ not FFI-safe | = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `Box<[u8]>`, which is not FFI-safe - --> $DIR/lint-fn.rs:80:34 + --> $DIR/lint-fn.rs:81:34 | LL | pub extern "C" fn boxed_slice(p: Box<[u8]>) { } | ^^^^^^^^^ not FFI-safe | - = note: box cannot be represented as a single pointer + = help: consider using a raw pointer instead + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `Box`, which is not FFI-safe - --> $DIR/lint-fn.rs:83:35 + --> $DIR/lint-fn.rs:84:35 | LL | pub extern "C" fn boxed_string(p: Box) { } | ^^^^^^^^ not FFI-safe | - = note: box cannot be represented as a single pointer + = help: consider using `*const u8` and a length instead + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `Box`, which is not FFI-safe - --> $DIR/lint-fn.rs:86:34 + --> $DIR/lint-fn.rs:87:34 | LL | pub extern "C" fn boxed_trait(p: Box) { } | ^^^^^^^^^^^^^^ not FFI-safe | - = note: box cannot be represented as a single pointer + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `char`, which is not FFI-safe - --> $DIR/lint-fn.rs:89:32 + --> $DIR/lint-fn.rs:90:32 | LL | pub extern "C" fn char_type(p: char) { } | ^^^^ not FFI-safe @@ -55,7 +71,7 @@ LL | pub extern "C" fn char_type(p: char) { } = note: the `char` type has no C equivalent error: `extern` fn uses type `(i32, i32)`, which is not FFI-safe - --> $DIR/lint-fn.rs:92:33 + --> $DIR/lint-fn.rs:93:33 | LL | pub extern "C" fn tuple_type(p: (i32, i32)) { } | ^^^^^^^^^^ not FFI-safe @@ -64,7 +80,7 @@ LL | pub extern "C" fn tuple_type(p: (i32, i32)) { } = note: tuples have unspecified layout error: `extern` fn uses type `(i32, i32)`, which is not FFI-safe - --> $DIR/lint-fn.rs:95:34 + --> $DIR/lint-fn.rs:96:34 | LL | pub extern "C" fn tuple_type2(p: I32Pair) { } | ^^^^^^^ not FFI-safe @@ -73,7 +89,7 @@ LL | pub extern "C" fn tuple_type2(p: I32Pair) { } = note: tuples have unspecified layout error: `extern` fn uses type `ZeroSize`, which is not FFI-safe - --> $DIR/lint-fn.rs:98:32 + --> $DIR/lint-fn.rs:99:32 | LL | pub extern "C" fn zero_size(p: ZeroSize) { } | ^^^^^^^^ not FFI-safe @@ -87,7 +103,7 @@ LL | pub struct ZeroSize; | ^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `ZeroSizeWithPhantomData`, which is not FFI-safe - --> $DIR/lint-fn.rs:101:40 + --> $DIR/lint-fn.rs:102:40 | LL | pub extern "C" fn zero_size_phantom(p: ZeroSizeWithPhantomData) { } | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -100,7 +116,7 @@ LL | pub struct ZeroSizeWithPhantomData(PhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-fn.rs:104:51 + --> $DIR/lint-fn.rs:105:51 | LL | pub extern "C" fn zero_size_phantom_toplevel() -> PhantomData { | ^^^^^^^^^^^^^^^^^ not FFI-safe @@ -108,7 +124,7 @@ LL | pub extern "C" fn zero_size_phantom_toplevel() -> PhantomData { = note: composed only of `PhantomData` error: `extern` fn uses type `fn()`, which is not FFI-safe - --> $DIR/lint-fn.rs:109:30 + --> $DIR/lint-fn.rs:110:30 | LL | pub extern "C" fn fn_type(p: RustFn) { } | ^^^^^^ not FFI-safe @@ -117,7 +133,7 @@ LL | pub extern "C" fn fn_type(p: RustFn) { } = note: this function pointer has Rust-specific calling convention error: `extern` fn uses type `fn()`, which is not FFI-safe - --> $DIR/lint-fn.rs:112:31 + --> $DIR/lint-fn.rs:113:31 | LL | pub extern "C" fn fn_type2(p: fn()) { } | ^^^^ not FFI-safe @@ -125,17 +141,17 @@ LL | pub extern "C" fn fn_type2(p: fn()) { } = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has Rust-specific calling convention -error: `extern` fn uses type `str`, which is not FFI-safe +error: `extern` fn uses type `&str`, which is not FFI-safe --> $DIR/lint-fn.rs:118:38 | LL | pub extern "C" fn transparent_str(p: TransparentStr) { } | ^^^^^^^^^^^^^^ not FFI-safe | = help: consider using `*const u8` and a length instead - = note: string slices have no C equivalent + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-fn.rs:172:43 + --> $DIR/lint-fn.rs:170:43 | LL | pub extern "C" fn unused_generic2() -> PhantomData { | ^^^^^^^^^^^^^^^^^ not FFI-safe @@ -143,7 +159,7 @@ LL | pub extern "C" fn unused_generic2() -> PhantomData { = note: composed only of `PhantomData` error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-fn.rs:185:39 + --> $DIR/lint-fn.rs:183:39 | LL | pub extern "C" fn used_generic4(x: Vec) { } | ^^^^^^ not FFI-safe @@ -152,7 +168,7 @@ LL | pub extern "C" fn used_generic4(x: Vec) { } = note: this struct has unspecified layout error: `extern` fn uses type `Vec`, which is not FFI-safe - --> $DIR/lint-fn.rs:188:41 + --> $DIR/lint-fn.rs:186:41 | LL | pub extern "C" fn used_generic5() -> Vec { | ^^^^^^ not FFI-safe @@ -160,5 +176,5 @@ LL | pub extern "C" fn used_generic5() -> Vec { = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout -error: aborting due to 17 previous errors +error: aborting due to 18 previous errors From 497e453155e7c91e5f651fee41510235e5738532 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Wed, 27 Aug 2025 22:30:22 +0200 Subject: [PATCH 09/20] ImproperCTypes: change what type is blamed, use nested help messages A major change to the content of linting messages, but not where they occur. Now, the "uses type `_`" part of the message mentions the type directly visible where the error occurs, and the nested note/help messages trace the link to the actual source of the FFI-unsafety --- compiler/rustc_lint/messages.ftl | 2 + .../rustc_lint/src/types/improper_ctypes.rs | 26 ++++++++--- .../extern-C-non-FFI-safe-arg-ice-52334.rs | 1 + tests/ui/extern/extern-C-str-arg-ice-80125.rs | 3 +- .../ui/lint/improper-ctypes/lint-113436-1.rs | 4 +- .../lint/improper-ctypes/lint-113436-1.stderr | 16 ++++++- tests/ui/lint/improper-ctypes/lint-73249-3.rs | 2 +- .../lint/improper-ctypes/lint-73249-3.stderr | 8 +++- tests/ui/lint/improper-ctypes/lint-73249-5.rs | 2 +- .../lint/improper-ctypes/lint-73249-5.stderr | 2 +- tests/ui/lint/improper-ctypes/lint-ctypes.rs | 8 +++- .../lint/improper-ctypes/lint-ctypes.stderr | 45 ++++++++++++++++--- tests/ui/lint/improper-ctypes/lint-fn.rs | 2 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 2 +- .../improper-ctypes/repr-rust-is-undefined.rs | 6 +-- .../repr-rust-is-undefined.stderr | 24 ++++++++-- 16 files changed, 123 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 2bf12d5044ac0..3f26f9e28118f 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -380,6 +380,8 @@ lint_improper_ctypes_str_reason = string slices have no C equivalent lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct lint_improper_ctypes_struct_fieldless_reason = this struct has no fields +lint_improper_ctypes_struct_dueto = this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field + lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct lint_improper_ctypes_struct_layout_reason = this struct has unspecified layout diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 6eec85df5fe69..8163d191309aa 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -436,7 +436,6 @@ impl<'tcx> FfiResult<'tcx> { /// For instance, if we have a repr(C) struct in a function's argument, FFI unsafeties inside the struct /// are to be blamed on the struct and not the members. /// This is where we use this wrapper, to tell "all FFI-unsafeties in there are caused by this `ty`" - #[expect(unused)] fn with_overrides(mut self, override_cause_ty: Option>) -> FfiResult<'tcx> { use FfiResult::*; @@ -982,7 +981,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { all_phantom &= match self.visit_type(state.get_next(ty), field_ty) { FfiSafe => false, FfiPhantom(..) => true, - r @ FfiUnsafe { .. } => return r, + r @ FfiUnsafe { .. } => { + return r.wrap_all(ty, fluent::lint_improper_ctypes_struct_dueto, None); + } } } @@ -1033,7 +1034,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ); } - if def.non_enum_variant().fields.is_empty() { + let ffires = if def.non_enum_variant().fields.is_empty() { FfiResult::new_with_reason( ty, if def.is_struct() { @@ -1049,7 +1050,15 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ) } else { self.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) - } + }; + + // Here, if there is something wrong, then the "fault" comes from inside the struct itself. + // Even if we add more details to the lint, the initial line must specify that + // the FFI-unsafety is because of the struct + // Plus, if the struct is from another crate, then there's not much that can be done anyways + // + // So, we override the "cause type" of the lint. + ffires.with_overrides(Some(ty)) } fn visit_enum( @@ -1096,10 +1105,13 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } }); if let ControlFlow::Break(result) = ret { - return result; + // this enum is visited in the middle of another lint, + // so we override the "cause type" of the lint + // (for more detail, see comment in ``visit_struct_union`` before its call to ``result.with_overrides``) + result.with_overrides(Some(ty)) + } else { + FfiSafe } - - FfiSafe } /// Checks if the given type is "ffi-safe" (has a stable, well-defined diff --git a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs index 7dc2421bb9e43..8e037a9b413fd 100644 --- a/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs +++ b/tests/ui/extern/extern-C-non-FFI-safe-arg-ice-52334.rs @@ -8,6 +8,7 @@ type Foo = extern "C" fn(::std::ffi::CStr); //~^ WARN `extern` callback uses type extern "C" { fn meh(blah: Foo); + // ^ FIXME: the error isn't seen here but at least it's reported elsewhere } fn main() { diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.rs b/tests/ui/extern/extern-C-str-arg-ice-80125.rs index 1c1abbe996839..fa300ba9d173b 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.rs +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.rs @@ -7,7 +7,8 @@ pub struct Struct(ExternCallback); #[no_mangle] pub extern "C" fn register_something(bind: ExternCallback) -> Struct { -//~^ WARN `extern` fn uses type `Struct`, which is not FFI-safe +// ^ FIXME: the error isn't seen here, but at least it's reported elsewhere +//~^^ WARN `extern` fn uses type `Struct`, which is not FFI-safe Struct(bind) } diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.rs b/tests/ui/lint/improper-ctypes/lint-113436-1.rs index 1ca59c6868d6d..27dcd0184d90f 100644 --- a/tests/ui/lint/improper-ctypes/lint-113436-1.rs +++ b/tests/ui/lint/improper-ctypes/lint-113436-1.rs @@ -20,8 +20,8 @@ pub struct Bar { } extern "C" fn bar(x: Bar) -> Bar { - //~^ ERROR `extern` fn uses type `NotSafe`, which is not FFI-safe - //~^^ ERROR `extern` fn uses type `NotSafe`, which is not FFI-safe + //~^ ERROR `extern` fn uses type `Bar`, which is not FFI-safe + //~^^ ERROR `extern` fn uses type `Bar`, which is not FFI-safe todo!() } diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr b/tests/ui/lint/improper-ctypes/lint-113436-1.stderr index f01dc3b6e0d1e..b230475df1c6b 100644 --- a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr +++ b/tests/ui/lint/improper-ctypes/lint-113436-1.stderr @@ -1,9 +1,15 @@ -error: `extern` fn uses type `NotSafe`, which is not FFI-safe +error: `extern` fn uses type `Bar`, which is not FFI-safe --> $DIR/lint-113436-1.rs:22:22 | LL | extern "C" fn bar(x: Bar) -> Bar { | ^^^ not FFI-safe | + = note: this struct/enum/union (`Bar`) is FFI-unsafe due to a `NotSafe` field +note: the type is defined here + --> $DIR/lint-113436-1.rs:16:1 + | +LL | pub struct Bar { + | ^^^^^^^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here @@ -17,12 +23,18 @@ note: the lint level is defined here LL | #![deny(improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `extern` fn uses type `NotSafe`, which is not FFI-safe +error: `extern` fn uses type `Bar`, which is not FFI-safe --> $DIR/lint-113436-1.rs:22:30 | LL | extern "C" fn bar(x: Bar) -> Bar { | ^^^ not FFI-safe | + = note: this struct/enum/union (`Bar`) is FFI-unsafe due to a `NotSafe` field +note: the type is defined here + --> $DIR/lint-113436-1.rs:16:1 + | +LL | pub struct Bar { + | ^^^^^^^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here diff --git a/tests/ui/lint/improper-ctypes/lint-73249-3.rs b/tests/ui/lint/improper-ctypes/lint-73249-3.rs index 8bdf536bf77e6..aff2a182e3f49 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-3.rs +++ b/tests/ui/lint/improper-ctypes/lint-73249-3.rs @@ -18,7 +18,7 @@ pub struct A { } extern "C" { - pub fn lint_me() -> A; //~ ERROR: uses type `Qux` + pub fn lint_me() -> A; //~ ERROR: `extern` block uses type `A` } fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-3.stderr b/tests/ui/lint/improper-ctypes/lint-73249-3.stderr index ebc9eb5eb8274..dc6f6fb08ed33 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-3.stderr +++ b/tests/ui/lint/improper-ctypes/lint-73249-3.stderr @@ -1,9 +1,15 @@ -error: `extern` block uses type `Qux`, which is not FFI-safe +error: `extern` block uses type `A`, which is not FFI-safe --> $DIR/lint-73249-3.rs:21:25 | LL | pub fn lint_me() -> A; | ^ not FFI-safe | + = note: this struct/enum/union (`A`) is FFI-unsafe due to a `Qux` field +note: the type is defined here + --> $DIR/lint-73249-3.rs:16:1 + | +LL | pub struct A { + | ^^^^^^^^^^^^ = note: opaque types have no C equivalent note: the lint level is defined here --> $DIR/lint-73249-3.rs:2:9 diff --git a/tests/ui/lint/improper-ctypes/lint-73249-5.rs b/tests/ui/lint/improper-ctypes/lint-73249-5.rs index cc6da59950d7a..8ad5be4e6301e 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-5.rs +++ b/tests/ui/lint/improper-ctypes/lint-73249-5.rs @@ -18,7 +18,7 @@ pub struct A { } extern "C" { - pub fn lint_me() -> A; //~ ERROR: uses type `Qux` + pub fn lint_me() -> A; //~ ERROR: uses type `A` } fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr b/tests/ui/lint/improper-ctypes/lint-73249-5.stderr index 484927f57fead..df2da2da1e37f 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr +++ b/tests/ui/lint/improper-ctypes/lint-73249-5.stderr @@ -1,4 +1,4 @@ -error: `extern` block uses type `Qux`, which is not FFI-safe +error: `extern` block uses type `A`, which is not FFI-safe --> $DIR/lint-73249-5.rs:21:25 | LL | pub fn lint_me() -> A; diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 23e6a22a2630d..66bb7ffa271e1 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -83,9 +83,15 @@ extern "C" { pub fn fn_type(p: RustFn); //~ ERROR uses type `fn()` pub fn fn_type2(p: fn()); //~ ERROR uses type `fn()` pub fn fn_contained(p: RustBoxRet); - pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `&str` + pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `TransparentStr` pub fn transparent_fn(p: TransparentBoxFn); pub fn raw_array(arr: [u8; 8]); //~ ERROR: uses type `[u8; 8]` + pub fn multi_errors_per_arg( + f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) + ); + //~^^ ERROR: uses type `char` + //~| ERROR: uses type `&dyn Debug` + //~| ERROR: uses type `TwoBadTypes<'_>` pub fn struct_unsized_ptr_no_metadata(p: &UnsizedStructBecauseForeign); pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); //~ ERROR uses type `&UnsizedStructBecauseDyn` diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index be42b90dd4231..d2fde67491442 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -109,7 +109,7 @@ LL | pub fn fn_type2(p: fn()); = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has Rust-specific calling convention -error: `extern` block uses type `&str`, which is not FFI-safe +error: `extern` block uses type `TransparentStr`, which is not FFI-safe --> $DIR/lint-ctypes.rs:86:31 | LL | pub fn transparent_str(p: TransparentStr); @@ -127,8 +127,43 @@ LL | pub fn raw_array(arr: [u8; 8]); = help: consider passing a pointer to the array = note: passing raw arrays by value is not FFI-safe +error: `extern` callback uses type `char`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:90:12 + | +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), TwoBadTypes<'b>)` is FFI-unsafe due to `char` + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent + +error: `extern` callback uses type `&dyn Debug`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:90:12 + | +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), TwoBadTypes<'b>)` is FFI-unsafe due to `&dyn Debug` + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` callback uses type `TwoBadTypes<'_>`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:90:12 + | +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), TwoBadTypes<'b>)` is FFI-unsafe due to `TwoBadTypes<'_>` + = note: this struct/enum/union (`TwoBadTypes<'_>`) is FFI-unsafe due to a `&[u8]` field +note: the type is defined here + --> $DIR/lint-ctypes.rs:57:1 + | +LL | pub struct TwoBadTypes<'a> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider using a raw pointer instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:91:47 + --> $DIR/lint-ctypes.rs:97:47 | LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -136,7 +171,7 @@ LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:93:26 + --> $DIR/lint-ctypes.rs:99:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -145,7 +180,7 @@ LL | pub fn no_niche_a(a: Option>); = note: enum has no representation hint error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:95:26 + --> $DIR/lint-ctypes.rs:101:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -153,5 +188,5 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 16 previous errors +error: aborting due to 19 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint-fn.rs b/tests/ui/lint/improper-ctypes/lint-fn.rs index 9fdb6fa4db998..731d97f2bcd52 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.rs +++ b/tests/ui/lint/improper-ctypes/lint-fn.rs @@ -116,7 +116,7 @@ pub extern "C" fn fn_type2(p: fn()) { } pub extern "C" fn fn_contained(p: RustBadRet) { } pub extern "C" fn transparent_str(p: TransparentStr) { } -//~^ ERROR: uses type `&str` +//~^ ERROR: uses type `TransparentStr` pub extern "C" fn transparent_fn(p: TransparentBadFn) { } diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index 2e0cc0fca487b..5819b5f176015 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -141,7 +141,7 @@ LL | pub extern "C" fn fn_type2(p: fn()) { } = help: consider using an `extern fn(...) -> ...` function pointer instead = note: this function pointer has Rust-specific calling convention -error: `extern` fn uses type `&str`, which is not FFI-safe +error: `extern` fn uses type `TransparentStr`, which is not FFI-safe --> $DIR/lint-fn.rs:118:38 | LL | pub extern "C" fn transparent_str(p: TransparentStr) { } diff --git a/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.rs b/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.rs index 379c4132404bf..5e73441750362 100644 --- a/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.rs +++ b/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.rs @@ -32,12 +32,12 @@ struct D { extern "C" { fn foo(x: A); //~ ERROR type `A`, which is not FFI-safe - fn bar(x: B); //~ ERROR type `A` + fn bar(x: B); //~ ERROR type `B` fn baz(x: C); fn qux(x: A2); //~ ERROR type `A` - fn quux(x: B2); //~ ERROR type `A` + fn quux(x: B2); //~ ERROR type `B` fn corge(x: C2); - fn fred(x: D); //~ ERROR type `A` + fn fred(x: D); //~ ERROR type `D` } fn main() { } diff --git a/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr b/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr index 5f0465bcf00c7..7735f8d09b60c 100644 --- a/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr +++ b/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr @@ -17,12 +17,18 @@ note: the lint level is defined here LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ -error: `extern` block uses type `A`, which is not FFI-safe +error: `extern` block uses type `B`, which is not FFI-safe --> $DIR/repr-rust-is-undefined.rs:35:15 | LL | fn bar(x: B); | ^ not FFI-safe | + = note: this struct/enum/union (`B`) is FFI-unsafe due to a `A` field +note: the type is defined here + --> $DIR/repr-rust-is-undefined.rs:13:1 + | +LL | struct B { + | ^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here @@ -45,12 +51,18 @@ note: the type is defined here LL | struct A { | ^^^^^^^^ -error: `extern` block uses type `A`, which is not FFI-safe +error: `extern` block uses type `B`, which is not FFI-safe --> $DIR/repr-rust-is-undefined.rs:38:16 | LL | fn quux(x: B2); | ^^ not FFI-safe | + = note: this struct/enum/union (`B`) is FFI-unsafe due to a `A` field +note: the type is defined here + --> $DIR/repr-rust-is-undefined.rs:13:1 + | +LL | struct B { + | ^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here @@ -59,12 +71,18 @@ note: the type is defined here LL | struct A { | ^^^^^^^^ -error: `extern` block uses type `A`, which is not FFI-safe +error: `extern` block uses type `D`, which is not FFI-safe --> $DIR/repr-rust-is-undefined.rs:40:16 | LL | fn fred(x: D); | ^ not FFI-safe | + = note: this struct/enum/union (`D`) is FFI-unsafe due to a `A` field +note: the type is defined here + --> $DIR/repr-rust-is-undefined.rs:28:1 + | +LL | struct D { + | ^^^^^^^^ = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout note: the type is defined here From 4cdf426e30a8ceb2a9d9d178dade2747a6751186 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:13:02 +0200 Subject: [PATCH 10/20] ImproperCTypes: change handling of ADTs A change in how type checking goes through structs/enums/unions, mostly to be able to yield multiple lints if multiple fields are unsafe --- compiler/rustc_lint/messages.ftl | 19 +- compiler/rustc_lint/src/types.rs | 20 ++ .../rustc_lint/src/types/improper_ctypes.rs | 205 ++++++++++++------ .../extern/extern-C-str-arg-ice-80125.stderr | 4 +- tests/ui/extern/issue-16250.stderr | 4 +- .../lint/improper-ctypes/lint-113436-1.stderr | 8 +- .../lint/improper-ctypes/lint-73249-5.stderr | 6 + .../ui/lint/improper-ctypes/lint-94223.stderr | 16 +- tests/ui/lint/improper-ctypes/lint-ctypes.rs | 1 + .../lint/improper-ctypes/lint-ctypes.stderr | 46 ++-- tests/ui/lint/improper-ctypes/lint-fn.stderr | 16 +- .../improper-ctypes/lint-transparent-help.rs | 21 ++ .../lint-transparent-help.stderr | 28 +++ .../repr-rust-is-undefined.stderr | 20 +- .../repr/repr-transparent-issue-87496.stderr | 2 +- .../extern_crate_improper.stderr | 6 +- tests/ui/union/union-repr-c.stderr | 2 +- 17 files changed, 303 insertions(+), 121 deletions(-) create mode 100644 tests/ui/lint/improper-ctypes/lint-transparent-help.rs create mode 100644 tests/ui/lint/improper-ctypes/lint-transparent-help.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 3f26f9e28118f..7b883d43fd58c 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -377,24 +377,25 @@ lint_improper_ctypes_slice_reason = slices have no C equivalent lint_improper_ctypes_str_help = consider using `*const u8` and a length instead lint_improper_ctypes_str_reason = string slices have no C equivalent -lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct -lint_improper_ctypes_struct_fieldless_reason = this struct has no fields +lint_improper_ctypes_struct_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead lint_improper_ctypes_struct_dueto = this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field -lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct +lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct +lint_improper_ctypes_struct_fieldless_reason = `{$ty}` has no fields -lint_improper_ctypes_struct_layout_reason = this struct has unspecified layout -lint_improper_ctypes_struct_non_exhaustive = this struct is non-exhaustive -lint_improper_ctypes_struct_zst = this struct contains only zero-sized fields +lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `{$ty}` +lint_improper_ctypes_struct_layout_reason = `{$ty}` has unspecified layout +lint_improper_ctypes_struct_non_exhaustive = `{$ty}` is non-exhaustive +lint_improper_ctypes_struct_zst = `{$ty}` contains only zero-sized fields lint_improper_ctypes_tuple_help = consider using a struct instead - lint_improper_ctypes_tuple_reason = tuples have unspecified layout -lint_improper_ctypes_union_fieldless_help = consider adding a member to this union +lint_improper_ctypes_union_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead +lint_improper_ctypes_union_fieldless_help = consider adding a member to this union lint_improper_ctypes_union_fieldless_reason = this union has no fields -lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union +lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` attribute to this union lint_improper_ctypes_union_layout_reason = this union has unspecified layout lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 2a75ae0f87f02..14ce8011f147a 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -707,6 +707,26 @@ pub(crate) fn transparent_newtype_field<'a, 'tcx>( }) } +/// for a given ADT variant, list which fields are non-1ZST +/// (`repr(transparent)` guarantees that there is at most one) +pub(crate) fn map_non_1zst_fields<'a, 'tcx>( + tcx: TyCtxt<'tcx>, + variant: &'a ty::VariantDef, +) -> Vec { + let typing_env = ty::TypingEnv::non_body_analysis(tcx, variant.def_id); + variant + .fields + .iter() + .map(|field| { + let field_ty = tcx.type_of(field.did).instantiate_identity(); + let is_1zst = tcx + .layout_of(typing_env.as_query_input(field_ty)) + .is_ok_and(|layout| layout.is_1zst()); + !is_1zst + }) + .collect() +} + /// Is type known to be non-null? fn ty_is_known_nonnull<'tcx>( tcx: TyCtxt<'tcx>, diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 8163d191309aa..a9be08c26214b 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -177,37 +177,6 @@ fn get_type_from_field<'tcx>( cx.tcx.try_normalize_erasing_regions(cx.typing_env(), field_ty).unwrap_or(field_ty) } -/// Check a variant of a non-exhaustive enum for improper ctypes -/// -/// We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added". -/// This includes linting, on a best-effort basis. There are valid additions that are unlikely. -/// -/// Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely", -/// so we don't need the lint to account for it. -/// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }. -pub(crate) fn check_non_exhaustive_variant( - non_exhaustive_variant_list: bool, - variant: &ty::VariantDef, -) -> ControlFlow { - // non_exhaustive suggests it is possible that someone might break ABI - // see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344 - // so warn on complex enums being used outside their crate - if non_exhaustive_variant_list { - // which is why we only warn about really_tagged_union reprs from https://rust.tf/rfc2195 - // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }` - // but exempt enums with unit ctors like C's (e.g. from rust-bindgen) - if variant_has_complex_ctor(variant) { - return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive); - } - } - - if variant.field_list_has_applicable_non_exhaustive() { - return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive_variant); - } - - ControlFlow::Continue(()) -} - fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { // CtorKind::Const means a "unit" ctor !matches!(variant.ctor_kind(), Some(CtorKind::Const)) @@ -378,7 +347,6 @@ impl<'tcx> FfiResult<'tcx> { } /// If the FfiPhantom variant, turns it into a FfiUnsafe version. /// Otherwise, keep unchanged. - #[expect(unused)] fn forbid_phantom(self) -> Self { match self { Self::FfiPhantom(ty) => { @@ -960,34 +928,113 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ) -> FfiResult<'tcx> { use FfiResult::*; - let transparent_with_all_zst_fields = if def.repr().transparent() { - if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { - // Transparent newtypes have at most one non-ZST field which needs to be checked.. - let field_ty = get_type_from_field(self.cx, field, args); - return self.visit_type(state.get_next(ty), field_ty); + let mut ffires_accumulator = FfiSafe; + + let (transparent_with_all_zst_fields, field_list) = + if !matches!(def.adt_kind(), AdtKind::Enum) && def.repr().transparent() { + // determine if there is 0 or 1 non-1ZST field, and which it is. + // (note: for enums, "transparent" means 1-variant) + if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { + // Transparent newtypes have at most one non-ZST field which needs to be checked later + (false, vec![field]) + } else { + // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all + // `PhantomData`). + (true, variant.fields.iter().collect::>()) + } } else { - // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all - // `PhantomData`). - true - } - } else { - false - }; + (false, variant.fields.iter().collect::>()) + }; // 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 { + let mut fields_ok_list = vec![true; field_list.len()]; + + for (field_i, field) in field_list.into_iter().enumerate() { let field_ty = get_type_from_field(self.cx, field, args); - all_phantom &= match self.visit_type(state.get_next(ty), field_ty) { - FfiSafe => false, + let ffi_res = self.visit_type(state.get_next(ty), field_ty); + + // checking that this is not an FfiUnsafe due to an unit type: + // visit_type should be smart enough to not consider it unsafe if called from another ADT + #[cfg(debug_assertions)] + if let FfiUnsafe(ref reasons) = ffi_res { + if let (1, Some(FfiUnsafeExplanation { reason, .. })) = + (reasons.len(), reasons.first()) + { + let FfiUnsafeReason { ty, .. } = reason.as_ref(); + debug_assert!(!ty.is_unit()); + } + } + + all_phantom &= match ffi_res { FfiPhantom(..) => true, + FfiSafe => false, r @ FfiUnsafe { .. } => { - return r.wrap_all(ty, fluent::lint_improper_ctypes_struct_dueto, None); + fields_ok_list[field_i] = false; + ffires_accumulator += r; + false } } } - if all_phantom { + // if we have bad fields, also report a possible transparent_with_all_zst_fields + // (if this combination is somehow possible) + // otherwise, having all fields be phantoms + // takes priority over transparent_with_all_zst_fields + if let FfiUnsafe(explanations) = ffires_accumulator { + debug_assert!(def.repr().c() || def.repr().transparent() || def.repr().int.is_some()); + + if def.repr().transparent() || matches!(def.adt_kind(), AdtKind::Enum) { + let field_ffires = FfiUnsafe(explanations).wrap_all( + ty, + fluent::lint_improper_ctypes_struct_dueto, + None, + ); + if transparent_with_all_zst_fields { + field_ffires + + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_struct_zst, + None, + ) + } else { + field_ffires + } + } else { + // since we have a repr(C) struct/union, there's a chance that we have some unsafe fields, + // but also exactly one non-1ZST field that is FFI-safe: + // we want to suggest repr(transparent) here. + // (FIXME(ctypes): confirm that this makes sense for unions once #60405 / RFC2645 stabilises) + let non_1zst_fields = super::map_non_1zst_fields(self.cx.tcx, variant); + let (last_non_1zst, non_1zst_count) = non_1zst_fields.into_iter().enumerate().fold( + (None, 0_usize), + |(prev_nz, count), (field_i, is_nz)| { + if is_nz { (Some(field_i), count + 1) } else { (prev_nz, count) } + }, + ); + let help = if non_1zst_count == 1 + && last_non_1zst.map(|field_i| fields_ok_list[field_i]) == Some(true) + { + match def.adt_kind() { + AdtKind::Struct => { + Some(fluent::lint_improper_ctypes_struct_consider_transparent) + } + AdtKind::Union => { + Some(fluent::lint_improper_ctypes_union_consider_transparent) + } + AdtKind::Enum => bug!("cannot suggest an enum to be repr(transparent)"), + } + } else { + None + }; + + FfiUnsafe(explanations).wrap_all( + ty, + fluent::lint_improper_ctypes_struct_dueto, + help, + ) + } + } else if all_phantom { FfiPhantom(ty) } else if transparent_with_all_zst_fields { FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_struct_zst, None) @@ -1093,24 +1140,58 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // FIXME(ctypes): connect `def.repr().int` to visit_numeric // (for now it's OK, `repr(char)` doesn't exist and visit_numeric doesn't warn on anything else) - let non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); + let enum_non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); // Check the contained variants. - let ret = def.variants().iter().try_for_each(|variant| { - check_non_exhaustive_variant(non_exhaustive, variant) - .map_break(|reason| FfiResult::new_with_reason(ty, reason, None))?; - match self.visit_variant_fields(state, ty, def, variant, args) { - FfiSafe => ControlFlow::Continue(()), - r => ControlFlow::Break(r), - } + // non_exhaustive suggests it is possible that someone might break ABI + // See: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344 + // so warn on complex enums being used outside their crate. + // + // We treat `#[non_exhaustive]` enum variants as unsafe if the enum is passed by-value, + // as additions it will change it size. + // + // We treat `#[non_exhaustive] enum` as "ensure that code will compile if new variants are added". + // This includes linting, on a best-effort basis. There are valid additions that are unlikely. + // + // Adding a data-carrying variant to an existing C-like enum that is passed to C is "unlikely", + // so we don't need the lint to account for it. + // e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }. + // Which is why we only warn about really_tagged_union reprs from https://rust.tf/rfc2195 + // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }` + // but exempt enums with unit ctors like C's (e.g. from rust-bindgen) + + let (mut improper_on_nonexhaustive_flag, mut nonexhaustive_variant_flag) = (false, false); + def.variants().iter().for_each(|variant| { + improper_on_nonexhaustive_flag |= + enum_non_exhaustive && variant_has_complex_ctor(variant); + nonexhaustive_variant_flag |= variant.field_list_has_applicable_non_exhaustive(); }); - if let ControlFlow::Break(result) = ret { + + if improper_on_nonexhaustive_flag { + FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_non_exhaustive, None) + } else if nonexhaustive_variant_flag { + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_non_exhaustive_variant, + None, + ) + } else { + let ffires = def + .variants() + .iter() + .map(|variant| { + let variant_res = self.visit_variant_fields(state, ty, def, variant, args); + // FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms? + // (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?) + variant_res.forbid_phantom() + }) + .reduce(|r1, r2| r1 + r2) + .unwrap(); // always at least one variant if we hit this branch + // this enum is visited in the middle of another lint, // so we override the "cause type" of the lint - // (for more detail, see comment in ``visit_struct_union`` before its call to ``result.with_overrides``) - result.with_overrides(Some(ty)) - } else { - FfiSafe + // (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``) + ffires.with_overrides(Some(ty)) } } diff --git a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr index 6eded6a78cb74..d448f6b26f9a6 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr @@ -14,8 +14,8 @@ warning: `extern` fn uses type `Struct`, which is not FFI-safe LL | pub extern "C" fn register_something(bind: ExternCallback) -> Struct { | ^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `Struct` + = note: `Struct` has unspecified layout note: the type is defined here --> $DIR/extern-C-str-arg-ice-80125.rs:6:1 | diff --git a/tests/ui/extern/issue-16250.stderr b/tests/ui/extern/issue-16250.stderr index 9d3e88114616b..f2a3dfc1e8eb3 100644 --- a/tests/ui/extern/issue-16250.stderr +++ b/tests/ui/extern/issue-16250.stderr @@ -4,8 +4,8 @@ error: `extern` block uses type `Foo`, which is not FFI-safe LL | pub fn foo(x: (Foo)); | ^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `Foo` + = note: `Foo` has unspecified layout note: the type is defined here --> $DIR/issue-16250.rs:3:1 | diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr b/tests/ui/lint/improper-ctypes/lint-113436-1.stderr index b230475df1c6b..dacf5ffd67022 100644 --- a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr +++ b/tests/ui/lint/improper-ctypes/lint-113436-1.stderr @@ -10,8 +10,8 @@ note: the type is defined here | LL | pub struct Bar { | ^^^^^^^^^^^^^^ - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `NotSafe` + = note: `NotSafe` has unspecified layout note: the type is defined here --> $DIR/lint-113436-1.rs:13:1 | @@ -35,8 +35,8 @@ note: the type is defined here | LL | pub struct Bar { | ^^^^^^^^^^^^^^ - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `NotSafe` + = note: `NotSafe` has unspecified layout note: the type is defined here --> $DIR/lint-113436-1.rs:13:1 | diff --git a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr b/tests/ui/lint/improper-ctypes/lint-73249-5.stderr index df2da2da1e37f..f42924f4d5b56 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr +++ b/tests/ui/lint/improper-ctypes/lint-73249-5.stderr @@ -4,6 +4,12 @@ error: `extern` block uses type `A`, which is not FFI-safe LL | pub fn lint_me() -> A; | ^ not FFI-safe | + = note: this struct/enum/union (`A`) is FFI-unsafe due to a `Qux` field +note: the type is defined here + --> $DIR/lint-73249-5.rs:16:1 + | +LL | pub struct A { + | ^^^^^^^^^^^^ = note: opaque types have no C equivalent note: the lint level is defined here --> $DIR/lint-73249-5.rs:2:9 diff --git a/tests/ui/lint/improper-ctypes/lint-94223.stderr b/tests/ui/lint/improper-ctypes/lint-94223.stderr index f079c2705e7cd..a308552e84780 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/lint-94223.stderr @@ -81,8 +81,8 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe` + = note: `FfiUnsafe` has unspecified layout note: the type is defined here --> $DIR/lint-94223.rs:34:1 | @@ -95,8 +95,8 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe` + = note: `FfiUnsafe` has unspecified layout note: the type is defined here --> $DIR/lint-94223.rs:34:1 | @@ -109,8 +109,8 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe` + = note: `FfiUnsafe` has unspecified layout note: the type is defined here --> $DIR/lint-94223.rs:34:1 | @@ -123,8 +123,8 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe` + = note: `FfiUnsafe` has unspecified layout note: the type is defined here --> $DIR/lint-94223.rs:34:1 | diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 66bb7ffa271e1..8505395c4db29 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -92,6 +92,7 @@ extern "C" { //~^^ ERROR: uses type `char` //~| ERROR: uses type `&dyn Debug` //~| ERROR: uses type `TwoBadTypes<'_>` + //~| ERROR: uses type `TwoBadTypes<'_>` pub fn struct_unsized_ptr_no_metadata(p: &UnsizedStructBecauseForeign); pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); //~ ERROR uses type `&UnsizedStructBecauseDyn` diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index d2fde67491442..f83aaa5639b1f 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -63,7 +63,7 @@ LL | pub fn zero_size(p: ZeroSize); | ^^^^^^^^ not FFI-safe | = help: consider adding a member to this struct - = note: this struct has no fields + = note: `ZeroSize` has no fields note: the type is defined here --> $DIR/lint-ctypes.rs:24:1 | @@ -115,6 +115,12 @@ error: `extern` block uses type `TransparentStr`, which is not FFI-safe LL | pub fn transparent_str(p: TransparentStr); | ^^^^^^^^^^^^^^ not FFI-safe | + = note: this struct/enum/union (`TransparentStr`) is FFI-unsafe due to a `&str` field +note: the type is defined here + --> $DIR/lint-ctypes.rs:32:1 + | +LL | pub struct TransparentStr(&'static str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ = help: consider using `*const u8` and a length instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer @@ -128,31 +134,43 @@ LL | pub fn raw_array(arr: [u8; 8]); = note: passing raw arrays by value is not FFI-safe error: `extern` callback uses type `char`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:90:12 + --> $DIR/lint-ctypes.rs:90:36 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^ not FFI-safe | - = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), TwoBadTypes<'b>)` is FFI-unsafe due to `char` = help: consider using `u32` or `libc::wchar_t` instead = note: the `char` type has no C equivalent error: `extern` callback uses type `&dyn Debug`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:90:12 + --> $DIR/lint-ctypes.rs:90:44 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^^ not FFI-safe | - = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), TwoBadTypes<'b>)` is FFI-unsafe due to `&dyn Debug` = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` callback uses type `TwoBadTypes<'_>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:90:12 + --> $DIR/lint-ctypes.rs:90:59 + | +LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`TwoBadTypes<'_>`) is FFI-unsafe due to a `char` field +note: the type is defined here + --> $DIR/lint-ctypes.rs:57:1 + | +LL | pub struct TwoBadTypes<'a> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent + +error: `extern` callback uses type `TwoBadTypes<'_>`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:90:59 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | ^^^^^^^^^^^^^^^ not FFI-safe | - = note: the function pointer to `for<'a, 'b> extern "C" fn(char, &'a (dyn Debug + 'a), TwoBadTypes<'b>)` is FFI-unsafe due to `TwoBadTypes<'_>` = note: this struct/enum/union (`TwoBadTypes<'_>`) is FFI-unsafe due to a `&[u8]` field note: the type is defined here --> $DIR/lint-ctypes.rs:57:1 @@ -163,7 +181,7 @@ LL | pub struct TwoBadTypes<'a> { = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:97:47 + --> $DIR/lint-ctypes.rs:98:47 | LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -171,7 +189,7 @@ LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:99:26 + --> $DIR/lint-ctypes.rs:100:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -180,7 +198,7 @@ LL | pub fn no_niche_a(a: Option>); = note: enum has no representation hint error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:101:26 + --> $DIR/lint-ctypes.rs:102:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -188,5 +206,5 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 19 previous errors +error: aborting due to 20 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index 5819b5f176015..edc1adcbf5b33 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -95,7 +95,7 @@ LL | pub extern "C" fn zero_size(p: ZeroSize) { } | ^^^^^^^^ not FFI-safe | = help: consider adding a member to this struct - = note: this struct has no fields + = note: `ZeroSize` has no fields note: the type is defined here --> $DIR/lint-fn.rs:25:1 | @@ -147,6 +147,12 @@ error: `extern` fn uses type `TransparentStr`, which is not FFI-safe LL | pub extern "C" fn transparent_str(p: TransparentStr) { } | ^^^^^^^^^^^^^^ not FFI-safe | + = note: this struct/enum/union (`TransparentStr`) is FFI-unsafe due to a `&str` field +note: the type is defined here + --> $DIR/lint-fn.rs:39:1 + | +LL | pub struct TransparentStr(&'static str); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ = help: consider using `*const u8` and a length instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer @@ -164,8 +170,8 @@ error: `extern` fn uses type `Vec`, which is not FFI-safe LL | pub extern "C" fn used_generic4(x: Vec) { } | ^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `Vec` + = note: `Vec` has unspecified layout error: `extern` fn uses type `Vec`, which is not FFI-safe --> $DIR/lint-fn.rs:186:41 @@ -173,8 +179,8 @@ error: `extern` fn uses type `Vec`, which is not FFI-safe LL | pub extern "C" fn used_generic5() -> Vec { | ^^^^^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `Vec` + = note: `Vec` has unspecified layout error: aborting due to 18 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint-transparent-help.rs b/tests/ui/lint/improper-ctypes/lint-transparent-help.rs new file mode 100644 index 0000000000000..578e45359448a --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-transparent-help.rs @@ -0,0 +1,21 @@ +#![deny(improper_ctypes_definitions)] +use std::marker::PhantomData; +use std::collections::HashMap; +use std::ffi::c_void; + +// [option 1] oops, we forgot repr(C) +struct DictPhantom<'a, A,B:'a>{ + value_info: PhantomData<&'a B>, + full_dict_info: PhantomData>, +} + +#[repr(C)] // [option 2] oops, we meant repr(transparent) +struct MyTypedRawPointer<'a,T:'a>{ + ptr: *const c_void, + metadata: DictPhantom<'a,T,T>, +} + +extern "C" fn example_use(_e: MyTypedRawPointer) {} +//~^ ERROR: uses type `MyTypedRawPointer<'_, i32>` + +fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-transparent-help.stderr b/tests/ui/lint/improper-ctypes/lint-transparent-help.stderr new file mode 100644 index 0000000000000..6528679675a98 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-transparent-help.stderr @@ -0,0 +1,28 @@ +error: `extern` fn uses type `MyTypedRawPointer<'_, i32>`, which is not FFI-safe + --> $DIR/lint-transparent-help.rs:18:31 + | +LL | extern "C" fn example_use(_e: MyTypedRawPointer) {} + | ^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: `MyTypedRawPointer<'_, i32>` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead + = note: this struct/enum/union (`MyTypedRawPointer<'_, i32>`) is FFI-unsafe due to a `DictPhantom<'_, i32, i32>` field +note: the type is defined here + --> $DIR/lint-transparent-help.rs:13:1 + | +LL | struct MyTypedRawPointer<'a,T:'a>{ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `DictPhantom<'_, i32, i32>` + = note: `DictPhantom<'_, i32, i32>` has unspecified layout +note: the type is defined here + --> $DIR/lint-transparent-help.rs:7:1 + | +LL | struct DictPhantom<'a, A,B:'a>{ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $DIR/lint-transparent-help.rs:1:9 + | +LL | #![deny(improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr b/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr index 7735f8d09b60c..b3d99f1c1de34 100644 --- a/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr +++ b/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr @@ -4,8 +4,8 @@ error: `extern` block uses type `A`, which is not FFI-safe LL | fn foo(x: A); | ^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `A` + = note: `A` has unspecified layout note: the type is defined here --> $DIR/repr-rust-is-undefined.rs:8:1 | @@ -29,8 +29,8 @@ note: the type is defined here | LL | struct B { | ^^^^^^^^ - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `A` + = note: `A` has unspecified layout note: the type is defined here --> $DIR/repr-rust-is-undefined.rs:8:1 | @@ -43,8 +43,8 @@ error: `extern` block uses type `A`, which is not FFI-safe LL | fn qux(x: A2); | ^^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `A` + = note: `A` has unspecified layout note: the type is defined here --> $DIR/repr-rust-is-undefined.rs:8:1 | @@ -63,8 +63,8 @@ note: the type is defined here | LL | struct B { | ^^^^^^^^ - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `A` + = note: `A` has unspecified layout note: the type is defined here --> $DIR/repr-rust-is-undefined.rs:8:1 | @@ -83,8 +83,8 @@ note: the type is defined here | LL | struct D { | ^^^^^^^^ - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct - = note: this struct has unspecified layout + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `A` + = note: `A` has unspecified layout note: the type is defined here --> $DIR/repr-rust-is-undefined.rs:8:1 | diff --git a/tests/ui/repr/repr-transparent-issue-87496.stderr b/tests/ui/repr/repr-transparent-issue-87496.stderr index f55024749a688..352ac61c4f696 100644 --- a/tests/ui/repr/repr-transparent-issue-87496.stderr +++ b/tests/ui/repr/repr-transparent-issue-87496.stderr @@ -4,7 +4,7 @@ warning: `extern` block uses type `TransparentCustomZst`, which is not FFI-safe LL | fn good17(p: TransparentCustomZst); | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe | - = note: this struct contains only zero-sized fields + = note: `TransparentCustomZst` contains only zero-sized fields note: the type is defined here --> $DIR/repr-transparent-issue-87496.rs:6:1 | diff --git a/tests/ui/rfcs/rfc-2008-non-exhaustive/improper_ctypes/extern_crate_improper.stderr b/tests/ui/rfcs/rfc-2008-non-exhaustive/improper_ctypes/extern_crate_improper.stderr index afc3d3838ad38..75801ea4134e6 100644 --- a/tests/ui/rfcs/rfc-2008-non-exhaustive/improper_ctypes/extern_crate_improper.stderr +++ b/tests/ui/rfcs/rfc-2008-non-exhaustive/improper_ctypes/extern_crate_improper.stderr @@ -17,7 +17,7 @@ error: `extern` block uses type `NormalStruct`, which is not FFI-safe LL | pub fn non_exhaustive_normal_struct(_: NormalStruct); | ^^^^^^^^^^^^ not FFI-safe | - = note: this struct is non-exhaustive + = note: `NormalStruct` is non-exhaustive error: `extern` block uses type `UnitStruct`, which is not FFI-safe --> $DIR/extern_crate_improper.rs:19:42 @@ -25,7 +25,7 @@ error: `extern` block uses type `UnitStruct`, which is not FFI-safe LL | pub fn non_exhaustive_unit_struct(_: UnitStruct); | ^^^^^^^^^^ not FFI-safe | - = note: this struct is non-exhaustive + = note: `UnitStruct` is non-exhaustive error: `extern` block uses type `TupleStruct`, which is not FFI-safe --> $DIR/extern_crate_improper.rs:21:43 @@ -33,7 +33,7 @@ error: `extern` block uses type `TupleStruct`, which is not FFI-safe LL | pub fn non_exhaustive_tuple_struct(_: TupleStruct); | ^^^^^^^^^^^ not FFI-safe | - = note: this struct is non-exhaustive + = note: `TupleStruct` is non-exhaustive error: `extern` block uses type `NonExhaustiveVariants`, which is not FFI-safe --> $DIR/extern_crate_improper.rs:23:38 diff --git a/tests/ui/union/union-repr-c.stderr b/tests/ui/union/union-repr-c.stderr index 0beb7c376f3ad..dc8335da09b28 100644 --- a/tests/ui/union/union-repr-c.stderr +++ b/tests/ui/union/union-repr-c.stderr @@ -4,7 +4,7 @@ error: `extern` block uses type `W`, which is not FFI-safe LL | static FOREIGN2: W; | ^ not FFI-safe | - = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union + = help: consider adding a `#[repr(C)]` attribute to this union = note: this union has unspecified layout note: the type is defined here --> $DIR/union-repr-c.rs:9:1 From cffe6bffa39d494edf62956214a3e002d37a5290 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:18:16 +0200 Subject: [PATCH 11/20] ImproperCTypes: change handling of slices correctly handle !Sized arrays at the tail-end of structs and a cosmetic change to the array/slice-related lints, --- compiler/rustc_lint/messages.ftl | 3 +- .../rustc_lint/src/types/improper_ctypes.rs | 40 +++++++++++++++---- .../lint/extern-C-fnptr-lints-slices.stderr | 2 +- .../ui/lint/improper-ctypes/lint-94223.stderr | 14 +++---- .../lint/improper-ctypes/lint-ctypes.stderr | 4 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 4 +- 6 files changed, 45 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 7b883d43fd58c..1faad88deb158 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -371,8 +371,7 @@ lint_improper_ctypes_only_phantomdata = composed only of `PhantomData` lint_improper_ctypes_opaque = opaque types have no C equivalent -lint_improper_ctypes_slice_help = consider using a raw pointer instead - +lint_improper_ctypes_slice_help = consider using a raw pointer to the slice's first element (and a length) instead lint_improper_ctypes_slice_reason = slices have no C equivalent lint_improper_ctypes_str_help = consider using `*const u8` and a length instead diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index a9be08c26214b..85e198d02b82f 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -641,8 +641,8 @@ enum OuterTyKind { }, /// For struct/enum/union fields AdtField, - /// Placeholder for properties that will be used eventually - UnusedVariant, + /// For arrays/slices but also tuples + OtherItem, } impl OuterTyKind { @@ -660,7 +660,7 @@ impl OuterTyKind { Self::AdtField } } - ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => Self::UnusedVariant, + ty::Tuple(..) | ty::Array(..) | ty::Slice(_) => Self::OtherItem, k @ _ => bug!("Unexpected outer type {:?} of kind {:?}", ty, k), } } @@ -730,6 +730,15 @@ impl VisitorState { Self::entry_point(RootUseFlags::STATIC_TY) } + /// Whether the type is used as the type of a static variable. + fn is_direct_in_static(&self) -> bool { + let ret = self.root_use_flags.contains(RootUseFlags::STATIC); + if ret { + debug_assert!(!self.root_use_flags.contains(RootUseFlags::FUNC)); + } + ret && matches!(self.outer_ty_kind, OuterTyKind::None) + } + /// Whether the type is used in a function. fn is_in_function(&self) -> bool { let ret = self.root_use_flags.contains(RootUseFlags::FUNC); @@ -784,6 +793,11 @@ impl VisitorState { fn is_raw_pointee(&self) -> bool { matches!(self.outer_ty_kind, OuterTyKind::Pointee { raw: true, .. }) } + + /// Whether the current type directly in the memory layout of the parent ty + fn is_memory_inlined(&self) -> bool { + matches!(self.outer_ty_kind, OuterTyKind::AdtField | OuterTyKind::OtherItem) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -1254,11 +1268,21 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { Some(fluent::lint_improper_ctypes_char_help), ), - ty::Slice(_) => FfiResult::new_with_reason( - ty, - fluent::lint_improper_ctypes_slice_reason, - Some(fluent::lint_improper_ctypes_slice_help), - ), + ty::Slice(inner_ty) => { + // ty::Slice is used for !Sized arrays, since they are the pointee for actual slices + let slice_is_actually_array = + state.is_memory_inlined() || state.is_direct_in_static(); + + if slice_is_actually_array { + self.visit_type(state.get_next(ty), inner_ty) + } else { + FfiResult::new_with_reason( + ty, + fluent::lint_improper_ctypes_slice_reason, + Some(fluent::lint_improper_ctypes_slice_help), + ) + } + } ty::Dynamic(..) => { FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_dyn, None) diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr index f0c0cc8167fd0..02dad17490006 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -4,7 +4,7 @@ error: `extern` callback uses type `&[u8]`, which is not FFI-safe LL | pub type F = extern "C" fn(&[u8]); | ^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here --> $DIR/extern-C-fnptr-lints-slices.rs:1:8 diff --git a/tests/ui/lint/improper-ctypes/lint-94223.stderr b/tests/ui/lint/improper-ctypes/lint-94223.stderr index a308552e84780..faed81c218427 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/lint-94223.stderr @@ -4,7 +4,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | pub fn bad(f: extern "C" fn([u8])) {} | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent note: the lint level is defined here --> $DIR/lint-94223.rs:2:38 @@ -18,7 +18,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | pub fn bad_twice(f: Result) {} | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe @@ -27,7 +27,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | pub fn bad_twice(f: Result) {} | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe @@ -36,7 +36,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | struct BadStruct(extern "C" fn([u8])); | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe @@ -45,7 +45,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | A(extern "C" fn([u8])), | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe @@ -54,7 +54,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | A(extern "C" fn([u8])), | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe @@ -63,7 +63,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | type Foo = extern "C" fn([u8]); | ^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent error: `extern` callback uses type `Option<&::FooType>`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index f83aaa5639b1f..4978a42cef024 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -4,7 +4,7 @@ error: `extern` block uses type `&[u32]`, which is not FFI-safe LL | pub fn slice_type(p: &[u32]); | ^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here --> $DIR/lint-ctypes.rs:5:9 @@ -177,7 +177,7 @@ note: the type is defined here | LL | pub struct TwoBadTypes<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index edc1adcbf5b33..1658a5c23e08d 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -18,7 +18,7 @@ error: `extern` fn uses type `&[u32]`, which is not FFI-safe LL | pub extern "C" fn slice_type(p: &[u32]) { } | ^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here --> $DIR/lint-fn.rs:2:26 @@ -41,7 +41,7 @@ error: `extern` fn uses type `Box<[u8]>`, which is not FFI-safe LL | pub extern "C" fn boxed_slice(p: Box<[u8]>) { } | ^^^^^^^^^ not FFI-safe | - = help: consider using a raw pointer instead + = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` fn uses type `Box`, which is not FFI-safe From 7b90f5a7aa4aed7d9dc9535ada46fb375445e766 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:35:24 +0200 Subject: [PATCH 12/20] ImproperCTypes: handle uninhabited types Add some logic to the type checking to refuse uninhabited types as arguments, and treat uninhabited variants of an enum as FFI-safe if at least one variant is inhabited. --- compiler/rustc_lint/messages.ftl | 3 + .../rustc_lint/src/types/improper_ctypes.rs | 101 +++++++++++---- tests/ui/lint/improper-ctypes/lint-enum.rs | 2 +- .../ui/lint/improper-ctypes/lint-enum.stderr | 25 +++- .../lint/improper-ctypes/lint_uninhabited.rs | 74 +++++++++++ .../improper-ctypes/lint_uninhabited.stderr | 121 ++++++++++++++++++ tests/ui/structs-enums/foreign-struct.rs | 15 ++- 7 files changed, 307 insertions(+), 34 deletions(-) create mode 100644 tests/ui/lint/improper-ctypes/lint_uninhabited.rs create mode 100644 tests/ui/lint/improper-ctypes/lint_uninhabited.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 1faad88deb158..98ca8b52692d1 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -391,6 +391,9 @@ lint_improper_ctypes_struct_zst = `{$ty}` contains only zero-sized fields lint_improper_ctypes_tuple_help = consider using a struct instead lint_improper_ctypes_tuple_reason = tuples have unspecified layout +lint_improper_ctypes_uninhabited_enum = zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +lint_improper_ctypes_uninhabited_never = the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables + lint_improper_ctypes_union_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead lint_improper_ctypes_union_fieldless_help = consider adding a member to this union lint_improper_ctypes_union_fieldless_reason = this union has no fields diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 85e198d02b82f..473c7a2c8edcd 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -14,7 +14,7 @@ use rustc_middle::ty::{ TypeVisitable, TypeVisitableExt, }; use rustc_session::{declare_lint, declare_lint_pass}; -use rustc_span::def_id::LocalDefId; +use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::{Span, sym}; use tracing::debug; @@ -360,7 +360,6 @@ impl<'tcx> FfiResult<'tcx> { /// if the note at their core reason is one in a provided list. /// If the FfiResult is not FfiUnsafe, or if no reasons are plucked, /// then return FfiSafe. - #[expect(unused)] fn take_with_core_note(&mut self, notes: &[DiagMessage]) -> Self { match self { Self::FfiUnsafe(this) => { @@ -805,14 +804,30 @@ impl VisitorState { /// and ``visit_*`` methods to recurse. struct ImproperCTypesVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, + /// The module id of the item being checked for FFI-safety + mod_id: DefId, /// To prevent problems with recursive types, /// add a types-in-check cache. ty_cache: FxHashSet>, } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>) -> Self { - Self { cx, ty_cache: FxHashSet::default() } + fn new(cx: &'a LateContext<'tcx>, mod_id: DefId) -> Self { + Self { cx, mod_id, ty_cache: FxHashSet::default() } + } + + /// Checks whether an uninhabited type (one without valid values) is safe-ish to have here. + fn visit_uninhabited(&self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { + if state.is_in_function_return() { + FfiResult::FfiSafe + } else { + let desc = match ty.kind() { + ty::Adt(..) => fluent::lint_improper_ctypes_uninhabited_enum, + ty::Never => fluent::lint_improper_ctypes_uninhabited_never, + r @ _ => bug!("unexpected ty_kind in uninhabited type handling: {:?}", r), + }; + FfiResult::new_with_reason(ty, desc, None) + } } /// Return the right help for Cstring and Cstr-linked unsafety. @@ -948,6 +963,23 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if !matches!(def.adt_kind(), AdtKind::Enum) && def.repr().transparent() { // determine if there is 0 or 1 non-1ZST field, and which it is. // (note: for enums, "transparent" means 1-variant) + if !ty.is_inhabited_from(self.cx.tcx, self.mod_id, self.cx.typing_env()) { + // let's consider transparent structs to be maybe unsafe if uninhabited, + // even if that is because of fields otherwise ignored in FFI-safety checks + ffires_accumulator += variant + .fields + .iter() + .map(|field| { + let field_ty = get_type_from_field(self.cx, field, args); + let mut field_res = self.visit_type(state.get_next(ty), field_ty); + field_res.take_with_core_note(&[ + fluent::lint_improper_ctypes_uninhabited_enum, + fluent::lint_improper_ctypes_uninhabited_never, + ]) + }) + .reduce(|r1, r2| r1 + r2) + .unwrap() // if uninhabited, then >0 fields + } if let Some(field) = super::transparent_newtype_field(self.cx.tcx, variant) { // Transparent newtypes have at most one non-ZST field which needs to be checked later (false, vec![field]) @@ -1133,8 +1165,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { use FfiResult::*; if def.variants().is_empty() { - // Empty enums are okay... although sort of useless. - return FfiSafe; + // Empty enums are implicitly handled as the never type: + return self.visit_uninhabited(state, ty); } // Check for a repr() attribute to specify the size of the // discriminant. @@ -1190,11 +1222,21 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { None, ) } else { - let ffires = def + // small caveat to checking the variants: we authorise up to n-1 invariants + // to be unsafe because uninhabited. + // so for now let's isolate those unsafeties + let mut variants_uninhabited_ffires = vec![FfiSafe; def.variants().len()]; + + let mut ffires = def .variants() .iter() - .map(|variant| { - let variant_res = self.visit_variant_fields(state, ty, def, variant, args); + .enumerate() + .map(|(variant_i, variant)| { + let mut variant_res = self.visit_variant_fields(state, ty, def, variant, args); + variants_uninhabited_ffires[variant_i] = variant_res.take_with_core_note(&[ + fluent::lint_improper_ctypes_uninhabited_enum, + fluent::lint_improper_ctypes_uninhabited_never, + ]); // FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms? // (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?) variant_res.forbid_phantom() @@ -1202,6 +1244,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { .reduce(|r1, r2| r1 + r2) .unwrap(); // always at least one variant if we hit this branch + if variants_uninhabited_ffires.iter().all(|res| matches!(res, FfiUnsafe(..))) { + // if the enum is uninhabited, because all its variants are uninhabited + ffires += variants_uninhabited_ffires.into_iter().reduce(|r1, r2| r1 + r2).unwrap(); + } + // this enum is visited in the middle of another lint, // so we override the "cause type" of the lint // (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``) @@ -1366,7 +1413,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty::Foreign(..) => FfiSafe, - ty::Never => FfiSafe, + ty::Never => self.visit_uninhabited(state, ty), // While opaque types are checked for earlier, if a projection in a struct field // normalizes to an opaque type, then it will reach this branch. @@ -1443,6 +1490,7 @@ impl<'tcx> ImproperCTypesLint { current_depth: usize, depths: Vec, decls: Vec<&'tcx hir::FnDecl<'tcx>>, + hir_ids: Vec, tys: Vec>, } @@ -1455,6 +1503,7 @@ impl<'tcx> ImproperCTypesLint { { self.decls.push(*decl); self.depths.push(self.current_depth); + self.hir_ids.push(ty.hir_id); } hir::intravisit::walk_ty(self, ty); @@ -1477,6 +1526,7 @@ impl<'tcx> ImproperCTypesLint { } let mut visitor = FnPtrFinder { + hir_ids: Vec::new(), tys: Vec::new(), decls: Vec::new(), depths: Vec::new(), @@ -1486,12 +1536,15 @@ impl<'tcx> ImproperCTypesLint { visitor.visit_ty_unambig(hir_ty); let all_types = iter::zip( - visitor.depths.drain(..), + iter::zip(visitor.depths.drain(..), visitor.hir_ids.drain(..)), iter::zip(visitor.tys.drain(..), visitor.decls.drain(..)), ); - for (depth, (fn_ptr_ty, decl)) in all_types { + + for ((depth, hir_id), (fn_ptr_ty, decl)) in all_types { let mir_sig = get_fn_sig_from_mir_ty(cx, fn_ptr_ty); - self.check_foreign_fn(cx, CItemKind::Callback, mir_sig, decl, depth); + let mod_id = cx.tcx.parent_module(hir_id).to_def_id(); + + self.check_foreign_fn(cx, CItemKind::Callback, mir_sig, decl, mod_id, depth); } } @@ -1533,9 +1586,10 @@ impl<'tcx> ImproperCTypesLint { } /// Check that an extern "ABI" static variable is of a ffi-safe type. - fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::OwnerId, span: Span) { - let ty = cx.tcx.type_of(id).instantiate_identity(); - let mut visitor = ImproperCTypesVisitor::new(cx); + fn check_foreign_static(&mut self, cx: &LateContext<'tcx>, id: hir::HirId, span: Span) { + let ty = cx.tcx.type_of(id.owner).instantiate_identity(); + let mod_id = cx.tcx.parent_module(id).to_def_id(); + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); let ffi_res = visitor.check_type(VisitorState::static_var(), ty); self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern); } @@ -1547,6 +1601,7 @@ impl<'tcx> ImproperCTypesLint { fn_mode: CItemKind, sig: Sig<'tcx>, decl: &'tcx hir::FnDecl<'_>, + mod_id: DefId, depth: usize, ) { let sig = cx.tcx.instantiate_bound_regions_with_erased(sig); @@ -1554,7 +1609,7 @@ impl<'tcx> ImproperCTypesLint { for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Arg); state.depth = depth; - let mut visitor = ImproperCTypesVisitor::new(cx); + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); let ffi_res = visitor.check_type(state, *input_ty); self.process_ffi_result(cx, input_hir.span, ffi_res, fn_mode); } @@ -1562,7 +1617,7 @@ impl<'tcx> ImproperCTypesLint { if let hir::FnRetTy::Return(ret_hir) = decl.output { let mut state = VisitorState::entry_point_from_fnmode(fn_mode, FnPos::Ret); state.depth = depth; - let mut visitor = ImproperCTypesVisitor::new(cx); + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); let ffi_res = visitor.check_type(state, sig.output()); self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode); } @@ -1690,12 +1745,13 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // their surroundings, and their type is often declared inline self.check_fn_for_external_abi_fnptr(cx, it.owner_id.def_id, sig.decl); let mir_sig = cx.tcx.fn_sig(it.owner_id.def_id).instantiate_identity(); + let mod_id = cx.tcx.parent_module_from_def_id(it.owner_id.def_id).to_def_id(); if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::ImportedExtern, mir_sig, sig.decl, 0); + self.check_foreign_fn(cx, CItemKind::ImportedExtern, mir_sig, sig.decl, mod_id, 0); } } hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => { - self.check_foreign_static(cx, it.owner_id, ty.span); + self.check_foreign_static(cx, it.hir_id(), ty.span); } hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (), } @@ -1766,9 +1822,10 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { // "the element rendered unsafe" because their unsafety doesn't affect // their surroundings, and their type is often declared inline self.check_fn_for_external_abi_fnptr(cx, id, decl); - let mir_sig = cx.tcx.fn_sig(id).instantiate_identity(); if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::ExportedFunction, mir_sig, decl, 0); + let mir_sig = cx.tcx.fn_sig(id).instantiate_identity(); + let mod_id = cx.tcx.parent_module_from_def_id(id).to_def_id(); + self.check_foreign_fn(cx, CItemKind::ExportedFunction, mir_sig, decl, mod_id, 0); } } } diff --git a/tests/ui/lint/improper-ctypes/lint-enum.rs b/tests/ui/lint/improper-ctypes/lint-enum.rs index f900f998d06cb..2a69275702c6f 100644 --- a/tests/ui/lint/improper-ctypes/lint-enum.rs +++ b/tests/ui/lint/improper-ctypes/lint-enum.rs @@ -78,7 +78,7 @@ struct Field(()); enum NonExhaustive {} extern "C" { - fn zf(x: Z); + fn zf(x: Z); //~ ERROR `extern` block uses type `Z` fn uf(x: U); //~ ERROR `extern` block uses type `U` fn bf(x: B); //~ ERROR `extern` block uses type `B` fn tf(x: T); //~ ERROR `extern` block uses type `T` diff --git a/tests/ui/lint/improper-ctypes/lint-enum.stderr b/tests/ui/lint/improper-ctypes/lint-enum.stderr index 35d1dcb87fd82..25a05d2c75089 100644 --- a/tests/ui/lint/improper-ctypes/lint-enum.stderr +++ b/tests/ui/lint/improper-ctypes/lint-enum.stderr @@ -1,3 +1,21 @@ +error: `extern` block uses type `Z`, which is not FFI-safe + --> $DIR/lint-enum.rs:81:14 + | +LL | fn zf(x: Z); + | ^ not FFI-safe + | + = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +note: the type is defined here + --> $DIR/lint-enum.rs:8:1 + | +LL | enum Z {} + | ^^^^^^ +note: the lint level is defined here + --> $DIR/lint-enum.rs:2:9 + | +LL | #![deny(improper_ctypes)] + | ^^^^^^^^^^^^^^^ + error: `extern` block uses type `U`, which is not FFI-safe --> $DIR/lint-enum.rs:82:14 | @@ -11,11 +29,6 @@ note: the type is defined here | LL | enum U { | ^^^^^^ -note: the lint level is defined here - --> $DIR/lint-enum.rs:2:9 - | -LL | #![deny(improper_ctypes)] - | ^^^^^^^^^^^^^^^ error: `extern` block uses type `B`, which is not FFI-safe --> $DIR/lint-enum.rs:83:14 @@ -207,5 +220,5 @@ LL | fn result_unit_t_e(x: Result<(), ()>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 21 previous errors +error: aborting due to 22 previous errors diff --git a/tests/ui/lint/improper-ctypes/lint_uninhabited.rs b/tests/ui/lint/improper-ctypes/lint_uninhabited.rs new file mode 100644 index 0000000000000..32cbb09644426 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint_uninhabited.rs @@ -0,0 +1,74 @@ +#![feature(never_type)] + +#![allow(dead_code, unused_variables)] +#![deny(improper_ctypes, improper_ctypes_definitions)] + +use std::mem::transmute; + +enum Uninhabited{} + +#[repr(C)] +struct AlsoUninhabited{ + a: Uninhabited, + b: i32, +} + +#[repr(C)] +enum Inhabited{ + OhNo(Uninhabited), + OhYes(i32), +} + +struct EmptyRust; + +#[repr(transparent)] +struct HalfHiddenUninhabited { + is_this_a_tuple: (i8,i8), + zst_inh: EmptyRust, + zst_uninh: !, +} + +extern "C" { + +fn bad_entry(e: AlsoUninhabited); //~ ERROR: uses type `AlsoUninhabited` +fn bad_exit()->AlsoUninhabited; + +fn bad0_entry(e: Uninhabited); //~ ERROR: uses type `Uninhabited` +fn bad0_exit()->Uninhabited; + +fn good_entry(e: Inhabited); +fn good_exit()->Inhabited; + +fn never_entry(e:!); //~ ERROR: uses type `!` +fn never_exit()->!; + +} + +extern "C" fn impl_bad_entry(e: AlsoUninhabited) {} //~ ERROR: uses type `AlsoUninhabited` +extern "C" fn impl_bad_exit()->AlsoUninhabited { + AlsoUninhabited{ + a: impl_bad0_exit(), + b: 0, + } +} + +extern "C" fn impl_bad0_entry(e: Uninhabited) {} //~ ERROR: uses type `Uninhabited` +extern "C" fn impl_bad0_exit()->Uninhabited { + unsafe{transmute(())} //~ WARN: does not permit zero-initialization +} + +extern "C" fn impl_good_entry(e: Inhabited) {} +extern "C" fn impl_good_exit() -> Inhabited { + Inhabited::OhYes(0) +} + +extern "C" fn impl_never_entry(e:!){} //~ ERROR: uses type `!` +extern "C" fn impl_never_exit()->! { + loop{} +} + +extern "C" fn weird_pattern(e:HalfHiddenUninhabited){} +//~^ ERROR: uses type `HalfHiddenUninhabited` + + +fn main(){} diff --git a/tests/ui/lint/improper-ctypes/lint_uninhabited.stderr b/tests/ui/lint/improper-ctypes/lint_uninhabited.stderr new file mode 100644 index 0000000000000..9488dd0f62378 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint_uninhabited.stderr @@ -0,0 +1,121 @@ +error: `extern` block uses type `AlsoUninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:33:17 + | +LL | fn bad_entry(e: AlsoUninhabited); + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = help: `AlsoUninhabited` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead + = note: this struct/enum/union (`AlsoUninhabited`) is FFI-unsafe due to a `Uninhabited` field +note: the type is defined here + --> $DIR/lint_uninhabited.rs:11:1 + | +LL | struct AlsoUninhabited{ + | ^^^^^^^^^^^^^^^^^^^^^^ + = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +note: the type is defined here + --> $DIR/lint_uninhabited.rs:8:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $DIR/lint_uninhabited.rs:4:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` block uses type `Uninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:36:18 + | +LL | fn bad0_entry(e: Uninhabited); + | ^^^^^^^^^^^ not FFI-safe + | + = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +note: the type is defined here + --> $DIR/lint_uninhabited.rs:8:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ + +error: `extern` block uses type `!`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:42:18 + | +LL | fn never_entry(e:!); + | ^ not FFI-safe + | + = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables + +error: `extern` fn uses type `AlsoUninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:47:33 + | +LL | extern "C" fn impl_bad_entry(e: AlsoUninhabited) {} + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = help: `AlsoUninhabited` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead + = note: this struct/enum/union (`AlsoUninhabited`) is FFI-unsafe due to a `Uninhabited` field +note: the type is defined here + --> $DIR/lint_uninhabited.rs:11:1 + | +LL | struct AlsoUninhabited{ + | ^^^^^^^^^^^^^^^^^^^^^^ + = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +note: the type is defined here + --> $DIR/lint_uninhabited.rs:8:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $DIR/lint_uninhabited.rs:4:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `Uninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:55:34 + | +LL | extern "C" fn impl_bad0_entry(e: Uninhabited) {} + | ^^^^^^^^^^^ not FFI-safe + | + = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables +note: the type is defined here + --> $DIR/lint_uninhabited.rs:8:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ + +warning: the type `Uninhabited` does not permit zero-initialization + --> $DIR/lint_uninhabited.rs:57:12 + | +LL | unsafe{transmute(())} + | ^^^^^^^^^^^^^ this code causes undefined behavior when executed + | +note: enums with no inhabited variants have no valid value + --> $DIR/lint_uninhabited.rs:8:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ + = note: `#[warn(invalid_value)]` on by default + +error: `extern` fn uses type `!`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:65:34 + | +LL | extern "C" fn impl_never_entry(e:!){} + | ^ not FFI-safe + | + = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables + +error: `extern` fn uses type `HalfHiddenUninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:70:31 + | +LL | extern "C" fn weird_pattern(e:HalfHiddenUninhabited){} + | ^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`HalfHiddenUninhabited`) is FFI-unsafe due to a `!` field +note: the type is defined here + --> $DIR/lint_uninhabited.rs:25:1 + | +LL | struct HalfHiddenUninhabited { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables + +error: aborting due to 7 previous errors; 1 warning emitted + diff --git a/tests/ui/structs-enums/foreign-struct.rs b/tests/ui/structs-enums/foreign-struct.rs index f339c191ae806..763996de63643 100644 --- a/tests/ui/structs-enums/foreign-struct.rs +++ b/tests/ui/structs-enums/foreign-struct.rs @@ -1,17 +1,22 @@ //@ run-pass #![allow(dead_code)] -#![allow(non_camel_case_types)] // Passing enums by value - -pub enum void {} +#[repr(C)] +pub enum PoorQualityAnyEnum { + None = 0, + Int = 1, + Long = 2, + Float = 17, + Double = 18, +} mod bindgen { - use super::void; + use super::PoorQualityAnyEnum; extern "C" { - pub fn printf(v: void); + pub fn printf(v: PoorQualityAnyEnum); } } From 13d2b11c875e02d1b92385456260e34c3dabc38f Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:45:19 +0200 Subject: [PATCH 13/20] ImproperCTypes: handle the Option case Properly treat the fact that a pattern can create assumptions that are used by Option-like enums to be smaller, making those enums FFI-safe without `[repr(C)]`. --- compiler/rustc_lint/src/types.rs | 159 +++++++++++++++++- tests/ui/lint/improper-ctypes/lint-ctypes.rs | 5 + .../lint/improper-ctypes/lint-ctypes.stderr | 63 ++++--- 3 files changed, 197 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 14ce8011f147a..7c86d1624f02f 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -1,10 +1,10 @@ use std::iter; -use rustc_abi::{BackendRepr, TagEncoding, Variants, WrappingRange}; +use rustc_abi::{BackendRepr, Size, TagEncoding, Variants, WrappingRange}; use rustc_hir::{Expr, ExprKind, HirId, LangItem}; use rustc_middle::bug; use rustc_middle::ty::layout::{LayoutOf, SizeSkeleton}; -use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{self, Const, ScalarInt, Ty, TyCtxt, TypeVisitableExt}; use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; use rustc_span::{Span, Symbol, sym}; use tracing::debug; @@ -864,7 +864,7 @@ fn is_niche_optimization_candidate<'tcx>( /// Check if this enum can be safely exported based on the "nullable pointer optimization". If it /// can, return the type that `ty` can be safely converted to, otherwise return `None`. /// Currently restricted to function pointers, boxes, references, `core::num::NonZero`, -/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. +/// `core::ptr::NonNull`, `#[repr(transparent)]` newtypes, and int-range pattern types. pub(crate) fn repr_nullable_ptr<'tcx>( tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, @@ -893,6 +893,14 @@ pub(crate) fn repr_nullable_ptr<'tcx>( _ => return None, }; + if let ty::Pat(base, pat) = field_ty.kind() { + if pattern_has_disallowed_values(*pat) || matches!(base.kind(), ty::Char) { + return get_nullable_type_from_pat(tcx, typing_env, *base, *pat); + } else { + return None; + } + } + if !ty_is_known_nonnull(tcx, typing_env, field_ty) { return None; } @@ -934,6 +942,151 @@ pub(crate) fn repr_nullable_ptr<'tcx>( } } +/// Returns whether a pattern type actually has disallowed values. +pub(crate) fn pattern_has_disallowed_values<'tcx>(pat: ty::Pattern<'tcx>) -> bool { + // note the logic in this function assumes that signed ints use one's complement representation, + // which I believe is a requirement for rust + + /// Find numeric metadata on a pair of range bounds. + /// If None, assume that there are no bounds specified + /// and that this is a usize. in other words, all values are allowed. + fn unwrap_start_end<'tcx>( + start: Const<'tcx>, + end: Const<'tcx>, + ) -> (bool, Size, ScalarInt, ScalarInt) { + let usable_bound = match (start.try_to_value(), end.try_to_value()) { + (Some(ty), _) | (_, Some(ty)) => ty, + (None, None) => bug!( + "pattern range should have at least one defined value: {:?} - {:?}", + start, + end, + ), + }; + let usable_size = usable_bound.valtree.unwrap_leaf().size(); + let is_signed = match usable_bound.ty.kind() { + ty::Int(_) => true, + ty::Uint(_) | ty::Char => false, + kind @ _ => bug!("unexpected non-scalar base for pattern bounds: {:?}", kind), + }; + + let end = match end.try_to_value() { + Some(end) => end.valtree.unwrap_leaf(), + None => { + let max_val = if is_signed { + usable_size.signed_int_max() as u128 + } else { + usable_size.unsigned_int_max() + }; + ScalarInt::try_from_uint(max_val, usable_size).unwrap() + } + }; + let start = match start.try_to_value() { + Some(start) => start.valtree.unwrap_leaf(), + None => { + let min_val = if is_signed { + (usable_size.signed_int_min() as u128) & usable_size.unsigned_int_max() + } else { + 0_u128 + }; + ScalarInt::try_from_uint(min_val, usable_size).unwrap() + } + }; + (is_signed, usable_size, start, end) + } + + match *pat { + ty::PatternKind::NotNull => true, + ty::PatternKind::Range { start, end } => { + let (is_signed, scalar_size, start, end) = unwrap_start_end(start, end); + let (scalar_min, scalar_max) = if is_signed { + ( + (scalar_size.signed_int_min() as u128) & scalar_size.unsigned_int_max(), + scalar_size.signed_int_max() as u128, + ) + } else { + (0, scalar_size.unsigned_int_max()) + }; + + (start.to_bits(scalar_size), end.to_bits(scalar_size)) != (scalar_min, scalar_max) + } + ty::PatternKind::Or(patterns) => { + // first, get a simplified an sorted view of the ranges + let (is_signed, scalar_size, mut ranges) = { + let (is_signed, size, start, end) = match &*patterns[0] { + ty::PatternKind::Range { start, end } => unwrap_start_end(*start, *end), + ty::PatternKind::Or(_) => bug!("recursive \"or\" patterns?"), + ty::PatternKind::NotNull => bug!("nonnull pattern in \"or\" pattern?"), + }; + (is_signed, size, vec![(start, end)]) + }; + let scalar_max = if is_signed { + scalar_size.signed_int_max() as u128 + } else { + scalar_size.unsigned_int_max() + }; + ranges.reserve(patterns.len() - 1); + for pat in patterns.iter().skip(1) { + match *pat { + ty::PatternKind::Range { start, end } => { + let (is_this_signed, this_scalar_size, start, end) = + unwrap_start_end(start, end); + assert_eq!(is_signed, is_this_signed); + assert_eq!(scalar_size, this_scalar_size); + ranges.push((start, end)) + } + ty::PatternKind::Or(_) => bug!("recursive \"or\" patterns?"), + ty::PatternKind::NotNull => bug!("nonnull pattern in \"or\" pattern?"), + } + } + ranges.sort_by_key(|(start, _end)| { + let is_positive = + if is_signed { start.to_bits(scalar_size) <= scalar_max } else { true }; + (is_positive, start.to_bits(scalar_size)) + }); + + // then, range per range, look at the sizes of the gaps left in between + // (`prev_tail` is the highest value currently accounted for by the ranges, + // unless the first range has not been dealt with yet) + let mut prev_tail = scalar_max; + + for (range_i, (start, end)) in ranges.into_iter().enumerate() { + let (start, end) = (start.to_bits(scalar_size), end.to_bits(scalar_size)); + + // if the start of the current range is lower + // than the current-highest-range-end, ... + let current_range_overlap = + if is_signed && prev_tail > scalar_max && start <= scalar_max { + false + } else if start <= u128::overflowing_add(prev_tail, 1).0 { + range_i > 0 // no overlap possible when dealing with the first range + } else { + false + }; + if current_range_overlap { + // update the current-highest-range-end, if the current range has a higher end + if is_signed { + if prev_tail > scalar_max && end <= scalar_max { + prev_tail = end; + } else if prev_tail <= scalar_max && end > scalar_max { + // nothing to do here + } else { + // prev_tail and end have the same sign + prev_tail = u128::max(prev_tail, end) + } + } else { + // prev_tail and end have the same sign + prev_tail = u128::max(prev_tail, end) + } + } else { + // no range overlap: there are disallowed values + return true; + } + } + prev_tail != scalar_max + } + } +} + fn get_nullable_type_from_pat<'tcx>( tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 8505395c4db29..5305461cd721d 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -1,5 +1,7 @@ #![feature(rustc_private)] #![feature(extern_types)] +#![feature(pattern_types, rustc_attrs)] +#![feature(pattern_type_macro)] #![allow(private_interfaces)] #![deny(improper_ctypes)] @@ -8,6 +10,7 @@ use std::cell::UnsafeCell; use std::marker::PhantomData; use std::ffi::{c_int, c_uint}; use std::fmt::Debug; +use std::pat::pattern_type; unsafe extern "C" {type UnsizedOpaque;} trait Bar { } @@ -72,6 +75,8 @@ extern "C" { pub fn box_type(p: Box); pub fn opt_box_type(p: Option>); pub fn char_type(p: char); //~ ERROR uses type `char` + pub fn pat_type1() -> Option; //~ ERROR uses type `Option<(u32) is 0..>` + pub fn pat_type2(p: Option); // no error! pub fn trait_type(p: &dyn Bar); //~ ERROR uses type `&dyn Bar` pub fn tuple_type(p: (i32, i32)); //~ ERROR uses type `(i32, i32)` pub fn tuple_type2(p: I32Pair); //~ ERROR uses type `(i32, i32)` diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index 4978a42cef024..d74c35f8cd0ce 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -1,5 +1,5 @@ error: `extern` block uses type `&[u32]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:70:26 + --> $DIR/lint-ctypes.rs:73:26 | LL | pub fn slice_type(p: &[u32]); | ^^^^^^ not FFI-safe @@ -7,13 +7,13 @@ LL | pub fn slice_type(p: &[u32]); = help: consider using a raw pointer to the slice's first element (and a length) instead = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer note: the lint level is defined here - --> $DIR/lint-ctypes.rs:5:9 + --> $DIR/lint-ctypes.rs:7:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `&str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:71:24 + --> $DIR/lint-ctypes.rs:74:24 | LL | pub fn str_type(p: &str); | ^^^^ not FFI-safe @@ -22,7 +22,7 @@ LL | pub fn str_type(p: &str); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `char`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:74:25 + --> $DIR/lint-ctypes.rs:77:25 | LL | pub fn char_type(p: char); | ^^^^ not FFI-safe @@ -30,8 +30,17 @@ LL | pub fn char_type(p: char); = help: consider using `u32` or `libc::wchar_t` instead = note: the `char` type has no C equivalent +error: `extern` block uses type `Option<(u32) is 0..>`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:78:27 + | +LL | pub fn pat_type1() -> Option; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum + = note: enum has no representation hint + error: `extern` block uses type `&dyn Bar`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:75:26 + --> $DIR/lint-ctypes.rs:80:26 | LL | pub fn trait_type(p: &dyn Bar); | ^^^^^^^^ not FFI-safe @@ -39,7 +48,7 @@ LL | pub fn trait_type(p: &dyn Bar); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `(i32, i32)`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:76:26 + --> $DIR/lint-ctypes.rs:81:26 | LL | pub fn tuple_type(p: (i32, i32)); | ^^^^^^^^^^ not FFI-safe @@ -48,7 +57,7 @@ LL | pub fn tuple_type(p: (i32, i32)); = note: tuples have unspecified layout error: `extern` block uses type `(i32, i32)`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:77:27 + --> $DIR/lint-ctypes.rs:82:27 | LL | pub fn tuple_type2(p: I32Pair); | ^^^^^^^ not FFI-safe @@ -57,7 +66,7 @@ LL | pub fn tuple_type2(p: I32Pair); = note: tuples have unspecified layout error: `extern` block uses type `ZeroSize`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:78:25 + --> $DIR/lint-ctypes.rs:83:25 | LL | pub fn zero_size(p: ZeroSize); | ^^^^^^^^ not FFI-safe @@ -65,26 +74,26 @@ LL | pub fn zero_size(p: ZeroSize); = help: consider adding a member to this struct = note: `ZeroSize` has no fields note: the type is defined here - --> $DIR/lint-ctypes.rs:24:1 + --> $DIR/lint-ctypes.rs:27:1 | LL | pub struct ZeroSize; | ^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `ZeroSizeWithPhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:79:33 + --> $DIR/lint-ctypes.rs:84:33 | LL | pub fn zero_size_phantom(p: ZeroSizeWithPhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: composed only of `PhantomData` note: the type is defined here - --> $DIR/lint-ctypes.rs:63:1 + --> $DIR/lint-ctypes.rs:66:1 | LL | pub struct ZeroSizeWithPhantomData(::std::marker::PhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:82:12 + --> $DIR/lint-ctypes.rs:87:12 | LL | -> ::std::marker::PhantomData; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -92,7 +101,7 @@ LL | -> ::std::marker::PhantomData; = note: composed only of `PhantomData` error: `extern` block uses type `fn()`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:83:23 + --> $DIR/lint-ctypes.rs:88:23 | LL | pub fn fn_type(p: RustFn); | ^^^^^^ not FFI-safe @@ -101,7 +110,7 @@ LL | pub fn fn_type(p: RustFn); = note: this function pointer has Rust-specific calling convention error: `extern` block uses type `fn()`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:84:24 + --> $DIR/lint-ctypes.rs:89:24 | LL | pub fn fn_type2(p: fn()); | ^^^^ not FFI-safe @@ -110,14 +119,14 @@ LL | pub fn fn_type2(p: fn()); = note: this function pointer has Rust-specific calling convention error: `extern` block uses type `TransparentStr`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:86:31 + --> $DIR/lint-ctypes.rs:91:31 | LL | pub fn transparent_str(p: TransparentStr); | ^^^^^^^^^^^^^^ not FFI-safe | = note: this struct/enum/union (`TransparentStr`) is FFI-unsafe due to a `&str` field note: the type is defined here - --> $DIR/lint-ctypes.rs:32:1 + --> $DIR/lint-ctypes.rs:35:1 | LL | pub struct TransparentStr(&'static str); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -125,7 +134,7 @@ LL | pub struct TransparentStr(&'static str); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `[u8; 8]`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:88:27 + --> $DIR/lint-ctypes.rs:93:27 | LL | pub fn raw_array(arr: [u8; 8]); | ^^^^^^^ not FFI-safe @@ -134,7 +143,7 @@ LL | pub fn raw_array(arr: [u8; 8]); = note: passing raw arrays by value is not FFI-safe error: `extern` callback uses type `char`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:90:36 + --> $DIR/lint-ctypes.rs:95:36 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) | ^^^^ not FFI-safe @@ -143,7 +152,7 @@ LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) = note: the `char` type has no C equivalent error: `extern` callback uses type `&dyn Debug`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:90:44 + --> $DIR/lint-ctypes.rs:95:44 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) | ^^^^^^^^^^ not FFI-safe @@ -151,14 +160,14 @@ LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` callback uses type `TwoBadTypes<'_>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:90:59 + --> $DIR/lint-ctypes.rs:95:59 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) | ^^^^^^^^^^^^^^^ not FFI-safe | = note: this struct/enum/union (`TwoBadTypes<'_>`) is FFI-unsafe due to a `char` field note: the type is defined here - --> $DIR/lint-ctypes.rs:57:1 + --> $DIR/lint-ctypes.rs:60:1 | LL | pub struct TwoBadTypes<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -166,14 +175,14 @@ LL | pub struct TwoBadTypes<'a> { = note: the `char` type has no C equivalent error: `extern` callback uses type `TwoBadTypes<'_>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:90:59 + --> $DIR/lint-ctypes.rs:95:59 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) | ^^^^^^^^^^^^^^^ not FFI-safe | = note: this struct/enum/union (`TwoBadTypes<'_>`) is FFI-unsafe due to a `&[u8]` field note: the type is defined here - --> $DIR/lint-ctypes.rs:57:1 + --> $DIR/lint-ctypes.rs:60:1 | LL | pub struct TwoBadTypes<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -181,7 +190,7 @@ LL | pub struct TwoBadTypes<'a> { = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `&UnsizedStructBecauseDyn`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:98:47 + --> $DIR/lint-ctypes.rs:103:47 | LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -189,7 +198,7 @@ LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:100:26 + --> $DIR/lint-ctypes.rs:105:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -198,7 +207,7 @@ LL | pub fn no_niche_a(a: Option>); = note: enum has no representation hint error: `extern` block uses type `Option>`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:102:26 + --> $DIR/lint-ctypes.rs:107:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -206,5 +215,5 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 20 previous errors +error: aborting due to 21 previous errors From 587dd95023b8cbd1b14c6991f8818247ef7a5021 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:47:02 +0200 Subject: [PATCH 14/20] ImproperCTypes: refactor handling opaque aliases Put the handling of opaque aliases in the actual `visit___` methods instead of awkwardly pre-checking for them --- .../rustc_lint/src/types/improper_ctypes.rs | 76 ++++++++++++++----- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 473c7a2c8edcd..9ceec7666198c 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -166,6 +166,23 @@ fn get_fn_sig_from_mir_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Sig<'tc } } +// FIXME(ctypes): it seems that tests/ui/lint/opaque-ty-ffi-normalization-cycle.rs relies this: +// we consider opaque aliases that normalise to something else to be unsafe. +// ...is it the behaviour we want? +/// a modified version of cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty).unwrap_or(ty) +/// so that opaque types prevent normalisation once region erasure occurs +fn erase_and_maybe_normalize<'tcx>(cx: &LateContext<'tcx>, value: Ty<'tcx>) -> Ty<'tcx> { + if (!value.has_aliases()) || value.has_opaque_types() { + cx.tcx.erase_and_anonymize_regions(value) + } else { + cx.tcx.try_normalize_erasing_regions(cx.typing_env(), value).unwrap_or(value) + // note: the code above ^^^ would only cause a call to the commented code below vvv + //let value = cx.tcx.erase_and_anonymize_regions(value); + //let mut folder = TryNormalizeAfterErasingRegionsFolder::new(cx.tcx, cx.typing_env()); + //value.try_fold_with(&mut folder).unwrap_or(value) + } +} + /// Getting the (normalized) type out of a field (for, e.g., an enum variant or a tuple). #[inline] fn get_type_from_field<'tcx>( @@ -174,7 +191,7 @@ fn get_type_from_field<'tcx>( args: GenericArgsRef<'tcx>, ) -> Ty<'tcx> { let field_ty = field.ty(cx.tcx, args); - cx.tcx.try_normalize_erasing_regions(cx.typing_env(), field_ty).unwrap_or(field_ty) + erase_and_maybe_normalize(cx, field_ty) } fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { @@ -553,10 +570,7 @@ fn get_type_sizedness<'tcx, 'a>(cx: &'a LateContext<'tcx>, ty: Ty<'tcx>) -> Type Some(item_ty) => *item_ty, None => bug!("Empty tuple (AKA unit type) should be Sized, right?"), }; - let item_ty = cx - .tcx - .try_normalize_erasing_regions(cx.typing_env(), item_ty) - .unwrap_or(item_ty); + let item_ty = erase_and_maybe_normalize(cx, item_ty); match get_type_sizedness(cx, item_ty) { s @ (TypeSizedness::MetaSized | TypeSizedness::Unsized @@ -1415,25 +1429,52 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty::Never => self.visit_uninhabited(state, ty), - // While opaque types are checked for earlier, if a projection in a struct field - // normalizes to an opaque type, then it will reach this branch. + // This is only half of the checking-for-opaque-aliases story: + // since they are liable to vanish on normalisation, we need a specific to find them through + // other aliases, which is called in the next branch of this `match ty.kind()` statement ty::Alias(ty::Opaque, ..) => { FfiResult::new_with_reason(ty, fluent::lint_improper_ctypes_opaque, None) } - // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe, + // `extern "C" fn` function definitions can have type parameters, which may or may not be FFI-safe, // so they are currently ignored for the purposes of this lint. - ty::Param(..) | ty::Alias(ty::Projection | ty::Inherent, ..) - if state.can_expect_ty_params() => - { - FfiSafe + // function pointers can do the same + // + // however, these ty_kind:s can also be encountered because the type isn't normalized yet. + ty::Param(..) | ty::Alias(ty::Projection | ty::Inherent | ty::Free, ..) => { + if ty.has_opaque_types() { + // FIXME(ctypes): this is suboptimal because we give up + // on reporting anything *else* than the opaque part of the type + // but this is better than not reporting anything, or crashing + self.visit_for_opaque_ty(ty).unwrap() + } else { + // in theory, thanks to erase_and_maybe_normalize, + // normalisation has already occurred + debug_assert_eq!( + self.cx + .tcx + .try_normalize_erasing_regions(self.cx.typing_env(), ty,) + .unwrap_or(ty), + ty, + ); + + if matches!( + ty.kind(), + ty::Param(..) | ty::Alias(ty::Projection | ty::Inherent, ..) + ) && state.can_expect_ty_params() + { + FfiSafe + } else { + // ty::Alias(ty::Free), and all params/aliases for something + // defined beyond the FFI boundary + bug!("unexpected type in foreign function: {:?}", ty) + } + } } ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"), - ty::Param(..) - | ty::Alias(ty::Projection | ty::Inherent | ty::Free, ..) - | ty::Infer(..) + ty::Infer(..) | ty::Bound(..) | ty::Error(_) | ty::Closure(..) @@ -1469,10 +1510,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } fn check_type(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { - let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.typing_env(), ty).unwrap_or(ty); - if let Some(res) = self.visit_for_opaque_ty(ty) { - return res; - } + let ty = erase_and_maybe_normalize(self.cx, ty); self.visit_type(state, ty) } } From 43c99a78ede69b08836093c55e10de4c121b1253 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:50:56 +0200 Subject: [PATCH 15/20] ImproperCTypes: also check in traits Add new areas that are checked by ImproperCTypes lints: Function declarations(*) and definitions in traits and impls *) from the perspective of an FFI boundary, those are actually definitions --- .../rustc_lint/src/types/improper_ctypes.rs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 9ceec7666198c..b621e5ba934fc 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -1866,4 +1866,54 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { self.check_foreign_fn(cx, CItemKind::ExportedFunction, mir_sig, decl, mod_id, 0); } } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, tr_it: &hir::TraitItem<'tcx>) { + match tr_it.kind { + hir::TraitItemKind::Const(hir_ty, _) => { + let ty = cx.tcx.type_of(hir_ty.hir_id.owner.def_id).instantiate_identity(); + self.check_type_for_external_abi_fnptr(cx, hir_ty, ty); + } + hir::TraitItemKind::Fn(sig, trait_fn) => { + match trait_fn { + // if the method is defined here, + // there is a matching ``LateLintPass::check_fn`` call, + // let's not redo that work + hir::TraitFn::Provided(_) => return, + hir::TraitFn::Required(_) => (), + } + let local_id = tr_it.owner_id.def_id; + + self.check_fn_for_external_abi_fnptr(cx, local_id, sig.decl); + if !sig.header.abi.is_rustic_abi() { + let mir_sig = cx.tcx.fn_sig(local_id).instantiate_identity(); + let mod_id = cx.tcx.parent_module_from_def_id(local_id).to_def_id(); + self.check_foreign_fn(cx, CItemKind::ExportedFunction, mir_sig, sig.decl, mod_id, 0); + } + } + hir::TraitItemKind::Type(_, ty_maybe) => { + if let Some(hir_ty) = ty_maybe { + let ty = cx.tcx.type_of(hir_ty.hir_id.owner.def_id).instantiate_identity(); + self.check_type_for_external_abi_fnptr(cx, hir_ty, ty); + } + } + } + } + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, im_it: &hir::ImplItem<'tcx>) { + // note: we do not skip these checks eventhough they might generate dupe warnings because: + // - the corresponding trait might be in another crate + // - the corresponding trait might have some templating involved, so only the impl has the full type information + match im_it.kind { + hir::ImplItemKind::Type(hir_ty) => { + let ty = cx.tcx.type_of(hir_ty.hir_id.owner.def_id).instantiate_identity(); + self.check_type_for_external_abi_fnptr(cx, hir_ty, ty); + } + hir::ImplItemKind::Fn(_sig, _) => { + // see ``LateLintPass::check_fn`` + } + hir::ImplItemKind::Const(hir_ty, _) => { + let ty = cx.tcx.type_of(hir_ty.hir_id.owner.def_id).instantiate_identity(); + self.check_type_for_external_abi_fnptr(cx, hir_ty, ty); + } + } + } } From 1a6c124e960b73dbd6477f0f6213ef2e7ec33f33 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 22:55:54 +0200 Subject: [PATCH 16/20] ImproperCTypes: also check 'exported' static variables Added the missing case for FFI-exposed pieces of code: static variables with the `no_mangle` or `export_name` annotations. This adds a new lint, which is managed by the rest of the ImproperCTypes architecture. --- .../rustc_lint/src/types/improper_ctypes.rs | 81 ++++++++++++++++--- .../exported_symbol_wrong_type.rs | 1 + tests/ui/lint/improper-ctypes/lint-ctypes.rs | 11 ++- .../lint/improper-ctypes/lint-ctypes.stderr | 18 ++++- ...-allocations-dont-inherit-codegen-attrs.rs | 2 + 5 files changed, 101 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index b621e5ba934fc..daa7944c7cc44 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -57,27 +57,32 @@ declare_lint! { declare_lint! { /// The `improper_ctypes_definitions` lint detects incorrect use of - /// [`extern` function] definitions. - /// (In other words, functions to be used by foreign code.) + /// [`extern` function] definitions and [`no_mangle`] / [`export_name`] static variable definitions. + /// (In other words, functions and global variables to be used by foreign code.) /// /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier + /// [`no_mangle`]: https://doc.rust-lang.org/stable/reference/abi.html#the-no_mangle-attribute + /// [`export_name`]: https://doc.rust-lang.org/stable/reference/abi.html#the-export_name-attribute /// /// ### Example /// /// ```rust /// # #![allow(unused)] /// pub extern "C" fn str_type(p: &str) { } + /// # #[used] + /// # #[unsafe(no_mangle)] + /// static PLUGIN_ABI_MIN_VERSION: &'static str = "0.0.5"; /// ``` /// /// {{produces}} /// /// ### Explanation /// - /// There are many parameter and return types that may be specified in an - /// `extern` function that are not compatible with the given ABI. This - /// lint is an alert that these types should not be used. The lint usually - /// should provide a description of the issue, along with possibly a hint - /// on how to resolve it. + /// There are many types that may be specified at interfaces exposed to foreign code, + /// but are not follow the rules to ensure proper ABI compatibility. + /// This lint is issued when a mistake is detected. + /// The lint usually should provide a description of the issue, + /// along with possibly a hint on how to resolve it. pub(crate) IMPROPER_CTYPES_DEFINITIONS, Warn, "proper use of libc types in foreign item definitions" @@ -282,6 +287,9 @@ enum CItemKind { ExportedFunction, /// `extern "C"` function pointers -> also IMPROPER_CTYPES, Callback, + /// `no_mangle`/`export_name` static variables, assumed to be used from across an FFI boundary, + /// -> also IMPROPER_CTYPES_DEFINITIONS + ExportedStatic, } /// Annotates whether we are in the context of a function's argument types or return type. @@ -694,6 +702,8 @@ struct VisitorState { impl RootUseFlags { // The values that can be set. const STATIC_TY: Self = Self::STATIC; + const EXPORTED_STATIC_TY: Self = + Self::from_bits(Self::STATIC.bits() | Self::DEFINED.bits()).unwrap(); const ARGUMENT_TY_IN_DEFINITION: Self = Self::from_bits(Self::FUNC.bits() | Self::DEFINED.bits()).unwrap(); const RETURN_TY_IN_DEFINITION: Self = @@ -734,6 +744,9 @@ impl VisitorState { (CItemKind::ExportedFunction, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DEFINITION, (CItemKind::ImportedExtern, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_DECLARATION, (CItemKind::Callback, FnPos::Arg) => RootUseFlags::ARGUMENT_TY_IN_FNPTR, + (CItemKind::ExportedStatic, _) => bug!( + "VisitorState::entry_point_from_fnmode() should not be used for static variables!" + ), }; Self::entry_point(p_flags) } @@ -743,6 +756,11 @@ impl VisitorState { Self::entry_point(RootUseFlags::STATIC_TY) } + /// Get the proper visitor state for a locally-defined static variable's type + fn static_var_def() -> Self { + Self::entry_point(RootUseFlags::EXPORTED_STATIC_TY) + } + /// Whether the type is used as the type of a static variable. fn is_direct_in_static(&self) -> bool { let ret = self.root_use_flags.contains(RootUseFlags::STATIC); @@ -1632,6 +1650,15 @@ impl<'tcx> ImproperCTypesLint { self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern); } + /// Check that a `#[no_mangle]`/`#[export_name = _]` static variable is of a ffi-safe type. + fn check_exported_static(&self, cx: &LateContext<'tcx>, id: hir::HirId, span: Span) { + let ty = cx.tcx.type_of(id.owner).instantiate_identity(); + let mod_id = cx.tcx.parent_module(id).to_def_id(); + let mut visitor = ImproperCTypesVisitor::new(cx, mod_id); + let ffi_res = visitor.check_type(VisitorState::static_var_def(), ty); + self.process_ffi_result(cx, span, ffi_res, CItemKind::ExportedStatic); + } + /// Check if a function's argument types and result type are "ffi-safe". fn check_foreign_fn( &mut self, @@ -1738,10 +1765,13 @@ impl<'tcx> ImproperCTypesLint { // Internally, we treat this differently, but at the end of the day // their linting needs to be enabled/disabled alongside that of "FFI-imported" items. CItemKind::Callback => IMPROPER_CTYPES, + // Same thing with static variables, which are "FFI-exported" + CItemKind::ExportedStatic => IMPROPER_CTYPES_DEFINITIONS, }; let desc = match fn_mode { CItemKind::ImportedExtern => "`extern` block", CItemKind::ExportedFunction => "`extern` fn", + CItemKind::ExportedStatic => "foreign-code-reachable static", CItemKind::Callback => "`extern` callback", }; for reason in reasons.iter_mut() { @@ -1785,7 +1815,14 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { let mir_sig = cx.tcx.fn_sig(it.owner_id.def_id).instantiate_identity(); let mod_id = cx.tcx.parent_module_from_def_id(it.owner_id.def_id).to_def_id(); if !abi.is_rustic_abi() { - self.check_foreign_fn(cx, CItemKind::ImportedExtern, mir_sig, sig.decl, mod_id, 0); + self.check_foreign_fn( + cx, + CItemKind::ImportedExtern, + mir_sig, + sig.decl, + mod_id, + 0, + ); } } hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => { @@ -1805,6 +1842,25 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { ty, cx.tcx.type_of(item.owner_id).instantiate_identity(), ); + + // FIXME: cx.tcx.has_attr no worky + // if matches!(item.kind, hir::ItemKind::Static(..)) + // && (cx.tcx.has_attr(item.owner_id, sym::no_mangle) + // || cx.tcx.has_attr(item.owner_id, sym::export_name)) + if matches!(item.kind, hir::ItemKind::Static(..)) { + let is_exported_static = cx.tcx.get_all_attrs(item.owner_id).iter().any(|x| { + matches!( + x, + hir::Attribute::Parsed( + hir::attrs::AttributeKind::NoMangle(_) + | hir::attrs::AttributeKind::ExportName { .. } + ) + ) + }); + if is_exported_static { + self.check_exported_static(cx, item.hir_id(), ty.span); + } + } } // See `check_fn` for declarations, `check_foreign_items` for definitions in extern blocks hir::ItemKind::Fn { .. } => {} @@ -1887,7 +1943,14 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { if !sig.header.abi.is_rustic_abi() { let mir_sig = cx.tcx.fn_sig(local_id).instantiate_identity(); let mod_id = cx.tcx.parent_module_from_def_id(local_id).to_def_id(); - self.check_foreign_fn(cx, CItemKind::ExportedFunction, mir_sig, sig.decl, mod_id, 0); + self.check_foreign_fn( + cx, + CItemKind::ExportedFunction, + mir_sig, + sig.decl, + mod_id, + 0, + ); } } hir::TraitItemKind::Type(_, ty_maybe) => { diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.rs b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.rs index e273e354334f8..e7bad493f4b93 100644 --- a/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.rs +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_wrong_type.rs @@ -1,4 +1,5 @@ #[no_mangle] +#[allow(improper_c_var_definitions)] static FOO: () = (); fn main() { diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.rs b/tests/ui/lint/improper-ctypes/lint-ctypes.rs index 5305461cd721d..108fd83577335 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -4,7 +4,7 @@ #![feature(pattern_type_macro)] #![allow(private_interfaces)] -#![deny(improper_ctypes)] +#![deny(improper_ctypes, improper_ctypes_definitions)] use std::cell::UnsafeCell; use std::marker::PhantomData; @@ -138,6 +138,15 @@ extern "C" { pub fn good19(_: &String); } +static DEFAULT_U32: u32 = 42; +#[no_mangle] +static EXPORTED_STATIC: &u32 = &DEFAULT_U32; +#[no_mangle] +static EXPORTED_STATIC_BAD: &'static str = "is this reaching you, plugin?"; +//~^ ERROR: uses type `&str` +#[export_name="EXPORTED_STATIC_MUT_BUT_RENAMED"] +static mut EXPORTED_STATIC_MUT: &u32 = &DEFAULT_U32; + #[cfg(not(target_arch = "wasm32"))] extern "C" { pub fn good1(size: *const c_int); diff --git a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index d74c35f8cd0ce..ce4d324a71a2e 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -9,7 +9,7 @@ LL | pub fn slice_type(p: &[u32]); note: the lint level is defined here --> $DIR/lint-ctypes.rs:7:9 | -LL | #![deny(improper_ctypes)] +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `&str`, which is not FFI-safe @@ -215,5 +215,19 @@ LL | pub fn no_niche_b(b: Option>); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint -error: aborting due to 21 previous errors +error: foreign-code-reachable static uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:145:29 + | +LL | static EXPORTED_STATIC_BAD: &'static str = "is this reaching you, plugin?"; + | ^^^^^^^^^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer +note: the lint level is defined here + --> $DIR/lint-ctypes.rs:7:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 22 previous errors diff --git a/tests/ui/statics/nested-allocations-dont-inherit-codegen-attrs.rs b/tests/ui/statics/nested-allocations-dont-inherit-codegen-attrs.rs index 0b7e659c7b75a..059af87a72234 100644 --- a/tests/ui/statics/nested-allocations-dont-inherit-codegen-attrs.rs +++ b/tests/ui/statics/nested-allocations-dont-inherit-codegen-attrs.rs @@ -1,5 +1,7 @@ //@ build-pass +#![allow(improper_c_var_definitions)] + // Make sure that the nested static allocation for `FOO` doesn't inherit `no_mangle`. #[no_mangle] pub static mut FOO: &mut [i32] = &mut [42]; From ee6f4fe31bc024374e12bdfd68a5970349cec96c Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 23:21:27 +0200 Subject: [PATCH 17/20] ImproperCTypes: don't consider packed reprs `[repr(C,packed)]` structs shouldn't be considered FFI-safe --- .../rustc_lint/src/types/improper_ctypes.rs | 23 ++++++++++++------- .../repr-rust-is-undefined.stderr | 20 ++++------------ 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index daa7944c7cc44..9fa678bb20aac 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -251,22 +251,21 @@ fn check_struct_for_power_alignment<'tcx>( item: &'tcx hir::Item<'tcx>, adt_def: AdtDef<'tcx>, ) { - let tcx = cx.tcx; - // Only consider structs (not enums or unions) on AIX. - if tcx.sess.target.os != "aix" || !adt_def.is_struct() { + if cx.tcx.sess.target.os != "aix" || !adt_def.is_struct() { return; } // The struct must be repr(C), but ignore it if it explicitly specifies its alignment with // either `align(N)` or `packed(N)`. - if adt_def.repr().c() && !adt_def.repr().packed() && adt_def.repr().align.is_none() { + debug_assert!(adt_def.repr().c() && !adt_def.repr().packed() && adt_def.repr().align.is_none()); + if cx.tcx.sess.target.os == "aix" && !adt_def.all_fields().next().is_none() { let struct_variant_data = item.expect_struct().2; for field_def in struct_variant_data.fields().iter().skip(1) { // Struct fields (after the first field) are checked for the // power alignment rule, as fields after the first are likely // to be the fields that are misaligned. - let ty = tcx.type_of(field_def.def_id).instantiate_identity(); + let ty = cx.tcx.type_of(field_def.def_id).instantiate_identity(); if check_arg_for_power_alignment(cx, ty) { cx.emit_span_lint(USES_POWER_ALIGNMENT, field_def.span, UsesPowerAlignment); } @@ -1060,7 +1059,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // otherwise, having all fields be phantoms // takes priority over transparent_with_all_zst_fields if let FfiUnsafe(explanations) = ffires_accumulator { - debug_assert!(def.repr().c() || def.repr().transparent() || def.repr().int.is_some()); + debug_assert!( + (def.repr().c() && !def.repr().packed()) + || def.repr().transparent() + || def.repr().int.is_some() + ); if def.repr().transparent() || matches!(def.adt_kind(), AdtKind::Enum) { let field_ffires = FfiUnsafe(explanations).wrap_all( @@ -1130,7 +1133,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ) -> FfiResult<'tcx> { debug_assert!(matches!(def.adt_kind(), AdtKind::Struct | AdtKind::Union)); - if !def.repr().c() && !def.repr().transparent() { + if !((def.repr().c() && !def.repr().packed()) || def.repr().transparent()) { + // FIXME(ctypes) packed reprs prevent C compatibility, right? return FfiResult::new_with_reason( ty, if def.is_struct() { @@ -1202,7 +1206,10 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } // Check for a repr() attribute to specify the size of the // discriminant. - if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() { + if !(def.repr().c() && !def.repr().packed()) + && !def.repr().transparent() + && def.repr().int.is_none() + { // Special-case types like `Option` and `Result` if let Some(inner_ty) = repr_nullable_ptr(self.cx.tcx, self.cx.typing_env(), ty) { return self.visit_type(state.get_next(ty), inner_ty); diff --git a/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr b/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr index b3d99f1c1de34..5c4df4f02cdce 100644 --- a/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr +++ b/tests/ui/lint/improper-ctypes/repr-rust-is-undefined.stderr @@ -23,19 +23,13 @@ error: `extern` block uses type `B`, which is not FFI-safe LL | fn bar(x: B); | ^ not FFI-safe | - = note: this struct/enum/union (`B`) is FFI-unsafe due to a `A` field + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `B` + = note: `B` has unspecified layout note: the type is defined here --> $DIR/repr-rust-is-undefined.rs:13:1 | LL | struct B { | ^^^^^^^^ - = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `A` - = note: `A` has unspecified layout -note: the type is defined here - --> $DIR/repr-rust-is-undefined.rs:8:1 - | -LL | struct A { - | ^^^^^^^^ error: `extern` block uses type `A`, which is not FFI-safe --> $DIR/repr-rust-is-undefined.rs:37:15 @@ -57,19 +51,13 @@ error: `extern` block uses type `B`, which is not FFI-safe LL | fn quux(x: B2); | ^^ not FFI-safe | - = note: this struct/enum/union (`B`) is FFI-unsafe due to a `A` field + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `B` + = note: `B` has unspecified layout note: the type is defined here --> $DIR/repr-rust-is-undefined.rs:13:1 | LL | struct B { | ^^^^^^^^ - = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `A` - = note: `A` has unspecified layout -note: the type is defined here - --> $DIR/repr-rust-is-undefined.rs:8:1 - | -LL | struct A { - | ^^^^^^^^ error: `extern` block uses type `D`, which is not FFI-safe --> $DIR/repr-rust-is-undefined.rs:40:16 From 9fc99c65633edc4e3fed3be4d4bd5e3b09113850 Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 23:22:38 +0200 Subject: [PATCH 18/20] ImproperCTypes: add tests - ensure proper coverage of as many edge cases in the type checking as possible - test for an issue that was fixed by this commit chain - understand where `[allow(improper_c*)]` needs to be to take effect (current behaviour not ideal, one should be able to flag a struct definitition as safe anyway) --- .../improper-ctypes/allow-improper-ctypes.rs | 159 +++++++++ .../allow-improper-ctypes.stderr | 104 ++++++ .../auxiliary/extern_crate_types.rs | 85 +++++ .../improper-ctypes/lint-nonexhaustive.rs | 35 ++ .../lint/improper-ctypes/lint-tykind-fuzz.rs | 311 ++++++++++++++++++ .../improper-ctypes/lint-tykind-fuzz.stderr | 301 +++++++++++++++++ ...int_uninhabited.rs => lint-uninhabited.rs} | 0 ...habited.stderr => lint-uninhabited.stderr} | 36 +- 8 files changed, 1013 insertions(+), 18 deletions(-) create mode 100644 tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs create mode 100644 tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr create mode 100644 tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs create mode 100644 tests/ui/lint/improper-ctypes/lint-nonexhaustive.rs create mode 100644 tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs create mode 100644 tests/ui/lint/improper-ctypes/lint-tykind-fuzz.stderr rename tests/ui/lint/improper-ctypes/{lint_uninhabited.rs => lint-uninhabited.rs} (100%) rename tests/ui/lint/improper-ctypes/{lint_uninhabited.stderr => lint-uninhabited.stderr} (85%) diff --git a/tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs new file mode 100644 index 0000000000000..a0cf32237179c --- /dev/null +++ b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs @@ -0,0 +1,159 @@ +#![deny(improper_ctypes, improper_ctypes_definitions)] + +//@ aux-build: extern_crate_types.rs +//@ compile-flags:--extern extern_crate_types +extern crate extern_crate_types as ext_crate; + +// //////////////////////////////////////////////////////// +// first, the same bank of types as in the extern crate + +// FIXME: maybe re-introduce improper_ctype_definitions (ctype singular) +// as a way to mark ADTs as "let's ignore that they are not actually FFI-unsafe" + +#[repr(C)] +struct SafeStruct (i32); + +#[repr(C)] +struct UnsafeStruct (String); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +struct AllowedUnsafeStruct (String); + +// refs are only unsafe if the value comes from the other side of the FFI boundary +// due to the non-null assumption +// (technically there are also assumptions about non-dandling, alignment, +// aliasing, lifetimes, etc...) +// the lint is not raised here, but will be if used in the wrong place +#[repr(C)] +struct UnsafeFromForeignStruct<'a> (&'a u32); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +struct AllowedUnsafeFromForeignStruct<'a> (&'a u32); + + +type SafeFnPtr = extern "C" fn(i32)->i32; + +type UnsafeFnPtr = extern "C" fn((i32, i32))->i32; +//~^ ERROR: `extern` callback uses type `(i32, i32)` + + +// for now, let's not lint on the nonzero assumption, +// because: +// - we don't know if the callback is rust-callee-foreign-caller or the other way around +// - having to cast around function signatures to get function pointers +// would be an awful experience +// so, let's assume that the unsafety in this fnptr +// will be pointed out indirectly by a lint elsewhere +// (note: there's one case where the error would be missed altogether: +// a rust-caller,non-rust-callee callback where the fnptr +// is given as an argument to a rust-callee,non-rust-caller +// FFI boundary) +#[allow(improper_ctypes)] +type AllowedUnsafeFnPtr = extern "C" fn(&[i32])->i32; + +type UnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +#[allow(improper_ctypes)] +type AllowedUnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +type UnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + +#[allow(improper_ctypes)] +type AllowedUnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + + +// //////////////////////////////////////////////////////// +// then, some functions that use them + +static INT: u32 = 42; + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn1a(e: &String) -> &str {&*e} +extern "C" fn fn1u(e: &String) -> &str {&*e} +//~^ ERROR: `extern` fn uses type `&str` +// | FIXME: not warning about the &String feels wrong, but it's behind a FFI-safe reference so... + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn2a(e: UnsafeStruct) {} +extern "C" fn fn2u(e: UnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `UnsafeStruct` +#[allow(improper_ctypes_definitions)] +extern "C" fn fn2oa(e: ext_crate::UnsafeStruct) {} +extern "C" fn fn2ou(e: ext_crate::UnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `ext_crate::UnsafeStruct` + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn3a(e: AllowedUnsafeStruct) {} +extern "C" fn fn3u(e: AllowedUnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `AllowedUnsafeStruct` +// ^^ FIXME: ...ideally the lint should not trigger here +#[allow(improper_ctypes_definitions)] +extern "C" fn fn3oa(e: ext_crate::AllowedUnsafeStruct) {} +extern "C" fn fn3ou(e: ext_crate::AllowedUnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `ext_crate::AllowedUnsafeStruct` +// ^^ FIXME: ...ideally the lint should not trigger here + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn4a(e: UnsafeFromForeignStruct) {} +extern "C" fn fn4u(e: UnsafeFromForeignStruct) {} +#[allow(improper_ctypes_definitions)] +extern "C" fn fn4oa(e: ext_crate::UnsafeFromForeignStruct) {} +extern "C" fn fn4ou(e: ext_crate::UnsafeFromForeignStruct) {} +// the block above might become unsafe if/once we lint on the value assumptions of types + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn5a() -> UnsafeFromForeignStruct<'static> { UnsafeFromForeignStruct(&INT)} +extern "C" fn fn5u() -> UnsafeFromForeignStruct<'static> { UnsafeFromForeignStruct(&INT)} +#[allow(improper_ctypes_definitions)] +extern "C" fn fn5oa() -> ext_crate::UnsafeFromForeignStruct<'static> { + ext_crate::UnsafeFromForeignStruct(&INT) +} +extern "C" fn fn5ou() -> ext_crate::UnsafeFromForeignStruct<'static> { + ext_crate::UnsafeFromForeignStruct(&INT) +} + +#[allow(improper_ctypes_definitions)] +extern "C" fn fn6a() -> AllowedUnsafeFromForeignStruct<'static> { + AllowedUnsafeFromForeignStruct(&INT) +} +extern "C" fn fn6u() -> AllowedUnsafeFromForeignStruct<'static> { + AllowedUnsafeFromForeignStruct(&INT) +} +#[allow(improper_ctypes_definitions)] +extern "C" fn fn6oa() -> ext_crate::AllowedUnsafeFromForeignStruct<'static> { + ext_crate::AllowedUnsafeFromForeignStruct(&INT) +} +extern "C" fn fn6ou() -> ext_crate::AllowedUnsafeFromForeignStruct<'static> { + ext_crate::AllowedUnsafeFromForeignStruct(&INT) +} + +// //////////////////////////////////////////////////////// +// special cases: struct-in-fnptr and fnptr-in-struct + +#[repr(C)] +struct FakeVTable{ + make_new: extern "C" fn() -> A, + combine: extern "C" fn(&[A]) -> A, + //~^ ERROR: `extern` callback uses type `&[A]` + drop: extern "C" fn(A), + something_else: (A, usize), +} + +type FakeVTableMaker = extern "C" fn() -> FakeVTable; +//~^ ERROR: `extern` callback uses type `FakeVTable` + +#[repr(C)] +#[allow(improper_ctypes)] +struct FakeVTableAllowed{ + make_new: extern "C" fn() -> A, + combine: extern "C" fn(&[A]) -> A, + drop: extern "C" fn(A), + something_else: (A, usize), +} + +#[allow(improper_ctypes)] +type FakeVTableMakerAllowed = extern "C" fn() -> FakeVTable; + +fn main(){} diff --git a/tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr new file mode 100644 index 0000000000000..84137584a92a3 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr @@ -0,0 +1,104 @@ +error: `extern` callback uses type `(i32, i32)`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:38:34 + | +LL | type UnsafeFnPtr = extern "C" fn((i32, i32))->i32; + | ^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout +note: the lint level is defined here + --> $DIR/allow-improper-ctypes.rs:1:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&str`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:74:35 + | +LL | extern "C" fn fn1u(e: &String) -> &str {&*e} + | ^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer +note: the lint level is defined here + --> $DIR/allow-improper-ctypes.rs:1:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `UnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:80:23 + | +LL | extern "C" fn fn2u(e: UnsafeStruct) {} + | ^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`UnsafeStruct`) is FFI-unsafe due to a `String` field +note: the type is defined here + --> $DIR/allow-improper-ctypes.rs:17:1 + | +LL | struct UnsafeStruct (String); + | ^^^^^^^^^^^^^^^^^^^ + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `String` + = note: `String` has unspecified layout + +error: `extern` fn uses type `ext_crate::UnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:84:24 + | +LL | extern "C" fn fn2ou(e: ext_crate::UnsafeStruct) {} + | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`ext_crate::UnsafeStruct`) is FFI-unsafe due to a `String` field + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `String` + = note: `String` has unspecified layout + +error: `extern` fn uses type `AllowedUnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:89:23 + | +LL | extern "C" fn fn3u(e: AllowedUnsafeStruct) {} + | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`AllowedUnsafeStruct`) is FFI-unsafe due to a `String` field +note: the type is defined here + --> $DIR/allow-improper-ctypes.rs:21:1 + | +LL | struct AllowedUnsafeStruct (String); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `String` + = note: `String` has unspecified layout + +error: `extern` fn uses type `ext_crate::AllowedUnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:94:24 + | +LL | extern "C" fn fn3ou(e: ext_crate::AllowedUnsafeStruct) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`ext_crate::AllowedUnsafeStruct`) is FFI-unsafe due to a `String` field + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `String` + = note: `String` has unspecified layout + +error: `extern` callback uses type `&[A]`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:138:28 + | +LL | combine: extern "C" fn(&[A]) -> A, + | ^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` callback uses type `FakeVTable`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:144:43 + | +LL | type FakeVTableMaker = extern "C" fn() -> FakeVTable; + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this struct/enum/union (`FakeVTable`) is FFI-unsafe due to a `(u32, usize)` field +note: the type is defined here + --> $DIR/allow-improper-ctypes.rs:136:1 + | +LL | struct FakeVTable{ + | ^^^^^^^^^^^^^^^^^^^^ + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: aborting due to 8 previous errors + diff --git a/tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs b/tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs new file mode 100644 index 0000000000000..3337bd0f588a8 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs @@ -0,0 +1,85 @@ +/// a bank of types (structs, function pointers) that are safe or unsafe for whatever reason, +/// with or without said unsafety being explicitely ignored + +#[repr(C)] +pub struct SafeStruct (pub i32); + +#[repr(C)] +pub struct UnsafeStruct (pub String); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +pub struct AllowedUnsafeStruct (pub String); + +// refs are only unsafe if the value comes from the other side of the FFI boundary +// due to the non-null assumption +// (technically there are also assumptions about non-dandling, alignment, aliasing, +// lifetimes, etc...) +#[repr(C)] +pub struct UnsafeFromForeignStruct<'a> (pub &'a u32); + +#[repr(C)] +//#[allow(improper_ctype_definitions)] +pub struct AllowedUnsafeFromForeignStruct<'a> (pub &'a u32); + + +pub type SafeFnPtr = extern "C" fn(i32)->i32; + +pub type UnsafeFnPtr = extern "C" fn((i32,i32))->i32; + +#[allow(improper_c_callbacks)] +pub type AllowedUnsafeFnPtr = extern "C" fn(&[i32])->i32; + +pub type UnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +#[allow(improper_c_callbacks)] +pub type AllowedUnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +pub type UnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + +#[allow(improper_c_callbacks)] +pub type AllowedUnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + + +// //////////////////////////////////// +/// types used in specific issue-based tests that need extern-crate types + +#[repr(C)] +#[non_exhaustive] +pub struct NonExhaustiveStruct { + pub field: u8 +} + +#[repr(C)] +#[non_exhaustive] +pub enum NonExhaustiveEnum { + variant(u8), +} + +#[repr(C)] +#[non_exhaustive] +pub enum NonExhaustiveCEnum { + variant1, + variant2(()), +} + +#[repr(C)] +pub enum NonExhaustiveEnumVariant { + variant1, + #[non_exhaustive] + variant2((u32,)), +} + +extern "C" { + pub fn nonexhaustivestruct_create() -> *mut NonExhaustiveStruct; + pub fn nonexhaustivestruct_destroy(s: *mut NonExhaustiveStruct); + pub fn nonexhaustiveenum_create() -> *mut NonExhaustiveEnum; + pub fn nonexhaustiveenum_destroy(s: *mut NonExhaustiveEnum); + pub fn nonexhaustivecenum_create() -> *mut NonExhaustiveCEnum; + pub fn nonexhaustivecenum_destroy(s: *mut NonExhaustiveCEnum); + pub fn nonexhaustiveenumvariant_create() -> *mut NonExhaustiveEnumVariant; + pub fn nonexhaustiveenumvariant_destroy(s: *mut NonExhaustiveEnumVariant); + + pub fn nonexhaustivestruct_onstack() -> NonExhaustiveStruct; + pub fn nonexhaustivestruct_owned() -> NonExhaustiveStruct; +} diff --git a/tests/ui/lint/improper-ctypes/lint-nonexhaustive.rs b/tests/ui/lint/improper-ctypes/lint-nonexhaustive.rs new file mode 100644 index 0000000000000..bcb5fbd6d593b --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-nonexhaustive.rs @@ -0,0 +1,35 @@ +//@ check-pass +#![deny(improper_ctypes)] + +//@ aux-build: extern_crate_types.rs +//@ compile-flags:--extern extern_crate_types +extern crate extern_crate_types as ext_crate; + +// Properly deal with non_exhaustive types: +// the thorny logic is expressed in https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344 +// and its linked comments +// + +// Issue: https://github.com/rust-lang/rust/issues/132699 +// FFI-safe pointers to nonexhaustive structs should be FFI-safe too + +// BEGIN: this is the exact same code as in ext_crate, to compare the lints +#[repr(C)] +#[non_exhaustive] +pub struct OtherNonExhaustiveStruct { + pub field: u8 +} + +extern "C" { + pub fn othernonexhaustivestruct_create() -> *mut OtherNonExhaustiveStruct; + pub fn othernonexhaustivestruct_destroy(s: *mut OtherNonExhaustiveStruct); +} +// END + +use ext_crate::NonExhaustiveStruct; + +extern "C" { + pub fn use_struct(s: *mut NonExhaustiveStruct); +} + +fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs new file mode 100644 index 0000000000000..af460e218179c --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs @@ -0,0 +1,311 @@ +// Trying to cover as many ty_kinds as possible in the code for ImproperCTypes lint +//@ edition:2018 + +#![allow(dead_code,unused_variables)] +#![deny(improper_ctypes, improper_ctypes_definitions)] + +// we want ALL the ty_kinds, including the feature-gated ones +#![feature(extern_types)] +#![feature(never_type)] +#![feature(inherent_associated_types)] //~ WARN: is incomplete +#![feature(async_trait_bounds)] +#![feature(pattern_types, rustc_attrs)] +#![feature(pattern_type_macro)] + +// ty_kinds not found so far: +// Placeholder, Bound, Infer, Error, +// Alias +// FnDef, Closure, Coroutine, ClosureCoroutine, CoroutineWitness, + +use std::ptr::from_ref; +use std::ptr::NonNull; +use std::mem::{MaybeUninit, size_of}; +use std::num::NonZero; +use std::pat::pattern_type; + +#[repr(C)] +struct SomeStruct{ + a: u8, + b: i32, +} +impl SomeStruct{ + extern "C" fn klol( + // Ref[Struct] + &self + ){} +} + +#[repr(C)] +#[derive(Clone,Copy)] +struct TemplateStruct where T: std::ops::Add+Copy { + one: T, + two: T, +} +impl TemplateStruct { + type Out = ::Output; +} + +extern "C" fn tstruct_sum( + // Ref[Struct] + slf: Option<&TemplateStruct> + // Option> ...not Inherent. dangit +) -> Option::Out>> { + Some(Box::new(slf?.one + slf?.two)) +} + +#[repr(C)] +union SomeUnion{ + sz: u8, + us: i8, +} +#[repr(C)] +enum SomeEnum{ + Everything=42, + NotAU8=256, + SomePrimeNumber=23, +} + +pub trait TimesTwo: std::ops::Add + Sized + Clone + where for<'a> &'a Self: std::ops::Add<&'a Self>, + *const Self: std::ops::Add<*const Self>, + Box: std::ops::Add>, +{ + extern "C" fn t2_own( + // Param + self + // Alias + ) -> >::Output { + self.clone() + self + } + // it ICEs (https://github.com/rust-lang/rust/issues/134587) :( + //extern "C" fn t2_ptr( + // // Ref[Param] + // slf: *const Self + // // Alias + //) -> <*const Self as std::ops::Add<*const Self>>::Output { + // slf + slf + //} + extern "C" fn t2_box( + // Box[Param] + self: Box, + // Alias + ) -> as std::ops::Add>>::Output { + self.clone() + self + } + extern "C" fn t2_ref( + // Ref[Param] + &self + // Alias + ) -> <&Self as std::ops::Add<&Self>>::Output { + self + self + } +} + +extern "C" {type ExtType;} + +#[repr(C)] +pub struct StructWithDyn(dyn std::fmt::Debug); + +extern "C" { + // variadic args aren't listed as args in a way that allows type checking. + // this is fine (TM) + fn variadic_function(e: ...); +} + +extern "C" fn all_ty_kinds<'a,const N:usize,T>( + // UInt, Int, Float, Bool + u:u8, i:i8, f:f64, b:bool, + // Struct + s:String, //~ ERROR: uses type `String` + // Ref[Str] + s2:&str, //~ ERROR: uses type `&str` + // Char + c: char, //~ ERROR: uses type `char` + // Ref[Slice] + s3:&[u8], //~ ERROR: uses type `&[u8]` + // Array (this gets caught outside of the code we want to test) + s4:[u8;N], //~ ERROR: uses type `[u8; N]` + // Tuple + p:(u8, u8), //~ ERROR: uses type `(u8, u8)` + // also Tuple + (p2, p3):(u8, u8), //~ ERROR: uses type `(u8, u8)` + // Pat + nz: pattern_type!(u32 is 1..), + // Struct + SomeStruct{b:p4,..}: SomeStruct, + // Union + u2: SomeUnion, + // Enum, + e: SomeEnum, + // Param + d: impl Clone, + // Param + t: T, + // Ptr[Foreign] + e2: *mut ExtType, + // Ref[Struct] + e3: &StructWithDyn, //~ ERROR: uses type `&StructWithDyn` + // Never + x:!, //~ ERROR: uses type `!` + //r1: &u8, r2: *const u8, r3: Box, + // FnPtr + f2: fn(u8)->u8, //~ ERROR: uses type `fn(u8) -> u8` + // Ref[Dynamic] + f3: &'a dyn Fn(u8)->u8, //~ ERROR: uses type `&dyn Fn(u8) -> u8` + // Ref[Dynamic] + d2: &dyn std::cmp::PartialOrd, //~ ERROR: uses type `&dyn PartialOrd` + // Param, + a: impl async Fn(u8)->u8, //FIXME: eventually, be able to peer into type params + // Alias +) -> impl std::fmt::Debug { //~ ERROR: uses type `impl Debug` + 3_usize +} + +extern "C" fn all_ty_kinds_in_ptr( + // Ptr[UInt], Ptr[Int], Ptr[Float], Ptr[Bool] + u: *const u8, i: *const i8, f: *const f64, b: *const bool, + // Ptr[Struct] + s: *const String, + // Ptr[Str] + s2: *const str, //~ ERROR: uses type `*const str` + // Ptr[Char] + c: *const char, + // Ptr[Slice] + s3: *const [u8], //~ ERROR: uses type `*const [u8]` + // Ptr[Array] (this gets caught outside of the code we want to test) + s4: *const [u8;N], + // Ptr[Tuple] + p: *const (u8,u8), + // Tuple + (p2, p3):(*const u8, *const u8), //~ ERROR: uses type `(*const u8, *const u8)` + // Pat + nz: *const pattern_type!(u32 is 1..), + // Ptr[Struct] + SomeStruct{b: ref p4,..}: & SomeStruct, + // Ptr[Union] + u2: *const SomeUnion, + // Ptr[Enum], + e: *const SomeEnum, + // Param + d: *const impl Clone, + // Param + t: *const T, + // Ptr[Foreign] + e2: *mut ExtType, + // Ptr[Struct] + e3: *const StructWithDyn, //~ ERROR: uses type `*const StructWithDyn` + // Ptr[Never] + x: *const !, + //r1: &u8, r2: *const u8, r3: Box, + // Ptr[FnPtr] + f2: *const fn(u8)->u8, + // Ptr[Dynamic] + f3: *const dyn Fn(u8)->u8, //~ ERROR: uses type `*const dyn Fn(u8) -> u8` + // Ptr[Dynamic] + d2: *const dyn std::cmp::PartialOrd, //~ ERROR: uses type `*const dyn PartialOrd` + // Ptr[Param], + a: *const impl async Fn(u8)->u8, + // Alias +) -> *const dyn std::fmt::Debug { //~ ERROR: uses type `*const dyn Debug` + todo!() +} + +extern "C" { +fn all_ty_kinds_in_ref<'a>( + // Ref[UInt], Ref[Int], Ref[Float], Ref[Bool] + u: &u8, i: &'a i8, f: &f64, b: &bool, + // Ref[Struct] + s: &String, + // Ref[Str] + s2: &str, //~ ERROR: uses type `&str` + // Ref[Char] + c: &char, + // Ref[Slice] + s3: &[u8], //~ ERROR: uses type `&[u8]` + // deactivated here, because this is a function *declaration* (param N unacceptable) + // s4: &[u8;N], + // Ref[Tuple] + p: &(u8, u8), + // deactivated here, because this is a function *declaration* (patterns unacceptable) + // (p2, p3):(&u8, &u8), + // Pat + nz: &pattern_type!(u32 is 1..), + // deactivated here, because this is a function *declaration* (pattern unacceptable) + // SomeStruct{b: ref p4,..}: &SomeStruct, + // Ref[Union] + u2: &SomeUnion, + // Ref[Enum], + e: &SomeEnum, + // deactivated here, because this is a function *declaration* (impl type unacceptable) + // d: &impl Clone, + // deactivated here, because this is a function *declaration* (type param unacceptable) + // t: &T, + // Ref[Foreign] + e2: &ExtType, + // Ref[Struct] + e3: &StructWithDyn, //~ ERROR: uses type `&StructWithDyn` + // Ref[Never] + x: &!, + //r1: &u8, r2: &u8, r3: Box, + // Ref[FnPtr] + f2: &fn(u8)->u8, + // Ref[Dynamic] + f3: &dyn Fn(u8)->u8, //~ ERROR: uses type `&dyn Fn(u8) -> u8` + // Ref[Dynamic] + d2: &dyn std::cmp::PartialOrd, //~ ERROR: uses type `&dyn PartialOrd` + // deactivated here, because this is a function *declaration* (impl type unacceptable) + // a: &impl async Fn(u8)->u8, + // Ref[Dynamic] +) -> &'a dyn std::fmt::Debug; //~ ERROR: uses type `&dyn Debug` +} + +extern "C" fn all_ty_kinds_in_box( + // Box[UInt], Box[Int], Box[Float], Box[Bool] + u: Option>, i: Option>, f: Option>, b: Option>, + // Box[Struct] + s: Option>, + // Box[Str] + s2: Box, //~ ERROR: uses type `Box` + // Box[Char] + c: Box, + // Box[Slice] + s3: Box<[u8]>, //~ ERROR: uses type `Box<[u8]>` + // Box[Array] (this gets caught outside of the code we want to test) + s4: Option>, + // Box[Tuple] + p: Option>, + // also Tuple + (p2,p3):(Box, Box), //~ ERROR: uses type `(Box, Box)` + // Pat + nz: Option>, + // Ref[Struct] + SomeStruct{b: ref p4,..}: &SomeStruct, + // Box[Union] + u2: Option>, + // Box[Enum], + e: Option>, + // Box[Param] + d: Option>, + // Box[Param] + t: Option>, + // deactivated, we can't deallocate an external type in rust + //e2: Option>, + // Box[Struct] + e3: Box, //~ ERROR: uses type `Box` + // Box[Never] + x: Box, + //r1: Box, + // Box[FnPtr] + f2: Boxu8>, + // Box[Dynamic] + f3: Boxu8>, //~ ERROR: uses type `Box u8>` + // Box[Dynamic] + d2: Box>, //~ ERROR: uses type `Box>` + // Option[Box[Param]], + a: Optionu8>>, + // Box[Dynamic] +) -> Box { //~ ERROR: uses type `Box` + u.unwrap() +} + +fn main() {} diff --git a/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.stderr b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.stderr new file mode 100644 index 0000000000000..fa29541618e41 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.stderr @@ -0,0 +1,301 @@ +warning: the feature `inherent_associated_types` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/lint-tykind-fuzz.rs:10:12 + | +LL | #![feature(inherent_associated_types)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #8995 for more information + = note: `#[warn(incomplete_features)]` on by default + +error: `extern` fn uses type `String`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:119:7 + | +LL | s:String, + | ^^^^^^ not FFI-safe + | + = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `String` + = note: `String` has unspecified layout +note: the lint level is defined here + --> $DIR/lint-tykind-fuzz.rs:5:26 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&str`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:121:8 + | +LL | s2:&str, + | ^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `char`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:123:8 + | +LL | c: char, + | ^^^^ not FFI-safe + | + = help: consider using `u32` or `libc::wchar_t` instead + = note: the `char` type has no C equivalent + +error: `extern` fn uses type `&[u8]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:125:8 + | +LL | s3:&[u8], + | ^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `[u8; N]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:127:8 + | +LL | s4:[u8;N], + | ^^^^^^ not FFI-safe + | + = help: consider passing a pointer to the array + = note: passing raw arrays by value is not FFI-safe + +error: `extern` fn uses type `(u8, u8)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:129:7 + | +LL | p:(u8, u8), + | ^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `(u8, u8)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:131:14 + | +LL | (p2, p3):(u8, u8), + | ^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `&StructWithDyn`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:147:9 + | +LL | e3: &StructWithDyn, + | ^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `!`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:149:7 + | +LL | x:!, + | ^ not FFI-safe + | + = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables + +error: `extern` fn uses type `fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:152:9 + | +LL | f2: fn(u8)->u8, + | ^^^^^^^^^^ not FFI-safe + | + = help: consider using an `extern fn(...) -> ...` function pointer instead + = note: this function pointer has Rust-specific calling convention + +error: `extern` fn uses type `&dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:154:9 + | +LL | f3: &'a dyn Fn(u8)->u8, + | ^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `&dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:156:9 + | +LL | d2: &dyn std::cmp::PartialOrd, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `impl Debug`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:160:6 + | +LL | ) -> impl std::fmt::Debug { + | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: opaque types have no C equivalent + +error: `extern` fn uses type `*const str`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:170:9 + | +LL | s2: *const str, + | ^^^^^^^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const [u8]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:174:9 + | +LL | s3: *const [u8], + | ^^^^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `(*const u8, *const u8)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:180:14 + | +LL | (p2, p3):(*const u8, *const u8), + | ^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `*const StructWithDyn`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:196:9 + | +LL | e3: *const StructWithDyn, + | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:203:9 + | +LL | f3: *const dyn Fn(u8)->u8, + | ^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:205:9 + | +LL | d2: *const dyn std::cmp::PartialOrd, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `*const dyn Debug`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:209:6 + | +LL | ) -> *const dyn std::fmt::Debug { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&str`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:220:9 + | +LL | s2: &str, + | ^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer +note: the lint level is defined here + --> $DIR/lint-tykind-fuzz.rs:5:9 + | +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` block uses type `&[u8]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:224:9 + | +LL | s3: &[u8], + | ^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&StructWithDyn`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:246:9 + | +LL | e3: &StructWithDyn, + | ^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&dyn Fn(u8) -> u8`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:253:9 + | +LL | f3: &dyn Fn(u8)->u8, + | ^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&dyn PartialOrd`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:255:9 + | +LL | d2: &dyn std::cmp::PartialOrd, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` block uses type `&dyn Debug`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:259:6 + | +LL | ) -> &'a dyn std::fmt::Debug; + | ^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this reference to an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:268:9 + | +LL | s2: Box, + | ^^^^^^^^ not FFI-safe + | + = help: consider using `*const u8` and a length instead + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box<[u8]>`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:272:9 + | +LL | s3: Box<[u8]>, + | ^^^^^^^^^ not FFI-safe + | + = help: consider using a raw pointer to the slice's first element (and a length) instead + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `(Box, Box)`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:278:13 + | +LL | (p2,p3):(Box, Box), + | ^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `Box`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:294:9 + | +LL | e3: Box, + | ^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box u8>`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:301:9 + | +LL | f3: Boxu8>, + | ^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box>`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:303:9 + | +LL | d2: Box>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: `extern` fn uses type `Box`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:307:6 + | +LL | ) -> Box { + | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: this box for an unsized type contains metadata, which makes it incompatible with a C pointer + +error: aborting due to 33 previous errors; 1 warning emitted + diff --git a/tests/ui/lint/improper-ctypes/lint_uninhabited.rs b/tests/ui/lint/improper-ctypes/lint-uninhabited.rs similarity index 100% rename from tests/ui/lint/improper-ctypes/lint_uninhabited.rs rename to tests/ui/lint/improper-ctypes/lint-uninhabited.rs diff --git a/tests/ui/lint/improper-ctypes/lint_uninhabited.stderr b/tests/ui/lint/improper-ctypes/lint-uninhabited.stderr similarity index 85% rename from tests/ui/lint/improper-ctypes/lint_uninhabited.stderr rename to tests/ui/lint/improper-ctypes/lint-uninhabited.stderr index 9488dd0f62378..852b8115a5d46 100644 --- a/tests/ui/lint/improper-ctypes/lint_uninhabited.stderr +++ b/tests/ui/lint/improper-ctypes/lint-uninhabited.stderr @@ -1,5 +1,5 @@ error: `extern` block uses type `AlsoUninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:33:17 + --> $DIR/lint-uninhabited.rs:33:17 | LL | fn bad_entry(e: AlsoUninhabited); | ^^^^^^^^^^^^^^^ not FFI-safe @@ -7,37 +7,37 @@ LL | fn bad_entry(e: AlsoUninhabited); = help: `AlsoUninhabited` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead = note: this struct/enum/union (`AlsoUninhabited`) is FFI-unsafe due to a `Uninhabited` field note: the type is defined here - --> $DIR/lint_uninhabited.rs:11:1 + --> $DIR/lint-uninhabited.rs:11:1 | LL | struct AlsoUninhabited{ | ^^^^^^^^^^^^^^^^^^^^^^ = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables note: the type is defined here - --> $DIR/lint_uninhabited.rs:8:1 + --> $DIR/lint-uninhabited.rs:8:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ note: the lint level is defined here - --> $DIR/lint_uninhabited.rs:4:9 + --> $DIR/lint-uninhabited.rs:4:9 | LL | #![deny(improper_ctypes, improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `Uninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:36:18 + --> $DIR/lint-uninhabited.rs:36:18 | LL | fn bad0_entry(e: Uninhabited); | ^^^^^^^^^^^ not FFI-safe | = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables note: the type is defined here - --> $DIR/lint_uninhabited.rs:8:1 + --> $DIR/lint-uninhabited.rs:8:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ error: `extern` block uses type `!`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:42:18 + --> $DIR/lint-uninhabited.rs:42:18 | LL | fn never_entry(e:!); | ^ not FFI-safe @@ -45,7 +45,7 @@ LL | fn never_entry(e:!); = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables error: `extern` fn uses type `AlsoUninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:47:33 + --> $DIR/lint-uninhabited.rs:47:33 | LL | extern "C" fn impl_bad_entry(e: AlsoUninhabited) {} | ^^^^^^^^^^^^^^^ not FFI-safe @@ -53,50 +53,50 @@ LL | extern "C" fn impl_bad_entry(e: AlsoUninhabited) {} = help: `AlsoUninhabited` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead = note: this struct/enum/union (`AlsoUninhabited`) is FFI-unsafe due to a `Uninhabited` field note: the type is defined here - --> $DIR/lint_uninhabited.rs:11:1 + --> $DIR/lint-uninhabited.rs:11:1 | LL | struct AlsoUninhabited{ | ^^^^^^^^^^^^^^^^^^^^^^ = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables note: the type is defined here - --> $DIR/lint_uninhabited.rs:8:1 + --> $DIR/lint-uninhabited.rs:8:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ note: the lint level is defined here - --> $DIR/lint_uninhabited.rs:4:26 + --> $DIR/lint-uninhabited.rs:4:26 | LL | #![deny(improper_ctypes, improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `Uninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:55:34 + --> $DIR/lint-uninhabited.rs:55:34 | LL | extern "C" fn impl_bad0_entry(e: Uninhabited) {} | ^^^^^^^^^^^ not FFI-safe | = note: zero-variant enums and other uninhabited types are not allowed in function arguments and static variables note: the type is defined here - --> $DIR/lint_uninhabited.rs:8:1 + --> $DIR/lint-uninhabited.rs:8:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ warning: the type `Uninhabited` does not permit zero-initialization - --> $DIR/lint_uninhabited.rs:57:12 + --> $DIR/lint-uninhabited.rs:57:12 | LL | unsafe{transmute(())} | ^^^^^^^^^^^^^ this code causes undefined behavior when executed | note: enums with no inhabited variants have no valid value - --> $DIR/lint_uninhabited.rs:8:1 + --> $DIR/lint-uninhabited.rs:8:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ = note: `#[warn(invalid_value)]` on by default error: `extern` fn uses type `!`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:65:34 + --> $DIR/lint-uninhabited.rs:65:34 | LL | extern "C" fn impl_never_entry(e:!){} | ^ not FFI-safe @@ -104,14 +104,14 @@ LL | extern "C" fn impl_never_entry(e:!){} = note: the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables error: `extern` fn uses type `HalfHiddenUninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:70:31 + --> $DIR/lint-uninhabited.rs:70:31 | LL | extern "C" fn weird_pattern(e:HalfHiddenUninhabited){} | ^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: this struct/enum/union (`HalfHiddenUninhabited`) is FFI-unsafe due to a `!` field note: the type is defined here - --> $DIR/lint_uninhabited.rs:25:1 + --> $DIR/lint-uninhabited.rs:25:1 | LL | struct HalfHiddenUninhabited { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 89b1dde1307ea5fa90ece4cf0aaa88cc902421ae Mon Sep 17 00:00:00 2001 From: niacdoial Date: Thu, 28 Aug 2025 23:36:17 +0200 Subject: [PATCH 19/20] ImproperCTypes: misc. adaptations smooth things out to avoid conflicts with https://github.com/rust-lang/compiler-builtins/pull/1006 which has at time of writing not made it into rust-lang/rust's main branch --- tests/auxiliary/minicore.rs | 2 +- .../ui/statics/nested-allocations-dont-inherit-codegen-attrs.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index 4f4c653cb46e7..da5e56da937c6 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -29,7 +29,7 @@ asm_experimental_arch, unboxed_closures )] -#![allow(unused, improper_ctypes_definitions, internal_features)] +#![allow(unused, internal_features)] #![no_std] #![no_core] diff --git a/tests/ui/statics/nested-allocations-dont-inherit-codegen-attrs.rs b/tests/ui/statics/nested-allocations-dont-inherit-codegen-attrs.rs index 059af87a72234..c215473de9f18 100644 --- a/tests/ui/statics/nested-allocations-dont-inherit-codegen-attrs.rs +++ b/tests/ui/statics/nested-allocations-dont-inherit-codegen-attrs.rs @@ -1,6 +1,6 @@ //@ build-pass -#![allow(improper_c_var_definitions)] +#![allow(improper_ctypes_definitions)] // Make sure that the nested static allocation for `FOO` doesn't inherit `no_mangle`. #[no_mangle] From 85c1303e6b44d1bad768074333dfcc704c4a809c Mon Sep 17 00:00:00 2001 From: niacdoial Date: Fri, 29 Aug 2025 21:13:12 +0200 Subject: [PATCH 20/20] ImproperCTypes: rename associated tests Rustc is trying to shift away from test names that are just issue numbers, so we add this as part of its effort, since this commit chain is moving and rewriting these files anyway. The directory containing all tests is also renamed. --- .../{lint-94223.rs => ice-fnptr-slicearg.rs} | 5 ++- ...94223.stderr => ice-fnptr-slicearg.stderr} | 38 +++++++++---------- ...ss-73249-1.rs => ice-fully-normalize-1.rs} | 3 ++ ...nt-73249-2.rs => ice-fully-normalize-2.rs} | 3 ++ ...nt-73249-3.rs => ice-fully-normalize-3.rs} | 3 ++ ...-5.stderr => ice-fully-normalize-3.stderr} | 6 +-- ...ss-73249-4.rs => ice-fully-normalize-4.rs} | 3 ++ ...nt-73249-5.rs => ice-fully-normalize-5.rs} | 3 ++ ...-3.stderr => ice-fully-normalize-5.stderr} | 6 +-- ...stpass-73249.rs => ice-fully-normalize.rs} | 3 ++ ...ustpass-73747.rs => ice-normalize-cast.rs} | 3 ++ .../{mustpass-113900.rs => ice-normalize.rs} | 1 + ...tpass-134060.rs => ice-tykind-coverage.rs} | 2 + ...4060.stderr => ice-tykind-coverage.stderr} | 2 +- ...ss-73251.rs => issue-associated-opaque.rs} | 4 ++ ...436-1.rs => issue-fnptr-wrapped-unit-1.rs} | 3 ++ ...derr => issue-fnptr-wrapped-unit-1.stderr} | 14 +++---- ...-113436.rs => issue-fnptr-wrapped-unit.rs} | 5 ++- ...ass-66202.rs => issue-normalize-return.rs} | 1 + ...t-73251-1.rs => issue-project-opaque-1.rs} | 4 ++ ...1.stderr => issue-project-opaque-1.stderr} | 4 +- ...t-73251-2.rs => issue-project-opaque-2.rs} | 4 ++ ...2.stderr => issue-project-opaque-2.stderr} | 4 +- 23 files changed, 85 insertions(+), 39 deletions(-) rename tests/ui/lint/improper-ctypes/{lint-94223.rs => ice-fnptr-slicearg.rs} (90%) rename tests/ui/lint/improper-ctypes/{lint-94223.stderr => ice-fnptr-slicearg.stderr} (86%) rename tests/ui/lint/improper-ctypes/{mustpass-73249-1.rs => ice-fully-normalize-1.rs} (74%) rename tests/ui/lint/improper-ctypes/{lint-73249-2.rs => ice-fully-normalize-2.rs} (84%) rename tests/ui/lint/improper-ctypes/{lint-73249-3.rs => ice-fully-normalize-3.rs} (76%) rename tests/ui/lint/improper-ctypes/{lint-73249-5.stderr => ice-fully-normalize-3.stderr} (79%) rename tests/ui/lint/improper-ctypes/{mustpass-73249-4.rs => ice-fully-normalize-4.rs} (77%) rename tests/ui/lint/improper-ctypes/{lint-73249-5.rs => ice-fully-normalize-5.rs} (76%) rename tests/ui/lint/improper-ctypes/{lint-73249-3.stderr => ice-fully-normalize-5.stderr} (79%) rename tests/ui/lint/improper-ctypes/{mustpass-73249.rs => ice-fully-normalize.rs} (73%) rename tests/ui/lint/improper-ctypes/{mustpass-73747.rs => ice-normalize-cast.rs} (67%) rename tests/ui/lint/improper-ctypes/{mustpass-113900.rs => ice-normalize.rs} (83%) rename tests/ui/lint/improper-ctypes/{mustpass-134060.rs => ice-tykind-coverage.rs} (90%) rename tests/ui/lint/improper-ctypes/{mustpass-134060.stderr => ice-tykind-coverage.stderr} (90%) rename tests/ui/lint/improper-ctypes/{mustpass-73251.rs => issue-associated-opaque.rs} (63%) rename tests/ui/lint/improper-ctypes/{lint-113436-1.rs => issue-fnptr-wrapped-unit-1.rs} (77%) rename tests/ui/lint/improper-ctypes/{lint-113436-1.stderr => issue-fnptr-wrapped-unit-1.stderr} (79%) rename tests/ui/lint/improper-ctypes/{mustpass-113436.rs => issue-fnptr-wrapped-unit.rs} (78%) rename tests/ui/lint/improper-ctypes/{mustpass-66202.rs => issue-normalize-return.rs} (87%) rename tests/ui/lint/improper-ctypes/{lint-73251-1.rs => issue-project-opaque-1.rs} (66%) rename tests/ui/lint/improper-ctypes/{lint-73251-1.stderr => issue-project-opaque-1.stderr} (81%) rename tests/ui/lint/improper-ctypes/{lint-73251-2.rs => issue-project-opaque-2.rs} (77%) rename tests/ui/lint/improper-ctypes/{lint-73251-2.stderr => issue-project-opaque-2.stderr} (81%) diff --git a/tests/ui/lint/improper-ctypes/lint-94223.rs b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs similarity index 90% rename from tests/ui/lint/improper-ctypes/lint-94223.rs rename to tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs index 0c8d531f69247..8276329d5dd84 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.rs +++ b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs @@ -1,5 +1,8 @@ #![crate_type = "lib"] -#![deny(improper_ctypes_definitions, improper_ctypes)] +#![deny(improper_ctypes, improper_ctypes_definitions)] + +// Issue: https://github.com/rust-lang/rust/issues/94223 +// ice when a FnPtr has an unsized array argument pub fn bad(f: extern "C" fn([u8])) {} //~^ ERROR `extern` callback uses type `[u8]`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/lint-94223.stderr b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.stderr similarity index 86% rename from tests/ui/lint/improper-ctypes/lint-94223.stderr rename to tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.stderr index faed81c218427..70aefb3be088a 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.stderr @@ -1,5 +1,5 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:4:29 + --> $DIR/ice-fnptr-slicearg.rs:7:29 | LL | pub fn bad(f: extern "C" fn([u8])) {} | ^^^^ not FFI-safe @@ -7,13 +7,13 @@ LL | pub fn bad(f: extern "C" fn([u8])) {} = help: consider using a raw pointer to the slice's first element (and a length) instead = note: slices have no C equivalent note: the lint level is defined here - --> $DIR/lint-94223.rs:2:38 + --> $DIR/ice-fnptr-slicearg.rs:2:9 | -LL | #![deny(improper_ctypes_definitions, improper_ctypes)] - | ^^^^^^^^^^^^^^^ +LL | #![deny(improper_ctypes, improper_ctypes_definitions)] + | ^^^^^^^^^^^^^^^ error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:7:42 + --> $DIR/ice-fnptr-slicearg.rs:10:42 | LL | pub fn bad_twice(f: Result) {} | ^^^^ not FFI-safe @@ -22,7 +22,7 @@ LL | pub fn bad_twice(f: Result) {} = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:7:63 + --> $DIR/ice-fnptr-slicearg.rs:10:63 | LL | pub fn bad_twice(f: Result) {} | ^^^^ not FFI-safe @@ -31,7 +31,7 @@ LL | pub fn bad_twice(f: Result) {} = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:11:32 + --> $DIR/ice-fnptr-slicearg.rs:14:32 | LL | struct BadStruct(extern "C" fn([u8])); | ^^^^ not FFI-safe @@ -40,7 +40,7 @@ LL | struct BadStruct(extern "C" fn([u8])); = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:15:21 + --> $DIR/ice-fnptr-slicearg.rs:18:21 | LL | A(extern "C" fn([u8])), | ^^^^ not FFI-safe @@ -49,7 +49,7 @@ LL | A(extern "C" fn([u8])), = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:20:21 + --> $DIR/ice-fnptr-slicearg.rs:23:21 | LL | A(extern "C" fn([u8])), | ^^^^ not FFI-safe @@ -58,7 +58,7 @@ LL | A(extern "C" fn([u8])), = note: slices have no C equivalent error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:24:26 + --> $DIR/ice-fnptr-slicearg.rs:27:26 | LL | type Foo = extern "C" fn([u8]); | ^^^^ not FFI-safe @@ -67,7 +67,7 @@ LL | type Foo = extern "C" fn([u8]); = note: slices have no C equivalent error: `extern` callback uses type `Option<&::FooType>`, which is not FFI-safe - --> $DIR/lint-94223.rs:31:34 + --> $DIR/ice-fnptr-slicearg.rs:34:34 | LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -76,7 +76,7 @@ LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); = note: enum has no representation hint error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:41:31 + --> $DIR/ice-fnptr-slicearg.rs:44:31 | LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^ not FFI-safe @@ -84,13 +84,13 @@ LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe` = note: `FfiUnsafe` has unspecified layout note: the type is defined here - --> $DIR/lint-94223.rs:34:1 + --> $DIR/ice-fnptr-slicearg.rs:37:1 | LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:44:44 + --> $DIR/ice-fnptr-slicearg.rs:47:44 | LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^ not FFI-safe @@ -98,13 +98,13 @@ LL | pub static BAD_TWICE: Result $DIR/lint-94223.rs:34:1 + --> $DIR/ice-fnptr-slicearg.rs:37:1 | LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:44:70 + --> $DIR/ice-fnptr-slicearg.rs:47:70 | LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^ not FFI-safe @@ -112,13 +112,13 @@ LL | pub static BAD_TWICE: Result $DIR/lint-94223.rs:34:1 + --> $DIR/ice-fnptr-slicearg.rs:37:1 | LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe - --> $DIR/lint-94223.rs:48:36 + --> $DIR/ice-fnptr-slicearg.rs:51:36 | LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^ not FFI-safe @@ -126,7 +126,7 @@ LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `FfiUnsafe` = note: `FfiUnsafe` has unspecified layout note: the type is defined here - --> $DIR/lint-94223.rs:34:1 + --> $DIR/ice-fnptr-slicearg.rs:37:1 | LL | pub struct FfiUnsafe; | ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/mustpass-73249-1.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize-1.rs similarity index 74% rename from tests/ui/lint/improper-ctypes/mustpass-73249-1.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-1.rs index 0ca91ef294f05..6fef795351e15 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-73249-1.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-1.rs @@ -1,6 +1,9 @@ //@ check-pass #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + pub trait Foo { type Assoc: 'static; } diff --git a/tests/ui/lint/improper-ctypes/lint-73249-2.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize-2.rs similarity index 84% rename from tests/ui/lint/improper-ctypes/lint-73249-2.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-2.rs index 9286d822e22e3..006c8b27306b6 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-2.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-2.rs @@ -3,6 +3,9 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + trait Baz {} impl Baz for () {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-3.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize-3.rs similarity index 76% rename from tests/ui/lint/improper-ctypes/lint-73249-3.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-3.rs index aff2a182e3f49..31a69bd78f081 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-3.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-3.rs @@ -1,6 +1,9 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + pub trait Baz {} impl Baz for u32 {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr b/tests/ui/lint/improper-ctypes/ice-fully-normalize-3.stderr similarity index 79% rename from tests/ui/lint/improper-ctypes/lint-73249-5.stderr rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-3.stderr index f42924f4d5b56..b19975ec9dad1 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-5.stderr +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-3.stderr @@ -1,18 +1,18 @@ error: `extern` block uses type `A`, which is not FFI-safe - --> $DIR/lint-73249-5.rs:21:25 + --> $DIR/ice-fully-normalize-3.rs:24:25 | LL | pub fn lint_me() -> A; | ^ not FFI-safe | = note: this struct/enum/union (`A`) is FFI-unsafe due to a `Qux` field note: the type is defined here - --> $DIR/lint-73249-5.rs:16:1 + --> $DIR/ice-fully-normalize-3.rs:19:1 | LL | pub struct A { | ^^^^^^^^^^^^ = note: opaque types have no C equivalent note: the lint level is defined here - --> $DIR/lint-73249-5.rs:2:9 + --> $DIR/ice-fully-normalize-3.rs:2:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/mustpass-73249-4.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize-4.rs similarity index 77% rename from tests/ui/lint/improper-ctypes/mustpass-73249-4.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-4.rs index 37099c1313ade..785d002cae8b5 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-73249-4.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-4.rs @@ -1,6 +1,9 @@ //@ check-pass #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + use std::marker::PhantomData; trait Foo { diff --git a/tests/ui/lint/improper-ctypes/lint-73249-5.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize-5.rs similarity index 76% rename from tests/ui/lint/improper-ctypes/lint-73249-5.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-5.rs index 8ad5be4e6301e..61d2a06101538 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-5.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-5.rs @@ -1,6 +1,9 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + pub trait Baz {} impl Baz for u32 {} diff --git a/tests/ui/lint/improper-ctypes/lint-73249-3.stderr b/tests/ui/lint/improper-ctypes/ice-fully-normalize-5.stderr similarity index 79% rename from tests/ui/lint/improper-ctypes/lint-73249-3.stderr rename to tests/ui/lint/improper-ctypes/ice-fully-normalize-5.stderr index dc6f6fb08ed33..c8513322fbbc8 100644 --- a/tests/ui/lint/improper-ctypes/lint-73249-3.stderr +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize-5.stderr @@ -1,18 +1,18 @@ error: `extern` block uses type `A`, which is not FFI-safe - --> $DIR/lint-73249-3.rs:21:25 + --> $DIR/ice-fully-normalize-5.rs:24:25 | LL | pub fn lint_me() -> A; | ^ not FFI-safe | = note: this struct/enum/union (`A`) is FFI-unsafe due to a `Qux` field note: the type is defined here - --> $DIR/lint-73249-3.rs:16:1 + --> $DIR/ice-fully-normalize-5.rs:19:1 | LL | pub struct A { | ^^^^^^^^^^^^ = note: opaque types have no C equivalent note: the lint level is defined here - --> $DIR/lint-73249-3.rs:2:9 + --> $DIR/ice-fully-normalize-5.rs:2:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/mustpass-73249.rs b/tests/ui/lint/improper-ctypes/ice-fully-normalize.rs similarity index 73% rename from tests/ui/lint/improper-ctypes/mustpass-73249.rs rename to tests/ui/lint/improper-ctypes/ice-fully-normalize.rs index c5f2318ef0af0..a0f8d1ce18665 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-73249.rs +++ b/tests/ui/lint/improper-ctypes/ice-fully-normalize.rs @@ -1,6 +1,9 @@ //@ check-pass #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// "ICE: could not fully normalize" + pub trait Foo { type Assoc; } diff --git a/tests/ui/lint/improper-ctypes/mustpass-73747.rs b/tests/ui/lint/improper-ctypes/ice-normalize-cast.rs similarity index 67% rename from tests/ui/lint/improper-ctypes/mustpass-73747.rs rename to tests/ui/lint/improper-ctypes/ice-normalize-cast.rs index a2562e3b4213b..e779b599f7a4a 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-73747.rs +++ b/tests/ui/lint/improper-ctypes/ice-normalize-cast.rs @@ -1,5 +1,8 @@ //@ check-pass +// Issue: https://github.com/rust-lang/rust/issues/73747 +// ICE that seems to happen in type normalization when dealing with casts + #[repr(transparent)] struct NonNullRawComPtr { inner: std::ptr::NonNull<::VTable>, diff --git a/tests/ui/lint/improper-ctypes/mustpass-113900.rs b/tests/ui/lint/improper-ctypes/ice-normalize.rs similarity index 83% rename from tests/ui/lint/improper-ctypes/mustpass-113900.rs rename to tests/ui/lint/improper-ctypes/ice-normalize.rs index 3dd196a409448..076368f6ce540 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-113900.rs +++ b/tests/ui/lint/improper-ctypes/ice-normalize.rs @@ -1,5 +1,6 @@ //@ check-pass +// Issue: https://github.com/rust-lang/rust/issues/113900 // Extending `improper_ctypes` to check external-ABI fn-ptrs means that it can encounter // projections which cannot be normalized - unsurprisingly, this shouldn't crash the compiler. diff --git a/tests/ui/lint/improper-ctypes/mustpass-134060.rs b/tests/ui/lint/improper-ctypes/ice-tykind-coverage.rs similarity index 90% rename from tests/ui/lint/improper-ctypes/mustpass-134060.rs rename to tests/ui/lint/improper-ctypes/ice-tykind-coverage.rs index b30be99673687..fb21ad5f7d9df 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-134060.rs +++ b/tests/ui/lint/improper-ctypes/ice-tykind-coverage.rs @@ -3,6 +3,8 @@ //! comprehensive coverage when the changes are to be relanded, as this is a basic sanity check to //! check that the fuzzed example from #134060 doesn't ICE. +// Issue: https://github.com/rust-lang/rust/issues/134060 + //@ check-pass #![crate_type = "lib"] diff --git a/tests/ui/lint/improper-ctypes/mustpass-134060.stderr b/tests/ui/lint/improper-ctypes/ice-tykind-coverage.stderr similarity index 90% rename from tests/ui/lint/improper-ctypes/mustpass-134060.stderr rename to tests/ui/lint/improper-ctypes/ice-tykind-coverage.stderr index 9b2de49a7eb51..a9c776eeef88c 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-134060.stderr +++ b/tests/ui/lint/improper-ctypes/ice-tykind-coverage.stderr @@ -1,5 +1,5 @@ warning: `extern` fn uses type `()`, which is not FFI-safe - --> $DIR/mustpass-134060.rs:11:34 + --> $DIR/ice-tykind-coverage.rs:13:34 | LL | extern "C" fn foo_(&self, _: ()) -> i64 { | ^^ not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/mustpass-73251.rs b/tests/ui/lint/improper-ctypes/issue-associated-opaque.rs similarity index 63% rename from tests/ui/lint/improper-ctypes/mustpass-73251.rs rename to tests/ui/lint/improper-ctypes/issue-associated-opaque.rs index 15c1dfcaabf57..3d3f2b195b4eb 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-73251.rs +++ b/tests/ui/lint/improper-ctypes/issue-associated-opaque.rs @@ -3,6 +3,10 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73249 +// Decisions on whether projections that normalize to opaque types then to something else +// should warn or not + trait Foo { type Assoc; } diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.rs b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.rs similarity index 77% rename from tests/ui/lint/improper-ctypes/lint-113436-1.rs rename to tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.rs index 27dcd0184d90f..1fa7a1c7a62f9 100644 --- a/tests/ui/lint/improper-ctypes/lint-113436-1.rs +++ b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.rs @@ -1,5 +1,8 @@ #![deny(improper_ctypes_definitions)] +// Issue: https://github.com/rust-lang/rust/issues/113436 +// `()` in (fnptr!) return types and ADT fields should be safe + #[repr(C)] pub struct Foo { a: u8, diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.stderr similarity index 79% rename from tests/ui/lint/improper-ctypes/lint-113436-1.stderr rename to tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.stderr index dacf5ffd67022..0849882eade56 100644 --- a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr +++ b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit-1.stderr @@ -1,44 +1,44 @@ error: `extern` fn uses type `Bar`, which is not FFI-safe - --> $DIR/lint-113436-1.rs:22:22 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:25:22 | LL | extern "C" fn bar(x: Bar) -> Bar { | ^^^ not FFI-safe | = note: this struct/enum/union (`Bar`) is FFI-unsafe due to a `NotSafe` field note: the type is defined here - --> $DIR/lint-113436-1.rs:16:1 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:19:1 | LL | pub struct Bar { | ^^^^^^^^^^^^^^ = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `NotSafe` = note: `NotSafe` has unspecified layout note: the type is defined here - --> $DIR/lint-113436-1.rs:13:1 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:16:1 | LL | struct NotSafe(u32); | ^^^^^^^^^^^^^^ note: the lint level is defined here - --> $DIR/lint-113436-1.rs:1:9 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:1:9 | LL | #![deny(improper_ctypes_definitions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `Bar`, which is not FFI-safe - --> $DIR/lint-113436-1.rs:22:30 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:25:30 | LL | extern "C" fn bar(x: Bar) -> Bar { | ^^^ not FFI-safe | = note: this struct/enum/union (`Bar`) is FFI-unsafe due to a `NotSafe` field note: the type is defined here - --> $DIR/lint-113436-1.rs:16:1 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:19:1 | LL | pub struct Bar { | ^^^^^^^^^^^^^^ = help: consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `NotSafe` = note: `NotSafe` has unspecified layout note: the type is defined here - --> $DIR/lint-113436-1.rs:13:1 + --> $DIR/issue-fnptr-wrapped-unit-1.rs:16:1 | LL | struct NotSafe(u32); | ^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/mustpass-113436.rs b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit.rs similarity index 78% rename from tests/ui/lint/improper-ctypes/mustpass-113436.rs rename to tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit.rs index 83afaa24d2ef9..1cf4cf18d0269 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-113436.rs +++ b/tests/ui/lint/improper-ctypes/issue-fnptr-wrapped-unit.rs @@ -1,5 +1,8 @@ //@ check-pass -#![deny(improper_ctypes_definitions, improper_ctypes)] +#![deny(improper_ctypes_definitions)] + +// Issue: https://github.com/rust-lang/rust/issues/113436 +// `()` in (fnptr!) return types and ADT fields should be safe #[repr(C)] pub struct Wrap(T); diff --git a/tests/ui/lint/improper-ctypes/mustpass-66202.rs b/tests/ui/lint/improper-ctypes/issue-normalize-return.rs similarity index 87% rename from tests/ui/lint/improper-ctypes/mustpass-66202.rs rename to tests/ui/lint/improper-ctypes/issue-normalize-return.rs index e4cfa54c8d8b8..973c701601845 100644 --- a/tests/ui/lint/improper-ctypes/mustpass-66202.rs +++ b/tests/ui/lint/improper-ctypes/issue-normalize-return.rs @@ -2,6 +2,7 @@ #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/66202 // This test checks that return types are normalized before being checked for FFI-safety, and that // transparent newtype wrappers are FFI-safe if the type being wrapped is FFI-safe. diff --git a/tests/ui/lint/improper-ctypes/lint-73251-1.rs b/tests/ui/lint/improper-ctypes/issue-project-opaque-1.rs similarity index 66% rename from tests/ui/lint/improper-ctypes/lint-73251-1.rs rename to tests/ui/lint/improper-ctypes/issue-project-opaque-1.rs index 07ae05be69f6c..6fb16b0e83b9e 100644 --- a/tests/ui/lint/improper-ctypes/lint-73251-1.rs +++ b/tests/ui/lint/improper-ctypes/issue-project-opaque-1.rs @@ -1,6 +1,10 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73251 +// Decisions on whether projections that normalize to opaque types then to something else +// should warn or not + trait Baz {} impl Baz for u32 {} diff --git a/tests/ui/lint/improper-ctypes/lint-73251-1.stderr b/tests/ui/lint/improper-ctypes/issue-project-opaque-1.stderr similarity index 81% rename from tests/ui/lint/improper-ctypes/lint-73251-1.stderr rename to tests/ui/lint/improper-ctypes/issue-project-opaque-1.stderr index 749722f0e2203..3f5fff0465009 100644 --- a/tests/ui/lint/improper-ctypes/lint-73251-1.stderr +++ b/tests/ui/lint/improper-ctypes/issue-project-opaque-1.stderr @@ -1,12 +1,12 @@ error: `extern` block uses type `Qux`, which is not FFI-safe - --> $DIR/lint-73251-1.rs:24:21 + --> $DIR/issue-project-opaque-1.rs:28:21 | LL | fn lint_me() -> ::Assoc; | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: opaque types have no C equivalent note: the lint level is defined here - --> $DIR/lint-73251-1.rs:2:9 + --> $DIR/issue-project-opaque-1.rs:2:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/lint-73251-2.rs b/tests/ui/lint/improper-ctypes/issue-project-opaque-2.rs similarity index 77% rename from tests/ui/lint/improper-ctypes/lint-73251-2.rs rename to tests/ui/lint/improper-ctypes/issue-project-opaque-2.rs index c47118672e072..bebb39e128c63 100644 --- a/tests/ui/lint/improper-ctypes/lint-73251-2.rs +++ b/tests/ui/lint/improper-ctypes/issue-project-opaque-2.rs @@ -1,6 +1,10 @@ #![feature(type_alias_impl_trait)] #![deny(improper_ctypes)] +// Issue: https://github.com/rust-lang/rust/issues/73251 +// Decisions on whether projections that normalize to opaque types then to something else +// should warn or not + pub trait TraitA { type Assoc; } diff --git a/tests/ui/lint/improper-ctypes/lint-73251-2.stderr b/tests/ui/lint/improper-ctypes/issue-project-opaque-2.stderr similarity index 81% rename from tests/ui/lint/improper-ctypes/lint-73251-2.stderr rename to tests/ui/lint/improper-ctypes/issue-project-opaque-2.stderr index 3770b7d789f67..cc72e1e5b5336 100644 --- a/tests/ui/lint/improper-ctypes/lint-73251-2.stderr +++ b/tests/ui/lint/improper-ctypes/issue-project-opaque-2.stderr @@ -1,12 +1,12 @@ error: `extern` block uses type `AliasA`, which is not FFI-safe - --> $DIR/lint-73251-2.rs:38:21 + --> $DIR/issue-project-opaque-2.rs:42:21 | LL | fn lint_me() -> ::Assoc; | ^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: opaque types have no C equivalent note: the lint level is defined here - --> $DIR/lint-73251-2.rs:2:9 + --> $DIR/issue-project-opaque-2.rs:2:9 | LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^