From 071fc71f31065ab4f44c8bd56b7c1ff5fa894130 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 +++++++++++------- 1 file changed, 331 insertions(+), 194 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 7ca57b0094ee3..f29b0d949ea39 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 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) } } From 359cb792b0c82fc6ebace123e0e95d78d75d1c06 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 | 248 +++++++++++------- 1 file changed, 147 insertions(+), 101 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index f29b0d949ea39..f31249e675e88 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -283,8 +283,9 @@ enum IndirectionKind { } bitflags! { + /// "Permanent state" flags for VisitorState (kept when visiting new mir::Ty) #[derive(Clone, Copy, Debug, PartialEq, Eq)] - struct VisitorState: u8 { + struct PersistentStateFlags: u16 { /// For use in (externally-linked) static variables. const STATIC = 0b000001; /// For use in functions in general. @@ -297,9 +298,40 @@ bitflags! { /// (struct/enum/union definitions, FnPtrs). const THEORETICAL = 0b010000; } + + /// "Ephemeral state" flags for VisitorState (lost when visiting new mir::Ty, + /// mostly contain info about the Ty immediately containing the current one). + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + struct EphemeralStateFlags: 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 behaviour" reproduced for compatibility's sake + const NOOUT_FNPTR = 0b1000000; + } } -impl VisitorState { +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +struct VisitorState { + /// Flags describing both the immediate and overall context in which the current mir::Ty is + persistent_flags: PersistentStateFlags, + /// Flags describing both the immediate and overall context in which the current mir::Ty is + ephemeral_flags: EphemeralStateFlags, +} + +impl PersistentStateFlags { // The values that can be set. const STATIC_TY: Self = Self::STATIC; const ARGUMENT_TY_IN_DEFINITION: Self = @@ -314,109 +346,133 @@ impl VisitorState { const RETURN_TY_IN_FNPTR: Self = Self::from_bits(Self::FUNC.bits() | Self::THEORETICAL.bits() | Self::FN_RETURN.bits()) .unwrap(); +} + +impl EphemeralStateFlags { + /// modify self to change the ephemeral part of the flags + fn from_outer_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), + } + } +} + +impl VisitorState { + /// From an existing state, compute the state of any subtype of the current type. + fn get_next<'tcx>(&self, current_ty: Ty<'tcx>) -> Self { + debug_assert!(!matches!(current_ty.kind(), ty::FnPtr(..))); + Self { + persistent_flags: self.persistent_flags, + ephemeral_flags: EphemeralStateFlags::from_outer_ty(current_ty), + } + } + fn get_next_in_fnptr<'tcx>(&self, current_ty: Ty<'tcx>, is_ret: bool) -> Self { + debug_assert!(matches!(current_ty.kind(), ty::FnPtr(..))); + Self { + persistent_flags: if is_ret { + PersistentStateFlags::RETURN_TY_IN_FNPTR + } else { + PersistentStateFlags::ARGUMENT_TY_IN_FNPTR + }, + ephemeral_flags: EphemeralStateFlags::from_outer_ty(current_ty), + } + } + + /// Generate the state for an "outermost" type that needs to be checked + fn entry_point(persistent_flags: PersistentStateFlags) -> Self { + Self { persistent_flags, ephemeral_flags: EphemeralStateFlags::NO_OUTER_TY } + } /// 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, - } + let p_flags = match fn_mode { + CItemKind::Definition => PersistentStateFlags::ARGUMENT_TY_IN_DEFINITION, + CItemKind::Declaration => PersistentStateFlags::ARGUMENT_TY_IN_DECLARATION, + }; + Self::entry_point(p_flags) } /// 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, - } + let p_flags = match fn_mode { + CItemKind::Definition => PersistentStateFlags::RETURN_TY_IN_DEFINITION, + CItemKind::Declaration => PersistentStateFlags::RETURN_TY_IN_DECLARATION, + }; + Self::entry_point(p_flags) + } + + /// Get the proper visitor state for a static variable's type + #[inline] + fn static_var() -> Self { + Self::entry_point(PersistentStateFlags::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.persistent_flags.contains(PersistentStateFlags::FUNC); if ret { - debug_assert!(!self.contains(Self::STATIC)); + debug_assert!(!self.persistent_flags.contains(PersistentStateFlags::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.persistent_flags.contains(PersistentStateFlags::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 { + self.ephemeral_flags.contains(EphemeralStateFlags::NO_OUTER_TY) + && self.is_in_function_return() + } + + /// Whether the type is directly used in a function. + fn is_direct_in_function(&self) -> bool { + self.ephemeral_flags.contains(EphemeralStateFlags::NO_OUTER_TY) && 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.persistent_flags.contains(PersistentStateFlags::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.persistent_flags.contains(PersistentStateFlags::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.persistent_flags.contains(PersistentStateFlags::THEORETICAL) + || self.is_in_defined_function() } } @@ -505,7 +561,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 +582,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 +601,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, @@ -640,7 +696,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 +726,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 +768,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 @@ -750,7 +802,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { 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() + state.is_direct_function_return() } else { false }; @@ -783,10 +835,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } ty::Array(inner_ty, _) => { - if state.is_in_function() - && outer_ty.contains(OuterTyData::NO_OUTER_TY) + if state.is_direct_in_function() // FIXME(ctypes): VVV-this-VVV shouldn't be the case - && !outer_ty.contains(OuterTyData::NOOUT_FNPTR) + && !state.ephemeral_flags.contains(EphemeralStateFlags::NOOUT_FNPTR) { // C doesn't really support passing arrays by value - the only way to pass an array by value // is through a struct. @@ -798,7 +849,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 +865,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, false), *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, true), ret_ty) } ty::Foreign(..) => FfiSafe, @@ -894,7 +940,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 +969,7 @@ impl<'tcx> ImproperCTypesLint { self.spans.push(ty.span); } - hir::intravisit::walk_ty(self, ty) + hir::intravisit::walk_ty(self, ty); } } @@ -998,7 +1044,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); } @@ -1122,7 +1168,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 +1202,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 c74c323feea61d214f06d87cbb4fad268db3a45f 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 --------- .../mustpass-irregular-recursive-types.rs | 19 +++++++++++ .../mustpass-non-recursion-limit.rs | 32 ++++++++++++++++++ 4 files changed, 76 insertions(+), 23 deletions(-) delete mode 100644 tests/crashes/130310.rs create mode 100644 tests/ui/lint/improper-ctypes/mustpass-irregular-recursive-types.rs create mode 100644 tests/ui/lint/improper-ctypes/mustpass-non-recursion-limit.rs diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index f31249e675e88..c49bb7c95e525 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -329,6 +329,8 @@ struct VisitorState { persistent_flags: PersistentStateFlags, /// Flags describing both the immediate and overall context in which the current mir::Ty is ephemeral_flags: EphemeralStateFlags, + /// Type recursion depth, to prevent infinite recursion + depth: usize, } impl PersistentStateFlags { @@ -383,6 +385,7 @@ impl VisitorState { Self { persistent_flags: self.persistent_flags, ephemeral_flags: EphemeralStateFlags::from_outer_ty(current_ty), + depth: self.depth + 1, } } fn get_next_in_fnptr<'tcx>(&self, current_ty: Ty<'tcx>, is_ret: bool) -> Self { @@ -394,12 +397,13 @@ impl VisitorState { PersistentStateFlags::ARGUMENT_TY_IN_FNPTR }, ephemeral_flags: EphemeralStateFlags::from_outer_ty(current_ty), + depth: self.depth + 1, } } /// Generate the state for an "outermost" type that needs to be checked fn entry_point(persistent_flags: PersistentStateFlags) -> Self { - Self { persistent_flags, ephemeral_flags: EphemeralStateFlags::NO_OUTER_TY } + Self { persistent_flags, ephemeral_flags: EphemeralStateFlags::NO_OUTER_TY, depth: 0 } } /// Get the proper visitor state for a given function's arguments. @@ -733,9 +737,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; } @@ -956,6 +959,8 @@ impl<'tcx> ImproperCTypesLint { fn_mode: CItemKind, ) { struct FnPtrFinder<'tcx> { + current_depth: usize, + depths: Vec, spans: Vec, tys: Vec>, } @@ -963,13 +968,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; } } @@ -987,15 +995,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/mustpass-irregular-recursive-types.rs b/tests/ui/lint/improper-ctypes/mustpass-irregular-recursive-types.rs new file mode 100644 index 0000000000000..fcbe8a0daa192 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/mustpass-irregular-recursive-types.rs @@ -0,0 +1,19 @@ +//@ 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 + +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/mustpass-non-recursion-limit.rs b/tests/ui/lint/improper-ctypes/mustpass-non-recursion-limit.rs new file mode 100644 index 0000000000000..61e95dc5a464c --- /dev/null +++ b/tests/ui/lint/improper-ctypes/mustpass-non-recursion-limit.rs @@ -0,0 +1,32 @@ +//@ check-pass + +#![recursion_limit = "5"] +#![allow(unused)] +#![deny(improper_ctypes)] + +#[repr(C)] +struct F1(*const ()); +#[repr(C)] +struct F2(*const ()); +#[repr(C)] +struct F3(*const ()); +#[repr(C)] +struct F4(*const ()); +#[repr(C)] +struct F5(*const ()); +#[repr(C)] +struct F6(*const ()); + +#[repr(C)] +struct B { + f1: F1, + f2: F2, + f3: F3, + f4: F4, + f5: F5, + f6: F6, +} + +extern "C" fn foo(_: B) {} + +fn main() {} From 764d8fbc751996aa52098a29a09595fe3752c7c5 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 | 381 ++++++++++++++---- 2 files changed, 335 insertions(+), 94 deletions(-) diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 56d65ed08f9e3..8909dbbce843e 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1943,29 +1943,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 c49bb7c95e525..fd9ba2679f8d6 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! { @@ -260,10 +260,184 @@ enum CItemKind { Definition, } +#[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 @@ -478,6 +652,12 @@ impl VisitorState { self.persistent_flags.contains(PersistentStateFlags::THEORETICAL) || self.is_in_defined_function() } + + /// Whether the current type is an ADT field + #[inline] + fn is_field(&self) -> bool { + self.ephemeral_flags.contains(EphemeralStateFlags::IN_ADT) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -524,11 +704,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:) @@ -586,12 +766,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`). @@ -607,8 +782,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, } @@ -617,7 +790,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 } @@ -631,51 +804,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(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 { + 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) } @@ -703,18 +875,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(()), @@ -756,11 +931,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) } @@ -780,32 +955,34 @@ 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) => { let empty_and_safe = if tuple.is_empty() { // C functions can return void state.is_direct_function_return() + // `()` fields are safe + || state.is_field() } else { false }; @@ -813,11 +990,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if empty_and_safe { 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), + ) } } @@ -844,11 +1021,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 @@ -859,11 +1036,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); @@ -885,7 +1062,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, @@ -930,11 +1107,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> { @@ -1103,15 +1278,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); + } } } } @@ -1121,8 +1334,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 { @@ -1133,14 +1345,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 a2dc1fbbffbf635acfb23b72d37989250ca36775 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. --- .../example/std_example.rs | 2 +- compiler/rustc_lint/messages.ftl | 2 +- compiler/rustc_lint/src/lib.rs | 11 ++ compiler/rustc_lint/src/types.rs | 4 +- .../rustc_lint/src/types/improper_ctypes.rs | 135 +++++++++++++----- library/panic_abort/src/lib.rs | 2 +- library/panic_unwind/src/lib.rs | 2 +- .../clippy/tests/ui/inherent_to_string.rs | 2 +- src/tools/lint-docs/src/groups.rs | 5 + .../simd_feature_flag_difference.rs | 2 +- tests/assembly-llvm/naked-functions/wasm32.rs | 1 - tests/auxiliary/minicore.rs | 2 +- tests/ui/abi/abi-sysv64-register-usage.rs | 2 +- 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 +- tests/ui/abi/large-byval-align.rs | 2 +- tests/ui/abi/simd-abi-checks-avx.rs | 2 +- tests/ui/abi/simd-abi-checks-empty-list.rs | 2 +- tests/ui/abi/simd-abi-checks-s390x.rs | 2 +- tests/ui/abi/simd-abi-checks-sse.rs | 2 +- ...sized-args-in-c-abi-issues-94223-115845.rs | 2 +- tests/ui/asm/naked-functions-ffi.stderr | 2 +- tests/ui/attributes/export/exportable.rs | 2 +- .../backtrace/auxiliary/dylib-dep-helper.rs | 2 +- tests/ui/backtrace/dylib-dep.rs | 2 +- .../cmse-nonsecure-call/return-via-stack.rs | 2 +- .../cmse-nonsecure-call/via-registers.rs | 3 +- .../cmse-nonsecure-entry/params-via-stack.rs | 2 +- .../cmse-nonsecure-entry/return-via-stack.rs | 8 +- .../cmse-nonsecure-entry/via-registers.rs | 6 +- .../consts/const-extern-fn/const-extern-fn.rs | 4 +- .../ui/extern/empty-struct-extern-fn-16441.rs | 2 +- .../extern-C-non-FFI-safe-arg-ice-52334.rs | 2 +- ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 6 +- 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-26997.rs | 2 +- tests/ui/issues/issue-28600.rs | 2 +- tests/ui/issues/issue-38763.rs | 2 +- tests/ui/issues/issue-51907.rs | 6 +- 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 +- .../ui/lint/improper-ctypes/lint-113436-1.rs | 2 +- .../lint/improper-ctypes/lint-113436-1.stderr | 4 +- tests/ui/lint/improper-ctypes/lint-94223.rs | 28 ++-- .../ui/lint/improper-ctypes/lint-94223.stderr | 30 ++-- tests/ui/lint/improper-ctypes/lint-cstr.rs | 2 +- .../ui/lint/improper-ctypes/lint-cstr.stderr | 6 +- tests/ui/lint/improper-ctypes/lint-fn.rs | 4 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 4 +- .../lint-option-nonnull-unsized.rs | 2 +- .../lint-option-nonnull-unsized.stderr | 4 +- .../lint/improper-ctypes/mustpass-113436.rs | 2 +- .../improper-ctypes/mustpass-134060.stderr | 2 +- tests/ui/mir/mir_cast_fn_ret.rs | 4 +- tests/ui/mir/mir_codegen_calls.rs | 2 +- tests/ui/repr/align-with-extern-c-fn.rs | 2 +- .../repr/repr-transparent-issue-87496.stderr | 2 +- 61 files changed, 227 insertions(+), 141 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/example/std_example.rs b/compiler/rustc_codegen_cranelift/example/std_example.rs index 5d83066cffb88..6685cd89f518a 100644 --- a/compiler/rustc_codegen_cranelift/example/std_example.rs +++ b/compiler/rustc_codegen_cranelift/example/std_example.rs @@ -209,7 +209,7 @@ fn rust_call_abi() { struct I64X2([i64; 2]); #[cfg_attr(target_arch = "s390x", allow(dead_code))] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] extern "C" fn foo(_a: I64X2) {} #[cfg(target_arch = "x86_64")] diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 417e5a97069c3..17d4bcdc80660 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -350,7 +350,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 9bb53fea54a18..3401bd68c0ac9 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -335,6 +335,17 @@ fn register_builtins(store: &mut LintStore) { REFINING_IMPL_TRAIT_INTERNAL ); + add_lint_group!( + "improper_c_boundaries", + IMPROPER_C_CALLBACKS, + IMPROPER_C_FN_DEFINITIONS, + IMPROPER_CTYPES + ); + + // FIXME(ctypes, migration): when should this retrocompatibility-borne + // lint group disappear, if at all? Should it turn into a register_renamed? + add_lint_group!("improper_ctypes_definitions", IMPROPER_C_CALLBACKS, IMPROPER_C_FN_DEFINITIONS); + 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 a72b802eb5d13..d83f2440a9520 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_C_CALLBACKS, IMPROPER_C_FN_DEFINITIONS, IMPROPER_CTYPES, 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 fd9ba2679f8d6..25a0938562517 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -25,12 +25,14 @@ 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.) /// /// ### Example /// /// ```rust /// unsafe extern "C" { /// static STATIC: String; + /// fn some_func(a:String); /// } /// ``` /// @@ -44,14 +46,15 @@ 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" } declare_lint! { - /// The `improper_ctypes_definitions` lint detects incorrect use of + /// The `improper_c_fn_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,11 +74,39 @@ 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_C_FN_DEFINITIONS, Warn, "proper use of libc types in foreign item definitions" } +declare_lint! { + /// The `improper_c_callbacks` lint detects incorrect use of + /// [`extern` function] pointers. + /// (In other words, function signatures for callbacks.) + /// + /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier + /// + /// ### Example + /// + /// ```rust + /// # #![allow(unused)] + /// pub fn str_emmiter(call_me_back: extern "C" fn(&str)) { } + /// ``` + /// + /// {{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. + pub(crate) IMPROPER_C_CALLBACKS, + Warn, + "proper use of libc types in foreign-code-compatible callbacks" +} + declare_lint! { /// The `uses_power_alignment` lint detects specific `repr(C)` /// aggregates on AIX. @@ -133,8 +164,9 @@ declare_lint! { declare_lint_pass!(ImproperCTypesLint => [ IMPROPER_CTYPES, - IMPROPER_CTYPES_DEFINITIONS, - USES_POWER_ALIGNMENT + IMPROPER_C_FN_DEFINITIONS, + IMPROPER_C_CALLBACKS, + USES_POWER_ALIGNMENT, ]); /// Getting the (normalized) type out of a field (for, e.g., an enum variant or a tuple). @@ -254,10 +286,17 @@ fn check_struct_for_power_alignment<'tcx>( } } -#[derive(Clone, Copy)] +/// A way to keep track of what we want to lint for FFI-safety. +/// In other words, the nature of the "original item" being checked, and its relation +/// to FFI boundaries. +#[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_C_FN_DEFINITIONS, + ExportedFunction, + /// `extern "C"` function pointers -> IMPROPER_C_CALLBACKS, + Callback, } #[derive(Clone, Debug)] @@ -472,7 +511,9 @@ bitflags! { /// (struct/enum/union definitions, FnPtrs). const THEORETICAL = 0b010000; } +} +bitflags! { /// "Ephemeral state" flags for VisitorState (lost when visiting new mir::Ty, /// mostly contain info about the Ty immediately containing the current one). #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -583,8 +624,11 @@ impl VisitorState { /// Get the proper visitor state for a given function's arguments. fn argument_from_fnmode(fn_mode: CItemKind) -> Self { let p_flags = match fn_mode { - CItemKind::Definition => PersistentStateFlags::ARGUMENT_TY_IN_DEFINITION, - CItemKind::Declaration => PersistentStateFlags::ARGUMENT_TY_IN_DECLARATION, + CItemKind::ExportedFunction => PersistentStateFlags::ARGUMENT_TY_IN_DEFINITION, + CItemKind::ImportedExtern => PersistentStateFlags::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) } @@ -592,8 +636,11 @@ impl VisitorState { /// Get the proper visitor state for a given function's return type. fn return_from_fnmode(fn_mode: CItemKind) -> Self { let p_flags = match fn_mode { - CItemKind::Definition => PersistentStateFlags::RETURN_TY_IN_DEFINITION, - CItemKind::Declaration => PersistentStateFlags::RETURN_TY_IN_DECLARATION, + CItemKind::ExportedFunction => PersistentStateFlags::RETURN_TY_IN_DEFINITION, + CItemKind::ImportedExtern => PersistentStateFlags::RETURN_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) } @@ -699,7 +746,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; @@ -1189,7 +1237,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); } } @@ -1235,9 +1283,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". @@ -1252,16 +1300,16 @@ 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 visit_state = VisitorState::argument_from_fnmode(fn_mode); let mut visitor = ImproperCTypesVisitor::new(cx, *input_ty, fn_mode); - let ffi_res = visitor.check_type(state, *input_ty); + let ffi_res = visitor.check_type(visit_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 visit_state = VisitorState::return_from_fnmode(fn_mode); let mut visitor = ImproperCTypesVisitor::new(cx, sig.output(), fn_mode); - let ffi_res = visitor.check_type(state, sig.output()); + let ffi_res = visitor.check_type(visit_state, sig.output()); self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode); } } @@ -1338,12 +1386,14 @@ 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_C_FN_DEFINITIONS, + CItemKind::Callback => IMPROPER_C_CALLBACKS, }; 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() @@ -1359,13 +1409,21 @@ 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"). +/// +/// `IMPROPER_C_CALLBACKS` 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) +/// +/// `IMPROPER_C_FN_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`). /// -/// - `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. +/// and now combinatorics 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()); @@ -1376,11 +1434,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, ); @@ -1403,7 +1466,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 @@ -1437,7 +1500,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, ); } @@ -1462,9 +1525,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/library/panic_abort/src/lib.rs b/library/panic_abort/src/lib.rs index d1706b6525295..46bff10312cf2 100644 --- a/library/panic_abort/src/lib.rs +++ b/library/panic_abort/src/lib.rs @@ -23,7 +23,7 @@ use core::any::Any; use core::panic::PanicPayload; #[rustc_std_internal_symbol] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub unsafe extern "C" fn __rust_panic_cleanup(_: *mut u8) -> *mut (dyn Any + Send + 'static) { unreachable!() } diff --git a/library/panic_unwind/src/lib.rs b/library/panic_unwind/src/lib.rs index 83311f3238012..2c49a838e1c97 100644 --- a/library/panic_unwind/src/lib.rs +++ b/library/panic_unwind/src/lib.rs @@ -95,7 +95,7 @@ unsafe extern "C" { } #[rustc_std_internal_symbol] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub unsafe extern "C" fn __rust_panic_cleanup(payload: *mut u8) -> *mut (dyn Any + Send + 'static) { unsafe { Box::into_raw(imp::cleanup(payload)) } } diff --git a/src/tools/clippy/tests/ui/inherent_to_string.rs b/src/tools/clippy/tests/ui/inherent_to_string.rs index 30977f2d93ac6..4a57a4a8b425f 100644 --- a/src/tools/clippy/tests/ui/inherent_to_string.rs +++ b/src/tools/clippy/tests/ui/inherent_to_string.rs @@ -1,4 +1,4 @@ -#![allow(improper_ctypes_definitions)] +#![allow(improper_c_fn_definitions)] use std::fmt; diff --git a/src/tools/lint-docs/src/groups.rs b/src/tools/lint-docs/src/groups.rs index a24fbbc0ceab3..a0f6f5009bbd7 100644 --- a/src/tools/lint-docs/src/groups.rs +++ b/src/tools/lint-docs/src/groups.rs @@ -30,6 +30,11 @@ 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"), + ( + "improper-ctypes-definitions", + "Former lint that was since split intoimproper-c-fn-definitions and improper-c-callbacks", + ), ]; type LintGroups = BTreeMap>; diff --git a/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs b/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs index 200f1062a3e80..f7323faf0cca3 100644 --- a/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs +++ b/src/tools/miri/tests/fail/function_calls/simd_feature_flag_difference.rs @@ -1,5 +1,5 @@ //@only-target: x86_64 -#![allow(improper_ctypes_definitions)] +#![allow(improper_c_fn_definitions)] use std::arch::x86_64::*; use std::mem::transmute; diff --git a/tests/assembly-llvm/naked-functions/wasm32.rs b/tests/assembly-llvm/naked-functions/wasm32.rs index 77547e82041fe..1b1ab97ab20ca 100644 --- a/tests/assembly-llvm/naked-functions/wasm32.rs +++ b/tests/assembly-llvm/naked-functions/wasm32.rs @@ -98,7 +98,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/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index da880100a10cd..efbe85e26e566 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, improper_c_fn_definitions, internal_features)] #![no_std] #![no_core] diff --git a/tests/ui/abi/abi-sysv64-register-usage.rs b/tests/ui/abi/abi-sysv64-register-usage.rs index d2fb2ae53ac73..6f31e0533d5cd 100644 --- a/tests/ui/abi/abi-sysv64-register-usage.rs +++ b/tests/ui/abi/abi-sysv64-register-usage.rs @@ -48,7 +48,7 @@ pub struct LargeStruct(i64, i64, i64, i64, i64, i64, i64, i64); #[cfg(target_arch = "x86_64")] #[inline(never)] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "sysv64" fn large_struct_by_val(mut foo: LargeStruct) -> LargeStruct { foo.0 *= 1; foo.1 *= 2; diff --git a/tests/ui/abi/compatibility.rs b/tests/ui/abi/compatibility.rs index 68706f1e821ac..5bd74a1a89673 100644 --- a/tests/ui/abi/compatibility.rs +++ b/tests/ui/abi/compatibility.rs @@ -62,7 +62,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_c_fn_definitions, improper_c_callbacks)] // 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..06b57481bb571 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, improper_c_callbacks)] #[derive(Copy, Clone)] pub struct S { diff --git a/tests/ui/abi/large-byval-align.rs b/tests/ui/abi/large-byval-align.rs index c1de841178fcd..64c5ee392351d 100644 --- a/tests/ui/abi/large-byval-align.rs +++ b/tests/ui/abi/large-byval-align.rs @@ -5,7 +5,7 @@ #[repr(align(536870912))] pub struct A(i64); -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "C" fn foo(x: A) {} fn main() { diff --git a/tests/ui/abi/simd-abi-checks-avx.rs b/tests/ui/abi/simd-abi-checks-avx.rs index 7432381d15b72..54ebc89b7bca8 100644 --- a/tests/ui/abi/simd-abi-checks-avx.rs +++ b/tests/ui/abi/simd-abi-checks-avx.rs @@ -4,7 +4,7 @@ #![feature(portable_simd)] #![feature(simd_ffi)] -#![allow(improper_ctypes_definitions)] +#![allow(improper_c_fn_definitions)] use std::arch::x86_64::*; diff --git a/tests/ui/abi/simd-abi-checks-empty-list.rs b/tests/ui/abi/simd-abi-checks-empty-list.rs index d00445b29e055..2026ccbe4ea7f 100644 --- a/tests/ui/abi/simd-abi-checks-empty-list.rs +++ b/tests/ui/abi/simd-abi-checks-empty-list.rs @@ -6,7 +6,7 @@ //@ build-fail #![no_core] #![feature(no_core, repr_simd)] -#![allow(improper_ctypes_definitions)] +#![allow(improper_c_fn_definitions)] extern crate minicore; use minicore::*; diff --git a/tests/ui/abi/simd-abi-checks-s390x.rs b/tests/ui/abi/simd-abi-checks-s390x.rs index 877a25e8b083f..aa5860890a2c4 100644 --- a/tests/ui/abi/simd-abi-checks-s390x.rs +++ b/tests/ui/abi/simd-abi-checks-s390x.rs @@ -12,7 +12,7 @@ #![feature(no_core, repr_simd, s390x_target_feature)] #![no_core] #![crate_type = "lib"] -#![allow(non_camel_case_types, improper_ctypes_definitions)] +#![allow(non_camel_case_types, improper_c_fn_definitions)] extern crate minicore; use minicore::*; diff --git a/tests/ui/abi/simd-abi-checks-sse.rs b/tests/ui/abi/simd-abi-checks-sse.rs index 817f9b6d13bc6..bcb9da0ceb4b5 100644 --- a/tests/ui/abi/simd-abi-checks-sse.rs +++ b/tests/ui/abi/simd-abi-checks-sse.rs @@ -7,7 +7,7 @@ //@ needs-llvm-components: x86 #![feature(no_core, repr_simd)] #![no_core] -#![allow(improper_ctypes_definitions)] +#![allow(improper_c_fn_definitions)] extern crate minicore; use minicore::*; 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..0bd51ac5e610f 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_c_fn_definitions, improper_c_callbacks)] #![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..260c6895ff3f2 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_c_fn_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: 1 warning emitted diff --git a/tests/ui/attributes/export/exportable.rs b/tests/ui/attributes/export/exportable.rs index f592fce88cd49..cf8ef0501a66c 100644 --- a/tests/ui/attributes/export/exportable.rs +++ b/tests/ui/attributes/export/exportable.rs @@ -1,7 +1,7 @@ //@ compile-flags: -Zunstable-options -Csymbol-mangling-version=v0 #![crate_type = "sdylib"] -#![allow(incomplete_features, improper_ctypes_definitions)] +#![allow(incomplete_features, improper_c_fn_definitions)] #![feature(export_stable)] #![feature(inherent_associated_types)] diff --git a/tests/ui/backtrace/auxiliary/dylib-dep-helper.rs b/tests/ui/backtrace/auxiliary/dylib-dep-helper.rs index 565d8b65de055..b553aaa84bc33 100644 --- a/tests/ui/backtrace/auxiliary/dylib-dep-helper.rs +++ b/tests/ui/backtrace/auxiliary/dylib-dep-helper.rs @@ -3,7 +3,7 @@ #![crate_type = "cdylib"] #![crate_type = "rlib"] -#![allow(improper_ctypes_definitions)] +#![allow(improper_c_fn_definitions)] type Pos = (&'static str, u32); diff --git a/tests/ui/backtrace/dylib-dep.rs b/tests/ui/backtrace/dylib-dep.rs index 05fdb9afef80d..4fb1734adffe9 100644 --- a/tests/ui/backtrace/dylib-dep.rs +++ b/tests/ui/backtrace/dylib-dep.rs @@ -16,7 +16,7 @@ //@ run-pass #![allow(improper_ctypes)] -#![allow(improper_ctypes_definitions)] +#![allow(improper_c_fn_definitions)] extern crate dylib_dep_helper; extern crate auxiliary; 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..b5b6fb3f2430c 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_c_callbacks)] 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..10e08c9df4e4f 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_c_callbacks)] 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_c_callbacks)] pub fn returns( f1: extern "cmse-nonsecure-call" fn() -> u32, f2: extern "cmse-nonsecure-call" fn() -> u64, diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/params-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/params-via-stack.rs index d4f722fa1938b..c5ecae2300a0f 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/params-via-stack.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/params-via-stack.rs @@ -21,5 +21,5 @@ pub extern "cmse-nonsecure-entry" fn f3(_: u32, _: u64, _: u32) {} //~ ERROR [E0 pub extern "cmse-nonsecure-entry" fn f4(_: AlignRelevant, _: u32) {} //~ ERROR [E0798] #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "cmse-nonsecure-entry" fn f5(_: [u32; 5]) {} //~ ERROR [E0798] diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs index 0052a0977ed71..3d87333f4fa0c 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs @@ -43,19 +43,19 @@ pub extern "cmse-nonsecure-entry" fn f4() -> ReprCAlign16 { } #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "cmse-nonsecure-entry" fn f5() -> [u8; 5] { //~^ ERROR [E0798] [0xAA; 5] } #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "cmse-nonsecure-entry" fn u128() -> u128 { //~^ ERROR [E0798] 123 } #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "cmse-nonsecure-entry" fn i128() -> i128 { //~^ ERROR [E0798] 456 @@ -72,7 +72,7 @@ pub union ReprCUnionU64 { } #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU64 { //~^ ERROR [E0798] ReprRustUnionU64 { _unused: 1 } diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/via-registers.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/via-registers.rs index 3437328812561..b3934b2513aa4 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/via-registers.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/via-registers.rs @@ -32,14 +32,14 @@ pub extern "cmse-nonsecure-entry" fn inputs2(_: u32, _: u32, _: u32, _: u32) {} #[no_mangle] pub extern "cmse-nonsecure-entry" fn inputs3(_: u64, _: u64) {} #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "cmse-nonsecure-entry" fn inputs4(_: u128) {} #[no_mangle] pub extern "cmse-nonsecure-entry" fn inputs5(_: f64, _: f32, _: f32) {} #[no_mangle] pub extern "cmse-nonsecure-entry" fn inputs6(_: ReprTransparentStruct, _: U32Compound) {} #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "cmse-nonsecure-entry" fn inputs7(_: [u32; 4]) {} #[no_mangle] @@ -59,7 +59,7 @@ pub extern "cmse-nonsecure-entry" fn outputs4() -> f64 { 0.0 } #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "cmse-nonsecure-entry" fn outputs5() -> [u8; 4] { [0xAA; 4] } diff --git a/tests/ui/consts/const-extern-fn/const-extern-fn.rs b/tests/ui/consts/const-extern-fn/const-extern-fn.rs index 75ffa783a117b..9b188ab6e057e 100644 --- a/tests/ui/consts/const-extern-fn/const-extern-fn.rs +++ b/tests/ui/consts/const-extern-fn/const-extern-fn.rs @@ -16,12 +16,12 @@ const unsafe extern "C" fn bar2(val: bool) -> bool { !val } -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] const extern "C" fn unsize(x: &[u8; 3]) -> &[u8] { x } -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] const unsafe extern "C" fn closure() -> fn() { || {} } diff --git a/tests/ui/extern/empty-struct-extern-fn-16441.rs b/tests/ui/extern/empty-struct-extern-fn-16441.rs index 82f2eee611d06..5ed11af6de9d0 100644 --- a/tests/ui/extern/empty-struct-extern-fn-16441.rs +++ b/tests/ui/extern/empty-struct-extern-fn-16441.rs @@ -6,7 +6,7 @@ struct Empty; // This used to cause an ICE -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] extern "C" fn ice(_a: Empty) {} fn main() { 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..66d7b1f4114c7 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_c_callbacks)]` (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,7 @@ 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 + = note: `#[warn(improper_ctypes)]` (part of `#[warn(improper_c_boundaries)]`) 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..31f419af7b9f1 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_c_callbacks)]` (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_c_fn_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..51301cd90fab7 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_c_callbacks)] #![allow(non_camel_case_types)] #![allow(dead_code)] #![allow(unused_mut)] diff --git a/tests/ui/issues/issue-26997.rs b/tests/ui/issues/issue-26997.rs index 5441dc68bae6c..1138f17753762 100644 --- a/tests/ui/issues/issue-26997.rs +++ b/tests/ui/issues/issue-26997.rs @@ -6,7 +6,7 @@ pub struct Foo { } impl Foo { - #[allow(improper_ctypes_definitions)] + #[allow(improper_c_fn_definitions)] pub extern "C" fn foo_new() -> Foo { Foo { x: 21, y: 33 } } diff --git a/tests/ui/issues/issue-28600.rs b/tests/ui/issues/issue-28600.rs index a5427b94a57c5..3bbd40bfeb741 100644 --- a/tests/ui/issues/issue-28600.rs +++ b/tests/ui/issues/issue-28600.rs @@ -6,7 +6,7 @@ struct Test; impl Test { #[allow(dead_code)] #[allow(unused_variables)] - #[allow(improper_ctypes_definitions)] + #[allow(improper_c_fn_definitions)] pub extern "C" fn test(val: &str) { } diff --git a/tests/ui/issues/issue-38763.rs b/tests/ui/issues/issue-38763.rs index 87c758db1723c..328563945f447 100644 --- a/tests/ui/issues/issue-38763.rs +++ b/tests/ui/issues/issue-38763.rs @@ -5,7 +5,7 @@ pub struct Foo(i128); #[no_mangle] -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "C" fn foo(x: Foo) -> Foo { x } fn main() { diff --git a/tests/ui/issues/issue-51907.rs b/tests/ui/issues/issue-51907.rs index bf3f629df4970..8f43fdf03126d 100644 --- a/tests/ui/issues/issue-51907.rs +++ b/tests/ui/issues/issue-51907.rs @@ -1,14 +1,16 @@ //@ run-pass trait Foo { + #[allow(improper_c_fn_definitions)] extern "C" fn borrow(&self); + #[allow(improper_c_fn_definitions)] extern "C" fn take(self: Box); } struct Bar; impl Foo for Bar { - #[allow(improper_ctypes_definitions)] + #[allow(improper_c_fn_definitions)] extern "C" fn borrow(&self) {} - #[allow(improper_ctypes_definitions)] + #[allow(improper_c_fn_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..db7cc56adf147 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_c_callbacks)] // 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..aa72803b046c9 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_c_callbacks)] + | ^^^^^^^^^^^^^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.rs b/tests/ui/lint/improper-ctypes/lint-113436-1.rs index 1ca59c6868d6d..1f9f02dc573b9 100644 --- a/tests/ui/lint/improper-ctypes/lint-113436-1.rs +++ b/tests/ui/lint/improper-ctypes/lint-113436-1.rs @@ -1,4 +1,4 @@ -#![deny(improper_ctypes_definitions)] +#![deny(improper_c_fn_definitions)] #[repr(C)] pub struct Foo { diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr b/tests/ui/lint/improper-ctypes/lint-113436-1.stderr index f01dc3b6e0d1e..a65f2fe7c6591 100644 --- a/tests/ui/lint/improper-ctypes/lint-113436-1.stderr +++ b/tests/ui/lint/improper-ctypes/lint-113436-1.stderr @@ -14,8 +14,8 @@ LL | struct NotSafe(u32); note: the lint level is defined here --> $DIR/lint-113436-1.rs:1:9 | -LL | #![deny(improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #![deny(improper_c_fn_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `NotSafe`, which is not FFI-safe --> $DIR/lint-113436-1.rs:22:30 diff --git a/tests/ui/lint/improper-ctypes/lint-94223.rs b/tests/ui/lint/improper-ctypes/lint-94223.rs index ac24f61b0ac7a..fb07af4487f8a 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.rs +++ b/tests/ui/lint/improper-ctypes/lint-94223.rs @@ -1,49 +1,49 @@ #![crate_type = "lib"] -#![deny(improper_ctypes_definitions)] +#![deny(improper_c_fn_definitions, improper_c_callbacks)] 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; -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] extern "C" fn f(_: FfiUnsafe) { unimplemented!() } 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..9d9fb47ee4fef 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:36 | -LL | #![deny(improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #![deny(improper_c_fn_definitions, improper_c_callbacks)] + | ^^^^^^^^^^^^^^^^^^^^ -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-cstr.rs b/tests/ui/lint/improper-ctypes/lint-cstr.rs index b04decd0bcacc..356e4959dce51 100644 --- a/tests/ui/lint/improper-ctypes/lint-cstr.rs +++ b/tests/ui/lint/improper-ctypes/lint-cstr.rs @@ -1,5 +1,5 @@ #![crate_type = "lib"] -#![deny(improper_ctypes, improper_ctypes_definitions)] +#![deny(improper_ctypes, improper_c_fn_definitions, improper_c_callbacks)] use std::ffi::{CStr, CString}; diff --git a/tests/ui/lint/improper-ctypes/lint-cstr.stderr b/tests/ui/lint/improper-ctypes/lint-cstr.stderr index da26306584311..2e761d4611f20 100644 --- a/tests/ui/lint/improper-ctypes/lint-cstr.stderr +++ b/tests/ui/lint/improper-ctypes/lint-cstr.stderr @@ -9,7 +9,7 @@ LL | fn take_cstr(s: CStr); note: the lint level is defined here --> $DIR/lint-cstr.rs:2:9 | -LL | #![deny(improper_ctypes, improper_ctypes_definitions)] +LL | #![deny(improper_ctypes, improper_c_fn_definitions, improper_c_callbacks)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `CStr`, which is not FFI-safe @@ -68,8 +68,8 @@ LL | extern "C" fn rust_take_cstr_ref(s: &CStr) {} note: the lint level is defined here --> $DIR/lint-cstr.rs:2:26 | -LL | #![deny(improper_ctypes, improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #![deny(improper_ctypes, improper_c_fn_definitions, improper_c_callbacks)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `CString`, which is not FFI-safe --> $DIR/lint-cstr.rs:32:36 diff --git a/tests/ui/lint/improper-ctypes/lint-fn.rs b/tests/ui/lint/improper-ctypes/lint-fn.rs index 0b84098e39067..0c850cb8f0603 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_c_fn_definitions, improper_c_callbacks)] use std::default::Default; use std::marker::PhantomData; @@ -149,7 +149,7 @@ pub extern "C" fn good16(p: TransparentUnit) { } pub extern "C" fn good17(p: TransparentCustomZst) { } -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "C" fn good18(_: &String) { } pub extern "C" fn good_i128_type(p: i128) { } diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index 34e3bd021b92d..a6c49fd182a32 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -9,8 +9,8 @@ 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_c_fn_definitions, improper_c_callbacks)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `str`, which is not FFI-safe --> $DIR/lint-fn.rs:73:31 diff --git a/tests/ui/lint/improper-ctypes/lint-option-nonnull-unsized.rs b/tests/ui/lint/improper-ctypes/lint-option-nonnull-unsized.rs index ca08eb23a57eb..57addd449ee3a 100644 --- a/tests/ui/lint/improper-ctypes/lint-option-nonnull-unsized.rs +++ b/tests/ui/lint/improper-ctypes/lint-option-nonnull-unsized.rs @@ -1,4 +1,4 @@ -#![deny(improper_ctypes_definitions)] +#![deny(improper_c_fn_definitions)] extern "C" fn foo() -> Option<&'static T> { //~^ ERROR `extern` fn uses type `Option<&T>`, which is not FFI-safe diff --git a/tests/ui/lint/improper-ctypes/lint-option-nonnull-unsized.stderr b/tests/ui/lint/improper-ctypes/lint-option-nonnull-unsized.stderr index b17fb6bd6145d..57a557b5d68e5 100644 --- a/tests/ui/lint/improper-ctypes/lint-option-nonnull-unsized.stderr +++ b/tests/ui/lint/improper-ctypes/lint-option-nonnull-unsized.stderr @@ -9,8 +9,8 @@ LL | extern "C" fn foo() -> Option<&'static T> { note: the lint level is defined here --> $DIR/lint-option-nonnull-unsized.rs:1:9 | -LL | #![deny(improper_ctypes_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #![deny(improper_c_fn_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/lint/improper-ctypes/mustpass-113436.rs b/tests/ui/lint/improper-ctypes/mustpass-113436.rs index d5acdc45f92e5..65796954c78ba 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_c_fn_definitions, improper_c_callbacks)] #[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..9ff35f4468af2 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_c_fn_definitions)]` (part of `#[warn(improper_c_boundaries)]`) on by default warning: 1 warning emitted diff --git a/tests/ui/mir/mir_cast_fn_ret.rs b/tests/ui/mir/mir_cast_fn_ret.rs index eebc6c03f4481..24fef4e38e172 100644 --- a/tests/ui/mir/mir_cast_fn_ret.rs +++ b/tests/ui/mir/mir_cast_fn_ret.rs @@ -1,10 +1,10 @@ //@ run-pass -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "C" fn tuple2() -> (u16, u8) { (1, 2) } -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "C" fn tuple3() -> (u8, u8, u8) { (1, 2, 3) } diff --git a/tests/ui/mir/mir_codegen_calls.rs b/tests/ui/mir/mir_codegen_calls.rs index b0749f565da08..451e1351971b3 100644 --- a/tests/ui/mir/mir_codegen_calls.rs +++ b/tests/ui/mir/mir_codegen_calls.rs @@ -74,7 +74,7 @@ fn test8() -> isize { Two::two() } -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] extern "C" fn simple_extern(x: u32, y: (u32, u32)) -> u32 { x + y.0 * y.1 } diff --git a/tests/ui/repr/align-with-extern-c-fn.rs b/tests/ui/repr/align-with-extern-c-fn.rs index 4d17d1e8816f2..9aecb53891775 100644 --- a/tests/ui/repr/align-with-extern-c-fn.rs +++ b/tests/ui/repr/align-with-extern-c-fn.rs @@ -10,7 +10,7 @@ #[repr(align(16))] pub struct A(#[allow(dead_code)] i64); -#[allow(improper_ctypes_definitions)] +#[allow(improper_c_fn_definitions)] pub extern "C" fn foo(x: A) {} fn main() { 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 84d388ec7664171d991bdcf22249a78e4e17a70a Mon Sep 17 00:00:00 2001 From: niacdoial Date: Tue, 26 Aug 2025 23:41:01 +0200 Subject: [PATCH 06/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 | 80 +++++++++++++++---- ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 4 +- tests/ui/lint/improper-ctypes/lint-cstr.rs | 32 ++++---- .../ui/lint/improper-ctypes/lint-cstr.stderr | 58 +++++++++----- 5 files changed, 134 insertions(+), 52 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 17d4bcdc80660..de317c22b287f 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -363,8 +363,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 @@ -386,8 +388,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 @@ -409,6 +411,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_incomplete_include = include macro expected single expression in source diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 25a0938562517..a8464cd8c92d0 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -350,7 +350,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) => { @@ -705,6 +704,18 @@ impl VisitorState { fn is_field(&self) -> bool { self.ephemeral_flags.contains(EphemeralStateFlags::IN_ADT) } + + /// Whether the current type is behind a pointer + #[inline] + fn is_pointee(&self) -> bool { + self.ephemeral_flags.contains(EphemeralStateFlags::IN_PTR) + } + + /// Whether the current type is behind a pointer that doesn't allow mutating this + #[inline] + fn is_nonmut_pointee(&self) -> bool { + self.is_pointee() && !self.ephemeral_flags.contains(EphemeralStateFlags::PTR_MUT) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -717,13 +728,30 @@ struct ImproperCTypesVisitor<'a, 'tcx> { cache: FxHashSet>, /// 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_fn_mode: CItemKind) -> Self { + Self { cx, base_fn_mode, 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". @@ -737,6 +765,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: @@ -977,13 +1034,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) } @@ -1232,7 +1284,7 @@ impl<'tcx> ImproperCTypesLint { 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 mut visitor = ImproperCTypesVisitor::new(cx, 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); @@ -1283,7 +1335,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::ImportedExtern); + let mut visitor = ImproperCTypesVisitor::new(cx, CItemKind::ImportedExtern); let ffi_res = visitor.check_type(VisitorState::static_var(), ty); self.process_ffi_result(cx, span, ffi_res, CItemKind::ImportedExtern); } @@ -1301,14 +1353,14 @@ impl<'tcx> ImproperCTypesLint { for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { let visit_state = VisitorState::argument_from_fnmode(fn_mode); - let mut visitor = ImproperCTypesVisitor::new(cx, *input_ty, fn_mode); + let mut visitor = ImproperCTypesVisitor::new(cx, fn_mode); let ffi_res = visitor.check_type(visit_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 visit_state = VisitorState::return_from_fnmode(fn_mode); - let mut visitor = ImproperCTypesVisitor::new(cx, sig.output(), fn_mode); + let mut visitor = ImproperCTypesVisitor::new(cx, fn_mode); let ffi_res = visitor.check_type(visit_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 66d7b1f4114c7..6e3598d5c2440 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_c_callbacks)]` (part of `#[warn(improper_c_boundaries)]`) on by default @@ -14,7 +14,7 @@ warning: `extern` block uses type `CStr`, which is not FFI-safe LL | fn meh(blah: Foo); | ^^^ 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 356e4959dce51..da4d7bac936b4 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 2e761d4611f20..65d93a1bf5cee 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_c_fn_definitions, improper_c_callbacks)] | ^^^^^^^^^^^^^^^ -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 218c43c38595962ea4c156de7be85884a6b6810a Mon Sep 17 00:00:00 2001 From: niacdoial Date: Tue, 26 Aug 2025 23:57:30 +0200 Subject: [PATCH 07/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 | 274 ++++++++++++------ tests/ui/lint/extern-C-fnptr-lints-slices.rs | 2 +- .../lint/extern-C-fnptr-lints-slices.stderr | 7 +- 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 | 49 +++- .../lint/improper-ctypes/lint-ctypes.stderr | 134 +++------ tests/ui/lint/improper-ctypes/lint-fn.rs | 11 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 84 ++++-- 10 files changed, 342 insertions(+), 240 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 de317c22b287f..05d86366571b4 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -357,7 +357,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 a8464cd8c92d0..15a4c4ed98382 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -483,7 +483,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). @@ -494,6 +494,132 @@ enum IndirectionKind { RawPtr, } +/// The different ways a given type can have/not have a fixed size +#[derive(Clone, Copy)] +enum TypeSizedness { + /// Type of definite size (pointers are C-compatible). + Definite, + /// Unsized type because it includes an opaque/foreign type (pointers are C-compatible). + UnsizedWithExternType, + /// Unsized type for other reasons (slice, string, dyn Trait, closure, ...) (pointers are not C-compatible). + UnsizedWithMetadata, + /// 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::Definite + } else { + // the overall type is !Sized or ?Sized + match ty.kind() { + ty::Slice(_) => TypeSizedness::UnsizedWithMetadata, + ty::Str => TypeSizedness::UnsizedWithMetadata, + ty::Dynamic(..) => TypeSizedness::UnsizedWithMetadata, + ty::Foreign(..) => TypeSizedness::UnsizedWithExternType, + 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(sym::cstring_type | sym::cstr_type) = + tcx.get_diagnostic_name(def.did()) + { + return TypeSizedness::UnsizedWithMetadata; + } + + // 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::UnsizedWithMetadata + | TypeSizedness::UnsizedWithExternType + | TypeSizedness::NotYetKnown) => s, + TypeSizedness::Definite => { + 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::UnsizedWithMetadata + | TypeSizedness::UnsizedWithExternType + | TypeSizedness::NotYetKnown) => s, + TypeSizedness::Definite => { + bug!("failed to find the reason why tuple `{:?}` is unsized", ty) + } + } + } + + ty_kind @ (ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Array(..) + | ty::RawPtr(..) + | ty::Ref(..) + | ty::FnPtr(..) + | ty::Never + | ty::Pat(..) // these are (for now) numeric types with a range-based restriction + ) => { + // 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! { /// "Permanent state" flags for VisitorState (kept when visiting new mir::Ty) #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -685,13 +811,6 @@ impl VisitorState { self.persistent_flags.contains(PersistentStateFlags::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.persistent_flags.contains(PersistentStateFlags::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 @@ -716,6 +835,12 @@ impl VisitorState { fn is_nonmut_pointee(&self) -> bool { self.is_pointee() && !self.ephemeral_flags.contains(EphemeralStateFlags::PTR_MUT) } + + /// Whether the current type is behind a raw pointer + #[inline] + fn is_raw_pointee(&self) -> bool { + self.ephemeral_flags.contains(EphemeralStateFlags::IN_PTR | EphemeralStateFlags::PTR_RAW) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -725,15 +850,12 @@ struct ImproperCTypesVisitor<'a, 'tcx> { cx: &'a LateContext<'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_fn_mode: CItemKind, + ty_cache: FxHashSet>, } impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>, base_fn_mode: CItemKind) -> Self { - Self { cx, base_fn_mode, cache: FxHashSet::default() } + fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { cx, ty_cache: FxHashSet::default() } } /// Return the right help for Cstring and Cstr-linked unsafety. @@ -762,7 +884,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() { @@ -794,64 +915,48 @@ 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 - // (including within FnPtr:s) - // are not treated as pointers but as 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 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. + match get_type_sizedness(self.cx, inner_ty) { + TypeSizedness::UnsizedWithExternType | TypeSizedness::Definite => { + // 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::UnsizedWithMetadata => { + 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); } } } @@ -1017,7 +1122,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; } @@ -1032,6 +1138,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()) { @@ -1083,6 +1194,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() } else { false }; @@ -1231,7 +1344,6 @@ impl<'tcx> ImproperCTypesLint { state: VisitorState, hir_ty: &hir::Ty<'tcx>, ty: Ty<'tcx>, - fn_mode: CItemKind, ) { struct FnPtrFinder<'tcx> { current_depth: usize, @@ -1284,7 +1396,7 @@ impl<'tcx> ImproperCTypesLint { iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)), ); for (depth, (fn_ptr_ty, span)) in all_types { - let mut visitor = ImproperCTypesVisitor::new(cx, fn_mode); + let mut visitor = ImproperCTypesVisitor::new(cx); let bridge_state = VisitorState { depth, ..state }; // FIXME(ctypes): make a check_for_fnptr let ffi_res = visitor.check_type(bridge_state, fn_ptr_ty); @@ -1307,12 +1419,12 @@ impl<'tcx> ImproperCTypesLint { for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { let state = VisitorState::argument_from_fnmode(fn_mode); - self.check_type_for_external_abi_fnptr(cx, state, input_hir, *input_ty, fn_mode); + self.check_type_for_external_abi_fnptr(cx, state, input_hir, *input_ty); } if let hir::FnRetTy::Return(ret_hir) = decl.output { let state = VisitorState::return_from_fnmode(fn_mode); - self.check_type_for_external_abi_fnptr(cx, state, ret_hir, sig.output(), fn_mode); + self.check_type_for_external_abi_fnptr(cx, state, ret_hir, sig.output()); } } @@ -1335,7 +1447,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, CItemKind::ImportedExtern); + 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); } @@ -1353,14 +1465,14 @@ impl<'tcx> ImproperCTypesLint { for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { let visit_state = VisitorState::argument_from_fnmode(fn_mode); - let mut visitor = ImproperCTypesVisitor::new(cx, fn_mode); + let mut visitor = ImproperCTypesVisitor::new(cx); let ffi_res = visitor.check_type(visit_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 visit_state = VisitorState::return_from_fnmode(fn_mode); - let mut visitor = ImproperCTypesVisitor::new(cx, fn_mode); + let mut visitor = ImproperCTypesVisitor::new(cx); let ffi_res = visitor.check_type(visit_state, sig.output()); self.process_ffi_result(cx, ret_hir.span, ffi_res, fn_mode); } @@ -1518,7 +1630,6 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { 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 @@ -1552,7 +1663,6 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { VisitorState::static_var(), field.ty, cx.tcx.type_of(field.def_id).instantiate_identity(), - CItemKind::ImportedExtern, ); } diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.rs b/tests/ui/lint/extern-C-fnptr-lints-slices.rs index db7cc56adf147..ee1676cabb51b 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 aa72803b046c9..fa488d14d0304 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -1,11 +1,12 @@ -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:14 | 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: the function pointer to `for<'a> extern "C" fn(&'a [u8])` is FFI-unsafe due to `&[u8]` + = 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-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 7dc06079fa32c..726f3188f05e6 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -1,12 +1,16 @@ #![feature(rustc_private)] +#![feature(extern_types)] #![allow(private_interfaces)] -#![deny(improper_ctypes)] +#![deny(improper_ctypes, improper_c_callbacks)] +#![deny(improper_c_fn_definitions)] 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 +24,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 +32,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 +43,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 +83,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); //~ ERROR: uses type `Box` - pub fn transparent_str(p: TransparentStr); //~ ERROR: uses type `str` - pub fn transparent_fn(p: TransparentBadFn); //~ ERROR: uses type `Box` + 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 6f8b951c53d70..7de489f6b24f8 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 - | -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 struct Foo; - | ^^^^^^^^^^^^^^ -note: the lint level is defined here - --> $DIR/lint-ctypes.rs:4: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 +error: `extern` block uses type `&[u32]`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:71:26 | LL | pub 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-ctypes.rs:5:9 + | +LL | #![deny(improper_ctypes, improper_c_callbacks)] + | ^^^^^^^^^^^^^^^ -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:72: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:75: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:76: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:77: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:78: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:79: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:25: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:80: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:64: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:83: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:84: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:85:24 | LL | pub fn fn_type2(p: fn()); | ^^^^ not FFI-safe @@ -155,35 +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 `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 +error: `extern` block uses type `&str`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:87: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 - -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 + = 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:89:27 | LL | pub fn raw_array(arr: [u8; 8]); | ^^^^^^^ not FFI-safe @@ -191,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:92: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:94:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -201,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:96:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -209,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 21 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 0c850cb8f0603..047b5df6de532 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,11 +114,13 @@ pub extern "C" fn fn_type2(p: fn()) { } //~^ ERROR uses type `fn()` pub extern "C" fn fn_contained(p: RustBadRet) { } +//~^ ERROR: uses type `(u32, u64)` pub extern "C" fn transparent_str(p: TransparentStr) { } -//~^ ERROR: uses type `str` +//~^ ERROR: uses type `&str` pub extern "C" fn transparent_fn(p: TransparentBadFn) { } +//~^ ERROR: uses type `(u32, u64)` 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 a6c49fd182a32..e5d10369ed969 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:23 + | +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:36 + | +LL | #![deny(improper_c_fn_definitions, improper_c_callbacks)] + | ^^^^^^^^^^^^^^^^^^^^ + +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:9 | LL | #![deny(improper_c_fn_definitions, improper_c_callbacks)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ -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,35 @@ 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 - --> $DIR/lint-fn.rs:117:38 +error: `extern` fn uses type `(u32, u64)`, which is not FFI-safe + --> $DIR/lint-fn.rs:116:35 + | +LL | pub extern "C" fn fn_contained(p: RustBadRet) { } + | ^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout + +error: `extern` fn uses type `&str`, which is not FFI-safe + --> $DIR/lint-fn.rs:119: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 `(u32, u64)`, which is not FFI-safe + --> $DIR/lint-fn.rs:122:37 + | +LL | pub extern "C" fn transparent_fn(p: TransparentBadFn) { } + | ^^^^^^^^^^^^^^^^ not FFI-safe + | + = help: consider using a struct instead + = note: tuples have unspecified layout 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 +177,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 +186,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 @@ -160,5 +194,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 20 previous errors From c6eb3862025703b72dcf4c90ade0d0af42deb17b Mon Sep 17 00:00:00 2001 From: niacdoial Date: Wed, 27 Aug 2025 21:13:09 +0200 Subject: [PATCH 08/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 | 2 + .../rustc_lint/src/types/improper_ctypes.rs | 115 +++++++++++------- .../ui/lint/improper-ctypes/lint-94223.stderr | 12 ++ tests/ui/lint/improper-ctypes/lint-fn.rs | 13 +- tests/ui/lint/improper-ctypes/lint-fn.stderr | 39 ++---- 5 files changed, 106 insertions(+), 75 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 05d86366571b4..1f26bf5105ad3 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -376,7 +376,9 @@ lint_improper_ctypes_enum_repr_help = 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_indirect_reason = the function pointer to `{$ty}` is FFI-unsafe due to `{$inner_ty}` 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 15a4c4ed98382..ddcb6a3f56935 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; @@ -169,6 +169,8 @@ declare_lint_pass!(ImproperCTypesLint => [ USES_POWER_ALIGNMENT, ]); +type Sig<'tcx> = Binder<'tcx, FnSig<'tcx>>; + /// Getting the (normalized) type out of a field (for, e.g., an enum variant or a tuple). #[inline] fn get_type_from_field<'tcx>( @@ -372,7 +374,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) => { @@ -728,16 +729,17 @@ impl VisitorState { depth: self.depth + 1, } } - fn get_next_in_fnptr<'tcx>(&self, current_ty: Ty<'tcx>, is_ret: bool) -> Self { - debug_assert!(matches!(current_ty.kind(), ty::FnPtr(..))); + + /// Generate the state for an "outermost" type to a FnPtr + fn fnptr_entry_point(depth: usize, is_ret: bool) -> Self { Self { + depth, persistent_flags: if is_ret { PersistentStateFlags::RETURN_TY_IN_FNPTR } else { PersistentStateFlags::ARGUMENT_TY_IN_FNPTR }, - ephemeral_flags: EphemeralStateFlags::from_outer_ty(current_ty), - depth: self.depth + 1, + ephemeral_flags: EphemeralStateFlags::NO_OUTER_TY | EphemeralStateFlags::NOOUT_FNPTR, } } @@ -858,6 +860,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { Self { cx, ty_cache: FxHashSet::default() } } + /// Checks whether an `extern "ABI" fn` function pointer is indeed FFI-safe to call. + fn visit_fnptr(&mut self, depth: usize, ty: Ty<'tcx>, sig: Sig<'tcx>) -> FfiResult<'tcx> { + use FfiResult::*; + debug_assert!(!sig.abi().is_rustic_abi()); + + let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); + + let mut all_ffires = FfiSafe; + + for arg in sig.inputs() { + let ffi_res = self.visit_type(VisitorState::fnptr_entry_point(depth, false), *arg); + all_ffires += ffi_res.forbid_phantom().wrap_all( + ty, + fluent::lint_improper_ctypes_fnptr_indirect_reason, + None, + ); + } + + let ret_ty = sig.output(); + + let ffi_res = self.visit_type(VisitorState::fnptr_entry_point(depth, true), ret_ty); + all_ffires += ffi_res.forbid_phantom().wrap_all( + ty, + fluent::lint_improper_ctypes_fnptr_indirect_reason, + None, + ); + all_ffires + } + /// 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, _) @@ -1246,26 +1277,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, false), *arg) { - FfiSafe => {} - r => return r, - } + ) + } else { + FfiSafe } - - let ret_ty = sig.output(); - self.visit_type(state.get_next_in_fnptr(ty, true), ret_ty) } ty::Foreign(..) => FfiSafe, @@ -1330,9 +1356,30 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if let Some(res) = self.visit_for_opaque_ty(ty) { return res; } - self.visit_type(state, ty) } + + /// Checks the FFI-safety of a callback (`extern "ABI"` FnPtr) + /// that is found in a no-FFI-safety-needed context. + fn check_fnptr(&mut self, depth: usize, ty: Ty<'tcx>) -> FfiResult<'tcx> { + let ty = self.cx.tcx.try_normalize_erasing_regions(self.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 { + self.visit_fnptr(depth, ty, sig) + } + } + r @ _ => { + bug!("expected to inspect the type of an `extern \"ABI\"` FnPtr, not {:?}", r,) + } + } + } } impl<'tcx> ImproperCTypesLint { @@ -1341,7 +1388,6 @@ impl<'tcx> ImproperCTypesLint { fn check_type_for_external_abi_fnptr( &mut self, cx: &LateContext<'tcx>, - state: VisitorState, hir_ty: &hir::Ty<'tcx>, ty: Ty<'tcx>, ) { @@ -1397,9 +1443,7 @@ impl<'tcx> ImproperCTypesLint { ); for (depth, (fn_ptr_ty, span)) in all_types { let mut visitor = ImproperCTypesVisitor::new(cx); - let bridge_state = VisitorState { depth, ..state }; - // FIXME(ctypes): make a check_for_fnptr - let ffi_res = visitor.check_type(bridge_state, fn_ptr_ty); + let ffi_res = visitor.check_fnptr(depth, fn_ptr_ty); self.process_ffi_result(cx, span, ffi_res, CItemKind::Callback); } @@ -1410,7 +1454,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<'_>, ) { @@ -1418,13 +1461,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::argument_from_fnmode(fn_mode); - self.check_type_for_external_abi_fnptr(cx, state, input_hir, *input_ty); + self.check_type_for_external_abi_fnptr(cx, input_hir, *input_ty); } if let hir::FnRetTy::Return(ret_hir) = decl.output { - let state = VisitorState::return_from_fnmode(fn_mode); - self.check_type_for_external_abi_fnptr(cx, state, ret_hir, sig.output()); + self.check_type_for_external_abi_fnptr(cx, ret_hir, sig.output()); } } @@ -1445,6 +1486,7 @@ 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); @@ -1597,6 +1639,7 @@ 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); if !abi.is_rustic_abi() { self.check_foreign_fn( cx, @@ -1604,13 +1647,6 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { 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, - ); } } hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => { @@ -1627,7 +1663,6 @@ 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(), ); @@ -1660,7 +1695,6 @@ 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(), ); @@ -1686,10 +1720,9 @@ 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); 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); } } } diff --git a/tests/ui/lint/improper-ctypes/lint-94223.stderr b/tests/ui/lint/improper-ctypes/lint-94223.stderr index 9d9fb47ee4fef..3238ee6635e4f 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/lint-94223.stderr @@ -4,6 +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 | + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` = help: consider using a raw pointer instead = note: slices have no C equivalent note: the lint level is defined here @@ -18,6 +19,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | pub fn bad_twice(f: Result) {} | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` = help: consider using a raw pointer instead = note: slices have no C equivalent @@ -27,6 +29,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | pub fn bad_twice(f: Result) {} | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` = help: consider using a raw pointer instead = note: slices have no C equivalent @@ -36,6 +39,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | struct BadStruct(extern "C" fn([u8])); | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` = help: consider using a raw pointer instead = note: slices have no C equivalent @@ -45,6 +49,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | A(extern "C" fn([u8])), | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` = help: consider using a raw pointer instead = note: slices have no C equivalent @@ -54,6 +59,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | A(extern "C" fn([u8])), | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` = help: consider using a raw pointer instead = note: slices have no C equivalent @@ -63,6 +69,7 @@ error: `extern` callback uses type `[u8]`, which is not FFI-safe LL | type Foo = extern "C" fn([u8]); | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` = help: consider using a raw pointer instead = note: slices have no C equivalent @@ -72,6 +79,7 @@ error: `extern` callback uses type `Option<&::FooType>`, which is LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `for<'a> extern "C" fn(Option<&'a ::FooType>)` is FFI-unsafe due to `Option<&::FooType>` = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint @@ -81,6 +89,7 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe` = 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 @@ -95,6 +104,7 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe` = 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 @@ -109,6 +119,7 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe` = 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 @@ -123,6 +134,7 @@ error: `extern` callback uses type `FfiUnsafe`, which is not FFI-safe LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe` = 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-fn.rs b/tests/ui/lint/improper-ctypes/lint-fn.rs index 047b5df6de532..af801b36682f6 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_c_fn_definitions, improper_c_callbacks)] +#![deny(improper_c_callbacks, improper_c_fn_definitions)] use std::default::Default; use std::marker::PhantomData; @@ -114,21 +114,22 @@ pub extern "C" fn fn_type2(p: fn()) { } //~^ ERROR uses type `fn()` pub extern "C" fn fn_contained(p: RustBadRet) { } -//~^ ERROR: uses type `(u32, u64)` +// ^ 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) { } -//~^ ERROR: uses type `(u32, u64)` +// ^ 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() -> ()) { } @@ -144,7 +145,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 e5d10369ed969..f07aef8f57621 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -4,13 +4,14 @@ error: `extern` callback uses type `(u32, u64)`, which is not FFI-safe LL | pub type RustBadRet = extern "C" fn() -> (u32,u64); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn() -> (u32, u64)` is FFI-unsafe due to `(u32, u64)` = help: consider using a struct instead = note: tuples have unspecified layout note: the lint level is defined here - --> $DIR/lint-fn.rs:2:36 + --> $DIR/lint-fn.rs:2:9 | -LL | #![deny(improper_c_fn_definitions, improper_c_callbacks)] - | ^^^^^^^^^^^^^^^^^^^^ +LL | #![deny(improper_c_callbacks, improper_c_fn_definitions)] + | ^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `&[u32]`, which is not FFI-safe --> $DIR/lint-fn.rs:70:33 @@ -21,10 +22,10 @@ LL | pub extern "C" fn slice_type(p: &[u32]) { } = 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-fn.rs:2:9 + --> $DIR/lint-fn.rs:2:31 | -LL | #![deny(improper_c_fn_definitions, improper_c_callbacks)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #![deny(improper_c_callbacks, improper_c_fn_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `&str`, which is not FFI-safe --> $DIR/lint-fn.rs:73:31 @@ -141,15 +142,6 @@ 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 `(u32, u64)`, which is not FFI-safe - --> $DIR/lint-fn.rs:116:35 - | -LL | pub extern "C" fn fn_contained(p: RustBadRet) { } - | ^^^^^^^^^^ not FFI-safe - | - = help: consider using a struct instead - = note: tuples have unspecified layout - error: `extern` fn uses type `&str`, which is not FFI-safe --> $DIR/lint-fn.rs:119:38 | @@ -159,17 +151,8 @@ LL | pub extern "C" fn transparent_str(p: TransparentStr) { } = 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 `(u32, u64)`, which is not FFI-safe - --> $DIR/lint-fn.rs:122:37 - | -LL | pub extern "C" fn transparent_fn(p: TransparentBadFn) { } - | ^^^^^^^^^^^^^^^^ not FFI-safe - | - = help: consider using a struct instead - = note: tuples have unspecified layout - error: `extern` fn uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-fn.rs:172:43 + --> $DIR/lint-fn.rs:173:43 | LL | pub extern "C" fn unused_generic2() -> PhantomData { | ^^^^^^^^^^^^^^^^^ not FFI-safe @@ -177,7 +160,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:186:39 | LL | pub extern "C" fn used_generic4(x: Vec) { } | ^^^^^^ not FFI-safe @@ -186,7 +169,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:189:41 | LL | pub extern "C" fn used_generic5() -> Vec { | ^^^^^^ not FFI-safe @@ -194,5 +177,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 20 previous errors +error: aborting due to 18 previous errors From d4dc99bf0ee41fea141b3f5c7a0331f570c356ba 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 | 30 ++++++++--- .../extern-C-non-FFI-safe-arg-ice-52334.rs | 2 +- ...extern-C-non-FFI-safe-arg-ice-52334.stderr | 13 +---- tests/ui/extern/extern-C-str-arg-ice-80125.rs | 2 +- .../extern/extern-C-str-arg-ice-80125.stderr | 14 ++---- .../lint/extern-C-fnptr-lints-slices.stderr | 2 +- .../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 | 50 +++++++++++++++++-- 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 +++++++-- 19 files changed, 137 insertions(+), 54 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 1f26bf5105ad3..ed5a3bb73d7f8 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -395,6 +395,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 ddcb6a3f56935..5f78c20a86088 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -431,7 +431,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::*; @@ -1024,7 +1023,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); + } } } @@ -1075,7 +1076,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() { @@ -1091,7 +1092,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ) } else { self.visit_variant_fields(state, ty, def, def.non_enum_variant(), args) - } + }; + + // from now on in the function, we lint the actual insides of the struct/union: if something is 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 + // - if the struct is from the same crate, there is another warning on its definition anyway + // (unless it's about Boxes and references without Option<_> + // which is partly why we keep the details as to why that struct is FFI-unsafe) + // - if the struct is from another crate, then there's not much that can be done anyways + // + // this enum is visited in the middle of another lint, + // so we override the "cause type" of the lint + ffires.with_overrides(Some(ty)) } fn visit_enum( @@ -1138,10 +1151,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 2ebdbf0e14d19..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,7 +8,7 @@ type Foo = extern "C" fn(::std::ffi::CStr); //~^ WARN `extern` callback uses type extern "C" { fn meh(blah: Foo); - //~^ WARN `extern` block uses type + // ^ FIXME: the error isn't seen here but at least it's reported elsewhere } 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 6e3598d5c2440..0f45dde885175 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,19 +4,10 @@ warning: `extern` callback uses type `CStr`, which is not FFI-safe LL | type Foo = extern "C" fn(::std::ffi::CStr); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(CStr)` is FFI-unsafe due to `CStr` = 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_c_callbacks)]` (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 `*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 - -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..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,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 +// ^ 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/extern/extern-C-str-arg-ice-80125.stderr b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr index 31f419af7b9f1..ff96b3bde8dba 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr @@ -4,20 +4,11 @@ warning: `extern` callback uses type `str`, which is not FFI-safe LL | type ExternCallback = extern "C" fn(*const u8, u32, str); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | + = note: the function pointer to `extern "C" fn(*const u8, u32, str)` is FFI-unsafe due to `str` = help: consider using `*const u8` and a length instead = note: string slices have no C equivalent = note: `#[warn(improper_c_callbacks)]` (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_c_fn_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 +22,7 @@ note: the type is defined here | LL | pub struct Struct(ExternCallback); | ^^^^^^^^^^^^^^^^^ + = note: `#[warn(improper_c_fn_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 fa488d14d0304..774b5b8c0cfcc 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -5,7 +5,7 @@ LL | pub type F = extern "C" fn(&[u8]); | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `for<'a> extern "C" fn(&'a [u8])` is FFI-unsafe due to `&[u8]` - = help: consider using a raw pointer to the slice's first element (and a length) instead + = 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/extern-C-fnptr-lints-slices.rs:1:8 diff --git a/tests/ui/lint/improper-ctypes/lint-113436-1.rs b/tests/ui/lint/improper-ctypes/lint-113436-1.rs index 1f9f02dc573b9..118ba2075e60d 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 a65f2fe7c6591..344f366565fc0 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_c_fn_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 726f3188f05e6..c7bac00e8a6f8 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -84,9 +84,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 7de489f6b24f8..3c07d501a70d8 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:87:31 | LL | pub fn transparent_str(p: TransparentStr); @@ -127,8 +127,48 @@ 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:91: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 +note: the lint level is defined here + --> $DIR/lint-ctypes.rs:5:26 + | +LL | #![deny(improper_ctypes, improper_c_callbacks)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: `extern` callback uses type `&dyn Debug`, which is not FFI-safe + --> $DIR/lint-ctypes.rs:91: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:91: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:58: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:92:47 + --> $DIR/lint-ctypes.rs:98:47 | LL | pub fn struct_unsized_ptr_has_metadata(p: &UnsizedStructBecauseDyn); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -136,7 +176,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:94:26 + --> $DIR/lint-ctypes.rs:100:26 | LL | pub fn no_niche_a(a: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -145,7 +185,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:96:26 + --> $DIR/lint-ctypes.rs:102:26 | LL | pub fn no_niche_b(b: Option>); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -153,5 +193,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 af801b36682f6..f265289763499 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.rs +++ b/tests/ui/lint/improper-ctypes/lint-fn.rs @@ -117,7 +117,7 @@ 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 `TransparentStr` pub extern "C" fn transparent_fn(p: TransparentBadFn) { } // ^ possible FIXME: it doesn't see the actual FnPtr's error... diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index f07aef8f57621..a38173d89c021 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -142,7 +142,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:119: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 5b9fb7a2751ea3208a9a9d1a045db1fb25ed2f06 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 | 171 ++++++++++++++---- .../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 +- .../lint/improper-ctypes/lint-ctypes.stderr | 8 +- 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 +- 16 files changed, 270 insertions(+), 81 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 ed5a3bb73d7f8..ffc17b22b1d00 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -392,24 +392,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 d83f2440a9520..785a7ee8a8855 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 5f78c20a86088..13a8da6232093 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -182,7 +182,9 @@ 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 +/// Check a variant of a non-exhaustive enum for improper ctypes. +/// Returns two bools: "we have FFI-unsafety due to non-exhaustive enum" and +/// "we have FFI-unsafety due to a non-exhaustive enum variant". /// /// 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. @@ -190,10 +192,10 @@ fn get_type_from_field<'tcx>( /// 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( +pub(crate) fn flag_non_exhaustive_variant( non_exhaustive_variant_list: bool, variant: &ty::VariantDef, -) -> ControlFlow { +) -> (bool, bool) { // 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 @@ -202,15 +204,15 @@ pub(crate) fn check_non_exhaustive_variant( // 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); + return (true, false); } } if variant.field_list_has_applicable_non_exhaustive() { - return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive_variant); + return (false, true); } - ControlFlow::Continue(()) + (false, false) } fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { @@ -1002,34 +1004,114 @@ 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 { + // we assume the repr() of this ADT is either non-packed C or transparent. + 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) @@ -1141,22 +1223,41 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { 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| FfiResult::new_with_reason(ty, reason, None))?; - match self.visit_variant_fields(state, ty, def, variant, args) { - FfiSafe => ControlFlow::Continue(()), - r => ControlFlow::Break(r), - } + let (mut nonexhaustive_flag, mut nonexhaustive_variant_flag) = (false, false); + def.variants().iter().for_each(|variant| { + let (nonex_enum, nonex_var) = flag_non_exhaustive_variant(non_exhaustive, variant); + nonexhaustive_flag |= nonex_enum; + nonexhaustive_variant_flag |= nonex_var; }); - if let ControlFlow::Break(result) = ret { + + // "nonexhaustive" lints only happen outside of the crate defining the enum, so no CItemKind override + // (meaning: the fault lies in the function call, not the enum) + if 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 ff96b3bde8dba..f19264a73e787 100644 --- a/tests/ui/extern/extern-C-str-arg-ice-80125.stderr +++ b/tests/ui/extern/extern-C-str-arg-ice-80125.stderr @@ -15,8 +15,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 344f366565fc0..764c166dda002 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 3238ee6635e4f..6b20337c4b01d 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/lint-94223.stderr @@ -90,8 +90,8 @@ LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe` - = 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 | @@ -105,8 +105,8 @@ LL | pub static BAD_TWICE: Result $DIR/lint-94223.rs:34:1 | @@ -120,8 +120,8 @@ LL | pub static BAD_TWICE: Result $DIR/lint-94223.rs:34:1 | @@ -135,8 +135,8 @@ LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `extern "C" fn(FfiUnsafe)` is FFI-unsafe due to `FfiUnsafe` - = 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.stderr b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr index 3c07d501a70d8..92944b0196a99 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:25: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:33: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 diff --git a/tests/ui/lint/improper-ctypes/lint-fn.stderr b/tests/ui/lint/improper-ctypes/lint-fn.stderr index a38173d89c021..8fd01efff54b4 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -96,7 +96,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 | @@ -148,6 +148,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 @@ -165,8 +171,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:189:41 @@ -174,8 +180,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..d2f3a0147376c --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-transparent-help.rs @@ -0,0 +1,21 @@ +#![deny(improper_c_fn_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..de8e7bd42cfc0 --- /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_c_fn_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 d409c7e3247ef918d7a6853c93746dfd1fc10a9f 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 | 47 +++++++++++++------ .../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, 46 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index ffc17b22b1d00..1bc8b93b8b119 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -386,8 +386,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 13a8da6232093..1a6fc3167ae62 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -659,9 +659,6 @@ bitflags! { /// 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 behaviour" reproduced for compatibility's sake - const NOOUT_FNPTR = 0b1000000; } } @@ -696,7 +693,7 @@ impl EphemeralStateFlags { /// modify self to change the ephemeral part of the flags fn from_outer_ty<'tcx>(ty: Ty<'tcx>) -> Self { match ty.kind() { - ty::FnPtr(..) => Self::NO_OUTER_TY | Self::NOOUT_FNPTR, + ty::FnPtr(..) => Self::NO_OUTER_TY, k @ (ty::RawPtr(..) | ty::Ref(..)) => { let mut ret = Self::IN_PTR; if ty.is_mutable_ptr() { @@ -740,7 +737,7 @@ impl VisitorState { } else { PersistentStateFlags::ARGUMENT_TY_IN_FNPTR }, - ephemeral_flags: EphemeralStateFlags::NO_OUTER_TY | EphemeralStateFlags::NOOUT_FNPTR, + ephemeral_flags: EphemeralStateFlags::NO_OUTER_TY, } } @@ -779,6 +776,15 @@ impl VisitorState { Self::entry_point(PersistentStateFlags::STATIC_TY) } + /// Whether the type is used as the type of a static variable. + fn is_direct_in_static(&self) -> bool { + let ret = self.persistent_flags.contains(PersistentStateFlags::STATIC); + if ret { + debug_assert!(!self.persistent_flags.contains(PersistentStateFlags::FUNC)); + } + ret && self.ephemeral_flags.contains(EphemeralStateFlags::NO_OUTER_TY) + } + /// Whether the type is used in a function. fn is_in_function(&self) -> bool { let ret = self.persistent_flags.contains(PersistentStateFlags::FUNC); @@ -844,6 +850,12 @@ impl VisitorState { fn is_raw_pointee(&self) -> bool { self.ephemeral_flags.contains(EphemeralStateFlags::IN_PTR | EphemeralStateFlags::PTR_RAW) } + + /// Whether the current type directly in the memory layout of the parent ty + #[inline] + fn is_memory_inlined(&self) -> bool { + self.ephemeral_flags.contains(EphemeralStateFlags::MEMORY_INLINED) + } } /// Visitor used to recursively traverse MIR types and evaluate FFI-safety. @@ -1320,11 +1332,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) @@ -1376,10 +1398,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 the case - && !state.ephemeral_flags.contains(EphemeralStateFlags::NOOUT_FNPTR) - { + 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( diff --git a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr index 774b5b8c0cfcc..fa488d14d0304 100644 --- a/tests/ui/lint/extern-C-fnptr-lints-slices.stderr +++ b/tests/ui/lint/extern-C-fnptr-lints-slices.stderr @@ -5,7 +5,7 @@ LL | pub type F = extern "C" fn(&[u8]); | ^^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `for<'a> extern "C" fn(&'a [u8])` is FFI-unsafe due to `&[u8]` - = 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 6b20337c4b01d..7f0219deb8c58 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.stderr +++ b/tests/ui/lint/improper-ctypes/lint-94223.stderr @@ -5,7 +5,7 @@ LL | pub fn bad(f: extern "C" fn([u8])) {} | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` - = 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:36 @@ -20,7 +20,7 @@ LL | pub fn bad_twice(f: Result) {} | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` - = 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 @@ -30,7 +30,7 @@ LL | pub fn bad_twice(f: Result) {} | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` - = 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 @@ -40,7 +40,7 @@ LL | struct BadStruct(extern "C" fn([u8])); | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` - = 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 @@ -50,7 +50,7 @@ LL | A(extern "C" fn([u8])), | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` - = 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 @@ -60,7 +60,7 @@ LL | A(extern "C" fn([u8])), | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` - = 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 @@ -70,7 +70,7 @@ LL | type Foo = extern "C" fn([u8]); | ^^^^^^^^^^^^^^^^^^^ not FFI-safe | = note: the function pointer to `extern "C" fn([u8])` is FFI-unsafe due to `[u8]` - = 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 92944b0196a99..a168e6ec765d5 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 @@ -170,7 +170,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 8fd01efff54b4..a2bd450e07496 100644 --- a/tests/ui/lint/improper-ctypes/lint-fn.stderr +++ b/tests/ui/lint/improper-ctypes/lint-fn.stderr @@ -19,7 +19,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:31 @@ -42,7 +42,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 b74bc7b6b180a5c39a5253efc236b5405695ca75 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 | 61 +++++++-- tests/ui/lint/improper-ctypes/lint-enum.rs | 2 +- .../ui/lint/improper-ctypes/lint-enum.stderr | 25 +++- .../lint/improper-ctypes/lint_uninhabited.rs | 75 +++++++++++ .../improper-ctypes/lint_uninhabited.stderr | 121 ++++++++++++++++++ tests/ui/structs-enums/foreign-struct.rs | 15 ++- 7 files changed, 282 insertions(+), 20 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 1bc8b93b8b119..06b8ca0e9938f 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -406,6 +406,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 1a6fc3167ae62..77c1ac79a4f6a 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -389,7 +389,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) => { @@ -902,6 +901,20 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { all_ffires } + /// 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. fn visit_cstr(&mut self, state: VisitorState, ty: Ty<'tcx>) -> FfiResult<'tcx> { debug_assert!(matches!(ty.kind(), ty::Adt(def, _) @@ -1022,6 +1035,24 @@ 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_privately_uninhabited(self.cx.tcx, 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 + // FIXME(ctypes): and also maybe this should be "!is_inhabited_from" but from where? + 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]) @@ -1212,8 +1243,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. @@ -1242,7 +1273,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { nonexhaustive_flag |= nonex_enum; nonexhaustive_variant_flag |= nonex_var; }); - // "nonexhaustive" lints only happen outside of the crate defining the enum, so no CItemKind override // (meaning: the fault lies in the function call, not the enum) if nonexhaustive_flag { @@ -1254,11 +1284,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() @@ -1266,6 +1306,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``) @@ -1432,7 +1477,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. 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..28dfcb3788acf --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint_uninhabited.rs @@ -0,0 +1,75 @@ +#![feature(never_type)] + +#![allow(dead_code, unused_variables)] +#![deny(improper_ctypes)] +#![deny(improper_c_fn_definitions, improper_c_callbacks)] + +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..49ec8bf16dbe1 --- /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:34: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:12: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:9:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $DIR/lint_uninhabited.rs:4:9 + | +LL | #![deny(improper_ctypes)] + | ^^^^^^^^^^^^^^^ + +error: `extern` block uses type `Uninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:37: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:9:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ + +error: `extern` block uses type `!`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:43: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:48: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:12: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:9:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $DIR/lint_uninhabited.rs:5:9 + | +LL | #![deny(improper_c_fn_definitions, improper_c_callbacks)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `Uninhabited`, which is not FFI-safe + --> $DIR/lint_uninhabited.rs:56: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:9:1 + | +LL | enum Uninhabited{} + | ^^^^^^^^^^^^^^^^ + +warning: the type `Uninhabited` does not permit zero-initialization + --> $DIR/lint_uninhabited.rs:58: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:9: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:66: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:71: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:26: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 67ff00ca477c7367426ddb75b1713733784337be 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 | 156 +++++++++++++++++- tests/ui/lint/improper-ctypes/lint-ctypes.rs | 5 + .../lint/improper-ctypes/lint-ctypes.stderr | 61 ++++--- 3 files changed, 193 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 785a7ee8a8855..9acae8e7da0ba 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; @@ -863,7 +863,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>, @@ -892,6 +892,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; } @@ -933,6 +941,148 @@ 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::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?"), + }; + (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?"), + } + } + 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 c7bac00e8a6f8..bad91f6a358a4 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, improper_c_callbacks)] @@ -9,6 +11,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 { } @@ -73,6 +76,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 a168e6ec765d5..2b3e370a9a2ad 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:71:26 + --> $DIR/lint-ctypes.rs:74: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, improper_c_callbacks)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `&str`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:72:24 + --> $DIR/lint-ctypes.rs:75: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:75:25 + --> $DIR/lint-ctypes.rs:78: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:79: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:76:26 + --> $DIR/lint-ctypes.rs:81: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:77:26 + --> $DIR/lint-ctypes.rs:82: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:78:27 + --> $DIR/lint-ctypes.rs:83: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:79:25 + --> $DIR/lint-ctypes.rs:84: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:25:1 + --> $DIR/lint-ctypes.rs:28:1 | LL | pub struct ZeroSize; | ^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `ZeroSizeWithPhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:80:33 + --> $DIR/lint-ctypes.rs:85: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:64:1 + --> $DIR/lint-ctypes.rs:67:1 | LL | pub struct ZeroSizeWithPhantomData(::std::marker::PhantomData); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` block uses type `PhantomData`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:83:12 + --> $DIR/lint-ctypes.rs:88: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:84:23 + --> $DIR/lint-ctypes.rs:89: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:85:24 + --> $DIR/lint-ctypes.rs:90: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:87:31 + --> $DIR/lint-ctypes.rs:92: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:33:1 + --> $DIR/lint-ctypes.rs:36: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:89:27 + --> $DIR/lint-ctypes.rs:94: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:91:12 + --> $DIR/lint-ctypes.rs:96:12 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -143,13 +152,13 @@ LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) = 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-ctypes.rs:5:26 + --> $DIR/lint-ctypes.rs:7:26 | LL | #![deny(improper_ctypes, improper_c_callbacks)] | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `&dyn Debug`, which is not FFI-safe - --> $DIR/lint-ctypes.rs:91:12 + --> $DIR/lint-ctypes.rs:96:12 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -158,7 +167,7 @@ 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:91:12 + --> $DIR/lint-ctypes.rs:96:12 | LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -166,7 +175,7 @@ LL | f: for<'a> extern "C" fn(a:char, b:&dyn Debug, c: TwoBadTypes<'a>) = 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:58:1 + --> $DIR/lint-ctypes.rs:61:1 | LL | pub struct TwoBadTypes<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -174,7 +183,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 @@ -182,7 +191,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 @@ -191,7 +200,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 @@ -199,5 +208,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 From e7db8b84f1079017fb7783a9b3e4711b8f37aa31 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 | 75 ++++++++++++++----- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 77c1ac79a4f6a..4a0731062f798 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -171,6 +171,23 @@ declare_lint_pass!(ImproperCTypesLint => [ type Sig<'tcx> = Binder<'tcx, FnSig<'tcx>>; +// 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_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_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>( @@ -179,7 +196,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) } /// Check a variant of a non-exhaustive enum for improper ctypes. @@ -569,7 +586,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::UnsizedWithMetadata | TypeSizedness::UnsizedWithExternType @@ -1479,25 +1496,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(..) @@ -1533,17 +1577,14 @@ 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) } /// Checks the FFI-safety of a callback (`extern "ABI"` FnPtr) /// that is found in a no-FFI-safety-needed context. fn check_fnptr(&mut self, depth: usize, ty: Ty<'tcx>) -> FfiResult<'tcx> { - let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.typing_env(), ty).unwrap_or(ty); + let ty = erase_and_maybe_normalize(self.cx, ty); match *ty.kind() { ty::FnPtr(sig_tys, hdr) => { From e59850a4f3e3a79e91a5282cd29cd973abb1f67a 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 | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 4a0731062f798..1cc87904b8e25 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -1947,4 +1947,52 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesLint { self.check_foreign_fn(cx, CItemKind::ExportedFunction, id, decl); } } + + 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; + if sig.header.abi.is_rustic_abi() { + self.check_fn_for_external_abi_fnptr(cx, local_id, sig.decl); + } else { + self.check_foreign_fn(cx, CItemKind::ExportedFunction, local_id, sig.decl); + } + } + 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 ea86322ca3a4ff801331892faf8fb9bd974c0657 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. --- compiler/rustc_lint/src/lib.rs | 1 + compiler/rustc_lint/src/types.rs | 3 +- .../rustc_lint/src/types/improper_ctypes.rs | 71 +++++++++++++++++++ .../exported_symbol_wrong_type.rs | 1 + tests/ui/lint/improper-ctypes/lint-ctypes.rs | 11 ++- .../lint/improper-ctypes/lint-ctypes.stderr | 16 ++++- ...-allocations-dont-inherit-codegen-attrs.rs | 2 + 7 files changed, 102 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 3401bd68c0ac9..0cca28d6c9c4c 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -339,6 +339,7 @@ fn register_builtins(store: &mut LintStore) { "improper_c_boundaries", IMPROPER_C_CALLBACKS, IMPROPER_C_FN_DEFINITIONS, + IMPROPER_C_VAR_DEFINITIONS, IMPROPER_CTYPES ); diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 9acae8e7da0ba..cd879a74765ab 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -12,7 +12,8 @@ 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::{ - IMPROPER_C_CALLBACKS, IMPROPER_C_FN_DEFINITIONS, IMPROPER_CTYPES, ImproperCTypesLint, + IMPROPER_C_CALLBACKS, IMPROPER_C_FN_DEFINITIONS, IMPROPER_C_VAR_DEFINITIONS, IMPROPER_CTYPES, + ImproperCTypesLint, }; use crate::lints::{ diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 1cc87904b8e25..cbc13bf4591c0 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -79,6 +79,37 @@ declare_lint! { "proper use of libc types in foreign item definitions" } +declare_lint! { + /// The `improper_c_var_definitions` lint detects incorrect use of + /// [`no_mangle`] and [`export_name`] static variable definitions. + /// (In other words, static variables accessible by name by foreign code.) + /// + /// [`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 + /// # #[unsafe(no_mangle)] + /// # #[used] + /// static mut PLUGIN_ABI_MIN_VERSION: &'static str = "0.0.5"; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The compiler has several checks to verify that types used in + /// static variables exposed to foreign code are safe and follow + /// certain rules to ensure proper compatibility with the foreign interfaces. + /// This lint is issued when it 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. + pub(crate) IMPROPER_C_VAR_DEFINITIONS, + Warn, + "proper use of libc types in foreign-reachable static variable definitions" +} + declare_lint! { /// The `improper_c_callbacks` lint detects incorrect use of /// [`extern` function] pointers. @@ -165,6 +196,7 @@ declare_lint! { declare_lint_pass!(ImproperCTypesLint => [ IMPROPER_CTYPES, IMPROPER_C_FN_DEFINITIONS, + IMPROPER_C_VAR_DEFINITIONS, IMPROPER_C_CALLBACKS, USES_POWER_ALIGNMENT, ]); @@ -316,6 +348,8 @@ enum CItemKind { ImportedExtern, /// `extern "C"` function definitions, to be used elsewhere -> IMPROPER_C_FN_DEFINITIONS, ExportedFunction, + /// `no_mangle`/`export_name` static variables, assumed to be used from across an FFI boundary, + ExportedStatic, /// `extern "C"` function pointers -> IMPROPER_C_CALLBACKS, Callback, } @@ -691,6 +725,8 @@ struct VisitorState { impl PersistentStateFlags { // 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 = @@ -792,6 +828,12 @@ impl VisitorState { Self::entry_point(PersistentStateFlags::STATIC_TY) } + /// Get the proper visitor state for a locally-defined static variable's type + #[inline] + fn static_var_def() -> Self { + Self::entry_point(PersistentStateFlags::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.persistent_flags.contains(PersistentStateFlags::STATIC); @@ -1716,6 +1758,14 @@ 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::OwnerId, span: Span) { + let ty = cx.tcx.type_of(id).instantiate_identity(); + let mut visitor = ImproperCTypesVisitor::new(cx); + 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, @@ -1816,11 +1866,13 @@ impl<'tcx> ImproperCTypesLint { let lint = match fn_mode { CItemKind::ImportedExtern => IMPROPER_CTYPES, CItemKind::ExportedFunction => IMPROPER_C_FN_DEFINITIONS, + CItemKind::ExportedStatic => IMPROPER_C_VAR_DEFINITIONS, CItemKind::Callback => IMPROPER_C_CALLBACKS, }; 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() { @@ -1888,6 +1940,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.owner_id, ty.span); + } + } } // See `check_fn` for declarations, `check_foreign_items` for definitions in extern blocks hir::ItemKind::Fn { .. } => {} 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 bad91f6a358a4..ad267b589ccdc 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.rs +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.rs @@ -5,7 +5,7 @@ #![allow(private_interfaces)] #![deny(improper_ctypes, improper_c_callbacks)] -#![deny(improper_c_fn_definitions)] +#![deny(improper_c_fn_definitions, improper_c_var_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 2b3e370a9a2ad..2585b9b0515e7 100644 --- a/tests/ui/lint/improper-ctypes/lint-ctypes.stderr +++ b/tests/ui/lint/improper-ctypes/lint-ctypes.stderr @@ -208,5 +208,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 20 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:8:36 + | +LL | #![deny(improper_c_fn_definitions, improper_c_var_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 21 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 26a7e89d492d390c461f7ae38158a37a43697bec 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 | 25 +++++++++++-------- .../repr-rust-is-undefined.stderr | 20 +++------------ 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index cbc13bf4591c0..6646a6b147add 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -316,22 +316,17 @@ fn check_struct_for_power_alignment<'tcx>( item: &'tcx hir::Item<'tcx>, adt_def: AdtDef<'tcx>, ) { - let tcx = cx.tcx; // repr(C) structs also with packed or aligned representation // should be ignored. - if adt_def.repr().c() - && !adt_def.repr().packed() - && adt_def.repr().align.is_none() - && tcx.sess.target.os == "aix" - && !adt_def.all_fields().next().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 def_id = field_def.def_id; - let ty = tcx.type_of(def_id).instantiate_identity(); + let ty = cx.tcx.type_of(def_id).instantiate_identity(); if check_arg_for_power_alignment(cx, ty) { cx.emit_span_lint(USES_POWER_ALIGNMENT, field_def.span, UsesPowerAlignment); } @@ -1161,7 +1156,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // takes priority over transparent_with_all_zst_fields if let FfiUnsafe(explanations) = ffires_accumulator { // we assume the repr() of this ADT is either non-packed C or transparent. - 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( @@ -1231,7 +1230,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() { @@ -1307,7 +1307,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 721f23be86ad760e58ee87ee00ebe420cbf10f93 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 | 160 +++++++++ .../allow-improper-ctypes.stderr | 107 ++++++ .../auxiliary/extern_crate_types.rs | 56 ++++ .../improper-ctypes/issue-nonexhaust-ptr.rs | 31 ++ .../lint/improper-ctypes/lint-tykind-fuzz.rs | 312 ++++++++++++++++++ .../improper-ctypes/lint-tykind-fuzz.stderr | 301 +++++++++++++++++ ...int_uninhabited.rs => lint-uninhabited.rs} | 0 ...habited.stderr => lint-uninhabited.stderr} | 36 +- 8 files changed, 985 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/issue-nonexhaust-ptr.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..fdadfb6ecb313 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.rs @@ -0,0 +1,160 @@ +#![deny(improper_ctypes, improper_c_fn_definitions)] +#![deny(improper_c_callbacks)] + +//@ 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_c_callbacks)] +type AllowedUnsafeFnPtr = extern "C" fn(&[i32])->i32; + +type UnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +#[allow(improper_c_callbacks)] +type AllowedUnsafeRustCalleeFnPtr = extern "C" fn(i32)->&'static i32; + +type UnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + +#[allow(improper_c_callbacks)] +type AllowedUnsafeForeignCalleeFnPtr = extern "C" fn(&i32); + + +// //////////////////////////////////////////////////////// +// then, some functions that use them + +static INT: u32 = 42; + +#[allow(improper_c_fn_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_c_fn_definitions)] +extern "C" fn fn2a(e: UnsafeStruct) {} +extern "C" fn fn2u(e: UnsafeStruct) {} +//~^ ERROR: `extern` fn uses type `UnsafeStruct` +#[allow(improper_c_fn_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_c_fn_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_c_fn_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_c_fn_definitions)] +extern "C" fn fn4a(e: UnsafeFromForeignStruct) {} +extern "C" fn fn4u(e: UnsafeFromForeignStruct) {} +#[allow(improper_c_fn_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_c_fn_definitions)] +extern "C" fn fn5a() -> UnsafeFromForeignStruct<'static> { UnsafeFromForeignStruct(&INT)} +extern "C" fn fn5u() -> UnsafeFromForeignStruct<'static> { UnsafeFromForeignStruct(&INT)} +#[allow(improper_c_fn_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_c_fn_definitions)] +extern "C" fn fn6a() -> AllowedUnsafeFromForeignStruct<'static> { + AllowedUnsafeFromForeignStruct(&INT) +} +extern "C" fn fn6u() -> AllowedUnsafeFromForeignStruct<'static> { + AllowedUnsafeFromForeignStruct(&INT) +} +#[allow(improper_c_fn_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_c_callbacks)] +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_c_callbacks)] +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..e6d0a761f7f0d --- /dev/null +++ b/tests/ui/lint/improper-ctypes/allow-improper-ctypes.stderr @@ -0,0 +1,107 @@ +error: `extern` callback uses type `(i32, i32)`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:39:20 + | +LL | type UnsafeFnPtr = extern "C" fn((i32, i32))->i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: the function pointer to `extern "C" fn((i32, i32)) -> i32` is FFI-unsafe due to `(i32, i32)` + = help: consider using a struct instead + = note: tuples have unspecified layout +note: the lint level is defined here + --> $DIR/allow-improper-ctypes.rs:2:9 + | +LL | #![deny(improper_c_callbacks)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&str`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:75: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_c_fn_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `UnsafeStruct`, which is not FFI-safe + --> $DIR/allow-improper-ctypes.rs:81: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:18: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:85: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:90: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:22: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:95: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:139:14 + | +LL | combine: extern "C" fn(&[A]) -> A, + | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: the function pointer to `for<'a> extern "C" fn(&'a [A]) -> A` is FFI-unsafe due to `&[A]` + = 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:145:24 + | +LL | type FakeVTableMaker = extern "C" fn() -> FakeVTable; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: the function pointer to `extern "C" fn() -> FakeVTable` is FFI-unsafe due to `FakeVTable` + = 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:137: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..0fefcf23fc2b0 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/auxiliary/extern_crate_types.rs @@ -0,0 +1,56 @@ +/// 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 +} + +extern "C" { + pub fn nonexhaustivestruct_create() -> *mut NonExhaustiveStruct; + pub fn nonexhaustivestruct_destroy(s: *mut NonExhaustiveStruct); +} diff --git a/tests/ui/lint/improper-ctypes/issue-nonexhaust-ptr.rs b/tests/ui/lint/improper-ctypes/issue-nonexhaust-ptr.rs new file mode 100644 index 0000000000000..ba9e431bd7385 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/issue-nonexhaust-ptr.rs @@ -0,0 +1,31 @@ +//@ 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; + + +// 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..d178e21e1d015 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/lint-tykind-fuzz.rs @@ -0,0 +1,312 @@ +// 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_c_fn_definitions)] +#![deny(improper_c_callbacks)] + +// 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>, + // Box[Foreign] + 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..40afc1cdf547f --- /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:11: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:120: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_c_fn_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `extern` fn uses type `&str`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:122: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:124: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:126: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:128: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:130: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:132: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:148: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:150: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:153: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:155: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:157: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:161: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:171: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:175: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:181: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:197: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:204: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:206: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:210: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:221: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_c_fn_definitions)] + | ^^^^^^^^^^^^^^^ + +error: `extern` block uses type `&[u8]`, which is not FFI-safe + --> $DIR/lint-tykind-fuzz.rs:225: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:247: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:254: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:256: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:260: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:269: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:273: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:279: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:295: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:302: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:304: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:308: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 49ec8bf16dbe1..62425cd8c012c 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:34:17 + --> $DIR/lint-uninhabited.rs:34: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:12:1 + --> $DIR/lint-uninhabited.rs:12: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:9:1 + --> $DIR/lint-uninhabited.rs:9: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)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `Uninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:37:18 + --> $DIR/lint-uninhabited.rs:37: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:9:1 + --> $DIR/lint-uninhabited.rs:9:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ error: `extern` block uses type `!`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:43:18 + --> $DIR/lint-uninhabited.rs:43: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:48:33 + --> $DIR/lint-uninhabited.rs:48: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:12:1 + --> $DIR/lint-uninhabited.rs:12: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:9:1 + --> $DIR/lint-uninhabited.rs:9:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ note: the lint level is defined here - --> $DIR/lint_uninhabited.rs:5:9 + --> $DIR/lint-uninhabited.rs:5:9 | LL | #![deny(improper_c_fn_definitions, improper_c_callbacks)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: `extern` fn uses type `Uninhabited`, which is not FFI-safe - --> $DIR/lint_uninhabited.rs:56:34 + --> $DIR/lint-uninhabited.rs:56: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:9:1 + --> $DIR/lint-uninhabited.rs:9:1 | LL | enum Uninhabited{} | ^^^^^^^^^^^^^^^^ warning: the type `Uninhabited` does not permit zero-initialization - --> $DIR/lint_uninhabited.rs:58:12 + --> $DIR/lint-uninhabited.rs:58: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:9:1 + --> $DIR/lint-uninhabited.rs:9: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:66:34 + --> $DIR/lint-uninhabited.rs:66: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:71:31 + --> $DIR/lint-uninhabited.rs:71: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:26:1 + --> $DIR/lint-uninhabited.rs:26:1 | LL | struct HalfHiddenUninhabited { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 1ab94116c9cd25c7f26af25481d23491ccde363d 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index efbe85e26e566..3fb2cc761507b 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -29,7 +29,7 @@ asm_experimental_arch, unboxed_closures )] -#![allow(unused, improper_c_fn_definitions, internal_features)] +#![allow(unused, internal_features)] #![no_std] #![no_core] From 0df23fad851812ddbcae1887b72364a1d21d2af6 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} | 3 ++ ...94223.stderr => ice-fnptr-slicearg.stderr} | 34 +++++++++---------- ...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 ++ ...es.rs => ice-irregular-recursive-types.rs} | 0 ...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 +-- .../mustpass-non-recursion-limit.rs | 32 ----------------- 25 files changed, 82 insertions(+), 68 deletions(-) rename tests/ui/lint/improper-ctypes/{lint-94223.rs => ice-fnptr-slicearg.rs} (93%) rename tests/ui/lint/improper-ctypes/{lint-94223.stderr => ice-fnptr-slicearg.stderr} (89%) 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-irregular-recursive-types.rs => ice-irregular-recursive-types.rs} (100%) 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%) delete mode 100644 tests/ui/lint/improper-ctypes/mustpass-non-recursion-limit.rs diff --git a/tests/ui/lint/improper-ctypes/lint-94223.rs b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs similarity index 93% rename from tests/ui/lint/improper-ctypes/lint-94223.rs rename to tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs index fb07af4487f8a..2a3aab63c50f8 100644 --- a/tests/ui/lint/improper-ctypes/lint-94223.rs +++ b/tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.rs @@ -1,6 +1,9 @@ #![crate_type = "lib"] #![deny(improper_c_fn_definitions, improper_c_callbacks)] +// 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 89% rename from tests/ui/lint/improper-ctypes/lint-94223.stderr rename to tests/ui/lint/improper-ctypes/ice-fnptr-slicearg.stderr index 7f0219deb8c58..a5b0b8068997c 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:15 + --> $DIR/ice-fnptr-slicearg.rs:7:15 | LL | pub fn bad(f: extern "C" fn([u8])) {} | ^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -8,13 +8,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:36 + --> $DIR/ice-fnptr-slicearg.rs:2:36 | LL | #![deny(improper_c_fn_definitions, improper_c_callbacks)] | ^^^^^^^^^^^^^^^^^^^^ error: `extern` callback uses type `[u8]`, which is not FFI-safe - --> $DIR/lint-94223.rs:7:28 + --> $DIR/ice-fnptr-slicearg.rs:10:28 | LL | pub fn bad_twice(f: Result) {} | ^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -24,7 +24,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:49 + --> $DIR/ice-fnptr-slicearg.rs:10:49 | LL | pub fn bad_twice(f: Result) {} | ^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -34,7 +34,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:18 + --> $DIR/ice-fnptr-slicearg.rs:14:18 | LL | struct BadStruct(extern "C" fn([u8])); | ^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -44,7 +44,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:7 + --> $DIR/ice-fnptr-slicearg.rs:18:7 | LL | A(extern "C" fn([u8])), | ^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -54,7 +54,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:7 + --> $DIR/ice-fnptr-slicearg.rs:23:7 | LL | A(extern "C" fn([u8])), | ^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -64,7 +64,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:12 + --> $DIR/ice-fnptr-slicearg.rs:27:12 | LL | type Foo = extern "C" fn([u8]); | ^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -74,7 +74,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:20 + --> $DIR/ice-fnptr-slicearg.rs:34:20 | LL | pub type Foo2 = extern "C" fn(Option<&::FooType>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -84,7 +84,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:17 + --> $DIR/ice-fnptr-slicearg.rs:44:17 | LL | pub static BAD: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -93,13 +93,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:30 + --> $DIR/ice-fnptr-slicearg.rs:47:30 | LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -108,13 +108,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:56 + --> $DIR/ice-fnptr-slicearg.rs:47:56 | LL | pub static BAD_TWICE: Result = Ok(f); | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -123,13 +123,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:22 + --> $DIR/ice-fnptr-slicearg.rs:51:22 | LL | pub const BAD_CONST: extern "C" fn(FfiUnsafe) = f; | ^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -138,7 +138,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-irregular-recursive-types.rs b/tests/ui/lint/improper-ctypes/ice-irregular-recursive-types.rs similarity index 100% rename from tests/ui/lint/improper-ctypes/mustpass-irregular-recursive-types.rs rename to tests/ui/lint/improper-ctypes/ice-irregular-recursive-types.rs 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 9ff35f4468af2..33a54fb40b75c 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 118ba2075e60d..8eee29f4dd66e 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_c_fn_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 764c166dda002..e586d874e8a8e 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_c_fn_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 65796954c78ba..2f46b95b96466 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_c_fn_definitions, improper_c_callbacks)] +#![deny(improper_c_fn_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)] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/improper-ctypes/mustpass-non-recursion-limit.rs b/tests/ui/lint/improper-ctypes/mustpass-non-recursion-limit.rs deleted file mode 100644 index 61e95dc5a464c..0000000000000 --- a/tests/ui/lint/improper-ctypes/mustpass-non-recursion-limit.rs +++ /dev/null @@ -1,32 +0,0 @@ -//@ check-pass - -#![recursion_limit = "5"] -#![allow(unused)] -#![deny(improper_ctypes)] - -#[repr(C)] -struct F1(*const ()); -#[repr(C)] -struct F2(*const ()); -#[repr(C)] -struct F3(*const ()); -#[repr(C)] -struct F4(*const ()); -#[repr(C)] -struct F5(*const ()); -#[repr(C)] -struct F6(*const ()); - -#[repr(C)] -struct B { - f1: F1, - f2: F2, - f3: F3, - f4: F4, - f5: F5, - f6: F6, -} - -extern "C" fn foo(_: B) {} - -fn main() {}